diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..7dcf11cad41 --- /dev/null +++ b/.clang-format @@ -0,0 +1,90 @@ +--- +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignEscapedNewlinesLeft: true +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never diff --git a/.ctags b/.ctags new file mode 100644 index 00000000000..13c27abbeff --- /dev/null +++ b/.ctags @@ -0,0 +1,7 @@ +--extra=+f +--exclude=Product +--exclude=build +--exclude=tags +--exclude=tests/scripts/framework/cache +--exclude=tools/cross/rootfs +--exclude=doxygen diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..b8eec3df83e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +tests/nnapi/specs/* linguist-detectable=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..d0931912a92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Working Path +/Product +/build +/tools/cross/rootfs + +# Compiled python3 code cache +**/__pycache__ +*.pyc + +# Test cache for model download +/tests/scripts/framework/cache + +# Test report +/report + +# doxygen +/doxygen + +# Generated by format checker +/format.patch + +# Default path for ndk +/tools/cross/ndk + +# ignore the embeded cl_kernels +/**/*.clembed +/**/*.hembed + +# External directory +/externals + +# tflkit info files +/tools/tflkit/*.info + +# Generated tests +/tests/nnapi/src/generated + +# Coverage +/gcov +/coverage + +# Makefile +/Makefile diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000000..12696d5aaab --- /dev/null +++ b/.mailmap @@ -0,0 +1,57 @@ +Aleksei Grebenkin +Andrew Tischenko +Andrey Shedko +Andrey Shedko Андрей Шедько/AI Tools Lab /SRR/Assistant Engineer/삼성전자 +Andrey Shedko Андрей Шедько/AI Tools Lab /SRR/Assistant Engineer/삼성전자 +Cheongyo Bahk +Chunseok Lee +Denis Maksimenko +Devansh Bansal +Dilshodzhon Poshshoev +Dmitry Mozolev +Efimov Alexander +Hanjoung Lee +Hyeongseok Oh +HyungGyu Choi +Hyunsik Yoon +Inki Dae +Ivan Ivanovich Kulagin +Ivan Vagin +Jiseob Jang +Jiyoung Yun +Jonghyun Park +Junghyun Kim +Kshitiz Bansal +Myungjae Lee +Pavel Fattakhov +Pavel Ilyutchenko +Pavel Ilyutchenko Павел Ильютченко/AI Tools Lab /SRR/Assistant Engineer/삼성전자 +Pavel Ilyutchenko Павел Ильютченко/AI Tools Lab /SRR/Assistant Engineer/삼성전자 +Prasanna R +Praveen Doreswamy Naidu +Roman Rusyaev +Saehie Park +Sanggyu Lee +Sangjung Woo +Sangmin Seo +Saulo Aldighieri Moraes +Saurav Babu +Seok Namkoong +Seongwoo Chae +Sergei Barannikov +Sergei Chicherin +Sergey Vostokov +Shubham Gupta +Siva Sai Vaddipati +Sujin Kim +Sung-Jae Lee +Sungjin Choi +Tanuj Tekriwal +Timur Otellovich Ablyazimov +Vishal Keshav +Vitaliy Cherepanov +Vladimir Plazun +Vladimir Plazun Vladimir Plazun/AI Tools Lab/Engineer/삼성전자 +Vladimir Plazun Vladimir Plazun/AI Tools Lab /SRR/Engineer/삼성전자 +Yongseop Kim +Yuri Novikov diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 00000000000..b0521526ddd --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,51 @@ +Aleksei Grebenkin +Andrew Tischenko +Andrey Shedko +Cheongyo Bahk +Chunseok Lee +Denis Maksimenko +Devansh Bansal +Dilshodzhon Poshshoev +Dmitry Mozolev +Efimov Alexander +Hanjoung Lee +Hyeongseok Oh +HyungGyu Choi +Hyunsik Yoon +Inki Dae +Ivan Ivanovich Kulagin +Ivan Vagin +Jiseob Jang +Jiyoung Yun +Jonghyun Park +Junghyun Kim +Kshitiz Bansal +Myungjae Lee +Pavel Fattakhov +Pavel Ilyutchenko +Prasanna R +Praveen Doreswamy Naidu +Roman Rusyaev +Saehie Park +Sanggyu Lee +Sangjung Woo +Sangmin Seo +Saulo Aldighieri Moraes +Saurav Babu +Seok Namkoong +Seongwoo Chae +Sergei Barannikov +Sergei Chicherin +Sergey Vostokov +Shubham Gupta +Siva Sai Vaddipati +Sujin Kim +Sung-Jae Lee +Sungjin Choi +Tanuj Tekriwal +Timur Otellovich Ablyazimov +Vishal Keshav +Vitaliy Cherepanov +Vladimir Plazun +Yongseop Kim +Yuri Novikov diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 00000000000..3c788156c42 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1 @@ +Copyright (c) <2019> All Rights Reserved. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..c1507bf42c5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,656 @@ +This file provides full text of licenses used in this project + +- Apache Licence 2.0 +- MIT +- BSD-2-Clause +- BSD 3-Clause +- Mozilla Public License 2.0 + +............................................................................... + + 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. + +............................................................................... + +The MIT License + +Copyright (c) + +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 BSD 2-Clause License + +Copyright + +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. + +............................................................................. + +The BSD 3-Clause License + +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. + +............................................................................. + +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 isIncompatible 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 + https://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. diff --git a/Makefile.template b/Makefile.template new file mode 100644 index 00000000000..588a300526a --- /dev/null +++ b/Makefile.template @@ -0,0 +1,182 @@ +HOST_ARCH?=$(shell uname -p) +TARGET_ARCH?=$(shell uname -p) +BUILD_TYPE?=Debug +CROSS_BUILD?=0 +HOST_OS?=linux +TARGET_OS?=linux +PARALLEL_BUILD?=1 +COVERAGE_BUILD?=0 +BENCHMARK_ACL_BUILD?=0 +OPTIONS?= + +# make TARGET and TYPE to lowercase +HOST_ARCH_LC=$(shell echo $(HOST_ARCH) | tr A-Z a-z) +TARGET_ARCH_LC=$(shell echo $(TARGET_ARCH) | tr A-Z a-z) +BUILD_TYPE_LC=$(shell echo $(BUILD_TYPE) | tr A-Z a-z) +# we need base name 'arm` for all arm arch +TARGET_ARCH_BASE=$(TARGET_ARCH_LC) +ifneq (,$(findstring arm64,$(TARGET_ARCH_BASE))) + TARGET_ARCH_LC=aarch64 +else ifneq (,$(findstring arm,$(TARGET_ARCH_BASE))) + TARGET_ARCH_LC=armv7l +else ifneq (,$(findstring aarch64,$(TARGET_ARCH_BASE))) + TARGET_ARCH_LC=aarch64 +endif +ifneq (,$(findstring android,$(TARGET_OS))) + # Anndroid only allow aarch64 target-arch + TARGET_ARCH_LC=aarch64 + TARGET_OS=android +endif +# Set CROSS_BUILD=1 when ROOTFS_DIR is given, and TARGET_ARCH is different to HOST_ARCH. +ifneq ($(ROOTFS_DIR),) +ifneq ($(TARGET_ARCH_LC),$(HOST_ARCH_LC)) + CROSS_BUILD=$(if $(wildcard $(ROOTFS_DIR)),1,0) +endif +endif +# the toolchain file, only for cross build +ifeq ($(CROSS_BUILD),1) + TOOLCHAIN_FILE=cmake/buildtool/cross/toolchain_$(TARGET_ARCH_LC)-$(TARGET_OS).cmake + OPTIONS+= -DCMAKE_TOOLCHAIN_FILE=$(TOOLCHAIN_FILE) +endif + +ifeq ($(COVERAGE_BUILD),1) + OPTIONS+= -DENABLE_COVERAGE=ON +else + OPTIONS+= -DENABLE_COVERAGE=OFF +endif + +ifeq ($(BENCHMARK_ACL_BUILD),1) + OPTIONS+= -DBUILD_BENCHMARK_ACL=1 +endif + +ifneq ($(EXT_ACL_FOLDER),) + OPTIONS+= -DBUILD_ARMCOMPUTE=OFF + OPTIONS+= -DARMCompute_EXTDIR=$(EXT_ACL_FOLDER) +endif + +ifneq ($(EXTERNAL_VOLUME),) + OPTIONS+= -DNNAS_EXTERNALS_DIR=$(EXTERNAL_VOLUME) +endif + +ifeq ($(TARGET_OS),android) + OPTIONS+= -DNDK_DIR=$(NDK_DIR) +endif + +ifneq ($(ANDROID_BUILD_TOOLS_DIR),) + OPTIONS+= -DANDROID_BUILD_TOOLS_DIR=$(ANDROID_BUILD_TOOLS_DIR) +endif + +ifneq ($(ANDROID_SDK_DIR),) + OPTIONS+= -DANDROID_SDK_DIR=$(ANDROID_SDK_DIR) +endif + +ifneq ($(TFLITE_MODEL_PATH),) + OPTIONS+= -DTFLITE_MODEL_PATH=$(TFLITE_MODEL_PATH) +endif + +ifneq ($(ANDROID_BOOST_ROOT),) + OPTIONS+= -DANDROID_BOOST_ROOT=$(ANDROID_BOOST_ROOT) +endif + +ifeq ($(PARALLEL_BUILD),1) + # Get number of processors (linux only for now) + ifeq ($(HOST_OS),linux) + NPROCS?=$(shell grep -c ^processor /proc/cpuinfo) + endif +endif + +NPROCS?=1 +WORKHOME=$(CURDIR)/Product +WORKFOLDER=$(TARGET_ARCH_LC)-$(TARGET_OS).$(BUILD_TYPE_LC) +WORKSPACE=$(WORKHOME)/$(WORKFOLDER) + +BUILD_FOLDER=$(WORKSPACE)/obj +INSTALL_PATH?=$(WORKSPACE)/out +OVERLAY_FOLDER?=$(WORKSPACE)/overlay +BUILD_ALIAS=$(WORKHOME)/obj +INSTALL_ALIAS=$(WORKHOME)/out + +TIMESTAMP_CONFIGURE=$(WORKSPACE)/CONFIGURE +TIMESTAMP_BUILD=$(WORKSPACE)/BUILD +TIMESTAMP_INSTALL=$(WORKSPACE)/INSTALL + +all: build + +### +### Command (public) +### +configure: configure_internal + +build: build_internal + +install: $(TIMESTAMP_INSTALL) + +create_tar: runtime_tar_internal + +clean: + rm -rf $(WORKSPACE) + +distclean: + rm -rf $(WORKSPACE) + rm -rf externals/*.stamp + rm -rf tests/nnapi/src/generated/ + +### +### Command (internal) +### +configure_internal: + NNFW_WORKSPACE="$(WORKSPACE)" NNFW_INSTALL_PREFIX=$(INSTALL_PATH) ./nnfw configure \ + -DCMAKE_BUILD_TYPE=$(BUILD_TYPE_LC) \ + -DNNFW_OVERLAY_DIR=$(OVERLAY_FOLDER) \ + $(OPTIONS) + touch $(TIMESTAMP_CONFIGURE) + +build_internal: $(BUILD_FOLDER) + NNFW_WORKSPACE="$(WORKSPACE)" NPROCS="$(NPROCS)" PARALLEL_BUILD="1" ./nnfw build + rm -rf $(BUILD_ALIAS) + ln -s $(BUILD_FOLDER) $(BUILD_ALIAS) + touch $(TIMESTAMP_BUILD) + +install_internal: + NNFW_WORKSPACE="$(WORKSPACE)" ./nnfw install + rm -rf $(INSTALL_ALIAS) + ln -s $(INSTALL_PATH) $(INSTALL_ALIAS) + touch $(TIMESTAMP_INSTALL) + +runtime_tar_internal: $(TIMESTAMP_BUILD) install_internal + tar -zcf nnfw-package.tar.gz -C $(INSTALL_PATH) lib include + mv nnfw-package.tar.gz $(INSTALL_PATH)/. + +install_internal_acl: +# Workaround to install acl for test (ignore error when there is no file to copy) + cp $(OVERLAY_FOLDER)/lib/* $(INSTALL_ALIAS)/lib || true + +build_test_suite: install_internal install_internal_acl + @echo "packaging test suite" + @rm -rf $(INSTALL_PATH)/test-suite.tar.gz +# TODO Divide runtime package, external library package, and test suite + @tar -zcf test-suite.tar.gz tests/scripts infra Product/out --dereference + @mv test-suite.tar.gz $(INSTALL_PATH)/. + +build_coverage_suite: install_internal install_internal_acl + @echo "packaging test-coverage suite" + @rm -rf $(INSTALL_PATH)/coverage-suite.tar.gz + @find Product -name "*.gcno" > include_lists.txt + @pwd | grep -o '/' | wc -l > tests/scripts/build_path_depth.txt + @tar -zcf coverage-suite.tar.gz tests/scripts infra Product/out --dereference -T include_lists.txt + @rm -rf include_lists.txt tests/scripts/build_path_depth.txt + @mv coverage-suite.tar.gz $(INSTALL_PATH)/. + +### +### Timestamps +### +$(WORKSPACE): + mkdir -p $@ + +$(BUILD_FOLDER): $(WORKSPACE) configure_internal + +$(TIMESTAMP_CONFIGURE): configure_internal + +$(TIMESTAMP_BUILD): $(TIMESTAMP_CONFIGURE) build_internal + +$(TIMESTAMP_INSTALL): $(TIMESTAMP_BUILD) install_internal install_internal_acl diff --git a/README.md b/README.md new file mode 100644 index 00000000000..8772bb12073 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# nnfw + +A high-performance, on-device neural network inference framework + +## Goal + +This project _nnfw_ aims at providing a high-performance, on-device neural network (NN) inference +framework that performs inference of a given NN model on processors, such as CPU, GPU, or NPU, in +the target platform, such as the Linux kernel based OS including Tizen. + +## Project Documents + +- [Roadmap](docs/nnfw/roadmap.md) +- [SW Requirement Specification](docs/nnfw/project/2019_requirement_specification.md) +- [SW High Level Design](docs/nnfw/project/2018_high_level_design.md) + +## Getting started + +- For the contribution, please refer to our [contribution guide](docs/HowToContribute.md). +- You can also find how-to documents [HERE](docs/nnfw/howto.md). + +## Maintainers + +- Sung-Jae Lee <> +- Chunseok Lee <> + +## Committers + +- Hyeongseok Oh <> +- Hanjoung Lee <> +- Sharan Allur <> + +## Feature Request (NEW) + +You can suggest development of nnfw's features that are not yet available. + +The functions requested so far can be checked in the [popular feature request](https://github.sec.samsung.net/STAR/nnfw/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3AFEATURE_REQUEST+sort%3Areactions-%2B1-desc) list. + +- If the feature you want is on the list, :+1: to the body of the issue. The feature with the most +:+1: is placed at the top of the list. When adding new features, we will prioritize them with this reference. +Of course, it is good to add an additional comment which describes your request in detail. + +- For features not listed, [create a new issue](https://github.sec.samsung.net/STAR/nnfw/issues/new). +Sooner or later, the maintainer will tag the `FEATURE_REQUEST` label and appear on the list. + +We expect one of the most frequent feature requests would be the operator kernel implementation. +It is good to make a request, but it is better if you contribute by yourself. See the following guide, +[How to Implement Operator Kernel](docs/nnfw/HowToImplementOperatorKernel.md), for help. + +We are looking forward to your participation. +Thank you in advance! + +# nncc +Re-targetable neural network (NN) model compilation framework + +## Goals +nncc, which stands for neural network compiler collection, aims to provide a general framework for +compiling a given NN model to an artifact that runs on various target devices such as CPU, GPU, or +NPU. + +## Maintainers + +- Sung-Jae Lee <> +- Jonghyun Park <> + +## Committers + +- Saehie Park <> +- Hyeongseok Oh <> +- Efimov Alexander <> + +## How to Contact + +- Please post questions, issues, or suggestions into [Issues](https://github.sec.samsung.net/STAR/nnfw/issues). + +---- + +## Notice + +### 22/07/2019 + +Congratulations! On July 22nd, 2019, _nnfw_ repo and +[_nncc_](https://github.sec.samsung.net/STAR/nncc) repo are finally integrated into single one. Now +all activities related to the development of _nnas(Neural Network Acceleration Solution)_ will +proceed in this integrated _nnfw_ repo. The old _nncc_ repo will only be maintained for follow up on +remaining issues and for preserving development history. The following notice will remain in place +until the update of documents in integrated repo is complete. + +### 02/05/2019 + +~~We are currently working on [_nncc_](https://github.sec.samsung.net/STAR/nncc) as a sibling project. +In our plan, the two projects will soon be integrated into one, and focusing on their roles as +front-end(_nncc_) and back-end(_nnfw_), respectively. It will accompany the physical combination of +the github repo.~~ You can find the latest roadmap of the integrated project +[here](https://github.sec.samsung.net/orgs/STAR/projects/1). diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt new file mode 100644 index 00000000000..7cf12f164e5 --- /dev/null +++ b/compiler/CMakeLists.txt @@ -0,0 +1,78 @@ +# TODO Validate the argument of "requires" +function(get_project_build_order VAR) + # This file will describe the dependencies among projects + set(DEPS_FILE "${CMAKE_CURRENT_BINARY_DIR}/compiler.deps") + + # Remove .deps file + file(REMOVE "${DEPS_FILE}") + + # Let's create .deps file + list_subdirectories(PROJECT_DIRS) + + foreach(PROJECT_DIR IN ITEMS ${PROJECT_DIRS}) + set(SUCC "${PROJECT_DIR}") + set(REQUIRES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_DIR}/requires.cmake") + + macro(require PRED) + file(APPEND "${DEPS_FILE}" "${PRED} ${SUCC} ") + endmacro(require) + + file(APPEND "${DEPS_FILE}" "${SUCC} ${SUCC} ") + if(EXISTS "${REQUIRES_FILE}") + include(${REQUIRES_FILE}) + endif(EXISTS "${REQUIRES_FILE}") + endforeach(PROJECT_DIR) + + # NOTE "tsort" is a part of the POSIX.1 standard. + # + # Reference: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tsort.html + execute_process(COMMAND tsort "${DEPS_FILE}" + OUTPUT_VARIABLE ORDER + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Remove newline characters + # TODO Check which one (UNIX_COMMAND or WINDOWS_COMMAND) is correct + separate_arguments(ORDER UNIX_COMMAND ${ORDER}) + + set(${VAR} "${ORDER}" PARENT_SCOPE) +endfunction(get_project_build_order) + +function(add_compiler_directory DIR) + string(TOUPPER ${DIR} PREFIX) + + option(BUILD_COMPILER_${PREFIX} "Build compiler/${dir}" ON) + set(BUILD_WHITELIST "" CACHE STRING "Set modules to be built") + + if(NOT BUILD_WHITELIST STREQUAL "") + set(ENABLE OFF) + set(CURRENT_DIR ${DIR}) + foreach(ACCEPTED_DIR IN ITEMS ${BUILD_WHITELIST}) + if(ACCEPTED_DIR STREQUAL CURRENT_DIR) + set(ENABLE ON) + endif() + endforeach(ACCEPTED_DIR) + else() + set(ENABLE ${BUILD_COMPILER_${PREFIX}}) + endif() + + # This line prevents some errors in this CMakeLists.txt + if(NOT DEFINED ENABLE) + message(FATAL_ERROR "Undefined ENABLE! Please check CMakeLists.txt") + endif() + + if(ENABLE) + message(STATUS "Configure ${PREFIX}") + add_subdirectory(${DIR}) + message(STATUS "Configure ${PREFIX} - Done") + endif(ENABLE) +endfunction(add_compiler_directory) + +function(add_compiler_directories) + get_project_build_order(PROJECT_DIRS) + + foreach(PROJECT_DIR IN ITEMS ${PROJECT_DIRS}) + add_compiler_directory(${PROJECT_DIR}) + endforeach(PROJECT_DIR) +endfunction(add_compiler_directories) + +add_compiler_directories() diff --git a/compiler/adtidas/CMakeLists.txt b/compiler/adtidas/CMakeLists.txt new file mode 100644 index 00000000000..0d84740b7c8 --- /dev/null +++ b/compiler/adtidas/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(adtidas INTERFACE) +target_include_directories(adtidas INTERFACE include) diff --git a/compiler/adtidas/include/adtidas/SmallVector.h b/compiler/adtidas/include/adtidas/SmallVector.h new file mode 100644 index 00000000000..1ad630c6380 --- /dev/null +++ b/compiler/adtidas/include/adtidas/SmallVector.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _ADTIDAS_SMALL_VECTOR_H_ +#define _ADTIDAS_SMALL_VECTOR_H_ + +#include +#include +#include + +namespace adt +{ + +/** + * @brief vector with cheap memory allocation + * @tparam T type of elements + * @tparam Capacity maximum number of elements + * @note much like std::array, but tracks number of used elements. Stored in stack + */ +template class small_vector +{ +public: + using value_type = T; + using reference = T &; + using iterator = T *; + using const_iterator = const T *; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using size_type = size_t; + + template small_vector(It begin, It end) : _size(std::distance(begin, end)) + { + assert(_size <= Capacity); + std::copy(begin, end, this->begin()); + } + + explicit small_vector(size_t size, value_type initializer = value_type()) : _size(size) + { + assert(_size <= Capacity); + std::fill(begin(), end(), initializer); + } + + explicit small_vector() : _size(0) {} + + small_vector(std::initializer_list l) : _size(l.size()) + { + assert(_size <= Capacity); + std::copy(std::begin(l), std::end(l), begin()); + } + + /** + * @return current size + */ + inline size_t size() const noexcept { return _size; } + + /** + * @return maximum number of elements this vector can hold + */ + constexpr size_t capacity() const { return Capacity; } + + /** + * @brief resize to given new size + * @note if new size is greater than current size, new elements are default-initialized + */ + void resize(size_t new_size) noexcept + { + assert(new_size <= Capacity); + if (new_size > _size) + { + std::fill(_storage + _size, _storage + new_size, T()); + } + _size = new_size; + } + + /** + * @return reference to the element at position idx + */ + inline reference operator[](size_t idx) noexcept + { + assert(idx < _size); + return _storage[idx]; + } + + /** + * @return value of element at position idx + */ + inline constexpr value_type operator[](size_t idx) const noexcept + { + // assert on the same line since c++11 does not allow multi-line constexpr functions + return assert(idx < _size), _storage[idx]; + } + + inline iterator begin() noexcept { return std::begin(_storage); } + inline iterator end() noexcept { return _storage + _size; } + + inline reverse_iterator rbegin() noexcept { return reverse_iterator{end()}; } + inline reverse_iterator rend() noexcept { return reverse_iterator{begin()}; } + + // const overloads + inline const_iterator begin() const noexcept { return std::begin(_storage); } + inline const_iterator end() const noexcept { return _storage + _size; } + + inline const_reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + inline const_reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + + inline void push_back(const value_type &e) noexcept + { + assert(_size < Capacity); + _storage[_size++] = e; + } + + inline void push_back(value_type &&e) noexcept + { + assert(_size < Capacity); + _storage[_size++] = std::move(e); + } + +private: + size_t _size; + value_type _storage[Capacity]{}; +}; + +template +bool operator==(const small_vector &lhs, const small_vector &rhs) +{ + if (lhs.size() != rhs.size()) + { + return false; + } + + bool equal = true; + size_t end = lhs.size(); + for (size_t i = 0; i < end; ++i) + { + equal &= (lhs[i] == rhs[i]); + } + + return equal; +} + +} // namespace adt + +#endif //_ADTIDAS_SMALL_VECTOR_H_ diff --git a/compiler/angkor/CMakeLists.txt b/compiler/angkor/CMakeLists.txt new file mode 100644 index 00000000000..44b5e90584a --- /dev/null +++ b/compiler/angkor/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB_RECURSE HEADERS "include/*.h") +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +# NOTE STATIC is deliberately used here to allow clients to use 'angkor' without installation +add_library(angkor STATIC ${HEADERS} ${SOURCES}) +set_target_properties(angkor PROPERTIES POSITION_INDEPENDENT_CODE ON) +set_target_properties(angkor PROPERTIES LINKER_LANGUAGE CXX) +target_include_directories(angkor PUBLIC include) +target_link_libraries(angkor PRIVATE nncc_common) +target_link_libraries(angkor PUBLIC nncc_coverage) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for test +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(angkor_test ${TESTS}) +target_link_libraries(angkor_test angkor) diff --git a/compiler/angkor/README.md b/compiler/angkor/README.md new file mode 100644 index 00000000000..f761b874060 --- /dev/null +++ b/compiler/angkor/README.md @@ -0,0 +1,51 @@ +# angkor + +## Purpose + +_angkor_ is a `nncc` core library + +## How to use + +_angkor_ implements abstract data type(ADT) for feature, kernel, tensor. +There are layout, shape information and enumerator and so on. + +To use some of these things, just insert `include`! +```cpp +#include +#include +#include +``` + +## Example + +- `compiler/coco/core/CMakeLists.txt` + +```cmake +target_link_libraries(coco_core PUBLIC angkor) +``` + +- `compiler/coco/core/src/IR/Arg.cpp` + +```cpp +#include "coco/IR/Arg.h" + +#include +#include + +namespace +{ +const nncc::core::ADT::tensor::LexicalLayout l; +} + +namespace coco +{ + +Arg::Arg(const nncc::core::ADT::tensor::Shape &shape) : _shape{shape}, _bag{nullptr} +{ + _map.resize(nncc::core::ADT::tensor::num_elements(shape)); +} + +// .... + +} +``` diff --git a/compiler/angkor/include/angkor/TensorIndex.h b/compiler/angkor/include/angkor/TensorIndex.h new file mode 100644 index 00000000000..2fc10509eb3 --- /dev/null +++ b/compiler/angkor/include/angkor/TensorIndex.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANGKOR_TENSOR_INDEX_H__ +#define __ANGKOR_TENSOR_INDEX_H__ + +#include "nncc/core/ADT/tensor/Index.h" + +namespace angkor +{ + +using TensorIndex = ::nncc::core::ADT::tensor::Index; + +} // namespace angkor + +#endif // __ANGKOR_TENSOR_INDEX_H__ diff --git a/compiler/angkor/include/angkor/TensorShape.h b/compiler/angkor/include/angkor/TensorShape.h new file mode 100644 index 00000000000..ab62bd8d9bc --- /dev/null +++ b/compiler/angkor/include/angkor/TensorShape.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANGKOR_TENSOR_SHAPE_H__ +#define __ANGKOR_TENSOR_SHAPE_H__ + +#include "nncc/core/ADT/tensor/Shape.h" + +namespace angkor +{ + +using TensorShape = ::nncc::core::ADT::tensor::Shape; + +} // namespace angkor + +#endif // __ANGKOR_TENSOR_SHAPE_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Accessor.h b/compiler/angkor/include/nncc/core/ADT/feature/Accessor.h new file mode 100644 index 00000000000..aa4621851e9 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Accessor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_ACCESSOR_H__ +#define __NNCC_CORE_ADT_FEATURE_ACCESSOR_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +template struct Accessor +{ + virtual ~Accessor() = default; + + virtual T &at(uint32_t ch, uint32_t row, uint32_t col) = 0; +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_ACCESSOR_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Buffer.h b/compiler/angkor/include/nncc/core/ADT/feature/Buffer.h new file mode 100644 index 00000000000..86fd60295ea --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Buffer.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_BUFFER_H__ +#define __NNCC_CORE_ADT_FEATURE_BUFFER_H__ + +#include "nncc/core/ADT/feature/View.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +template class Buffer final : public View +{ +public: + explicit Buffer(const Shape &shape, const Layout &layout) : View{shape, layout} + { + _buffer.resize(num_elements(shape)); + } + +public: + virtual T *base(void) { return _buffer.data(); } + virtual const T *base(void) const { return _buffer.data(); } + +private: + std::vector _buffer; +}; + +template Buffer make_buffer(const Shape &shape) +{ + return Buffer{shape, LayoutImpl{}}; +} + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_BUFFER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/CHWLayout.h b/compiler/angkor/include/nncc/core/ADT/feature/CHWLayout.h new file mode 100644 index 00000000000..d84841d10d1 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/CHWLayout.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_CHW_LAYOUT_H__ +#define __NNCC_CORE_ADT_FEATURE_CHW_LAYOUT_H__ + +#include "nncc/core/ADT/feature/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +struct CHWLayout final : public Layout +{ + CHWLayout(); +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_CHW_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/HWCLayout.h b/compiler/angkor/include/nncc/core/ADT/feature/HWCLayout.h new file mode 100644 index 00000000000..df885ad82ef --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/HWCLayout.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_HWC_LAYOUT_H__ +#define __NNCC_CORE_ADT_FEATURE_HWC_LAYOUT_H__ + +#include "nncc/core/ADT/feature/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +struct HWCLayout final : public Layout +{ + HWCLayout(); +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_HWC_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Layout.h b/compiler/angkor/include/nncc/core/ADT/feature/Layout.h new file mode 100644 index 00000000000..762545a84f4 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Layout.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_LAYOUT_H__ +#define __NNCC_CORE_ADT_FEATURE_LAYOUT_H__ + +#include "nncc/core/ADT/feature/Shape.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +class Layout +{ +public: + using Func = uint32_t (*)(const Shape &, uint32_t ch, uint32_t row, uint32_t col); + +public: + explicit Layout(const Func &func); + +public: + uint32_t offset(const Shape &shape, uint32_t ch, uint32_t row, uint32_t col) const + { + return _func(shape, ch, row, col); + } + +private: + Func _func; +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Overlay.h b/compiler/angkor/include/nncc/core/ADT/feature/Overlay.h new file mode 100644 index 00000000000..93d86f56b72 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Overlay.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_OVERLAY_H__ +#define __NNCC_CORE_ADT_FEATURE_OVERLAY_H__ + +#include "nncc/core/ADT/feature/View.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +template class Overlay final : public View +{ +public: + explicit Overlay(const Shape &shape, const Layout &layout, T *base) + : View{shape, layout}, _base{base} + { + // DO NOTHING + } + +public: + virtual T *base(void) { return _base; } + virtual const T *base(void) const { return _base; } + +private: + T *const _base; +}; + +template Overlay make_overlay(const Shape &shape, T *base) +{ + return Overlay{shape, LayoutImpl{}, base}; +} + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_OVERLAY_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Reader.h b/compiler/angkor/include/nncc/core/ADT/feature/Reader.h new file mode 100644 index 00000000000..9a6fb724b7a --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Reader.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_READER_H__ +#define __NNCC_CORE_ADT_FEATURE_READER_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +template struct Reader +{ + virtual ~Reader() = default; + + virtual T at(uint32_t ch, uint32_t row, uint32_t col) const = 0; +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_READER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/Shape.h b/compiler/angkor/include/nncc/core/ADT/feature/Shape.h new file mode 100644 index 00000000000..31932630891 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/Shape.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_SHAPE_H__ +#define __NNCC_CORE_ADT_FEATURE_SHAPE_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +// +// Shape of Feature Map for Convolution +// +class Shape +{ +public: + Shape(uint32_t depth, uint32_t height, uint32_t width) + : _depth{depth}, _height{height}, _width{width} + { + // DO NOTHING + } + +public: + uint32_t depth(void) const { return _depth; } + uint32_t height(void) const { return _height; } + uint32_t width(void) const { return _width; } + +private: + uint32_t _depth; + uint32_t _height; + uint32_t _width; +}; + +/** + * @brief The number of elements of a feature map of a given shape + * + * WARN The result is valid only when the expected value is less than 2^32 - 1 + */ +inline uint32_t num_elements(const Shape &shape) +{ + return shape.depth() * shape.height() * shape.width(); +} + +inline bool operator==(const Shape &l, const Shape &r) +{ + return (l.depth() == r.depth()) && (l.height() == r.height()) && (l.width() == r.width()); +} + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_SHAPE_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/feature/View.h b/compiler/angkor/include/nncc/core/ADT/feature/View.h new file mode 100644 index 00000000000..856e22b4b62 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/feature/View.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_FEATURE_VIEW_H__ +#define __NNCC_CORE_ADT_FEATURE_VIEW_H__ + +#include "nncc/core/ADT/feature/Shape.h" +#include "nncc/core/ADT/feature/Reader.h" +#include "nncc/core/ADT/feature/Accessor.h" +#include "nncc/core/ADT/feature/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +template class View : public Reader, public Accessor +{ +public: + explicit View(const Shape &shape, const Layout &layout) : _shape{shape}, _layout{layout} + { + // DO NOTHING + } + +public: + virtual T *base(void) = 0; + virtual const T *base(void) const = 0; + +public: + T at(uint32_t ch, uint32_t row, uint32_t col) const override final + { + return *(base() + _layout.offset(_shape, ch, row, col)); + } + +public: + T &at(uint32_t ch, uint32_t row, uint32_t col) override final + { + return *(base() + _layout.offset(_shape, ch, row, col)); + } + +public: + const Shape &shape(void) const { return _shape; } + +private: + const Shape _shape; + const Layout _layout; +}; + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_FEATURE_VIEW_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Accessor.h b/compiler/angkor/include/nncc/core/ADT/kernel/Accessor.h new file mode 100644 index 00000000000..5bc46de3687 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Accessor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_ACCESSOR_H__ +#define __NNCC_CORE_ADT_KERNEL_ACCESSOR_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template struct Accessor +{ + virtual ~Accessor() = default; + + virtual T &at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) = 0; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_ACCESSOR_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Buffer.h b/compiler/angkor/include/nncc/core/ADT/kernel/Buffer.h new file mode 100644 index 00000000000..3497d482922 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Buffer.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_BUFFER_H__ +#define __NNCC_CORE_ADT_KERNEL_BUFFER_H__ + +#include "nncc/core/ADT/kernel/View.h" +#include "nncc/core/ADT/kernel/ViewImpl.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template class Buffer final : public View +{ +public: + explicit Buffer(const Shape &shape, const Layout &layout) : _impl{shape, layout} + { + _buffer.resize(num_elements(shape)); + } + +public: + T at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) const override + { + return _impl.at(_buffer.begin(), nth, ch, row, col); + } + +public: + T &at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) override + { + return _impl.at(_buffer.begin(), nth, ch, row, col); + } + +public: + const Shape &shape(void) const override { return _impl.shape(); } + +private: + std::vector _buffer; + ViewImpl _impl; +}; + +template Buffer make_buffer(const Shape &shape) +{ + return Buffer{shape, LayoutImpl{}}; +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_BUFFER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/IndexEnumerator.h b/compiler/angkor/include/nncc/core/ADT/kernel/IndexEnumerator.h new file mode 100644 index 00000000000..4167ef97221 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/IndexEnumerator.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_INDEX_ENUMERATOR_H__ +#define __NNCC_CORE_ADT_KERNEL_INDEX_ENUMERATOR_H__ + +#include "nncc/core/ADT/kernel/Shape.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +class IndexEnumerator +{ +public: + explicit IndexEnumerator(const Shape &shape); + +public: + IndexEnumerator(IndexEnumerator &&) = delete; + IndexEnumerator(const IndexEnumerator &) = delete; + +public: + bool valid(void) const; + +public: + uint32_t count(void) const; + uint32_t depth(void) const; + uint32_t height(void) const; + uint32_t width(void) const; + +public: + void advance(void); + +private: + // Store max and current offset for count/depth/height/width + // + // NOTE Here explicit array is used instead of kernel::Shape to make + // a room for improvement such as enumeration order (NHWC, NCHW) + // support + uint32_t _max[4]; + uint32_t _cur[4]; + +private: + uint32_t _cursor; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_INDEX_ENUMERATOR_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Layout.h b/compiler/angkor/include/nncc/core/ADT/kernel/Layout.h new file mode 100644 index 00000000000..1e85e1ed40c --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Layout.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_LAYOUT_H__ +#define __NNCC_CORE_ADT_KERNEL_LAYOUT_H__ + +#include "nncc/core/ADT/kernel/Shape.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +class Layout +{ +public: + using Func = uint32_t (*)(const Shape &, uint32_t n, uint32_t ch, uint32_t row, uint32_t col); + +public: + Layout(const Func &func); + +public: + uint32_t offset(const Shape &shape, uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const + { + return _func(shape, n, ch, row, col); + } + +private: + Func _func; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/NCHWLayout.h b/compiler/angkor/include/nncc/core/ADT/kernel/NCHWLayout.h new file mode 100644 index 00000000000..72bd89fb9ca --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/NCHWLayout.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_NCHW_LAYOUT_H__ +#define __NNCC_CORE_ADT_KERNEL_NCHW_LAYOUT_H__ + +#include "nncc/core/ADT/kernel/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +struct NCHWLayout final : public Layout +{ + NCHWLayout(); +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_NCHW_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/NHWCLayout.h b/compiler/angkor/include/nncc/core/ADT/kernel/NHWCLayout.h new file mode 100644 index 00000000000..bb239b91fbf --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/NHWCLayout.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_NHWC_LAYOUT_H__ +#define __NNCC_CORE_ADT_KERNEL_NHWC_LAYOUT_H__ + +#include "nncc/core/ADT/kernel/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +struct NHWCLayout final : public Layout +{ + NHWCLayout(); +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_NHWC_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Overlay.h b/compiler/angkor/include/nncc/core/ADT/kernel/Overlay.h new file mode 100644 index 00000000000..e348a876977 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Overlay.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_OVERLAY_H__ +#define __NNCC_CORE_ADT_KERNEL_OVERLAY_H__ + +#include "nncc/core/ADT/kernel/View.h" +#include "nncc/core/ADT/kernel/ViewImpl.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template class Overlay final : public View +{ +public: + explicit Overlay(const Shape &shape, const Layout &layout, InputIt it) + : _impl{shape, layout}, _it{it} + { + // DO NOTHING + } + +public: + T at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) const override + { + return _impl.at(_it, nth, ch, row, col); + } + +public: + T &at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) override + { + return _impl.at(_it, nth, ch, row, col); + } + +public: + const Shape &shape(void) const override { return _impl.shape(); } + +private: + InputIt const _it; + ViewImpl _impl; +}; + +template struct OverlayFactory +{ + template static Overlay make(const Shape &shape, InputIt it) + { + return Overlay{shape, LayoutImpl{}, it}; + } +}; + +template Overlay make_overlay(const Shape &shape, T *base) +{ + return OverlayFactory::make(shape, base); +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_OVERLAY_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Reader.h b/compiler/angkor/include/nncc/core/ADT/kernel/Reader.h new file mode 100644 index 00000000000..af0267745cc --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Reader.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_READER_H__ +#define __NNCC_CORE_ADT_KERNEL_READER_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template struct Reader +{ + virtual ~Reader() = default; + + virtual T at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) const = 0; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_READER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/Shape.h b/compiler/angkor/include/nncc/core/ADT/kernel/Shape.h new file mode 100644 index 00000000000..d485d526b32 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/Shape.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_SHAPE_H__ +#define __NNCC_CORE_ADT_KERNEL_SHAPE_H__ + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +// +// Shape of Convolution Kernel +// +class Shape +{ +public: + Shape(uint32_t count, uint32_t depth, uint32_t height, uint32_t width) + : _count{count}, _depth{depth}, _height{height}, _width{width} + { + // DO NOTHING + } + +public: + uint32_t count(void) const { return _count; } + uint32_t depth(void) const { return _depth; } + uint32_t height(void) const { return _height; } + uint32_t width(void) const { return _width; } + +private: + uint32_t _count; + uint32_t _depth; + uint32_t _height; + uint32_t _width; +}; + +/** + * @brief Return the number of elements in a kernel of a given shape + * + * WARN The result is valid only when the expected value is less than 2^32 - 1 + */ +inline uint32_t num_elements(const Shape &shape) +{ + return shape.count() * shape.depth() * shape.height() * shape.width(); +} + +bool operator==(const Shape &lhs, const Shape &rhs); + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_SHAPE_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/View.h b/compiler/angkor/include/nncc/core/ADT/kernel/View.h new file mode 100644 index 00000000000..2ed682a5158 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/View.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_VIEW_H__ +#define __NNCC_CORE_ADT_KERNEL_VIEW_H__ + +#include "nncc/core/ADT/kernel/Shape.h" +#include "nncc/core/ADT/kernel/Reader.h" +#include "nncc/core/ADT/kernel/Accessor.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template struct View : public Reader, public Accessor +{ + virtual const Shape &shape(void) const = 0; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_VIEW_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/kernel/ViewImpl.h b/compiler/angkor/include/nncc/core/ADT/kernel/ViewImpl.h new file mode 100644 index 00000000000..f4e8ed5e26f --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/kernel/ViewImpl.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_KERNEL_VIEW_IMPL_H__ +#define __NNCC_CORE_ADT_KERNEL_VIEW_IMPL_H__ + +#include "nncc/core/ADT/kernel/Shape.h" +#include "nncc/core/ADT/kernel/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +template class ViewImpl +{ +public: + explicit ViewImpl(const Shape &shape, const Layout &layout) : _shape{shape}, _layout{layout} + { + // DO NOTHING + } + +public: + template + T at(InputIt it, uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) const + { + return *(it + _layout.offset(_shape, nth, ch, row, col)); + } + +public: + template + T &at(InputIt it, uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) + { + return *(it + _layout.offset(_shape, nth, ch, row, col)); + } + +public: + const Shape &shape(void) const { return _shape; } + +private: + const Shape _shape; + const Layout _layout; +}; + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_KERNEL_VIEW_IMPL_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Accessor.h b/compiler/angkor/include/nncc/core/ADT/tensor/Accessor.h new file mode 100644 index 00000000000..6a60b4b34c2 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Accessor.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_ACCESSOR_H__ +#define __NNCC_CORE_ADT_TENSOR_ACCESSOR_H__ + +#include "nncc/core/ADT/tensor/Index.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +template struct Accessor +{ + virtual ~Accessor() = default; + + virtual T &at(const Index &) = 0; +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_ACCESSOR_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Buffer.h b/compiler/angkor/include/nncc/core/ADT/tensor/Buffer.h new file mode 100644 index 00000000000..f62f3040f29 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Buffer.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_BUFFER_H__ +#define __NNCC_CORE_ADT_TENSOR_BUFFER_H__ + +#include "nncc/core/ADT/tensor/View.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +template class Buffer final : public View +{ +public: + explicit Buffer(const Shape &shape, const Layout &layout) : View{shape, layout} + { + _buffer.resize(num_elements(shape)); + } + +public: + T *base(void) override { return _buffer.data(); } + const T *base(void) const override { return _buffer.data(); } + +private: + std::vector _buffer; +}; + +template Buffer make_buffer(const Shape &shape) +{ + return Buffer{shape, LayoutImpl{}}; +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_BUFFER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Index.h b/compiler/angkor/include/nncc/core/ADT/tensor/Index.h new file mode 100644 index 00000000000..19beafafc97 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Index.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_INDEX_H__ +#define __NNCC_CORE_ADT_TENSOR_INDEX_H__ + +#include +#include +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +class Index +{ +public: + Index() = default; + Index(std::initializer_list &&l); + +public: + uint32_t rank(void) const; + +public: + Index &resize(uint32_t size); + +public: + Index &fill(uint32_t index); + +public: + uint32_t &at(uint32_t axis); + uint32_t at(uint32_t axis) const; + +private: + std::vector _indices; +}; + +// It throws an exception when rank of inputs does not match. +Index operator+(const Index &lhs, const Index &rhs); +bool operator==(const Index &lhs, const Index &rhs); + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_INDEX_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/IndexEnumerator.h b/compiler/angkor/include/nncc/core/ADT/tensor/IndexEnumerator.h new file mode 100644 index 00000000000..ef85b2c10aa --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/IndexEnumerator.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_INDEX_ENUMERATOR_H__ +#define __NNCC_CORE_ADT_TENSOR_INDEX_ENUMERATOR_H__ + +#include "nncc/core/ADT/tensor/Index.h" +#include "nncc/core/ADT/tensor/Shape.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +class IndexEnumerator +{ +public: + explicit IndexEnumerator(const Shape &shape); + +public: + IndexEnumerator(IndexEnumerator &&) = delete; + IndexEnumerator(const IndexEnumerator &) = delete; + +public: + bool valid(void) const { return _cursor < _shape.rank(); } + +public: + const Index ¤t(void) const { return _index; } + +public: + void advance(void); + +private: + const Shape _shape; + Index _index; + +private: + uint32_t _cursor; +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_INDEX_ENUMERATOR_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Layout.h b/compiler/angkor/include/nncc/core/ADT/tensor/Layout.h new file mode 100644 index 00000000000..0e410ff01f5 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Layout.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_LAYOUT_H__ +#define __NNCC_CORE_ADT_TENSOR_LAYOUT_H__ + +#include "nncc/core/ADT/tensor/Shape.h" +#include "nncc/core/ADT/tensor/Index.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +class Layout +{ +public: + using Func = uint32_t (*)(const Shape &, const Index &); + +public: + explicit Layout(const Func &func); + +public: + uint32_t offset(const Shape &shape, const Index &index) const { return _func(shape, index); } + +private: + Func _func; +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/LexicalLayout.h b/compiler/angkor/include/nncc/core/ADT/tensor/LexicalLayout.h new file mode 100644 index 00000000000..b497ad84461 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/LexicalLayout.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_LEXICAL_LAYOUT_H__ +#define __NNCC_CORE_ADT_TENSOR_LEXICAL_LAYOUT_H__ + +#include "nncc/core/ADT/tensor/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +struct LexicalLayout final : public Layout +{ + LexicalLayout(); +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_LEXICAL_LAYOUT_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Overlay.h b/compiler/angkor/include/nncc/core/ADT/tensor/Overlay.h new file mode 100644 index 00000000000..11ee5350c30 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Overlay.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_OVERLAY_H__ +#define __NNCC_CORE_ADT_TENSOR_OVERLAY_H__ + +#include "nncc/core/ADT/tensor/View.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +template class Overlay final : public View +{ +public: + explicit Overlay(const Shape &shape, const Layout &layout, T *base) + : View{shape, layout}, _base{base} + { + // DO NOTHING + } + +public: + T *base(void) override { return _base; } + const T *base(void) const override { return _base; } + +private: + T *const _base; +}; + +template Overlay make_overlay(const Shape &shape, T *base) +{ + return Overlay{shape, LayoutImpl{}, base}; +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_OVERLAY_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Reader.h b/compiler/angkor/include/nncc/core/ADT/tensor/Reader.h new file mode 100644 index 00000000000..49f1287d207 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Reader.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_READER_H__ +#define __NNCC_CORE_ADT_TENSOR_READER_H__ + +#include "nncc/core/ADT/tensor/Index.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +template struct Reader +{ + virtual ~Reader() = default; + + virtual T at(const Index &) const = 0; +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_READER_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/Shape.h b/compiler/angkor/include/nncc/core/ADT/tensor/Shape.h new file mode 100644 index 00000000000..3eaab0e54c7 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/Shape.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_SHAPE_H__ +#define __NNCC_CORE_ADT_TENSOR_SHAPE_H__ + +#include +#include +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +class Shape +{ +public: + Shape() = default; + Shape(std::initializer_list &&l); + +public: + uint32_t rank(void) const; + +public: + Shape &resize(uint32_t size); + +public: + uint32_t &dim(uint32_t axis); + uint32_t dim(uint32_t axis) const; + +public: + Shape &squeeze(void); + +private: + std::vector _dims; +}; + +/** + * NOTE num_elements returns 1 for rank-0 tensors + */ +uint64_t num_elements(const Shape &); + +Shape squeeze(const Shape &); + +bool operator==(const Shape &, const Shape &); + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_SHAPE_H__ diff --git a/compiler/angkor/include/nncc/core/ADT/tensor/View.h b/compiler/angkor/include/nncc/core/ADT/tensor/View.h new file mode 100644 index 00000000000..4c9a9153980 --- /dev/null +++ b/compiler/angkor/include/nncc/core/ADT/tensor/View.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __NNCC_CORE_ADT_TENSOR_VIEW_H__ +#define __NNCC_CORE_ADT_TENSOR_VIEW_H__ + +#include "nncc/core/ADT/tensor/Shape.h" +#include "nncc/core/ADT/tensor/Index.h" +#include "nncc/core/ADT/tensor/Reader.h" +#include "nncc/core/ADT/tensor/Accessor.h" +#include "nncc/core/ADT/tensor/Layout.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +template class View : public Reader, public Accessor +{ +public: + explicit View(const Shape &shape, const Layout &layout) + : _shape{shape}, _layout{std::move(layout)} + { + // DO NOTHING + } + +public: + virtual ~View() = default; + +public: + virtual T *base(void) = 0; + virtual const T *base(void) const = 0; + +public: + T at(const Index &index) const override { return *(base() + _layout.offset(_shape, index)); } + +public: + T &at(const Index &index) override { return *(base() + _layout.offset(_shape, index)); } + +public: + const Shape &shape(void) const { return _shape; } + +private: + const Shape _shape; + const Layout _layout; +}; + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc + +#endif // __NNCC_CORE_ADT_TENSOR_VIEW_H__ diff --git a/compiler/angkor/src/ADT/feature/Accessor.cpp b/compiler/angkor/src/ADT/feature/Accessor.cpp new file mode 100644 index 00000000000..03ff9a31eb3 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Accessor.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Accessor.h" + +// DO NOT REMOVE THIS FILE +// +// This file is introduced to check the self-completeness of 'Accessor.h' diff --git a/compiler/angkor/src/ADT/feature/Buffer.test.cpp b/compiler/angkor/src/ADT/feature/Buffer.test.cpp new file mode 100644 index 00000000000..1e44302512b --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Buffer.test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Buffer.h" +#include "nncc/core/ADT/feature/CHWLayout.h" + +#include + +using nncc::core::ADT::feature::Shape; +using nncc::core::ADT::feature::CHWLayout; +using nncc::core::ADT::feature::Buffer; + +using nncc::core::ADT::feature::make_buffer; + +TEST(ADT_FEATURE_BUFFER, ctor) +{ + const Shape shape{4, 6, 3}; + auto buffer = make_buffer(shape); + + ASSERT_EQ(buffer.shape().depth(), shape.depth()); + ASSERT_EQ(buffer.shape().height(), shape.height()); + ASSERT_EQ(buffer.shape().width(), shape.width()); +} + +TEST(ADT_FEATURE_BUFFER, access) +{ + const Shape shape{4, 6, 3}; + auto buffer = make_buffer(shape); + + ASSERT_EQ(buffer.at(3, 5, 2), 0); + buffer.at(3, 5, 2) = 4; + + // Casting is introduced to use 'const T &at(...) const' method + ASSERT_EQ(static_cast &>(buffer).at(3, 5, 2), 4); +} diff --git a/compiler/angkor/src/ADT/feature/CHWLayout.cpp b/compiler/angkor/src/ADT/feature/CHWLayout.cpp new file mode 100644 index 00000000000..31415a1bd58 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/CHWLayout.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/CHWLayout.h" + +using nncc::core::ADT::feature::Shape; + +static uint32_t CHW_offset(const Shape &shape, uint32_t ch, uint32_t row, uint32_t col) +{ + return (ch * shape.height() + row) * shape.width() + col; +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +CHWLayout::CHWLayout() : Layout{CHW_offset} +{ + // DO NOTHING +} + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/feature/CHWLayout.test.cpp b/compiler/angkor/src/ADT/feature/CHWLayout.test.cpp new file mode 100644 index 00000000000..5610df8f391 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/CHWLayout.test.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/CHWLayout.h" + +#include + +using namespace nncc::core::ADT::feature; + +TEST(ADT_FEATURE_CHW_LAYOUT, col_increase) +{ + const Shape shape{4, 3, 6}; + const CHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 2, 1) + 1, l.offset(shape, 1, 2, 2)); +} + +TEST(ADT_FEATURE_CHW_LAYOUT, row_increase) +{ + const Shape shape{4, 3, 6}; + const CHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1) + 6, l.offset(shape, 1, 2, 1)); +} + +TEST(ADT_FEATURE_CHW_LAYOUT, ch_increase) +{ + const Shape shape{4, 3, 6}; + const CHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1) + 6 * 3, l.offset(shape, 2, 1, 1)); +} diff --git a/compiler/angkor/src/ADT/feature/HWCLayout.cpp b/compiler/angkor/src/ADT/feature/HWCLayout.cpp new file mode 100644 index 00000000000..016535625dd --- /dev/null +++ b/compiler/angkor/src/ADT/feature/HWCLayout.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/HWCLayout.h" + +using nncc::core::ADT::feature::Shape; + +static uint32_t HWC_offset(const Shape &shape, uint32_t ch, uint32_t row, uint32_t col) +{ + return (row * shape.width() + col) * shape.depth() + ch; +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +HWCLayout::HWCLayout() : Layout{HWC_offset} +{ + // DO NOTHING +} + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/feature/HWCLayout.test.cpp b/compiler/angkor/src/ADT/feature/HWCLayout.test.cpp new file mode 100644 index 00000000000..d1f35975375 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/HWCLayout.test.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/HWCLayout.h" + +#include + +using namespace nncc::core::ADT::feature; + +TEST(ADT_FEATURE_HWC_LAYOUT, C_increase) +{ + const uint32_t C = 4; + const uint32_t H = 3; + const uint32_t W = 6; + + const Shape shape{C, H, W}; + const HWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1) + 1, l.offset(shape, 2, 1, 1)); +} + +TEST(ADT_FEATURE_HWC_LAYOUT, W_increase) +{ + const uint32_t C = 4; + const uint32_t H = 3; + const uint32_t W = 6; + + const Shape shape{C, H, W}; + const HWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 2, 1) + C, l.offset(shape, 1, 2, 2)); +} + +TEST(ADT_FEATURE_HWC_LAYOUT, H_increase) +{ + const uint32_t C = 4; + const uint32_t H = 3; + const uint32_t W = 6; + + const Shape shape{C, H, W}; + const HWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1) + W * C, l.offset(shape, 1, 2, 1)); +} diff --git a/compiler/angkor/src/ADT/feature/Layout.cpp b/compiler/angkor/src/ADT/feature/Layout.cpp new file mode 100644 index 00000000000..49ab7cbf965 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Layout.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Layout.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace feature +{ + +Layout::Layout(const Func &func) : _func{func} { assert(_func != nullptr); } + +} // namespace feature +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/feature/Layout.test.cpp b/compiler/angkor/src/ADT/feature/Layout.test.cpp new file mode 100644 index 00000000000..023594e163b --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Layout.test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Layout.h" + +#include + +using nncc::core::ADT::feature::Shape; +using nncc::core::ADT::feature::Layout; + +static uint32_t offset_0(const Shape &, uint32_t, uint32_t, uint32_t) { return 0; } +static uint32_t offset_1(const Shape &, uint32_t, uint32_t, uint32_t) { return 1; } + +TEST(ADT_FEATURE_LAYOUT, ctor) +{ + Layout l{offset_0}; + + ASSERT_EQ(l.offset(Shape{4, 3, 6}, 1, 1, 1), 0); +} + +TEST(ADT_FEATURE_LAYOUT, copy) +{ + Layout orig{offset_0}; + Layout copy{offset_1}; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6}, 1, 1, 1), 1); + + copy = orig; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6}, 1, 1, 1), 0); +} + +TEST(ADT_FEATURE_LAYOUT, move) +{ + Layout orig{offset_0}; + Layout move{offset_1}; + + ASSERT_EQ(move.offset(Shape{4, 3, 6}, 1, 1, 1), 1); + + move = std::move(orig); + + ASSERT_EQ(move.offset(Shape{4, 3, 6}, 1, 1, 1), 0); +} diff --git a/compiler/angkor/src/ADT/feature/Overlay.test.cpp b/compiler/angkor/src/ADT/feature/Overlay.test.cpp new file mode 100644 index 00000000000..c8e2943f826 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Overlay.test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Overlay.h" +#include "nncc/core/ADT/feature/CHWLayout.h" + +#include + +using nncc::core::ADT::feature::Shape; +using nncc::core::ADT::feature::CHWLayout; +using nncc::core::ADT::feature::Overlay; + +using nncc::core::ADT::feature::make_overlay; + +TEST(ADT_FEATURE_OVERLAY, ctor) +{ + const Shape shape{4, 6, 3}; + + int data[4 * 6 * 3] = { + 0, + }; + auto overlay = make_overlay(shape, data); + + ASSERT_EQ(overlay.shape().depth(), shape.depth()); + ASSERT_EQ(overlay.shape().height(), shape.height()); + ASSERT_EQ(overlay.shape().width(), shape.width()); +} + +TEST(ADT_FEATURE_OVERLAY, read) +{ + const Shape shape{4, 6, 3}; + + int data[4 * 6 * 3] = { + 0, + }; + const auto overlay = make_overlay(shape, data); + + CHWLayout layout{}; + + ASSERT_EQ(data[layout.offset(shape, 3, 5, 2)], 0); + data[layout.offset(shape, 3, 5, 2)] = 2; + ASSERT_EQ(overlay.at(3, 5, 2), 2); +} + +TEST(ADT_FEATURE_OVERLAY, access) +{ + const Shape shape{4, 6, 3}; + + int data[4 * 6 * 3] = { + 0, + }; + auto overlay = make_overlay(shape, data); + + CHWLayout layout{}; + + ASSERT_EQ(data[layout.offset(shape, 3, 5, 2)], 0); + overlay.at(3, 5, 2) = 4; + ASSERT_EQ(data[layout.offset(shape, 3, 5, 2)], 4); +} diff --git a/compiler/angkor/src/ADT/feature/Reader.cpp b/compiler/angkor/src/ADT/feature/Reader.cpp new file mode 100644 index 00000000000..5f1c0d22b86 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Reader.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/feature/Reader.h" + +// DO NOT REMOVE THIS FILE +// +// This file is introduced to check the self-completeness of 'Reader.h' diff --git a/compiler/angkor/src/ADT/feature/Shape.test.cpp b/compiler/angkor/src/ADT/feature/Shape.test.cpp new file mode 100644 index 00000000000..9216182f052 --- /dev/null +++ b/compiler/angkor/src/ADT/feature/Shape.test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +TEST(ADT_FEATURE_SHAPE, ctor) +{ + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + nncc::core::ADT::feature::Shape shape{C, H, W}; + + ASSERT_EQ(shape.depth(), C); + ASSERT_EQ(shape.height(), H); + ASSERT_EQ(shape.width(), W); +} + +TEST(ADT_FEATURE_SHAPE, num_elements) +{ + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + using nncc::core::ADT::feature::Shape; + using nncc::core::ADT::feature::num_elements; + + ASSERT_EQ(num_elements(Shape{C, H, W}), C * H * W); +} + +TEST(ADT_FEATURE_SHAPE, operator_eq) +{ + using nncc::core::ADT::feature::Shape; + + // NOTE We use ASSERT_TRUE/ASSERT_FALSE instead of ASSERT_EQ/ASSERT_NE as it is impossible to + // introduce negative tests with ASSERT_NE (it uses operator!= instead of operator==). + ASSERT_TRUE(Shape(1, 1, 1) == Shape(1, 1, 1)); + ASSERT_FALSE(Shape(1, 1, 1) == Shape(2, 1, 1)); + ASSERT_FALSE(Shape(1, 1, 1) == Shape(1, 2, 1)); + ASSERT_FALSE(Shape(1, 1, 1) == Shape(1, 1, 2)); +} diff --git a/compiler/angkor/src/ADT/kernel/Buffer.test.cpp b/compiler/angkor/src/ADT/kernel/Buffer.test.cpp new file mode 100644 index 00000000000..da344593e37 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Buffer.test.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Buffer.h" +#include "nncc/core/ADT/kernel/NCHWLayout.h" + +#include + +using nncc::core::ADT::kernel::Shape; +using nncc::core::ADT::kernel::NCHWLayout; +using nncc::core::ADT::kernel::Buffer; + +using nncc::core::ADT::kernel::make_buffer; + +TEST(ADT_KERNEL_BUFFER, ctor) +{ + const Shape shape{2, 4, 6, 3}; + auto buffer = make_buffer(shape); + + ASSERT_EQ(buffer.shape().count(), shape.count()); + ASSERT_EQ(buffer.shape().depth(), shape.depth()); + ASSERT_EQ(buffer.shape().height(), shape.height()); + ASSERT_EQ(buffer.shape().width(), shape.width()); +} + +TEST(ADT_KERNEL_BUFFER, access) +{ + const Shape shape{2, 4, 6, 3}; + auto buffer = make_buffer(shape); + + ASSERT_EQ(buffer.at(1, 3, 5, 2), 0); + buffer.at(1, 3, 5, 2) = 4; + + // Casting is introduced to use 'const T &at(...) const' method + ASSERT_EQ(static_cast &>(buffer).at(1, 3, 5, 2), 4); +} diff --git a/compiler/angkor/src/ADT/kernel/IndexEnumerator.cpp b/compiler/angkor/src/ADT/kernel/IndexEnumerator.cpp new file mode 100644 index 00000000000..0b1db090d18 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/IndexEnumerator.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/IndexEnumerator.h" + +#include +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +IndexEnumerator::IndexEnumerator(const Shape &shape) : _cursor(0) +{ + _max[0] = shape.width(); + _max[1] = shape.height(); + _max[2] = shape.depth(); + _max[3] = shape.count(); + + std::fill(_cur, _cur + 4, 0); + + // NOTE Null dimension should NOT exist + assert(std::find(_max, _max + 4, 0) == (_max + 4)); +} + +bool IndexEnumerator::valid(void) const { return _cursor < 4; } + +uint32_t IndexEnumerator::count(void) const { return _cur[3]; } +uint32_t IndexEnumerator::depth(void) const { return _cur[2]; } +uint32_t IndexEnumerator::height(void) const { return _cur[1]; } +uint32_t IndexEnumerator::width(void) const { return _cur[0]; } + +void IndexEnumerator::advance(void) +{ + while (_cursor < 4) + { + if (_cur[_cursor] + 1 < _max[_cursor]) + { + break; + } + + ++_cursor; + } + + if (_cursor == 4) + { + return; + } + + // Increment index + _cur[_cursor] += 1; + + // Reset indices for lower dimensions + for (uint32_t head = 0; head < _cursor; ++head) + { + _cur[head] = 0; + } + + // Reset cursor + _cursor = 0; +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/kernel/IndexEnumerator.test.cpp b/compiler/angkor/src/ADT/kernel/IndexEnumerator.test.cpp new file mode 100644 index 00000000000..21ba1920988 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/IndexEnumerator.test.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/IndexEnumerator.h" + +#include +#include + +#include + +using nncc::core::ADT::kernel::Shape; +using nncc::core::ADT::kernel::IndexEnumerator; + +TEST(ADT_KERNEL_INDEX_ENUMERATOR, iterate_full_range) +{ + const uint32_t N = 2; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + const Shape shape{N, C, H, W}; + + std::vector count; + count.resize(N * C * H * W, 0); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const uint32_t offset = ((e.count() * C + e.depth()) * H + e.height()) * W + e.width(); + count.at(offset) += 1; + } + + ASSERT_TRUE(std::all_of(count.begin(), count.end(), [](uint32_t n) { return n == 1; })); +} diff --git a/compiler/angkor/src/ADT/kernel/Layout.cpp b/compiler/angkor/src/ADT/kernel/Layout.cpp new file mode 100644 index 00000000000..acadd244837 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Layout.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Layout.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +Layout::Layout(const Func &func) : _func{func} +{ + // DO NOTHING +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/kernel/Layout.test.cpp b/compiler/angkor/src/ADT/kernel/Layout.test.cpp new file mode 100644 index 00000000000..94885cd4e56 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Layout.test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Layout.h" + +#include + +using nncc::core::ADT::kernel::Shape; +using nncc::core::ADT::kernel::Layout; + +static uint32_t offset_0(const Shape &, uint32_t, uint32_t, uint32_t, uint32_t) { return 0; } +static uint32_t offset_1(const Shape &, uint32_t, uint32_t, uint32_t, uint32_t) { return 1; } + +TEST(ADT_KERNEL_LAYOUT, ctor) +{ + Layout l{offset_0}; + + ASSERT_EQ(l.offset(Shape{4, 3, 6, 5}, 1, 1, 1, 1), 0); +} + +TEST(ADT_KERNEL_LAYOUT, copy) +{ + Layout orig{offset_0}; + Layout copy{offset_1}; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6, 5}, 1, 1, 1, 1), 1); + + copy = orig; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6, 5}, 1, 1, 1, 1), 0); +} + +TEST(ADT_KERNEL_LAYOUT, move) +{ + Layout orig{offset_0}; + Layout move{offset_1}; + + ASSERT_EQ(move.offset(Shape{4, 3, 6, 5}, 1, 1, 1, 1), 1); + + move = std::move(orig); + + ASSERT_EQ(move.offset(Shape{4, 3, 6, 5}, 1, 1, 1, 1), 0); +} diff --git a/compiler/angkor/src/ADT/kernel/NCHWLayout.cpp b/compiler/angkor/src/ADT/kernel/NCHWLayout.cpp new file mode 100644 index 00000000000..be7551182b7 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/NCHWLayout.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/NCHWLayout.h" + +using nncc::core::ADT::kernel::Shape; + +static uint32_t NCHW_offset(const Shape &shape, uint32_t n, uint32_t ch, uint32_t row, uint32_t col) +{ + return (((n * shape.depth() + ch) * shape.height() + row) * shape.width() + col); +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +NCHWLayout::NCHWLayout() : Layout{NCHW_offset} +{ + // DO NOTHING +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/kernel/NCHWLayout.test.cpp b/compiler/angkor/src/ADT/kernel/NCHWLayout.test.cpp new file mode 100644 index 00000000000..ba03b7b044e --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/NCHWLayout.test.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/NCHWLayout.h" + +#include + +using namespace nncc::core::ADT::kernel; + +TEST(ADT_KERNEL_KERNEL_NCHW_LAYOUT, col_increment) +{ + const Shape shape{4, 3, 6, 5}; + const NCHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + 1, l.offset(shape, 1, 1, 1, 2)); +} + +TEST(ADT_KERNEL_KERNEL_NCHW_LAYOUT, row_increment) +{ + const Shape shape{4, 3, 6, 5}; + const NCHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + 5, l.offset(shape, 1, 1, 2, 1)); +} + +TEST(ADT_KERNEL_KERNEL_NCHW_LAYOUT, ch_increment) +{ + const Shape shape{4, 3, 6, 5}; + const NCHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + 6 * 5, l.offset(shape, 1, 2, 1, 1)); +} + +TEST(ADT_KERNEL_KERNEL_NCHW_LAYOUT, n_increment) +{ + const Shape shape{4, 3, 6, 5}; + const NCHWLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + 3 * 6 * 5, l.offset(shape, 2, 1, 1, 1)); +} diff --git a/compiler/angkor/src/ADT/kernel/NHWCLayout.cpp b/compiler/angkor/src/ADT/kernel/NHWCLayout.cpp new file mode 100644 index 00000000000..8e05244256e --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/NHWCLayout.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/NHWCLayout.h" + +using nncc::core::ADT::kernel::Shape; + +static uint32_t NHWC_offset(const Shape &shape, uint32_t n, uint32_t ch, uint32_t row, uint32_t col) +{ + return ((n * shape.height() + row) * shape.width() + col) * shape.depth() + ch; +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +NHWCLayout::NHWCLayout() : Layout{NHWC_offset} +{ + // DO NOTHING +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/kernel/NHWCLayout.test.cpp b/compiler/angkor/src/ADT/kernel/NHWCLayout.test.cpp new file mode 100644 index 00000000000..2c5df7d898d --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/NHWCLayout.test.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/NHWCLayout.h" + +#include + +using nncc::core::ADT::kernel::Shape; +using nncc::core::ADT::kernel::NHWCLayout; + +TEST(ADT_KERNEL_KERNEL_NHWC_LAYOUT, ch_increment) +{ + const uint32_t N = 4; + const uint32_t C = 3; + const uint32_t H = 6; + const uint32_t W = 5; + + const Shape shape{N, C, H, W}; + const NHWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + 1, l.offset(shape, 1, 2, 1, 1)); +} + +TEST(ADT_KERNEL_KERNEL_NHWC_LAYOUT, col_increment) +{ + const uint32_t N = 4; + const uint32_t C = 3; + const uint32_t H = 6; + const uint32_t W = 5; + + const Shape shape{N, C, H, W}; + const NHWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + C, l.offset(shape, 1, 1, 1, 2)); +} + +TEST(ADT_KERNEL_KERNEL_NHWC_LAYOUT, row_increment) +{ + const uint32_t N = 4; + const uint32_t C = 3; + const uint32_t H = 6; + const uint32_t W = 5; + + const Shape shape{N, C, H, W}; + const NHWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + C * W, l.offset(shape, 1, 1, 2, 1)); +} + +TEST(ADT_KERNEL_KERNEL_NHWC_LAYOUT, n_increment) +{ + const uint32_t N = 4; + const uint32_t C = 3; + const uint32_t H = 6; + const uint32_t W = 5; + + const Shape shape{N, C, H, W}; + const NHWCLayout l; + + ASSERT_EQ(l.offset(shape, 1, 1, 1, 1) + H * W * C, l.offset(shape, 2, 1, 1, 1)); +} diff --git a/compiler/angkor/src/ADT/kernel/Overlay.test.cpp b/compiler/angkor/src/ADT/kernel/Overlay.test.cpp new file mode 100644 index 00000000000..e80ebbc3042 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Overlay.test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Overlay.h" +#include "nncc/core/ADT/kernel/NCHWLayout.h" + +#include + +using nncc::core::ADT::kernel::Shape; +using nncc::core::ADT::kernel::NCHWLayout; +using nncc::core::ADT::kernel::Overlay; + +using nncc::core::ADT::kernel::make_overlay; + +TEST(ADT_KERNEL_OVERLAY, ctor) +{ + const Shape shape{2, 4, 6, 3}; + + int data[2 * 4 * 6 * 3] = { + 0, + }; + auto overlay = make_overlay(shape, data); + + ASSERT_EQ(overlay.shape().count(), shape.count()); + ASSERT_EQ(overlay.shape().depth(), shape.depth()); + ASSERT_EQ(overlay.shape().height(), shape.height()); + ASSERT_EQ(overlay.shape().width(), shape.width()); +} + +TEST(ADT_KERNEL_OVERLAY, read) +{ + const Shape shape{2, 4, 6, 3}; + + int data[2 * 4 * 6 * 3] = { + 0, + }; + const auto overlay = make_overlay(shape, data); + + NCHWLayout layout{}; + + ASSERT_EQ(data[layout.offset(shape, 1, 3, 5, 2)], 0); + data[layout.offset(shape, 1, 3, 5, 2)] = 2; + ASSERT_EQ(overlay.at(1, 3, 5, 2), 2); +} + +TEST(ADT_KERNEL_OVERLAY, access) +{ + const Shape shape{2, 4, 6, 3}; + + int data[2 * 4 * 6 * 3] = { + 0, + }; + auto overlay = make_overlay(shape, data); + + NCHWLayout layout{}; + + ASSERT_EQ(data[layout.offset(shape, 1, 3, 5, 2)], 0); + overlay.at(1, 3, 5, 2) = 4; + ASSERT_EQ(data[layout.offset(shape, 1, 3, 5, 2)], 4); +} diff --git a/compiler/angkor/src/ADT/kernel/Reader.cpp b/compiler/angkor/src/ADT/kernel/Reader.cpp new file mode 100644 index 00000000000..9e34167c873 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Reader.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Reader.h" + +// DO NOT REMOVE THIS FILE +// This file is introduced to test the self-completeness of 'Reader.h' diff --git a/compiler/angkor/src/ADT/kernel/Shape.cpp b/compiler/angkor/src/ADT/kernel/Shape.cpp new file mode 100644 index 00000000000..8ad1edb674b --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Shape.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/kernel/Shape.h" + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace kernel +{ + +bool operator==(const Shape &l, const Shape &r) +{ + return (l.count() == r.count()) && (l.depth() == r.depth()) && (l.height() == r.height()) && + (l.width() == r.width()); +} + +} // namespace kernel +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/kernel/Shape.test.cpp b/compiler/angkor/src/ADT/kernel/Shape.test.cpp new file mode 100644 index 00000000000..da608fb7fd3 --- /dev/null +++ b/compiler/angkor/src/ADT/kernel/Shape.test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +TEST(ADT_KERNEL_SHAPE, ctor) +{ + const uint32_t N = 1; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + nncc::core::ADT::kernel::Shape shape{N, C, H, W}; + + ASSERT_EQ(shape.count(), N); + ASSERT_EQ(shape.depth(), C); + ASSERT_EQ(shape.height(), H); + ASSERT_EQ(shape.width(), W); +} + +TEST(ADT_KERNEL_SHAPE, num_elements) +{ + const uint32_t N = 1; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + using nncc::core::ADT::kernel::Shape; + using nncc::core::ADT::kernel::num_elements; + + ASSERT_EQ(num_elements(Shape{N, C, H, W}), N * C * H * W); +} + +TEST(ADT_KERNEL_SHAPE, operator_eq) +{ + using nncc::core::ADT::kernel::Shape; + + EXPECT_TRUE(Shape(1, 1, 1, 1) == Shape(1, 1, 1, 1)); + EXPECT_FALSE(Shape(1, 1, 1, 1) == Shape(1, 1, 1, 2)); + EXPECT_FALSE(Shape(1, 1, 1, 1) == Shape(1, 1, 2, 1)); + EXPECT_FALSE(Shape(1, 1, 1, 1) == Shape(1, 2, 1, 1)); + EXPECT_FALSE(Shape(1, 1, 1, 1) == Shape(2, 1, 1, 1)); +} diff --git a/compiler/angkor/src/ADT/tensor/Buffer.test.cpp b/compiler/angkor/src/ADT/tensor/Buffer.test.cpp new file mode 100644 index 00000000000..c2b6a998323 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Buffer.test.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Buffer.h" +#include "nncc/core/ADT/tensor/LexicalLayout.h" + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Buffer; + +using nncc::core::ADT::tensor::make_buffer; + +TEST(ADT_TENSOR_BUFFER, ctor) +{ + const Shape shape{2, 3}; + auto buffer = make_buffer(shape); + + ASSERT_EQ(buffer.shape(), shape); +} + +TEST(ADT_TENSOR_BUFFER, access) +{ + const Shape shape{2, 3}; + auto buffer = make_buffer(shape); + + const Index index{1, 2}; + + ASSERT_EQ(buffer.at(index), 0); + buffer.at(index) = 4; + + // Casting is introduced to use 'const T &at(...) const' method + ASSERT_EQ(static_cast &>(buffer).at(index), 4); +} diff --git a/compiler/angkor/src/ADT/tensor/Index.cpp b/compiler/angkor/src/ADT/tensor/Index.cpp new file mode 100644 index 00000000000..61f0a710605 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Index.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Index.h" + +#include +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +Index::Index(std::initializer_list &&l) : _indices{l} +{ + // DO NOTHING +} + +uint32_t Index::rank(void) const { return _indices.size(); } +Index &Index::resize(uint32_t size) +{ + _indices.resize(size); + return *this; +} + +Index &Index::fill(uint32_t index) +{ + std::fill(_indices.begin(), _indices.end(), index); + return (*this); +} + +uint32_t &Index::at(uint32_t axis) { return _indices.at(axis); } +uint32_t Index::at(uint32_t axis) const { return _indices.at(axis); } + +Index operator+(const Index &lhs, const Index &rhs) +{ + if (lhs.rank() != rhs.rank()) + throw std::runtime_error("Two tensors should have same rank"); + + Index ret; + ret.resize(lhs.rank()); + for (uint32_t axis = 0; axis < lhs.rank(); axis++) + { + ret.at(axis) = lhs.at(axis) + rhs.at(axis); + } + return ret; +} + +bool operator==(const Index &lhs, const Index &rhs) +{ + if (lhs.rank() != rhs.rank()) + return false; + for (uint32_t axis = 0; axis < lhs.rank(); axis++) + { + if (lhs.at(axis) != rhs.at(axis)) + return false; + } + return true; +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/tensor/Index.test.cpp b/compiler/angkor/src/ADT/tensor/Index.test.cpp new file mode 100644 index 00000000000..230602816e6 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Index.test.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Index.h" + +#include + +TEST(ADT_TENSOR_INDEX, ctor) +{ + nncc::core::ADT::tensor::Index index; + + ASSERT_EQ(index.rank(), 0); +} + +TEST(ADT_TENSOR_INDEX, ctor_initializer_list) +{ + const nncc::core::ADT::tensor::Index index{1, 3, 5, 7}; + + ASSERT_EQ(index.rank(), 4); + + ASSERT_EQ(index.at(0), 1); + ASSERT_EQ(index.at(1), 3); + ASSERT_EQ(index.at(2), 5); + ASSERT_EQ(index.at(3), 7); +} + +TEST(ADT_TENSOR_INDEX, operator_add) +{ + nncc::core::ADT::tensor::Index index1{1, 2, 3, 4}; + nncc::core::ADT::tensor::Index index2{5, 6, 7, 8}; + nncc::core::ADT::tensor::Index result{index1 + index2}; + + ASSERT_EQ(result.at(0), 6); + ASSERT_EQ(result.at(1), 8); + ASSERT_EQ(result.at(2), 10); + ASSERT_EQ(result.at(3), 12); +} + +TEST(ADT_TENSOR_INDEX, operator_eqaul) +{ + nncc::core::ADT::tensor::Index index1{1, 2, 3, 4}; + nncc::core::ADT::tensor::Index index2{1, 2, 3, 4}; + nncc::core::ADT::tensor::Index index3{5, 6, 7, 8}; + nncc::core::ADT::tensor::Index index4{1, 2}; + + ASSERT_TRUE(index1 == index2); + ASSERT_FALSE(index1 == index3); + ASSERT_FALSE(index1 == index4); +} + +TEST(ADT_TENSOR_INDEX, operator_add_different_size) +{ + nncc::core::ADT::tensor::Index index1{1, 2, 3, 4}; + nncc::core::ADT::tensor::Index index2{5, 6}; + + EXPECT_THROW(index1 + index2, std::runtime_error); +} + +TEST(ADT_TENSOR_INDEX, resize) +{ + nncc::core::ADT::tensor::Index index; + + index.resize(4); + + ASSERT_EQ(index.rank(), 4); +} + +TEST(ADT_TENSOR_INDEX, at) +{ + nncc::core::ADT::tensor::Index index; + + index.resize(4); + + uint32_t indices[4] = {3, 5, 2, 7}; + + for (uint32_t axis = 0; axis < 4; ++axis) + { + index.at(axis) = indices[axis]; + ASSERT_EQ(index.at(axis), indices[axis]); + } +} + +TEST(ADT_TENSOR_INDEX, copy) +{ + const nncc::core::ADT::tensor::Index original{3, 5, 2, 7}; + const nncc::core::ADT::tensor::Index copied{original}; + + ASSERT_EQ(original.rank(), copied.rank()); + + for (uint32_t axis = 0; axis < 4; ++axis) + { + ASSERT_EQ(original.at(axis), copied.at(axis)); + } +} + +TEST(ADT_TENSOR_INDEX, fill) +{ + nncc::core::ADT::tensor::Index index{1, 6}; + + index.fill(3); + + ASSERT_EQ(index.rank(), 2); + + ASSERT_EQ(index.at(0), 3); + ASSERT_EQ(index.at(1), 3); +} diff --git a/compiler/angkor/src/ADT/tensor/IndexEnumerator.cpp b/compiler/angkor/src/ADT/tensor/IndexEnumerator.cpp new file mode 100644 index 00000000000..623313a2eaa --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/IndexEnumerator.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +using nncc::core::ADT::tensor::Shape; + +inline uint32_t axis_of(const Shape &shape, uint32_t cursor) +{ + const uint32_t rank = shape.rank(); + assert(cursor < rank); + return rank - cursor - 1; +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +IndexEnumerator::IndexEnumerator(const Shape &shape) : _shape{shape}, _cursor(0) +{ + const uint32_t rank = _shape.rank(); + + // Initialize _index + _index.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + _index.at(axis) = 0; + } + + // Initialize _cursor + for (_cursor = 0; _cursor < rank; ++_cursor) + { + const auto axis = axis_of(_shape, _cursor); + + if (_index.at(axis) < _shape.dim(axis)) + { + break; + } + } +} + +void IndexEnumerator::advance(void) +{ + const uint32_t rank = _shape.rank(); + + // Find axis to be updated + while (_cursor < rank) + { + const auto axis = axis_of(_shape, _cursor); + + if ((_index.at(axis)) + 1 < _shape.dim(axis)) + { + break; + } + + ++_cursor; + } + + if (_cursor == rank) + { + return; + } + + // Update index + _index.at(axis_of(_shape, _cursor)) += 1; + + for (uint32_t pos = 0; pos < _cursor; ++pos) + { + const auto axis = axis_of(_shape, pos); + _index.at(axis) = 0; + } + + // Reset cursor + _cursor = 0; +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/tensor/IndexEnumerator.test.cpp b/compiler/angkor/src/ADT/tensor/IndexEnumerator.test.cpp new file mode 100644 index 00000000000..204a8aa2157 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/IndexEnumerator.test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; + +TEST(ADT_TENSOR_INDEX_ENUMERATOR, iterate_full_range) +{ + const uint32_t H = 3; + const uint32_t W = 4; + + const Shape shape{H, W}; + + std::vector count; + + count.resize(H * W, 0); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + + ASSERT_EQ(ind.rank(), 2); + count.at(ind.at(0) * W + ind.at(1)) += 1; + } + + ASSERT_TRUE(std::all_of(count.begin(), count.end(), [](uint32_t n) { return n == 1; })); +} diff --git a/compiler/angkor/src/ADT/tensor/Layout.cpp b/compiler/angkor/src/ADT/tensor/Layout.cpp new file mode 100644 index 00000000000..7faf7507d7b --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Layout.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Layout.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +Layout::Layout(const Func &func) : _func{func} { assert(_func != nullptr); } + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/tensor/Layout.test.cpp b/compiler/angkor/src/ADT/tensor/Layout.test.cpp new file mode 100644 index 00000000000..145adfecccb --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Layout.test.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Layout.h" + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; + +static uint32_t offset_0(const Shape &, const Index &) { return 0; } +static uint32_t offset_1(const Shape &, const Index &) { return 1; } + +TEST(ADT_TENSOR_LAYOUT, ctor) +{ + nncc::core::ADT::tensor::Layout l{offset_0}; + + ASSERT_EQ(l.offset(Shape{4, 3, 6}, Index{1, 1, 1}), 0); +} + +TEST(ADT_TENSOR_LAYOUT, copy) +{ + nncc::core::ADT::tensor::Layout orig{offset_0}; + nncc::core::ADT::tensor::Layout copy{offset_1}; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6}, Index{1, 1, 1}), 1); + + copy = orig; + + ASSERT_EQ(copy.offset(Shape{4, 3, 6}, Index{1, 1, 1}), 0); +} + +TEST(ADT_TENSOR_LAYOUT, move) +{ + nncc::core::ADT::tensor::Layout orig{offset_0}; + nncc::core::ADT::tensor::Layout move{offset_1}; + + ASSERT_EQ(move.offset(Shape{4, 3, 6}, Index{1, 1, 1}), 1); + + move = std::move(orig); + + ASSERT_EQ(move.offset(Shape{4, 3, 6}, Index{1, 1, 1}), 0); +} diff --git a/compiler/angkor/src/ADT/tensor/LexicalLayout.cpp b/compiler/angkor/src/ADT/tensor/LexicalLayout.cpp new file mode 100644 index 00000000000..671c60ceccc --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/LexicalLayout.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/LexicalLayout.h" + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; + +// NOTE This forward declaration is introduced to minimize code diff +static uint32_t lexical_offset(const Shape &shape, const Index &index) +{ + assert(shape.rank() > 0); + assert(shape.rank() == index.rank()); + + const uint32_t rank = shape.rank(); + + uint32_t res = index.at(0); + + for (uint32_t axis = 1; axis < rank; ++axis) + { + res *= shape.dim(axis); + res += index.at(axis); + } + + return res; +} + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +LexicalLayout::LexicalLayout() : Layout(lexical_offset) +{ + // DO NOTHING +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/tensor/LexicalLayout.test.cpp b/compiler/angkor/src/ADT/tensor/LexicalLayout.test.cpp new file mode 100644 index 00000000000..8f9b7296f91 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/LexicalLayout.test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/LexicalLayout.h" + +#include + +#include + +TEST(ADT_TENSOR_LEXICAL_LAYOUT, last) +{ + const nncc::core::ADT::tensor::Shape shape{4, 3, 6}; + const nncc::core::ADT::tensor::Index curr{1, 1, 1}; + const nncc::core::ADT::tensor::Index next{1, 1, 2}; + + const nncc::core::ADT::tensor::LexicalLayout l; + + ASSERT_EQ(l.offset(shape, curr) + 1, l.offset(shape, next)); +} + +TEST(ADT_TENSOR_LEXICAL_LAYOUT, lexical_middle) +{ + const nncc::core::ADT::tensor::Shape shape{4, 3, 6}; + const nncc::core::ADT::tensor::Index curr{1, 1, 1}; + const nncc::core::ADT::tensor::Index next{1, 2, 1}; + + const nncc::core::ADT::tensor::LexicalLayout l; + + ASSERT_EQ(l.offset(shape, curr) + 6, l.offset(shape, next)); +} + +TEST(ADT_TENSOR_LEXICAL_LAYOUT, lexical_first) +{ + const nncc::core::ADT::tensor::Shape shape{4, 3, 6}; + const nncc::core::ADT::tensor::Index curr{1, 1, 1}; + const nncc::core::ADT::tensor::Index next{2, 1, 1}; + + const nncc::core::ADT::tensor::LexicalLayout l; + + ASSERT_EQ(l.offset(shape, curr) + 6 * 3, l.offset(shape, next)); +} diff --git a/compiler/angkor/src/ADT/tensor/Overlay.test.cpp b/compiler/angkor/src/ADT/tensor/Overlay.test.cpp new file mode 100644 index 00000000000..aacb5a9a1d7 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Overlay.test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Overlay.h" +#include "nncc/core/ADT/tensor/LexicalLayout.h" + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Overlay; + +using nncc::core::ADT::tensor::make_overlay; + +TEST(ADT_TENSOR_OVERLAY, ctor) +{ + const Shape shape{2, 3}; + + int data[2 * 3] = { + 0, + }; + auto view = make_overlay(shape, data); + + ASSERT_EQ(view.shape(), shape); +} + +TEST(ADT_TENSOR_OVERLAY, read) +{ + const Shape shape{2, 3}; + + int data[2 * 3] = { + 0, + }; + const auto view = make_overlay(shape, data); + + LexicalLayout layout{}; + + const Index index{1, 2}; + + ASSERT_EQ(data[layout.offset(shape, index)], 0); + data[layout.offset(shape, index)] = 2; + ASSERT_EQ(view.at(index), 2); +} + +TEST(ADT_TENSOR_OVERLAY, access) +{ + const Shape shape{2, 3}; + + int data[2 * 3] = { + 0, + }; + auto view = make_overlay(shape, data); + + LexicalLayout layout{}; + + const Index index{1, 2}; + + ASSERT_EQ(data[layout.offset(shape, index)], 0); + view.at(index) = 4; + ASSERT_EQ(data[layout.offset(shape, index)], 4); +} diff --git a/compiler/angkor/src/ADT/tensor/Reader.cpp b/compiler/angkor/src/ADT/tensor/Reader.cpp new file mode 100644 index 00000000000..d79e66dac45 --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Reader.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Reader.h" + +// DO NOT REMOVE THIS FILE +// +// This file is introduced to check the self-completeness of 'Reader.h' diff --git a/compiler/angkor/src/ADT/tensor/Shape.cpp b/compiler/angkor/src/ADT/tensor/Shape.cpp new file mode 100644 index 00000000000..fb39ba1928b --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Shape.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Shape.h" + +#include + +namespace nncc +{ +namespace core +{ +namespace ADT +{ +namespace tensor +{ + +Shape::Shape(std::initializer_list &&l) : _dims{l} +{ + // DO NOTHING +} + +uint32_t Shape::rank(void) const { return _dims.size(); } +Shape &Shape::resize(uint32_t size) +{ + _dims.resize(size); + return *this; +} + +uint32_t &Shape::dim(uint32_t axis) { return _dims.at(axis); } +uint32_t Shape::dim(uint32_t axis) const { return _dims.at(axis); } + +Shape &Shape::squeeze(void) +{ + _dims.erase(std::remove(_dims.begin(), _dims.end(), 1), _dims.end()); + return *this; +} + +uint64_t num_elements(const Shape &shape) +{ + uint64_t res = 1; + + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + res *= shape.dim(axis); + } + + return res; +} + +Shape squeeze(const Shape &shape) +{ + Shape res{shape}; + res.squeeze(); + return res; +} + +bool operator==(const Shape &lhs, const Shape &rhs) +{ + if (lhs.rank() != rhs.rank()) + { + return false; + } + + for (uint32_t axis = 0; axis < lhs.rank(); ++axis) + { + if (lhs.dim(axis) != rhs.dim(axis)) + { + return false; + } + } + + return true; +} + +} // namespace tensor +} // namespace ADT +} // namespace core +} // namespace nncc diff --git a/compiler/angkor/src/ADT/tensor/Shape.test.cpp b/compiler/angkor/src/ADT/tensor/Shape.test.cpp new file mode 100644 index 00000000000..711ae3d404a --- /dev/null +++ b/compiler/angkor/src/ADT/tensor/Shape.test.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "nncc/core/ADT/tensor/Shape.h" + +#include + +TEST(ADT_TENSOR_SHAPE, ctor) +{ + nncc::core::ADT::tensor::Shape shape; + + ASSERT_EQ(shape.rank(), 0); +} + +TEST(ADT_TENSOR_SHAPE, ctor_initializer_list) +{ + nncc::core::ADT::tensor::Shape shape{1, 3, 5, 7}; + + ASSERT_EQ(shape.rank(), 4); + + ASSERT_EQ(shape.dim(0), 1); + ASSERT_EQ(shape.dim(1), 3); + ASSERT_EQ(shape.dim(2), 5); + ASSERT_EQ(shape.dim(3), 7); +} + +TEST(ADT_TENSOR_SHAPE, resize) +{ + nncc::core::ADT::tensor::Shape shape; + + shape.resize(4); + + ASSERT_EQ(shape.rank(), 4); +} + +TEST(ADT_TENSOR_SHAPE, dim) +{ + nncc::core::ADT::tensor::Shape shape; + + shape.resize(4); + + uint32_t dims[4] = {3, 5, 2, 7}; + + for (uint32_t axis = 0; axis < 4; ++axis) + { + shape.dim(axis) = dims[axis]; + ASSERT_EQ(shape.dim(axis), dims[axis]); + } +} + +TEST(ADT_TENSOR_SHAPE, copy) +{ + const nncc::core::ADT::tensor::Shape original{3, 5, 2, 7}; + const nncc::core::ADT::tensor::Shape copied{original}; + + ASSERT_EQ(original.rank(), copied.rank()); + + for (uint32_t axis = 0; axis < 4; ++axis) + { + ASSERT_EQ(original.dim(axis), copied.dim(axis)); + } +} + +TEST(ADT_TENSOR_SHAPE, num_elements_rank_0) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::num_elements; + + Shape rank_0_shape; + + ASSERT_EQ(num_elements(rank_0_shape), 1); +} + +TEST(ADT_TENSOR_SHAPE, num_elements_zero) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::num_elements; + + ASSERT_EQ(num_elements(Shape{0, 0, 0, 0}), 0); +} + +TEST(ADT_TENSOR_SHAPE, num_elements_nonzero) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::num_elements; + + ASSERT_EQ(num_elements(Shape{2, 3}), 6); +} + +TEST(ADT_TENSOR_SHAPE, num_elements_nulldim) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::num_elements; + + ASSERT_EQ(num_elements(Shape{2, 0, 3}), 0); +} + +TEST(ADT_TENSOR_SHAPE, squeeze_neg) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::squeeze; + + auto squeezed = squeeze(Shape{3, 5, 2}); + + ASSERT_EQ(squeezed.rank(), 3); + ASSERT_EQ(squeezed.dim(0), 3); + ASSERT_EQ(squeezed.dim(1), 5); + ASSERT_EQ(squeezed.dim(2), 2); +} + +TEST(ADT_TENSOR_SHAPE, squeeze_neg_0) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::squeeze; + + auto squeezed = squeeze(Shape{3, 0, 2}); + + ASSERT_EQ(squeezed.rank(), 3); + ASSERT_EQ(squeezed.dim(0), 3); + ASSERT_EQ(squeezed.dim(1), 0); + ASSERT_EQ(squeezed.dim(2), 2); +} + +TEST(ADT_TENSOR_SHAPE, squeeze_pos) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::squeeze; + + auto squeezed = squeeze(Shape{3, 1, 2}); + + ASSERT_EQ(squeezed.rank(), 2); + ASSERT_EQ(squeezed.dim(0), 3); + ASSERT_EQ(squeezed.dim(1), 2); +} + +TEST(ADT_TENSOR_SHAPE, squeeze_nested) +{ + using nncc::core::ADT::tensor::Shape; + using nncc::core::ADT::tensor::squeeze; + + Shape shape{3, 1, 2}; + + shape.squeeze().squeeze(); + + ASSERT_EQ(shape.rank(), 2); + ASSERT_EQ(shape.dim(0), 3); + ASSERT_EQ(shape.dim(1), 2); +} + +TEST(ADT_TENSOR_SHAPE, eq_negative_on_unmatched_rank) +{ + const nncc::core::ADT::tensor::Shape left{1, 1, 1}; + const nncc::core::ADT::tensor::Shape right{1, 1, 1, 1}; + + ASSERT_FALSE(left == right); +} + +TEST(ADT_TENSOR_SHAPE, eq_negative_on_unmatched_dim) +{ + const nncc::core::ADT::tensor::Shape left{2, 3}; + const nncc::core::ADT::tensor::Shape right{2, 4}; + + ASSERT_FALSE(left == right); +} + +TEST(ADT_TENSOR_SHAPE, eq_positive) +{ + const nncc::core::ADT::tensor::Shape left{2, 3}; + const nncc::core::ADT::tensor::Shape right{2, 3}; + + ASSERT_TRUE(left == right); +} diff --git a/compiler/angkor/src/TensorIndex.test.cpp b/compiler/angkor/src/TensorIndex.test.cpp new file mode 100644 index 00000000000..68cf3917a9d --- /dev/null +++ b/compiler/angkor/src/TensorIndex.test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "angkor/TensorIndex.h" + +#include + +TEST(TensorIndexTest, ctor) +{ + angkor::TensorIndex index; + + ASSERT_EQ(index.rank(), 0); +} + +TEST(TensorIndexTest, ctor_initializer_list) +{ + const angkor::TensorIndex index{1, 3, 5, 7}; + + ASSERT_EQ(index.rank(), 4); + + ASSERT_EQ(index.at(0), 1); + ASSERT_EQ(index.at(1), 3); + ASSERT_EQ(index.at(2), 5); + ASSERT_EQ(index.at(3), 7); +} + +TEST(TensorIndexTest, resize) +{ + angkor::TensorIndex index; + + index.resize(4); + + ASSERT_EQ(index.rank(), 4); +} + +TEST(TensorIndexTest, at) +{ + angkor::TensorIndex index; + + index.resize(4); + + uint32_t indices[4] = {3, 5, 2, 7}; + + for (uint32_t axis = 0; axis < 4; ++axis) + { + index.at(axis) = indices[axis]; + ASSERT_EQ(index.at(axis), indices[axis]); + } +} + +TEST(TensorIndexTest, copy) +{ + const angkor::TensorIndex original{3, 5, 2, 7}; + const angkor::TensorIndex copied{original}; + + ASSERT_EQ(original.rank(), copied.rank()); + + for (uint32_t axis = 0; axis < 4; ++axis) + { + ASSERT_EQ(original.at(axis), copied.at(axis)); + } +} + +TEST(TensorIndexTest, fill) +{ + angkor::TensorIndex index{1, 6}; + + index.fill(3); + + ASSERT_EQ(index.rank(), 2); + + ASSERT_EQ(index.at(0), 3); + ASSERT_EQ(index.at(1), 3); +} diff --git a/compiler/angkor/src/TensorShape.test.cpp b/compiler/angkor/src/TensorShape.test.cpp new file mode 100644 index 00000000000..5e6766a9690 --- /dev/null +++ b/compiler/angkor/src/TensorShape.test.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "angkor/TensorShape.h" + +#include + +TEST(TensorShapeTest, ctor) +{ + angkor::TensorShape shape; + + ASSERT_EQ(shape.rank(), 0); +} + +TEST(TensorShapeTest, ctor_initializer_list) +{ + angkor::TensorShape shape{1, 3, 5, 7}; + + ASSERT_EQ(shape.rank(), 4); + + ASSERT_EQ(shape.dim(0), 1); + ASSERT_EQ(shape.dim(1), 3); + ASSERT_EQ(shape.dim(2), 5); + ASSERT_EQ(shape.dim(3), 7); +} + +TEST(TensorShapeTest, resize) +{ + angkor::TensorShape shape; + + shape.resize(4); + + ASSERT_EQ(shape.rank(), 4); +} + +TEST(TensorShapeTest, dim) +{ + angkor::TensorShape shape; + + shape.resize(4); + + uint32_t dims[4] = {3, 5, 2, 7}; + + for (uint32_t axis = 0; axis < 4; ++axis) + { + shape.dim(axis) = dims[axis]; + ASSERT_EQ(shape.dim(axis), dims[axis]); + } +} + +TEST(TensorShapeTest, copy) +{ + const angkor::TensorShape original{3, 5, 2, 7}; + const angkor::TensorShape copied{original}; + + ASSERT_EQ(original.rank(), copied.rank()); + + for (uint32_t axis = 0; axis < 4; ++axis) + { + ASSERT_EQ(original.dim(axis), copied.dim(axis)); + } +} + +TEST(TensorShapeTest, eq_negative_on_unmatched_rank) +{ + const angkor::TensorShape left{1, 1, 1}; + const angkor::TensorShape right{1, 1, 1, 1}; + + ASSERT_FALSE(left == right); +} + +TEST(TensorShapeTest, eq_negative_on_unmatched_dim) +{ + const angkor::TensorShape left{2, 3}; + const angkor::TensorShape right{2, 4}; + + ASSERT_FALSE(left == right); +} + +TEST(TensorShapeTest, eq_positive) +{ + const angkor::TensorShape left{2, 3}; + const angkor::TensorShape right{2, 3}; + + ASSERT_TRUE(left == right); +} diff --git a/compiler/ann-api/CMakeLists.txt b/compiler/ann-api/CMakeLists.txt new file mode 100644 index 00000000000..d2c45f9f0dc --- /dev/null +++ b/compiler/ann-api/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(ann_api INTERFACE) +target_include_directories(ann_api INTERFACE include) diff --git a/compiler/ann-api/include/.FORMATDENY b/compiler/ann-api/include/.FORMATDENY new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/ann-api/include/NeuralNetworks.h b/compiler/ann-api/include/NeuralNetworks.h new file mode 100644 index 00000000000..606156927b0 --- /dev/null +++ b/compiler/ann-api/include/NeuralNetworks.h @@ -0,0 +1,2075 @@ +/* + * 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. + */ + +/** + * @addtogroup NeuralNetworks + * @{ + */ + +/** + * @file NeuralNetworks.h + */ + +#ifndef ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H +#define ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H + +/****************************************************************** + * + * IMPORTANT NOTICE: + * + * This file is part of Android's set of stable system headers + * exposed by the Android NDK (Native Development Kit). + * + * Third-party source AND binary code relies on the definitions + * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES. + * + * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES) + * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS + * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY + * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES + */ + +#if __ANDROID_API__ >= __ANDROID_API_O_MR1__ + +#include +#include +#include + +__BEGIN_DECLS + +/** + * Operand types. + * + * The type of operands that can be added to a model. + * + * Although we define many types, most operators accept just a few + * types. Most used are {@link ANEURALNETWORKS_TENSOR_FLOAT32}, + * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}, + * and {@link ANEURALNETWORKS_INT32}. + */ +typedef enum { + /** The following entries are used to declare scalars. */ + + /** A 32 bit floating point scalar value. */ + ANEURALNETWORKS_FLOAT32 = 0, + /** A signed 32 bit integer scalar value. */ + ANEURALNETWORKS_INT32 = 1, + /** An unsigned 32 bit integer scalar value. */ + ANEURALNETWORKS_UINT32 = 2, + + /** The following entries are used to declare tensors. */ + + /** A tensor of 32 bit floating point values. */ + ANEURALNETWORKS_TENSOR_FLOAT32 = 3, + /** A tensor of 32 bit integer values. */ + ANEURALNETWORKS_TENSOR_INT32 = 4, + /** A tensor of 8 bit integers that represent real numbers. + * + * Attached to this tensor are two numbers that can be used to convert + * the 8 bit integer to the real value and vice versa. These two numbers are: + * - scale: a 32 bit non-negative floating point value. + * - zeroPoint: an 32 bit integer, in range [0, 255]. + * + * The formula is: + * real_value = (integer_value - zeroPoint) * scale. + */ + ANEURALNETWORKS_TENSOR_QUANT8_ASYMM = 5, +} OperandCode; + +/** + * Operation types. + * + * The type of operations that can be added to a model. + */ +typedef enum { + /** Adds two tensors, element-wise. + * + * Takes two input tensors of identical type and compatible dimensions. The output + * is the sum of both input tensors, optionally modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the input operands. + * It starts with the trailing dimensions, and works its way forward. + * + * Example: + * + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same type, and compatible dimensions as input0. + * * 2: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The sum, a tensor of the same type as input0. + */ + ANEURALNETWORKS_ADD = 0, + + /** Performs a 2-D average pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and padding. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * sum_{i, j}(input[batch, row + i, col + j, channel]) / sum(1) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, and Channels) + * data layout. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension. + * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension. + * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension. + * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension. + * * 5: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 6: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 7: An INT32 value, specifying the filter width. + * * 8: An INT32 value, specifying the filter height. + * * 9: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the + * {@link PaddingCode} values. + * * 2: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 3: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 4: An INT32 value, specifying the filter width. + * * 5: An INT32 value, specifying the filter height. + * * 6: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth]. + */ + ANEURALNETWORKS_AVERAGE_POOL_2D = 1, + + /** Concatenates the input tensors along the given dimension. + * + * The input tensors must have identical type and the same dimensions except the + * dimension along the concatenation axis. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0 ~ n-1: The list of n input tensors, of shape [D0, D1, ..., Daxis(i), ..., Dm]. + * For inputs of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, all + * input tensors must have the same scale and zeroPoint. + * * n: An INT32 value, specifying the concatenation axis. + * + * Outputs: + * * 0: The output, a tensor of the same type as the input tensors. + * The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. + */ + ANEURALNETWORKS_CONCATENATION = 2, + + /** Performs an 2-D convolution operation. + * + * The CONV_2D op sweeps a 2-D filter that can mix channels together over a batch of + * images, applying the filter to each window of each image of the appropriate size. + * + * The output dimensions are functions of the filter dimensions, stride, and padding. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * sum_{i, j} ( + * input[batch, row + i, col + j, k] * + * filter[channel, row + i, col + j, k] + + * bias[channel] + * ) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: A 4-D tensor, of shape [depth_out, filter_height, filter_width, depth_in], + * specifying the filter. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should + * also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias + * should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and + * bias_scale == input_scale * filter_scale. + * * 3: An INT32 value, specifying the padding on the left, in the ‘width’ dimension. + * * 4: An INT32 value, specifying the padding on the right,in the ‘width’ dimension. + * * 5: An INT32 value, specifying the padding on the top, in the ‘height’ dimension. + * * 6: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension. + * * 7: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 8: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 9: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: A 4-D tensor, of shape [depth_out, filter_height, filter_width, depth_in], + * specifying the filter. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should + * also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias + * should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and + * bias_scale == input_scale * filter_scale. + * * 3: An INT32 value, specifying the implicit padding scheme, has to be one of the + * {@link PaddingCode} values. + * * 4: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 5: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 6: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth_out]. + * For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. + */ + ANEURALNETWORKS_CONV_2D = 3, + + /** Performs a depthwise 2-D convolution operation. + * + * Given an input tensor of shape [batches, height, width, depth_in] and a filter + * tensor of shape [1, filter_height, filter_width, depth_out] containing + * depth_out convolutional filters of depth 1, DEPTHWISE_CONV applies a different + * filter to each input channel (expanding from 1 channel to channel_multiplier channels + * for each), then concatenates the results together. + * + * The output has depth_out = depth_in * depth_multiplier channels. + * The output dimensions are functions of the filter dimensions, stride, and padding. + * + * The values in the output tensor are computed as: + * + * output[b, i, j, k * channel_multiplier + q] = + * sum_{di, dj} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, k] * + * filter[1, di, dj, k * channel_multiplier + q] + * ) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], + * specifying the filter. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should + * also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias + * should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and + * bias_scale == input_scale * filter_scale. + * * 3: An INT32 value, specifying the padding on the left, in the ‘width’ dimension. + * * 4: An INT32 value, specifying the padding on the right,in the ‘width’ dimension. + * * 5: An INT32 value, specifying the padding on the top, in the ‘height’ dimension. + * * 6: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension. + * * 7: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 8: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 9: An INT32 value, specifying the depthwise multiplier. + * * 10: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], + * specifying the filter. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should + * also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias + * should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and + * bias_scale == input_scale * filter_scale. + * * 3: An INT32 value, specifying the implicit padding scheme, has to be one of the + * {@link PaddingCode} values. + * * 4: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 5: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 6: An INT32 value, specifying the depthwise multiplier. + * * 7: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth_out]. + * For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. + */ + ANEURALNETWORKS_DEPTHWISE_CONV_2D = 4, + + /** Rearranges data from depth into blocks of spatial data. + * + * More specifically, this op outputs a copy of the input tensor where values from + * the depth dimension are moved in spatial blocks to the height and width dimensions. + * The value block_size indicates the input block size and how the data is moved. + * + * Chunks of data of size block_size * block_size from depth are rearranged into + * non-overlapping blocks of size block_size x block_size. + * + * The width of the output tensor is input_depth * block_size, whereas the height is + * input_height * block_size. + * The depth of the input tensor must be divisible by block_size * block_size + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: An INT32 value, specifying the block_size. block_size must be >=1 and + * block_size * block_size must be a divisor of the input depth. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batch, height*block_size, width*block_size, + * depth/(block_size*block_size)]. + */ + ANEURALNETWORKS_DEPTH_TO_SPACE = 5, + + /** Dequantizes the input tensor. + * + * The formula is: + * + * output = (input - zeroPoint) * scale. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor of type {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}. + * + * Outputs: + * * 0: The output tensor of same shape as input0, but with type + * {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + */ + ANEURALNETWORKS_DEQUANTIZE = 6, + + /** Looks up sub-tensors in the input tensor. + * + * This operator takes for input a tensor of values (Values) and + * a one-dimensional tensor of selection indices (Lookups). + * The output tensor is the concatenation of sub-tensors of Values as + * selected by Lookups. + * + * Think of Values as being sliced along its first dimension: + * The entries in Lookups select which slices are concatenated together + * to create the output tensor. + * + * For example, if Values has shape of [40, 200, 300] and + * Lookups has shape of [3], we would expect all three values + * found in Lookups to be between 0 and 39. The resulting tensor will + * have shape of [3, 200, 300]. + * + * If a value in Lookups is out of bounds, the operation will fail + * and an error will be reported. + * + * Inputs: + * * 0: Lookups. A 1-D tensor of {@link ANEURALNETWORKS_TENSOR_INT32} type. + * The values are indices into the first dimension of Values. + * * 1: Values. An n-D tensor, where n >= 2, from which sub-tensors are + * extracted. + * + * Output: + * * 0: A n-D tensor with the same rank and shape as the Values + * tensor, except for the first dimension which has the same size + * as Lookups' only dimension. + */ + ANEURALNETWORKS_EMBEDDING_LOOKUP = 7, + + /** Computes element-wise floor() on the input tensor. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * + * Outputs: + * * 0: The output tensor, of the same type and dimensions as the input tensor. + */ + ANEURALNETWORKS_FLOOR = 8, + + /** Denotes a fully (densely) connected layer, which connects all elements in the input + * tensor with each element in the output tensor. + * + * This layer implements the operation: + * + * outputs = activation(inputs * weights’ + bias) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. If rank is greater than 2, then it gets flattened to + * a 2-D Tensor. The 2-D Tensor is handled as if dimensions corresponded to shape + * [batch_size, input_size], where “batch_size” corresponds to the batching dimension, + * and “input_size” is the size of the input. + * * 1: A 2-D tensor, specifying the weights, of shape [num_units, input_size], where + * "num_units" corresponds to the number of output nodes. + * * 2: A 1-D tensor, of shape [num_units], specifying the bias. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_FLOAT32} type, the bias should + * also be of {@link ANEURALNETWORKS_TENSOR_FLOAT32}. + * For input tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the bias + * should be of {@link ANEURALNETWORKS_TENSOR_INT32}, with zeroPoint of 0 and + * bias_scale == input_scale * filter_scale. + * * 3: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output tensor, of shape [batch_size, num_units]. + * For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. + */ + ANEURALNETWORKS_FULLY_CONNECTED = 9, + + /** Looks up sub-tensors in the input tensor using a key-value map. + * + * This operator takes for input a tensor of values (Values), + * a one-dimensional tensor of selection values (Lookups) and + * a one-dimensional tensor that maps these values to Values + * indexes. The output tensor is the concatenation of sub-tensors of + * Values as selected by Lookups via Keys. + * + * Think of Values as being sliced along its outer-most dimension. + * The output is a concatenation of selected slices, with one slice + * for each entry of Lookups. The slice selected is the one at the + * same index as the Maps entry that matches the value in Lookups. + * + * For a hit, the corresponding sub-tensor of Values is included + * in the Output tensor. For a miss, the corresponding sub-tensor in + * Output will have zero values. + * + * For example, if Values has shape of [40, 200, 300], + * Keys should have a shape of [40]. If Lookups tensor has shape + * of [3], we're concatenating three slices, so the resulting tensor + * will have the shape of [3, 200, 300]. If the first entry in + * Lookups has the value 123456, we'll look for that value in Keys tensor. + * If the sixth entry of Keys contains 123456, we'll select the sixth + * slice of Values. If no entry in Keys has 123456, a slice of zeroes + * will be concatenated. + * + * Inputs: + * * 0: Lookups. A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with shape [ k ]. + * * 1: Keys. A 1-D {@link ANEURALNETWORKS_TENSOR_INT32} tensor with shape [ n ]; + * Keys and Values pair represent a map, i.e., the ith element + * in Keys (Keys[i]) is the key to select the ith sub-tensor + * in Values (Values[i]), where 0 <= i <= n-1. + * Keys tensor *MUST* be sorted in ascending order. + * * 2: Values. A tensor with shape of [ n, … ]; i.e., the first dimension must be n. + * + * Outputs: + * * 0: Output. A tensor with shape [ k …]. + * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup + * hits (True) or not (False). + * Stored as {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} with offset 0 and scale 1.0f. + * A non-zero byte represents True, a hit. A zero indicates otherwise. + */ + ANEURALNETWORKS_HASHTABLE_LOOKUP = 10, + + /** Applies L2 normalization along the depth dimension. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * input[batch, row, col, channel] / + * sqrt(sum_{c} pow(input[batch, row, col, c], 2)) + * + * For input tensor with more dimensions, independently normalizes each 1-D slice along dimension dim. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" data layout (i.e., Num_samples, Height, Width, and Channels). + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth]. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth]. + */ + ANEURALNETWORKS_L2_NORMALIZATION = 11, + + /** Performs an 2-D L2 pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and padding. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * sqrt(sum_{i, j} pow(input[batch, row + i, col + j, channel], 2) / sum(1)) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension. + * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension. + * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension. + * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension. + * * 5: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 6: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 7: An INT32 value, specifying the filter width. + * * 8: An INT32 value, specifying the filter height. + * * 9: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the + * {@link PaddingCode} values. + * * 2: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 3: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 4: An INT32 value, specifying the filter width. + * * 5: An INT32 value, specifying the filter height. + * * 6: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth]. + */ + ANEURALNETWORKS_L2_POOL_2D = 12, + + /** Applies Local Response Normalization along the depth dimension. + * + * The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the last + * dimension), and each vector is normalized independently. Within a given vector, + * each component is divided by the weighted, squared sum of inputs within depth_radius. + * + * The output is calculated using this formula: + * + * sqr_sum[a, b, c, d] = + * sum(pow(input[a, b, c, d - depth_radius : d + depth_radius + 1], 2) + * output = input / pow((bias + alpha * sqr_sum), beta) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the radius of the normalization window. + * * 2: A FLOAT32 value, specifying the bias, must not be zero. + * * 3: A FLOAT32 value, specifying the scale factor, alpha. + * * 4: A FLOAT32 value, specifying the exponent, beta. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION = 13, + + /** Computes sigmoid activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = 1 / (1 + exp(-input)) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, + * the scale must be 1.f / 256 and the zeroPoint must be 0. + */ + ANEURALNETWORKS_LOGISTIC = 14, + + /** + * Projects an input to a bit vector via locality senstive hashing. + * + * Inputs: + * * 0: Hash functions. Dim.size == 2, DataType: Float. + * Tensor[0].Dim[0]: Number of hash functions. + * Tensor[0].Dim[1]: Number of seeds per hash functions. + * Tensor[0].Dim[1] <= 32 in sparse case. + * + * * 1: Input. Dim.size >= 1, no restriction on DataType. + * * 2: Weight. Optional. Dim.size == 1, DataType: Float. + * If not set, each input element is considered to have the same weight of + * 1.0. + * Tensor[1].Dim[0] == Tensor[2].Dim[0] + * * 3: Type: + * Sparse: Value LSHProjectionType_SPARSE(=1). + * Computed bit vector is considered to be sparse. + * Each output element is an int32 made up of multiple bits computed from + * hash functions. + * + * Dense: Value LSHProjectionType_DENSE(=2). + * Computed bit vector is considered to be dense. Each output element + * represents a bit and can take the value of either 0 or 1. + * + * Outputs: + * * 0: If the projection type is sparse: + * Output.Dim == { Tensor[0].Dim[0] } + * A tensor of int32 that represents hash signatures. + * If the projection type is Dense: + * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] } + * A flattened tensor that represents projected bit vectors. + */ + ANEURALNETWORKS_LSH_PROJECTION = 15, + + /** + * Long short-term memory unit (LSTM) recurrent network layer. + * + * The default non-peephole implementation is based on: + * http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf + * S. Hochreiter and J. Schmidhuber. "Long Short-Term Memory". Neural + * Computation, 9(8):1735-1780, 1997. + * + * The peephole implementation is based on: + * https://research.google.com/pubs/archive/43905.pdf + * Hasim Sak, Andrew Senior, and Francoise Beaufays. "Long short-term memory + * recurrent neural network architectures for large scale acoustic modeling." + * INTERSPEECH, 2014. + * + * The coupling of input and forget gate (CIFG) is based on: + * http://arxiv.org/pdf/1503.04069.pdf + * Greff et al. "LSTM: A Search Space Odyssey" + * + * The class has the following independently optional inputs: + * * If input gate (if CIFG): “input_to_forget_weights”, + * “recurrent_to_input_weights”, “cell_to_input_weights”, “input_gate_bias”. + * * If no peephole connections: “cell_to_input_weights”, + * “cell_to_forget_weights”, “cell_to_output_weights”. + * * If no projection layer: “projection_weights” and “projection_bias”. + * * If no projection bias: “projection_bias”. + * + * Supported tensor types (type T): + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Inputs: + * * 0: Input. + * A 2-D tensor of type T, of shape [batch_size, input_size], where + * “batch_size” corresponds to the batching dimension, and “input_size” + * is the size of the input. + * * 1: input_to_input_weights. + * A 2-D tensor of type T, of shape [num_units, input_size], where + * “num_units” corresponds to the number of cell units. + * * 2: input_to_forget_weights. + * A 2-D tensor of type T, of shape [num_units, input_size]. + * * 3: input_to_cell_weights. + * A 2-D tensor of type T, of shape [num_units, input_size]. + * * 4: input_to_output_weights. + * A 2-D tensor of type T, of shape [num_units, input_size]. + * * 5: recurrent_to_input_weights. + * A 2-D tensor of type T, of shape [num_units, output_size], where + * “output_size” corresponds to either the number of cell units (i.e., + * “num_units”), or the second dimension of the “projection_weights”, if + * defined. + * * 6: recurrent_to_forget_weights. + * A 2-D tensor of type T, of shape [num_units, output_size]. + * * 7: recurrent_to_cell_weights. + * A 2-D tensor of type T, of shape [num_units, output_size]. + * * 8: recurrent_to_output_weights. + * A 2-D tensor of type T, of shape [num_units, output_size]. + * * 9: cell_to_input_weights. + * A 1-D tensor of type T, of shape [num_units]. + * * 10:cell_to_forget_weights. + * A 1-D tensor of type T, of shape [num_units]. + * * 11:cell_to_output_weights. + * A 1-D tensor of type T, of shape [num_units]. + * * 12:input_gate_bias. + * A 1-D tensor of type T, of shape [num_units]. + * * 13:forget_gate_bias. + * A 1-D tensor of type T, of shape [num_units]. + * * 14:cell_bias. + * A 1-D tensor of type T, of shape [num_units]. + * * 15:output_gate_bias. + * A 1-D tensor of type T, of shape [num_units]. + * * 16:projection_weights. + * A 2-D tensor of type T, of shape [output_size, num_units]. + * * 17:projection_bias. + * A 1-D tensor of type T, of shape [output_size]. + * * 18: output_state (in). + * A 2-D tensor of type T, of shape [batch_size, output_size]. + * * 19: cell_state (in). + * A 2-D tensor of type T, of shape [batch_size, num_units]. + * * 20:fused_activation_function. + * An optional {@link FuseCode} value indicating the activation + * function. + * If “NONE” is specified then it results in a linear activation. + * * 21:cell_clip. + * A clipping threshold for the cell state, such that values are bound + * within [-cell_clip, cell_clip]. If set to 0.0 then clipping is + * disabled. + * * 22:proj_clip. + * A clipping threshold for the output from the projection layer, such + * that values are bound within [-proj_clip, proj_clip]. If set to 0.0 + * then clipping is disabled. + * + * Outputs: + * * 0: scratch_buffer. + * A 3-D tensor of type T, of shape [batch_size, num_cell, 4]. + * * 1: output_state (out). + * A 2-D tensor of type T, of shape [batch_size, output_size]. + * * 2: cell_state (out). + * A 2-D tensor of type T, of shape [batch_size, num_units]. + * * 3: output. + * A 2-D tensor of type T, of shape [batch_size, output_size]. This is + * effectively the same as the current “output_state” value. + */ + ANEURALNETWORKS_LSTM = 16, + + /** Performs an 2-D max pooling operation. + * + * The output dimensions are functions of the filter dimensions, stride, and padding. + * + * The values in the output tensor are computed as: + * + * output[batch, row, col, channel] = + * max_{i, j} (input[batch, row + i, col + j, channel]) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Both explicit padding and implicit padding are supported. + * + * Inputs (explicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the padding on the left, in the ‘width’ dimension. + * * 2: An INT32 value, specifying the padding on the right,in the ‘width’ dimension. + * * 3: An INT32 value, specifying the padding on the top, in the ‘height’ dimension. + * * 4: An INT32 value, specifying the padding on the bottom, in the ‘height’ dimension. + * * 5: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 6: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 7: An INT32 value, specifying the filter width. + * * 8: An INT32 value, specifying the filter height. + * * 9: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Inputs (implicit padding): + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the implicit padding scheme, has to be one of the + * {@link PaddingCode} values. + * * 2: An INT32 value, specifying the stride when walking through input + * in the ‘width’ dimension. + * * 3: An INT32 value, specifying the stride when walking through input + * in the ‘height’ dimension. + * * 4: An INT32 value, specifying the filter width. + * * 5: An INT32 value, specifying the filter height. + * * 6: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, out_height, out_width, depth]. + */ + ANEURALNETWORKS_MAX_POOL_2D = 17, + + /** Multiplies two tensors, element-wise. + * + * Takes two input tensors of identical type and compatible dimensions. The output + * is the product of both input tensors, optionally modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the resulting output is the maximum size along each dimension of the + * input operands. It starts with the trailing dimensions, and works its way forward. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: A tensor. + * * 1: A tensor of the same type, and compatible dimensions as input0. + * * 2: An INT32 value, and has to be one of the {@link FuseCode} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * * 0: The product, a tensor of the same type as input0. + * For output tensor of {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, the following + * condition must be satisfied: output_scale > input1_scale * input2_scale. + */ + ANEURALNETWORKS_MUL = 18, + + /** Computes rectified linear activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = max(0, input) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ANEURALNETWORKS_RELU = 19, + + /** Computes rectified linear 1 activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = min(1.f, max(-1.f, input)) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ANEURALNETWORKS_RELU1 = 20, + + /** Computes rectified linear 6 activation on the input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = min(6, max(0, input)) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ANEURALNETWORKS_RELU6 = 21, + + /** Reshapes a tensor. + * + * Given tensor, this operation returns a tensor that has the same values as tensor, + * but with a newly specified shape. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the tensor to be reshaped. + * * 1: A 1-D tensor of type {@link ANEURALNETWORKS_TENSOR_INT32}, defining the shape + * of the output tensor. The number of elements implied by shape must be the same + * as the number of elements in the input tensor. + * + * Outputs: + * * 0: The output tensor, of shape specified by the input shape. + */ + ANEURALNETWORKS_RESHAPE = 22, + + /** Resizes images to given size using the bilinear interpretation. + * + * Resized images will be distorted if their output aspect ratio is not the same as + * input aspect ratio. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying the input. + * * 1: An INT32 value, specifying the output height of the output tensor. + * * 2: An INT32 value, specifying the output width of the output tensor. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batches, new_height, new_width, depth]. + */ + ANEURALNETWORKS_RESIZE_BILINEAR = 23, + + /** + * A basic recurrent neural network layer. + * + * This layer implements the operation: + * outputs = state = activation(inputs * input_weights + state * recurrent_weights + bias) + * + * Where: + * * “input_weights” is a weight matrix that multiplies the inputs; + * * “recurrent_weights” is a weight matrix that multiplies the current + * “state” which itself is the output from the previous time step + * computation; + * * “bias” is a bias vector (added to each output vector in the batch); + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * Supported tensor types (Type T): + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Inputs: + * * 0: input. + * A 2-D tensor of type T, of shape [batch_size, input_size], where + * “batch_size” corresponds to the batching dimension, and “input_size” is + * the size of the input. + * * 1: weights. + * A 2-D tensor of type T, of shape [num_units, input_size], where + * “num_units” corresponds to the number of units. + * * 2: recurrent_weights. + * A 2-D tensor of type T, of shape [num_units, num_units], with columns + * corresponding to the weights from each unit. + * * 3: bias. + * A 1-D tensor of type T, of shape [num_units]. + * * 4: hidden state (in). + * A 2-D tensor of type T, of shape [batch_size, num_units]. + * * 5: fused_activation_function. + * An optional {@link FuseCode} value indicating the activation + * function. If “NONE” is specified then it results in a linear + * activation. + * + * Outputs: + * * 0: hidden state (out). + * A 2-D tensor of type T, of shape [batch_size, num_units]. + * + * * 1: output. + * A 2-D tensor of type T, of shape [batch_size, num_units]. This is + * effectively the same as the current state value. + */ + ANEURALNETWORKS_RNN = 24, + + /** Computes the softmax activation on the input tensor element-wise, per batch, by + * normalizing the input vector so the maximum coefficient is zero. + * + * The output is calculated using this formula: + * + * output[batch, i] = + * exp((input[batch, i] - max(input[batch, :])) * beta) / + * sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)} + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 2 or 4. + * + * Inputs: + * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped. + * * 1: A FLOAT32 value, specifying the positive scaling factor for the exponent, beta. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + * For {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} type, + * the scale must be 1.f / 256 and the zeroPoint must be 0. + */ + ANEURALNETWORKS_SOFTMAX = 25, + + /** Rearranges blocks of spatial data, into depth. + * + * More specifically, this op outputs a copy of the input tensor where values from + * the height and width dimensions are moved to the depth dimension. + * The value block_size indicates the input block size and how the data is moved. + * + * Chunks of data of size block_size * block_size from depth are rearranged into + * non-overlapping blocks of size block_size x block_size. + * + * The depth of the output tensor is input_depth * block_size * block_size. + * The input tensor's height and width must be divisible by block_size. + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: 4, with "NHWC" data layout. + * + * Inputs: + * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], specifying the input. + * * 1: An INT32 value, specifying the block_size. block_size must be >=1 and + * block_size must be a divisor of both the input height and width. + * + * Outputs: + * * 0: The output 4-D tensor, of shape [batch, height/block_size, width/block_size, + * depth*block_size*block_size]. + */ + ANEURALNETWORKS_SPACE_TO_DEPTH = 26, + + /** + * SVDF op is a kind of stateful layer derived from the notion that a + * densely connected layer that's processing a sequence of input frames can + * be approximated by using a singular value decomposition of each of its + * nodes. The implementation is based on: + * + * https://research.google.com/pubs/archive/43813.pdf + * + * P. Nakkiran, R. Alvarez, R. Prabhavalkar, C. Parada. + * “Compressing Deep Neural Networks using a Rank-Constrained Topology”. + * INTERSPEECH, 2015. + * + * It processes the incoming input using a 2-stage filtering mechanism: + * * stage 1 performs filtering on the "features" dimension, whose outputs get + * pushed into a memory of fixed-size memory_size. + * * stage 2 performs filtering on the "time" dimension of the memory_size + * memoized outputs of stage 1. + * + * Specifically, for rank 1, this layer implements the operation: + * + * memory = push(conv1d(inputs, weights_feature, feature_dim, + * "ANEURALNETWORKS_PADDING_VALID")); + * outputs = activation(memory * weights_time + bias); + * + * Where: + * * “weights_feature” is a weights matrix that processes the inputs (by + * convolving the input with every “feature filter”), and whose outputs get + * pushed, stacked in order, into the fixed-size “memory” (the oldest entry + * gets dropped); + * * “weights_time” is a weights matrix that processes the “memory” (by a + * batched matrix multiplication on the num_units); + * * “bias” is an optional bias vector (added to each output vector in the + * batch); and + * * “activation” is the function passed as the “fused_activation_function” + * argument (if not “NONE”). + * + * Each rank adds a dimension to the weights matrices by means of stacking + * the filters. + * + * Supported tensor types (type T): + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Inputs: + * * 0: input. + * A 2-D tensor of type T, of shape [batch_size, input_size], where + * “batch_size” corresponds to the batching dimension, and “input_size” is + * the size of the input. + * * 1: weights_feature. + * A 2-D tensor of type T, of shape [num_units, input_size], where + * “num_units” corresponds to the number of units. + * * 2: weights_time. + * A 2-D tensor of type T, of shape [num_units, memory_size], where + * “memory_size” corresponds to the fixed-size of the memory. + * * 3: bias. + * An optional 1-D tensor of type T, of shape [num_units]. + * * 4: state (in). + * A 2-D tensor of type T, of shape [batch_size, (memory_size - 1) * num_units * rank]. + * * 5: rank. + * The rank of the SVD approximation. + * * 6: fused_activation_function. + * An optional {@link FuseCode} value indicating the activation function. + * If “NONE” is specified then it results in a linear activation. + * + * Outputs: + * * 0: state (out). + * A 2-D tensor of type T, of shape [batch_size, (memory_size - 1) * num_units * rank]. + * * 1: output. + * A 2-D tensor of type T, of shape [batch_size, num_units]. + */ + ANEURALNETWORKS_SVDF = 27, + + /** Computes hyperbolic tangent of input tensor element-wise. + * + * The output is calculated using this formula: + * + * output = tanh(input) + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4. + * + * Inputs: + * * 0: A tensor, specifying the input. + * + * Outputs: + * * 0: The output tensor of same shape as input0. + */ + ANEURALNETWORKS_TANH = 28, + + /** + * Element-wise division of two tensors. + * + * Takes two input tensors of identical type and compatible dimensions. The output + * is the result of dividing the first input tensor by the second, optionally + * modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the input operands. + * It starts with the trailing dimensions, and works its way forward. + * + * Example: + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * 0: An n-D tensor, specifying the first input. + * 1: A tensor of the same type, and compatible dimensions as input0. + * 2: An INT32 value, and has to be one of the {@link FusedActivationFunc} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * 0: A tensor of the same type as input0. + */ + ANEURALNETWORKS_DIV = 30, + + /** + * Pads a tensor. + * + * This operation pads a tensor according to the specified paddings. + * + * Supported tensor {@link OperandCode}: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be padded. + * * 1: A 2-D Tensor of {@link ANEURALNETWORKS_TENSOR_INT32}, the paddings + * for each spatial dimension of the input tensor. The shape of the + * tensor must be {rank(input0), 2}. + * padding[i, 0] specifies the number of elements to be padded in the + * front of dimension i. + * padding[i, 1] specifies the number of elements to be padded after the + * end of dimension i. + * + * Outputs: + * * 0: A tensor of the same {@link OperandCode} as input0. The + * output tensor has the same rank as input0, and each + * dimension of the output tensor has the same size as the + * corresponding dimension of the input tensor plus the size + * of the padding: + * output0.dimension[i] = + * padding[i, 0] + input0.dimension[i] + padding[i, 1] + * + * Available since API level 28. + */ + ANEURALNETWORKS_PAD = 32, + + /** + * Extracts a strided slice of a tensor. + * + * Roughly speaking, this op extracts a slice of size (end - begin) / stride + * from the given input tensor. Starting at the location specified by begin + * the slice continues by adding stride to the index until all dimensions + * are not less than end. Note that a stride can be negative, which causes a + * reverse slice. + * + * Supported tensor {@link OperandCode}: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * * 0: An n-D tensor, specifying the tensor to be sliced. + * * 1: A 1-D Tensor of {@link ANEURALNETWORKS_TENSOR_INT32}, the starts of + * the dimensions of the input tensor to be sliced. The length must be + * of rank(input0). + * * 2: A 1-D Tensor of {@link ANEURALNETWORKS_TENSOR_INT32}, the ends of + * the dimensions of the input tensor to be sliced. The length must be + * of rank(input0). + * * 3: A 1-D Tensor of {@link ANEURALNETWORKS_TENSOR_INT32}, the strides of + * the dimensions of the input tensor to be sliced. The length must be + * of rank(input0). + * * 4: An {@link ANEURALNETWORKS_INT32} scalar, begin_mask. If the ith bit + * of begin_mask is set, begin[i] is ignored and the fullest possible + * range in that dimension is used instead. + * * 5: An {@link ANEURALNETWORKS_INT32} scalar, end_mask. If the ith bit of + * end_mask is set, end[i] is ignored and the fullest possible range in + * that dimension is used instead. + * * 6: An {@link ANEURALNETWORKS_INT32} scalar, shrink_axis_mask. An int32 + * mask. If the ith bit of shrink_axis_mask is set, it implies that the + * ith specification shrinks the dimensionality by 1. A slice of size 1 + * starting from begin[i] in the dimension must be preserved. + * + * Outputs: + * * 0: A tensor of the same {@link OperandCode} as input0. + */ + ANEURALNETWORKS_STRIDED_SLICE = 35, + + /** + * Element-wise subtraction of two tensors. + * + * Takes two input tensors of identical type and compatible dimensions. The output + * is the result of subtracting the second input tensor from the first one, optionally + * modified by an activation function. + * + * Two dimensions are compatible when: + * 1. they are equal, or + * 2. one of them is 1 + * + * The size of the output is the maximum size along each dimension of the input operands. + * It starts with the trailing dimensions, and works its way forward. + * + * Example: + * input1.dimension = {4, 1, 2} + * input2.dimension = {5, 4, 3, 1} + * output.dimension = {5, 4, 3, 2} + * + * Supported tensor types: + * * {@link ANEURALNETWORKS_TENSOR_FLOAT32} + * + * Supported tensor rank: up to 4 + * + * Inputs: + * 0: An n-D tensor, specifying the first input. + * 1: A tensor of the same type, and compatible dimensions as input0. + * 2: An INT32 value, and has to be one of the {@link FusedActivationFunc} values. + * Specifies the activation to invoke on the result of each addition. + * + * Outputs: + * 0: A tensor of the same type as input0. + */ + ANEURALNETWORKS_SUB = 36, +} OperationCode; + +/** + * Fused activation function types. + * + */ +typedef enum { + /** NO fused activation function. */ + ANEURALNETWORKS_FUSED_NONE = 0, + /** Fused ReLU activation function. */ + ANEURALNETWORKS_FUSED_RELU = 1, + /** Fused ReLU1 activation function. */ + ANEURALNETWORKS_FUSED_RELU1 = 2, + /** Fused ReLU6 activation function. */ + ANEURALNETWORKS_FUSED_RELU6 = 3, +} FuseCode; + +/** + * Implicit padding algorithms. + * + */ +typedef enum { + /** + * SAME padding. + * Padding on both ends are the "same": + * padding_to_beginning = total_padding / 2 + * padding_to_end = (total_padding + 1)/2. + * i.e., for even number of padding, padding to both ends are exactly + * the same; for odd number of padding, padding to the ending is bigger + * than the padding to the beginning by 1. + * + * total_padding is a function of input, stride and filter size. + * It could be computed as follows: + * out_size = (input + stride - 1) / stride; + * needed_input = (out_size - 1) * stride + filter_size + * total_padding = max(0, needed_input - output_size) + * The computation is the same for the horizontal and vertical directions. + */ + ANEURALNETWORKS_PADDING_SAME = 1, + + /** + * VALID padding. + * No padding. When the input size is not evenly divisible by + * the filter size, the input at the end that could not fill + * the whole filter tile will simply be ignored. + */ + ANEURALNETWORKS_PADDING_VALID = 2, +} PaddingCode; + +/** + * Execution preferences. + */ +typedef enum { + /** + * Prefer executing in a way that minimizes battery drain. + * This is desirable for compilations that will be executed often. + */ + ANEURALNETWORKS_PREFER_LOW_POWER = 0, + /** + * Prefer returning a single answer as fast as possible, even if this causes + * more power consumption. + */ + ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER = 1, + /** + * Prefer maximizing the throughput of successive frames, for example when + * processing successive frames coming from the camera. + */ + ANEURALNETWORKS_PREFER_SUSTAINED_SPEED = 2, +} PreferenceCode; + +/** + * Result codes. + */ +typedef enum { + ANEURALNETWORKS_NO_ERROR = 0, + ANEURALNETWORKS_OUT_OF_MEMORY = 1, + ANEURALNETWORKS_INCOMPLETE = 2, + ANEURALNETWORKS_UNEXPECTED_NULL = 3, + ANEURALNETWORKS_BAD_DATA = 4, + ANEURALNETWORKS_OP_FAILED = 5, + ANEURALNETWORKS_UNMAPPABLE = 5, + ANEURALNETWORKS_BAD_STATE = 6, +} ResultCode; + +/** + * For {@link ANeuralNetworksModel_setOperandValue}, values with a + * length smaller or equal to this will be immediately copied into + * the model. The size is in bytes. + */ +enum { + ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES = 128 +}; + +/** + * ANeuralNetworksMemory is an opaque type that represents memory. + * + * This type is used to represent shared memory, memory mapped files, + * and similar memories. + * + * By using shared memory, a program can efficiently communicate to the + * runtime and drivers the tensors that define a model. See + * {@link ANeuralNetworksModel_setOperandValueFromMemory}. An application + * should typically create one shared memory object that contains every tensor + * needed to define a model. {@link ANeuralNetworksMemory_createFromFd} can be + * used to create shared memory from a file handle. {@link ANeuralNetworksMemory_createShared} + * can be used to directly created shared memory. + * + * Memory objects can also be used to specify the input and output arguments of + * an execution. See {@link ANeuralNetworksExecution_setInputFromMemory} + * and {@link ANeuralNetworksExecution_setOutputFromMemory}. + */ +typedef struct ANeuralNetworksMemory ANeuralNetworksMemory; + +/** + * ANeuralNetworksModel is an opaque type that contains a description of the + * mathematical operations that constitute the model. + * + *

The model will be built by calling

    + *
  • {@link ANeuralNetworksModel_create},
  • + *
  • {@link ANeuralNetworksModel_addOperation},
  • + *
  • {@link ANeuralNetworksModel_addOperand},
  • + *
+ * + * A model is completed by calling {@link ANeuralNetworksModel_finish}. + * A model is destroyed by calling {@link ANeuralNetworksModel_free}. + * + *

A model cannot be modified once {@link ANeuralNetworksModel_finish} + * has been called on it.

+ * + *

It is the application's responsibility to make sure that only one thread + * modifies a model at a given time. It is however safe for more than one + * thread to use the model once {@link ANeuralNetworksModel_finish} has returned.

+ * + *

It is also the application's responsibility to ensure that there are no other + * uses of the model after calling {@link ANeuralNetworksModel_free}. + * This includes any compilation or execution object created using the model.

+ */ +typedef struct ANeuralNetworksModel ANeuralNetworksModel; + +/** + * ANeuralNetworksCompilation is an opaque type that can be used to compile + * a machine learning model. + * + *

To use:

    + *
  • Create a new compilation instance by calling the + * {@link ANeuralNetworksCompilation_create} function.
  • + *
  • Set any desired properties on the compilation (for example, + * {@link ANeuralNetworksCompilation_setPreference}).
  • + *
  • Complete the compilation with {@link ANeuralNetworksCompilation_finish}.
  • + *
  • Use the compilation as many times as needed + * with {@link ANeuralNetworksExecution_create}.
  • + *
  • Destroy the compilation with {@link ANeuralNetworksCompilation_free} + * once all executions using the compilation have completed.

+ * + * A compilation is completed by calling {@link ANeuralNetworksCompilation_finish}. + * A compilation is destroyed by calling {@link ANeuralNetworksCompilation_free}. + * + *

A compilation cannot be modified once {@link ANeuralNetworksCompilation_finish} + * has been called on it.

+ * + *

It is the application's responsibility to make sure that only + * one thread modifies a compilation at a given time. It is however + * safe for more than one thread to use the compilation once + * {@link ANeuralNetworksCompilation_finish} has returned.

+ * + *

It is also the application's responsibility to ensure that there are no other + * uses of the compilation after calling {@link ANeuralNetworksCompilation_free}. + * This includes any execution object created using the compilation.

+ */ +typedef struct ANeuralNetworksCompilation ANeuralNetworksCompilation; + +/** + * ANeuralNetworksExecution is an opaque type that can be used to apply a machine + * learning model to a set of inputs. + * + *

To use:

    + *
  • Create a new execution instance by calling the + * {@link ANeuralNetworksExecution_create} function.
  • + *
  • Associate data to the model inputs with + * {@link ANeuralNetworksExecution_setInput} or + * {@link ANeuralNetworksExecution_setInputFromMemory}.
  • + *
  • Associate output buffers to the model outputs with + * {@link ANeuralNetworksExecution_setOutput} or + * {@link ANeuralNetworksExecution_setOutputFromMemory}.
  • + *
  • Apply the model with {@link ANeuralNetworksExecution_startCompute}.
  • + *
  • Wait for the execution to complete with {@link + * ANeuralNetworksEvent_wait}.
  • + *
  • Destroy the execution with + * {@link ANeuralNetworksExecution_free}.

+ * + *

An execution cannot be modified once {@link ANeuralNetworksExecution_startCompute} + * has been called on it.

+ * + *

An execution can be applied to a model with + * {@link ANeuralNetworksExecution_startCompute} only once. Create new executions + * to do new evaluations of the model.

+ * + *

It is the application's responsibility to make sure that only one thread + * modifies an execution at a given time. It is however safe for more than one + * thread to use {@link ANeuralNetworksEvent_wait} at the same time.

+ * + *

It is also the application's responsibility to ensure that there are no other + * uses of the request after calling {@link ANeuralNetworksExecution_free}.

+ */ +typedef struct ANeuralNetworksExecution ANeuralNetworksExecution; + +/** + * ANeuralNetworksOperandType describes the type of an operand. + * This structure is used to describe both scalars and tensors. + */ +typedef struct ANeuralNetworksOperandType { + /** The data type, e.g ANEURALNETWORKS_INT8. */ + int32_t type; + /** The number of dimensions. It should be 0 for scalars. */ + uint32_t dimensionCount; + /** The dimensions of the tensor. It should be nullptr for scalars. */ + const uint32_t* dimensions; + /** These two fields are only used for quantized tensors. + * They should be zero for scalars and non-fixed point tensors. + * The dequantized value of each entry is (value - zeroPoint) * scale. + */ + float scale; + int32_t zeroPoint; +} ANeuralNetworksOperandType; + +typedef int32_t ANeuralNetworksOperationType; + +/** + * ANeuralNetworksEvent is an opaque type that represents an event + * that will be signaled once an execution completes. + */ +typedef struct ANeuralNetworksEvent ANeuralNetworksEvent; + + +/** + * Creates a shared memory object from a file descriptor. + * + * The shared memory is backed by a file descriptor via mmap. + * See {@link ANeuralNetworksMemory} for a description on how to use + * this shared memory. + * + * @param size The requested size in bytes. + * Must not be larger than the file size. + * @param prot The desired memory protection for the mapping. + * It is either PROT_NONE or the bitwise OR of one or + * more of the following flags: PROT_READ, PROT_WRITE. + * @param fd The requested file descriptor. + * The file descriptor has to be mmap-able. The file + * descriptor will be duplicated. + * @param offset The offset to the beginning of the file of the area to map. + * The offset has to be aligned to a page size. + * @param memory The memory object to be created. + * Set to NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if the request completed normally. + */ +int ANeuralNetworksMemory_createFromFd(size_t size, int protect, int fd, size_t offset, + ANeuralNetworksMemory** memory); + +/** + * Delete a memory object. + * + * Destroys the object used by the run time to keep track of the memory. + * This will free the underlying actual memory if no other code has open + * handles to this memory. + * + * @param memory The memory object to be freed. + */ +void ANeuralNetworksMemory_free(ANeuralNetworksMemory* memory); + +/** + * Create an empty {@link ANeuralNetworksModel}. + * + *

This only creates the object. Computation is performed once + * {@link ANeuralNetworksExecution_startCompute} is invoked. + * + * The model should be constructed with calls to + * {@link ANeuralNetworksModel_addOperation} and + * {@link ANeuralNetworksModel_addOperand} + * + *

{@link ANeuralNetworksModel_finish} should be called once the model + * has been fully constructed.

+ * + *

{@link ANeuralNetworksModel_free} should be called once the model + * is no longer needed.

+ * + * @param model The {@link ANeuralNetworksModel} to be created. + * Set to NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_create(ANeuralNetworksModel** model); + +/** + * Destroy a model. + * + * The model need not have been finished by a call to + * {@link ANeuralNetworksModel_finish}. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be destroyed. Passing NULL is acceptable and + * results in no operation. + */ +void ANeuralNetworksModel_free(ANeuralNetworksModel* model); + +/** + * Indicate that we have finished modifying a model. Required before + * calling {@link ANeuralNetworksCompilation_create}. + * + * An application is responsible to make sure that no other thread uses + * the model at the same time. + * + * This function must only be called once for a given model. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be finished. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_finish(ANeuralNetworksModel* model); + +/** + * Add an operand to a model. + * + * The order in which the operands are added is important. The first one added + * to a model will have the index value 0, the second 1, etc. These indexes are + * used as operand identifiers in {@link ANeuralNetworksModel_addOperation}, + * {@link ANeuralNetworksExecution_setInput}, + * {@link ANeuralNetworksExecution_setInputFromMemory}, + * {@link ANeuralNetworksExecution_setOutput}, + * {@link ANeuralNetworksExecution_setOutputFromMemory} and + * {@link ANeuralNetworksExecution_setOperandValue}. + * + * To build a model that can accomodate inputs of various sizes, as you may want + * to do for a CNN, set the size of the dimensions that will vary at run time to 0. + * If you do so, provide the full dimensions when calling + * {@link ANeuralNetworksExecution_setInput} or {@link ANeuralNetworksExecution_setInputFromMemory}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been + * called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param type The {@link ANeuralNetworksOperandType} that describes the shape + * of the operand. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_addOperand(ANeuralNetworksModel* model, + const ANeuralNetworksOperandType* type); + +/** + * Sets an operand to a constant value. + * + * Values of length smaller or equal to + * {@link ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES} + * are immediately copied into the model. + * + * For values of length greater than {@link ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES}, + * a pointer to the buffer is stored within the model. The application is responsible + * for not changing the content of this region until all executions using this model + * have completed. As the data may be copied during processing, modifying the data + * after this call yields undefined results. + * + * For large tensors, using {@link ANeuralNetworksModel_setOperandValueFromMemory} + * is likely to be more efficient. + * + * To indicate that an optional operand should be considered missing, + * pass nullptr for buffer and 0 for length. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been + * called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param index The index of the model operand we're setting. + * @param buffer A pointer to the data to use. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_setOperandValue(ANeuralNetworksModel* model, int32_t index, + const void* buffer, size_t length); + +/** + * Sets an operand to a value stored in a memory object. + * + * The content of the memory is not copied. A reference to that memory is stored + * inside the model. The application is responsible for not changing the content + * of the memory region until all executions using this model have completed. + * As the data may be copied during processing, modifying the data after this call + * yields undefined results. + * + * To indicate that an optional operand should be considered missing, + * use {@link ANeuralNetworksModel_setOperandValue} instead, passing nullptr for buffer. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been + * called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @param model The model to be modified. + * @param index The index of the model operand we're setting. + * @param buffer A pointer to the data to use. + * @param memory The memory containing the data. + * @param offset This specifies the location of the data within the memory. + * The offset is in bytes from the start of memory. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_setOperandValueFromMemory(ANeuralNetworksModel* model, int32_t index, + const ANeuralNetworksMemory* memory, + size_t offset, size_t length); + +/** + * Add an operation to a model. + * + * @param model The model to be modified. + * @param type The type of the operation. + * @param inputCount The number of entries in the inputs array. + * @param inputs An array of indexes identifying each operand. + * @param outputCount The number of entries in the outputs array. + * @param outputs An array of indexes identifying each operand. + * + * The operands specified by inputs and outputs must have been + * previously added by calls to {@link ANeuralNetworksModel_addOperand}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been + * called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksModel_addOperation(ANeuralNetworksModel* model, + ANeuralNetworksOperationType type, uint32_t inputCount, + const uint32_t* inputs, uint32_t outputCount, + const uint32_t* outputs); + +/** + * Specfifies which operands will be the model's inputs and outputs. + * + * An operand cannot be used for both input and output. Doing so will + * return an error. + * + * @param model The model to be modified. + * @param inputCount The number of entries in the inputs array. + * @param inputs An array of indexes identifying the input operands. + * @param outputCount The number of entries in the outputs array. + * @param outputs An array of indexes identifying the output operands. + * + * The operands specified by inputs and outputs must have been + * previously added by calls to {@link ANeuralNetworksModel_addOperand}. + * + * Attempting to modify a model once {@link ANeuralNetworksModel_finish} has been + * called will return an error. + * + * See {@link ANeuralNetworksModel} for information on multithreaded usage. + * + */ +int ANeuralNetworksModel_identifyInputsAndOutputs(ANeuralNetworksModel* model, uint32_t inputCount, + const uint32_t* inputs, uint32_t outputCount, + const uint32_t* outputs); + +/** + * Create a {@link ANeuralNetworksCompilation} to compile the given model. + * + *

This only creates the object. Compilation is only performed once + * {@link ANeuralNetworksCompilation_finish} is invoked.

+ * + *

{@link ANeuralNetworksCompilation_finish} should be called once + * all desired properties have been set on the compilation.

+ * + *

{@link ANeuralNetworksModel_free} should be called once the compilation + * is no longer needed.

+ * + *

The provided model must outlive the compilation.

+ * + * The model must already have been finished by a call to + * {@link ANeuralNetworksModel_finish}. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded usage. + * + * @param model The {@link ANeuralNetworksModel} to be compiled. + * @param compilation The newly created object or NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA + * if the model is invalid. + */ +int ANeuralNetworksCompilation_create(ANeuralNetworksModel* model, + ANeuralNetworksCompilation** compilation); + +/** + * Destroy a compilation. + * + * The compilation need not have been finished by a call to + * {@link ANeuralNetworksModel_finish}. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded usage. + * + * @param compilation The compilation to be destroyed. Passing NULL is acceptable and + * results in no operation. + */ +void ANeuralNetworksCompilation_free(ANeuralNetworksCompilation* compilation); + +/** + * Sets the execution preference. + * + *

Provides guidance to the runtime when trade-offs are possible.

+ * + * See {@link ANeuralNetworksCompilation} for information on multithreaded usage. + * + * @param compilation The compilation to be modified. + * @param preference Either {@link PREFER_LOW_POWER}, + * {@link PREFER_SINGLE_FAST_ANSWER}, or + * {@link PREFER_SUSTAINED_SPEED}. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksCompilation_setPreference(ANeuralNetworksCompilation* compilation, + int32_t preference); + +/** + * Indicate that we have finished modifying a compilation. Required before + * calling {@link ANeuralNetworksExecution_create}. + * + * An application is responsible to make sure that no other thread uses + * the compilation at the same time. + * + * This function must only be called once for a given compilation. + * + * See {@link ANeuralNetworksCompilation} for information on multithreaded usage. + * + * @param compilation The compilation to be finished. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksCompilation_finish(ANeuralNetworksCompilation* compilation); + +/** + * Create a {@link ANeuralNetworksExecution} to apply the given compilation. + * This only creates the object. Computation is only performed once + * {@link ANeuralNetworksExecution_startCompute} is invoked. + * + *

The provided compilation must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param compilation The {@link ANeuralNetworksCompilation} to be evaluated. + * @param execution The newly created object or NULL if unsuccessful. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA + * if the compilation is invalid. + */ +int ANeuralNetworksExecution_create(ANeuralNetworksCompilation* compilation, + ANeuralNetworksExecution** execution); + +/** + * Destroy an execution. + * + *

If called on an execution for which + * {@link ANeuralNetworksExecution_startCompute} has been called, the + * function will return immediately but will mark the execution to be deleted + * once the computation completes. The related {@link ANeuralNetworksEvent} + * will be signaled and the {@link ANeuralNetworksEvent_wait} will return + * ANEURALNETWORKS_ERROR_DELETED. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be destroyed. Passing NULL is acceptable and + * results in no operation. + */ +void ANeuralNetworksExecution_free(ANeuralNetworksExecution* execution); + +/** + * Associate a user buffer with an input of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided buffer must outlive the execution.

+ * + * If the input is optional, you can indicate that it is omitted by + * passing nullptr for buffer and 0 for length. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the input argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This should be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other properties of the type must be the same as + * specified in the model. If the type is the same as specified + * when the model was built, NULL can be passed. + * @param buffer The buffer containing the data. + * @param length The length in bytes of the buffer. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the + * name is not recognized or the buffer is too small for the input. + */ +int ANeuralNetworksExecution_setInput(ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, const void* buffer, + size_t length); + +/** + * Associate part of a memory object with an input of the model of the + * {@link ANeuralNetworksExecution}. + * + *

The provided memory must outlive the execution.

+ * + * If the input is optional, you can indicate that it is omitted by + * using @{Link ANeuralNetworks_setInput} instead, passing nullptr for buffer + * and 0 for length. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the input argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param memory The memory containing the data. + * @param offset This specifies the location of the data whithin the memory. + * The offset is in bytes from the start of memory. + * @param length The size in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the + * name is not recognized or the buffer is too small for the input. + */ +int ANeuralNetworksExecution_setInputFromMemory(ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, + const ANeuralNetworksMemory* memory, size_t offset, + size_t length); + +/** + * Associate a user buffer with an output of the model of the + * {@link ANeuralNetworksExecution}. + * + * If the output is optional, you can indicate that it is omitted by + * passing nullptr for buffer and 0 for length. + * + *

The provided buffer must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the output argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param buffer The buffer where the data is to be written. + * @param length The length in bytes of the buffer. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the + * name is not recognized or the buffer is too small for the output. + */ +int ANeuralNetworksExecution_setOutput(ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, void* buffer, + size_t length); + +/** + * Associate part of a memory object with an output of the model of the + * {@link ANeuralNetworksExecution}. + * + * If the output is optional, you can indicate that it is omitted by + * using @{Link ANeuralNetworks_setOutput} instead, passing nullptr for buffer + * and 0 for length. + * + *

The provided memory must outlive the execution.

+ * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be modified. + * @param index The index of the output argument we are setting. It is + * an index into the lists passed to + * {@link ANeuralNetworksModel_identifyInputsAndOutputs}. It is not + * the index associated with {@link ANeuralNetworksModel_addOperand}. + * @param type The type of the operand. This can be used to specify the + * dimensions that were set to 0 when the operand was added to the + * model. All other values must be the same as specified in the + * model. If the type is the same as specified when the model + * was built, NULL can be passed. + * @param memory The memory where the data is to be stored. + * @param offset This specifies the location of the data whithin the memory. + * The offset is in bytes from the start of memory. + * @param length The length in bytes of the data value. + * + * @return ANEURALNETWORKS_NO_ERROR if successful, ANEURALNETWORKS_BAD_DATA if the + * name is not recognized or the buffer is too small for the output. + */ +int ANeuralNetworksExecution_setOutputFromMemory(ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, + const ANeuralNetworksMemory* memory, size_t offset, + size_t length); + +/** + * Schedule evaluation of the execution. + * + *

Schedules evaluation of the execution. Once the model has been + * applied and the outputs are ready to be consumed, the returned event will be + * signaled. Use {@link ANeuralNetworksEvent_wait} to wait for that event. + *

+ * + * Multiple executions can be scheduled and evaluated concurrently. The + * runtime makes no guarantee on the ordering of completion of + * executions. If it's important to the application, the application + * should enforce the ordering by using + * {@link ANeuralNetworksEvent_wait}. + * + * ANeuralNetworksEvent_wait must be called to recuperate the resources used + * by the execution. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @param execution The execution to be scheduled and executed. + * @param event The event that will be signaled on completion. event is set to + * NULL if there's an error. + * + * @return ANEURALNETWORKS_NO_ERROR if successful. + */ +int ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution* execution, + ANeuralNetworksEvent** event); + +/** + * Waits until the execution completes. + * + * More than one thread can wait on an event. When the execution completes, + * all threads will be released. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + * + * @return ANEURALNETWORKS_NO_ERROR if the execution completed normally. + */ +int ANeuralNetworksEvent_wait(ANeuralNetworksEvent* event); + +/** + * Destroys the event. + * + * See {@link ANeuralNetworksExecution} for information on multithreaded usage. + */ +void ANeuralNetworksEvent_free(ANeuralNetworksEvent* event); + +__END_DECLS + +#endif // __ANDROID_API__ >= 27 + +#endif // ANDROID_ML_NN_RUNTIME_NEURAL_NETWORKS_H + +/** @} */ diff --git a/compiler/ann-ref/.FORMATDENY b/compiler/ann-ref/.FORMATDENY new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/ann-ref/CMakeLists.txt b/compiler/ann-ref/CMakeLists.txt new file mode 100644 index 00000000000..0f3822514bf --- /dev/null +++ b/compiler/ann-ref/CMakeLists.txt @@ -0,0 +1,32 @@ +nnas_find_package(Eigen QUIET) + +if(NOT Eigen_FOUND) + return() +endif(NOT Eigen_FOUND) + +nnas_find_package(GEMMLowp QUIET) + +if(NOT GEMMLowp_FOUND) + return() +endif(NOT GEMMLowp_FOUND) + +nnas_include(TargetRequire) + +TargetRequire_Assert(ann_api eigen gemmlowp) + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +function(ann_ref_configure TARGET) + target_include_directories(${TARGET} PRIVATE src) + target_link_libraries(${TARGET} PRIVATE ann_api) + target_link_libraries(${TARGET} PRIVATE eigen) + target_link_libraries(${TARGET} PRIVATE gemmlowp) +endfunction(ann_ref_configure) + +add_library(ann_ref_static STATIC ${SOURCES}) +set_target_properties(ann_ref_static PROPERTIES POSITION_INDEPENDENT_CODE ON) +ann_ref_configure(ann_ref_static) + +add_library(ann_ref_shared SHARED ${SOURCES}) +set_target_properties(ann_ref_shared PROPERTIES OUTPUT_NAME neuralnetworks) +ann_ref_configure(ann_ref_shared) diff --git a/compiler/ann-ref/README.md b/compiler/ann-ref/README.md new file mode 100644 index 00000000000..6b13b5fdd38 --- /dev/null +++ b/compiler/ann-ref/README.md @@ -0,0 +1,7 @@ +# ann-ref + +_ann-ref_ is a reference Android NN API implementation for Linux. + +**DISCLAIMER** + +_ann-ref_ is incomplete in terms of its functionalities. diff --git a/compiler/ann-ref/requires.cmake b/compiler/ann-ref/requires.cmake new file mode 100644 index 00000000000..b6b647600d1 --- /dev/null +++ b/compiler/ann-ref/requires.cmake @@ -0,0 +1 @@ +require("ann-api") diff --git a/compiler/ann-ref/src/Assert.h b/compiler/ann-ref/src/Assert.h new file mode 100644 index 00000000000..744305607f4 --- /dev/null +++ b/compiler/ann-ref/src/Assert.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __ASSERT_H__ +#define __ASSERT_H__ + +#include "Logging.h" + +// Assert macro, as Android does not generally support assert. +#define ASSERT(v) \ + do \ + { \ + if (!(v)) \ + { \ + LOG(ERROR) << "'" << #v << "' failed at " << __FILE__ << ":" << __LINE__ << "'\n"; \ + abort(); \ + } \ + } while (0) + +#endif // __ASSERT_H__ diff --git a/compiler/ann-ref/src/CompilationBuilder.cpp b/compiler/ann-ref/src/CompilationBuilder.cpp new file mode 100644 index 00000000000..a14dbc1b63e --- /dev/null +++ b/compiler/ann-ref/src/CompilationBuilder.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ExecutionBuilder.h" +#include "CompilationBuilder.h" + +#include "Logging.h" + +CompilationBuilder::CompilationBuilder(const ModelBuilder *model) : mModel(model) +{ + VLOG(COMPILATION) << "CompilationBuilder::CompilationBuilder"; +} + +int CompilationBuilder::finish() +{ + if (mFinished) + { + LOG(ERROR) << "ANeuralNetworksCompilation_finish called more than once"; + return ANEURALNETWORKS_BAD_STATE; + } + // TODO validate the rest + + mFinished = true; + + return ANEURALNETWORKS_NO_ERROR; +} + +int CompilationBuilder::createExecution(ExecutionBuilder **execution) +{ + if (!mFinished) + { + LOG(ERROR) << "ANeuralNetworksExecution_create passed an unfinished compilation"; + *execution = nullptr; + return ANEURALNETWORKS_BAD_STATE; + } + *execution = new ExecutionBuilder(mModel); + return (*execution ? ANEURALNETWORKS_NO_ERROR : ANEURALNETWORKS_OUT_OF_MEMORY); +} diff --git a/compiler/ann-ref/src/CompilationBuilder.h b/compiler/ann-ref/src/CompilationBuilder.h new file mode 100644 index 00000000000..92c1ab4bf36 --- /dev/null +++ b/compiler/ann-ref/src/CompilationBuilder.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __COMPILATION_BUILDER_H__ +#define __COMPILATION_BUILDER_H__ + +#include "NeuralNetworks.h" + +class ModelBuilder; +class ExecutionBuilder; + +class CompilationBuilder +{ +public: + CompilationBuilder(const ModelBuilder *model); + +public: + int finish(); + + int createExecution(ExecutionBuilder **execution); + +private: + const ModelBuilder *mModel; + + // Once the compilation has been finished, we should not allow further + // modifications to the compilation. + bool mFinished = false; +}; + +#endif // __COMPILATION_BUILDER_H__ diff --git a/compiler/ann-ref/src/ExecutionBuilder.cpp b/compiler/ann-ref/src/ExecutionBuilder.cpp new file mode 100644 index 00000000000..9df78bfc3d2 --- /dev/null +++ b/compiler/ann-ref/src/ExecutionBuilder.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ExecutionBuilder.h" +#include "CompilationBuilder.h" +#include "ModelBuilder.h" + +#include "Executor.h" + +#include "Logging.h" +#include "Validation.h" + +static void setRequestArgumentArray(const std::vector &argumentInfos, + std::vector *ioInfos) +{ + size_t count = argumentInfos.size(); + ioInfos->resize(count); + for (size_t i = 0; i < count; i++) + { + const auto &info = argumentInfos[i]; + (*ioInfos)[i] = { + .hasNoValue = info.state == ModelArgumentInfo::HAS_NO_VALUE, + .location = info.locationAndLength, + .dimensions = info.dimensions, + }; + } +} + +bool setRunTimePoolInfosFromMemories(std::vector *poolInfos, + const std::vector &pools) +{ + poolInfos->resize(pools.size()); + for (size_t i = 0; i < pools.size(); i++) + { + auto &poolInfo = (*poolInfos)[i]; + if (!poolInfo.set(pools[i])) + { + LOG(ERROR) << "Could not map pool"; + return false; + } + } + return true; +} + +ExecutionBuilder::ExecutionBuilder(const ModelBuilder *model) + : mModel(model), mInputs(mModel->inputCount()), mOutputs(mModel->outputCount()) +{ + VLOG(EXECUTION) << "ExecutionBuilder::ExecutionBuilder"; +} + +int ExecutionBuilder::setInput(uint32_t index, const ANeuralNetworksOperandType *type, + const void *buffer, size_t length) +{ + uint32_t count = static_cast(mInputs.size()); + if (index >= count) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInput bad index " << index << " " << count; + return ANEURALNETWORKS_BAD_DATA; + } + if (type != nullptr) + { + int n = validateOperandType(*type, "ANeuralNetworksExecution_setInput", false); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + } + if (length > 0xFFFFFFFF) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInput input exceeds max length " << length; + return ANEURALNETWORKS_BAD_DATA; + } + uint32_t l = static_cast(length); + return mInputs[index].setFromPointer(mModel->getInputOperand(index), type, + const_cast(buffer), l); +} + +int ExecutionBuilder::setInputFromMemory(uint32_t index, const ANeuralNetworksOperandType *type, + const Memory *memory, size_t offset, size_t length) +{ + uint32_t count = static_cast(mInputs.size()); + if (index >= count) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInputFromMemory bad index " << index << " " << count; + return ANEURALNETWORKS_BAD_DATA; + } + if (!memory->validateSize(offset, length)) + { + return ANEURALNETWORKS_BAD_DATA; + } + uint32_t poolIndex = mMemories.add(memory); + return mInputs[index].setFromMemory(mModel->getInputOperand(index), type, poolIndex, offset, + length); +} + +int ExecutionBuilder::setOutput(uint32_t index, const ANeuralNetworksOperandType *type, + void *buffer, size_t length) +{ + uint32_t count = static_cast(mOutputs.size()); + if (index >= count) + { + LOG(ERROR) << "ANeuralNetworksExecution_setOutput bad index " << index << " " << count; + return ANEURALNETWORKS_BAD_DATA; + } + if (type != nullptr) + { + int n = validateOperandType(*type, "ANeuralNetworksExecution_setOutput", false); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + } + if (length > 0xFFFFFFFF) + { + LOG(ERROR) << "ANeuralNetworksExecution_setOutput input exceeds max length " << length; + return ANEURALNETWORKS_BAD_DATA; + } + uint32_t l = static_cast(length); + return mOutputs[index].setFromPointer(mModel->getOutputOperand(index), type, buffer, l); +} + +int ExecutionBuilder::setOutputFromMemory(uint32_t index, const ANeuralNetworksOperandType *type, + const Memory *memory, size_t offset, size_t length) +{ + // Should be similar to StepExecutor::setInputOrOutputFromTemporaryMemory() + + uint32_t count = static_cast(mOutputs.size()); + if (index >= count) + { + LOG(ERROR) << "ANeuralNetworksExecution_setOutputFromMemory bad index " << index << " " + << count; + return ANEURALNETWORKS_BAD_DATA; + } + if (!memory->validateSize(offset, length)) + { + return ANEURALNETWORKS_BAD_DATA; + } + // TODO validate the rest + uint32_t poolIndex = mMemories.add(memory); + return mOutputs[index].setFromMemory(mModel->getOutputOperand(index), type, poolIndex, offset, + length); +} + +int ExecutionBuilder::startCompute(void) +{ + Model model; + mModel->publish(&model); + + // modelPoolInfo holds the infomation of pre-allocated memory pools during model construction + std::vector modelPoolInfos; + if (!setRunTimePoolInfosFromMemories(&modelPoolInfos, model.pools)) + { + return ANEURALNETWORKS_UNMAPPABLE; + } + + std::vector requestPoolInfos; + uint32_t count = mMemories.size(); + requestPoolInfos.resize(count); + // Create as many pools as there are input / output + auto fixPointerArguments = [&requestPoolInfos](std::vector &argumentInfos) { + for (ModelArgumentInfo &argumentInfo : argumentInfos) + { + if (argumentInfo.state == ModelArgumentInfo::POINTER) + { + RunTimePoolInfo runTimeInfo; + runTimeInfo.buffer = static_cast(argumentInfo.buffer); + argumentInfo.locationAndLength.poolIndex = static_cast(requestPoolInfos.size()); + argumentInfo.locationAndLength.offset = 0; + requestPoolInfos.push_back(runTimeInfo); + } + } + }; + fixPointerArguments(mInputs); + fixPointerArguments(mOutputs); + + Request request; + setRequestArgumentArray(mInputs, &request.inputs); + setRequestArgumentArray(mOutputs, &request.outputs); + + Executor executor; + return executor.run(model, request, modelPoolInfos, requestPoolInfos); +} diff --git a/compiler/ann-ref/src/ExecutionBuilder.h b/compiler/ann-ref/src/ExecutionBuilder.h new file mode 100644 index 00000000000..0bf5ef75584 --- /dev/null +++ b/compiler/ann-ref/src/ExecutionBuilder.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __EXECUTION_BUILDER_H__ +#define __EXECUTION_BUILDER_H__ + +#include "NeuralNetworks.h" + +#include "ModelBuilder.h" +#include "ModelArgumentInfo.h" + +#include "Memory.h" + +#include + +class ModelBuilder; + +class ExecutionBuilder +{ +public: + ExecutionBuilder(const ModelBuilder *); + +public: + int setInput(uint32_t index, const ANeuralNetworksOperandType *type, const void *buffer, + size_t length); + int setInputFromMemory(uint32_t index, const ANeuralNetworksOperandType *type, + const Memory *memory, size_t offset, size_t length); + +public: + int setOutput(uint32_t index, const ANeuralNetworksOperandType *type, void *buffer, + size_t length); + int setOutputFromMemory(uint32_t index, const ANeuralNetworksOperandType *type, + const Memory *memory, size_t offset, size_t length); + +public: + int startCompute(void); + +private: + const ModelBuilder *mModel; + +private: + // The information we'll send to the driver about the inputs and outputs. + // Note that we build this in two steps: + // 1. As the arguments are specified, set the corresponding mInputs or mOutputs element. + // If set from a pointer, don't set the location in the RequestArgument but store it + // instead in mInputBuffers or mOutputBuffers. + // 2. Once we have all the inputs and outputs, if needed, allocate shared memory for + // the m*Buffers entries. Copy the input values into the shared memory. + // We do this to avoid creating a lot of shared memory objects if we have a lot of + // parameters specified via pointers. We also avoid copying in the case where + // some of the nodes will interpreted on the CPU anyway. + std::vector mInputs; + std::vector mOutputs; + +private: + MemoryTracker mMemories; +}; + +#endif // __EXECUTION_BUILDER_H__ diff --git a/compiler/ann-ref/src/Executor.cpp b/compiler/ann-ref/src/Executor.cpp new file mode 100644 index 00000000000..888fc9c81ad --- /dev/null +++ b/compiler/ann-ref/src/Executor.cpp @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Executor.h" + +#include "NeuralNetworks.h" +#include "Shape.h" + +#include "ops/Add.h" +#include "ops/Add.float.h" +#include "ops/Conv2D.h" +#include "ops/Conv2D.float.h" +#include "ops/DepthwiseConv2D.h" +#include "ops/DepthwiseConv2D.float.h" +#include "ops/AvgPool2D.h" +#include "ops/AvgPool2D.float.h" +#include "ops/MaxPool2D.h" +#include "ops/MaxPool2D.float.h" +#include "ops/Mul.h" +#include "ops/Mul.float.h" +#include "ops/ReLU.h" +#include "ops/ReLU.float.h" +#include "ops/ReLU6.h" +#include "ops/ReLU6.float.h" +#include "ops/Concatenation.h" +#include "ops/Concatenation.float.h" +#include "ops/Reshape.h" +#include "ops/Softmax.h" +#include "ops/Softmax.float.h" +#include "ops/FullyConnected.h" +#include "ops/FullyConnected.float.h" +#include "ops/Pad.h" +#include "ops/Sub.h" +#include "ops/Sub.float.h" +#include "ops/Div.h" +#include "ops/Div.float.h" + +#include "Logging.h" +#include "Assert.h" + +enum PaddingScheme +{ + kPaddingUnknown = 0, + kPaddingSame = 1, + kPaddingValid = 2, +}; + +inline void calculateExplicitPadding(int32_t in_size, int32_t stride, int32_t filter_size, + int32_t padding_implicit, int32_t *padding_head, + int32_t *padding_tail) +{ + *padding_head = 0; + *padding_tail = 0; + + if (padding_implicit == kPaddingSame) + { + int32_t out_size = (in_size + stride - 1) / stride; + int32_t tmp = (out_size - 1) * stride + filter_size; + if (tmp > in_size) + { + *padding_head = (tmp - in_size) / 2; + *padding_tail = (tmp - in_size) - *padding_head; + } + } +} + +template static inline T getScalarData(const RunTimeOperandInfo &info) +{ + // TODO: Check buffer is at least as long as size of data. + T *data = reinterpret_cast(info.buffer); + return data[0]; +} + +// Updates the RunTimeOperandInfo with the newly calculated shape. +// Allocate the buffer if we need to. +static bool setInfoAndAllocateIfNeeded(RunTimeOperandInfo *info, const Shape &shape) +{ + // For user-provided model output operands, the parameters must match the Shape + // calculated from the preparation step. + if (info->lifetime == OperandLifeTime::MODEL_OUTPUT) + { + if (info->type != shape.type || info->dimensions != shape.dimensions) + { + LOG(ERROR) << "Invalid type or dimensions for model output"; + return false; + } + if (info->type == OperandType::TENSOR_QUANT8_ASYMM && + (info->scale != shape.scale || info->zeroPoint != shape.offset)) + { + LOG(ERROR) << "Invalid scale or zeroPoint for model output"; + return false; + } + } + info->type = shape.type; + info->dimensions = shape.dimensions; + info->scale = shape.scale; + info->zeroPoint = shape.offset; + if (info->lifetime == OperandLifeTime::TEMPORARY_VARIABLE && info->buffer == nullptr) + { + uint32_t length = sizeOfData(info->type, info->dimensions); + info->buffer = new uint8_t[length]; + if (info->buffer == nullptr) + { + return false; + } + } + return true; +} + +// Ignore the .pools entry in model and request. This will have been taken care of +// by the caller. +int Executor::run(const Model &model, const Request &request, + const std::vector &modelPoolInfos, + const std::vector &requestPoolInfos) +{ + VLOG(CPUEXE) << "Executor::run()"; + + mModel = &model; + mRequest = &request; // TODO check if mRequest is needed + initializeRunTimeInfo(modelPoolInfos, requestPoolInfos); + // The model has serialized the operation in execution order. + for (const auto &operation : model.operations) + { + int n = executeOperation(operation); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + } + mModel = nullptr; + mRequest = nullptr; + VLOG(CPUEXE) << "Completed run normally"; + return ANEURALNETWORKS_NO_ERROR; +} + +bool Executor::initializeRunTimeInfo(const std::vector &modelPoolInfos, + const std::vector &requestPoolInfos) +{ + VLOG(CPUEXE) << "Executor::initializeRunTimeInfo"; + const size_t count = mModel->operands.size(); + mOperands.resize(count); + + // Start by setting the runtime info to what's in the model. + for (size_t i = 0; i < count; i++) + { + const Operand &from = mModel->operands[i]; + RunTimeOperandInfo &to = mOperands[i]; + to.type = from.type; + to.dimensions = from.dimensions; + to.scale = from.scale; + to.zeroPoint = from.zeroPoint; + to.length = from.location.length; + to.lifetime = from.lifetime; + switch (from.lifetime) + { + case OperandLifeTime::TEMPORARY_VARIABLE: + to.buffer = nullptr; + to.numberOfUsesLeft = from.numberOfConsumers; + break; + case OperandLifeTime::CONSTANT_COPY: + to.buffer = const_cast(&mModel->operandValues[from.location.offset]); + to.numberOfUsesLeft = 0; + break; + case OperandLifeTime::CONSTANT_REFERENCE: + { + auto poolIndex = from.location.poolIndex; + ASSERT(poolIndex < modelPoolInfos.size()); + auto &r = modelPoolInfos[poolIndex]; + to.buffer = r.buffer + from.location.offset; + to.numberOfUsesLeft = 0; + break; + } + case OperandLifeTime::MODEL_INPUT: + case OperandLifeTime::MODEL_OUTPUT: + case OperandLifeTime::NO_VALUE: + to.buffer = nullptr; + to.numberOfUsesLeft = 0; + break; + default: + ASSERT(false); + break; + } + } + + // Adjust the runtime info for the arguments passed to the model, + // modifying the buffer location, and possibly the dimensions. + auto updateForArguments = [this, &requestPoolInfos](const std::vector &indexes, + const std::vector &arguments) { + ASSERT(indexes.size() == arguments.size()); + for (size_t i = 0; i < indexes.size(); i++) + { + const uint32_t operandIndex = indexes[i]; + const RequestArgument &from = arguments[i]; + RunTimeOperandInfo &to = mOperands[operandIndex]; + if (from.dimensions.size() > 0) + { + // It's the responsibility of the caller to validate that + // from.dimensions only modifies the dimensions that were + // unspecified in the model. That's the case in SampleDriver.cpp + // with the call to validateRequest(). + // TODO make sure that's the case for the default CPU path. + to.dimensions = from.dimensions; + } + if (from.hasNoValue) + { + to.lifetime = OperandLifeTime::NO_VALUE; + ASSERT(to.buffer == nullptr); + } + else + { + auto poolIndex = from.location.poolIndex; + ASSERT(poolIndex < requestPoolInfos.size()); + auto &r = requestPoolInfos[poolIndex]; + to.buffer = r.buffer + from.location.offset; + } + } + }; + updateForArguments(mModel->inputIndexes, mRequest->inputs); + updateForArguments(mModel->outputIndexes, mRequest->outputs); + + return true; +} + +void Executor::freeNoLongerUsedOperands(const std::vector &inputs) +{ + for (uint32_t i : inputs) + { + auto &info = mOperands[i]; + // Check if it's a static or model input/output. + if (info.numberOfUsesLeft == 0) + { + continue; + } + info.numberOfUsesLeft--; + if (info.numberOfUsesLeft == 0) + { + ASSERT(info.buffer != nullptr); + delete[] info.buffer; + info.buffer = nullptr; + } + } +} + +int Executor::executeOperation(const Operation &operation) +{ + const std::vector &ins = operation.inputs; + const std::vector &outs = operation.outputs; + bool success = false; + + // Function to verify that the number of input and output parameters + // matches what is expected. Also checks that all the parameters have + // values. This function is to be used only for operations that do not + // accept optional arguments. + // TODO Have a version that works for optional arguments. + auto allParametersPresent = [&operation, &ins, &outs, this](size_t requiredIns, + size_t requiredOuts) -> bool { + auto verify = [&operation, this](size_t requiredCount, const std::vector &indexes, + const char *type) -> bool { + size_t actualCount = indexes.size(); + if (actualCount != requiredCount) + { + LOG(ERROR) << getOperationName(operation.type) << ": Invalid number of " << type + << " operands. Got " << actualCount << " of " << requiredCount; + return false; + } + for (size_t i = 0; i < actualCount; i++) + { + if (mOperands[indexes[i]].lifetime == OperandLifeTime::NO_VALUE) + { + LOG(ERROR) << getOperationName(operation.type) << " " << type << " operand " << i + << " is required but missing."; + return false; + } + } + return true; + }; + return verify(requiredIns, ins, "in") && verify(requiredOuts, outs, "out"); + }; + + switch (operation.type) + { + case OperationType::ADD: + { + if (!allParametersPresent(3, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &in1 = mOperands[ins[0]]; + const RunTimeOperandInfo &in2 = mOperands[ins[1]]; + int32_t activation = getScalarData(mOperands[ins[2]]); + + RunTimeOperandInfo &out = mOperands[outs[0]]; + Shape outShape = out.shape(); + + ASSERT(in1.type == OperandType::TENSOR_FLOAT32); + { + success = addPrepare(in1.shape(), in2.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&out, outShape) && + addFloat32(reinterpret_cast(in1.buffer), in1.shape(), + reinterpret_cast(in2.buffer), in2.shape(), activation, + reinterpret_cast(out.buffer), outShape); + } + } + break; + case OperationType::DEPTHWISE_CONV_2D: + { + const size_t inCount = ins.size(); + if ((inCount != 11 && inCount != 8) || !allParametersPresent(inCount, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + const RunTimeOperandInfo &filter = mOperands[ins[1]]; + const RunTimeOperandInfo &bias = mOperands[ins[2]]; + + int32_t padding_left, padding_right; + int32_t padding_top, padding_bottom; + int32_t stride_width, stride_height; + int32_t depth_multiplier; + int32_t activation; + + if (inCount == 11) + { + padding_left = getScalarData(mOperands[ins[3]]); + padding_right = getScalarData(mOperands[ins[4]]); + padding_top = getScalarData(mOperands[ins[5]]); + padding_bottom = getScalarData(mOperands[ins[6]]); + stride_width = getScalarData(mOperands[ins[7]]); + stride_height = getScalarData(mOperands[ins[8]]); + depth_multiplier = getScalarData(mOperands[ins[9]]); + activation = getScalarData(mOperands[ins[10]]); + } + else + { + int32_t padding_implicit = getScalarData(mOperands[ins[3]]); + stride_width = getScalarData(mOperands[ins[4]]); + stride_height = getScalarData(mOperands[ins[5]]); + depth_multiplier = getScalarData(mOperands[ins[6]]); + activation = getScalarData(mOperands[ins[7]]); + + Shape inputShape = input.shape(); + Shape filterShape = filter.shape(); + int32_t input_width = getSizeOfDimension(inputShape, 2); + int32_t input_height = getSizeOfDimension(inputShape, 1); + int32_t filter_width = getSizeOfDimension(filterShape, 2); + int32_t filter_height = getSizeOfDimension(filterShape, 1); + calculateExplicitPadding(input_width, stride_width, filter_width, padding_implicit, + &padding_left, &padding_right); + calculateExplicitPadding(input_height, stride_height, filter_height, padding_implicit, + &padding_top, &padding_bottom); + } + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = + depthwiseConvPrepare(input.shape(), filter.shape(), bias.shape(), padding_left, + padding_right, padding_top, padding_bottom, stride_width, + stride_height, &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + depthwiseConvFloat32(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(filter.buffer), filter.shape(), + reinterpret_cast(bias.buffer), bias.shape(), padding_left, + padding_right, padding_top, padding_bottom, stride_width, stride_height, + depth_multiplier, activation, reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::CONV_2D: + { + const size_t inCount = ins.size(); + if ((inCount != 10 && inCount != 7) || !allParametersPresent(inCount, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + const RunTimeOperandInfo &filter = mOperands[ins[1]]; + const RunTimeOperandInfo &bias = mOperands[ins[2]]; + + int32_t padding_left, padding_right; + int32_t padding_top, padding_bottom; + int32_t stride_width, stride_height; + int32_t activation; + + if (inCount == 10) + { + padding_left = getScalarData(mOperands[ins[3]]); + padding_right = getScalarData(mOperands[ins[4]]); + padding_top = getScalarData(mOperands[ins[5]]); + padding_bottom = getScalarData(mOperands[ins[6]]); + stride_width = getScalarData(mOperands[ins[7]]); + stride_height = getScalarData(mOperands[ins[8]]); + activation = getScalarData(mOperands[ins[9]]); + } + else + { + int32_t padding_implicit = getScalarData(mOperands[ins[3]]); + stride_width = getScalarData(mOperands[ins[4]]); + stride_height = getScalarData(mOperands[ins[5]]); + activation = getScalarData(mOperands[ins[6]]); + + Shape inputShape = input.shape(); + Shape filterShape = filter.shape(); + int32_t input_width = getSizeOfDimension(inputShape, 2); + int32_t input_height = getSizeOfDimension(inputShape, 1); + int32_t filter_width = getSizeOfDimension(filterShape, 2); + int32_t filter_height = getSizeOfDimension(filterShape, 1); + calculateExplicitPadding(input_width, stride_width, filter_width, padding_implicit, + &padding_left, &padding_right); + calculateExplicitPadding(input_height, stride_height, filter_height, padding_implicit, + &padding_top, &padding_bottom); + } + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = + convPrepare(input.shape(), filter.shape(), bias.shape(), padding_left, padding_right, + padding_top, padding_bottom, stride_width, stride_height, &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + convFloat32(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(filter.buffer), filter.shape(), + reinterpret_cast(bias.buffer), bias.shape(), padding_left, + padding_right, padding_top, padding_bottom, stride_width, stride_height, + activation, reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::AVERAGE_POOL_2D: + { + const size_t inCount = ins.size(); + if ((inCount != 10 && inCount != 7) || !allParametersPresent(inCount, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + + int32_t padding_left, padding_right; + int32_t padding_top, padding_bottom; + int32_t stride_width, stride_height; + int32_t filter_width, filter_height; + int32_t activation; + + if (inCount == 10) + { + padding_left = getScalarData(mOperands[ins[1]]); + padding_right = getScalarData(mOperands[ins[2]]); + padding_top = getScalarData(mOperands[ins[3]]); + padding_bottom = getScalarData(mOperands[ins[4]]); + stride_width = getScalarData(mOperands[ins[5]]); + stride_height = getScalarData(mOperands[ins[6]]); + filter_width = getScalarData(mOperands[ins[7]]); + filter_height = getScalarData(mOperands[ins[8]]); + activation = getScalarData(mOperands[ins[9]]); + } + else + { + int32_t padding_implicit = getScalarData(mOperands[ins[1]]); + stride_width = getScalarData(mOperands[ins[2]]); + stride_height = getScalarData(mOperands[ins[3]]); + filter_width = getScalarData(mOperands[ins[4]]); + filter_height = getScalarData(mOperands[ins[5]]); + activation = getScalarData(mOperands[ins[6]]); + + Shape inputShape = input.shape(); + int32_t input_width = getSizeOfDimension(inputShape, 2); + int32_t input_height = getSizeOfDimension(inputShape, 1); + calculateExplicitPadding(input_width, stride_width, filter_width, padding_implicit, + &padding_left, &padding_right); + calculateExplicitPadding(input_height, stride_height, filter_height, padding_implicit, + &padding_top, &padding_bottom); + } + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = averagePoolPrepare(input.shape(), padding_left, padding_right, padding_top, + padding_bottom, stride_width, stride_height, filter_width, + filter_height, &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + averagePoolFloat32(reinterpret_cast(input.buffer), input.shape(), padding_left, + padding_right, padding_top, padding_bottom, stride_width, stride_height, + filter_width, filter_height, activation, + reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::MAX_POOL_2D: + { + const size_t inCount = ins.size(); + if ((inCount != 10 && inCount != 7) || !allParametersPresent(inCount, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + + int32_t padding_left, padding_right; + int32_t padding_top, padding_bottom; + int32_t stride_width, stride_height; + int32_t filter_width, filter_height; + int32_t activation; + + if (inCount == 10) + { + padding_left = getScalarData(mOperands[ins[1]]); + padding_right = getScalarData(mOperands[ins[2]]); + padding_top = getScalarData(mOperands[ins[3]]); + padding_bottom = getScalarData(mOperands[ins[4]]); + stride_width = getScalarData(mOperands[ins[5]]); + stride_height = getScalarData(mOperands[ins[6]]); + filter_width = getScalarData(mOperands[ins[7]]); + filter_height = getScalarData(mOperands[ins[8]]); + activation = getScalarData(mOperands[ins[9]]); + } + else + { + int32_t padding_implicit = getScalarData(mOperands[ins[1]]); + stride_width = getScalarData(mOperands[ins[2]]); + stride_height = getScalarData(mOperands[ins[3]]); + filter_width = getScalarData(mOperands[ins[4]]); + filter_height = getScalarData(mOperands[ins[5]]); + activation = getScalarData(mOperands[ins[6]]); + + Shape inputShape = input.shape(); + int32_t input_width = getSizeOfDimension(inputShape, 2); + int32_t input_height = getSizeOfDimension(inputShape, 1); + calculateExplicitPadding(input_width, stride_width, filter_width, padding_implicit, + &padding_left, &padding_right); + calculateExplicitPadding(input_height, stride_height, filter_height, padding_implicit, + &padding_top, &padding_bottom); + } + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = maxPoolPrepare(input.shape(), padding_left, padding_right, padding_top, + padding_bottom, stride_width, stride_height, filter_width, + filter_height, &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + maxPoolFloat32(reinterpret_cast(input.buffer), input.shape(), padding_left, + padding_right, padding_top, padding_bottom, stride_width, stride_height, + filter_width, filter_height, activation, + reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::MUL: + { + if (!allParametersPresent(3, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &in1 = mOperands[ins[0]]; + const RunTimeOperandInfo &in2 = mOperands[ins[1]]; + int32_t activation = getScalarData(mOperands[ins[2]]); + + RunTimeOperandInfo &out = mOperands[outs[0]]; + Shape outShape = out.shape(); + + ASSERT(in1.type == OperandType::TENSOR_FLOAT32); + { + success = mulPrepare(in1.shape(), in2.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&out, outShape) && + mulFloat32(reinterpret_cast(in1.buffer), in1.shape(), + reinterpret_cast(in2.buffer), in2.shape(), activation, + reinterpret_cast(out.buffer), outShape); + } + } + break; + case OperationType::RELU: + { + if (!allParametersPresent(1, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = reluPrepare(input.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + reluFloat32(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::RELU6: + { + if (!allParametersPresent(1, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = relu6Prepare(input.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + relu6Float32(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::SOFTMAX: + { + if (!allParametersPresent(2, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + RunTimeOperandInfo &input = mOperands[ins[0]]; + float beta = getScalarData(mOperands[ins[1]]); + if (beta <= 0.0f) + { + LOG(ERROR) << "beta must be positive for softmax"; + return ANEURALNETWORKS_BAD_DATA; + } + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = softmaxPrepare(input.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + softmaxFloat32(reinterpret_cast(input.buffer), input.shape(), beta, + reinterpret_cast(output.buffer), output.shape()); + } + } + break; + case OperationType::FULLY_CONNECTED: + { + if (!allParametersPresent(4, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + RunTimeOperandInfo &input = mOperands[ins[0]]; + RunTimeOperandInfo &weights = mOperands[ins[1]]; + RunTimeOperandInfo &bias = mOperands[ins[2]]; + + int32_t activation = getScalarData(mOperands[ins[3]]); + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + ASSERT(input.type == OperandType::TENSOR_FLOAT32); + { + success = fullyConnectedPrepare(input.shape(), weights.shape(), bias.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + fullyConnectedFloat32(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(weights.buffer), weights.shape(), + reinterpret_cast(bias.buffer), bias.shape(), activation, + reinterpret_cast(output.buffer), outShape); + } + } + break; + case OperationType::CONCATENATION: + { + if (outs.size() != 1 || ins.size() < 2) + { + return ANEURALNETWORKS_BAD_DATA; + } + int numInputTensors = ins.size() - 1; + int32_t axis = getScalarData(mOperands[ins[numInputTensors]]); + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + const RunTimeOperandInfo &firstInput = mOperands[ins[0]]; + ASSERT(firstInput.type == OperandType::TENSOR_FLOAT32); + { + std::vector inputShapes(numInputTensors); + std::vector inputDataPtrs(numInputTensors); + + for (int i = 0; i < numInputTensors; i++) + { + RunTimeOperandInfo &input = mOperands[ins[i]]; + inputShapes[i] = input.shape(); + inputDataPtrs[i] = reinterpret_cast(input.buffer); + } + success = concatenationPrepare(inputShapes, axis, &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + concatenationFloat32(inputDataPtrs, inputShapes, axis, reinterpret_cast(output.buffer), + outShape); + } + } + break; + case OperationType::RESHAPE: + { + if (!allParametersPresent(2, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &input = mOperands[ins[0]]; + const RunTimeOperandInfo &targetShape = mOperands[ins[1]]; + + RunTimeOperandInfo &output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + success = reshapePrepare(input.shape(), reinterpret_cast(targetShape.buffer), + getNumberOfElements(targetShape.shape()), &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + reshapeGeneric(reinterpret_cast(input.buffer), input.shape(), + reinterpret_cast(output.buffer), outShape); + } + break; + case OperationType::PAD: + { + if (!allParametersPresent(2, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo& input = mOperands[ins[0]]; + const RunTimeOperandInfo& paddings = mOperands[ins[1]]; + + RunTimeOperandInfo& output = mOperands[outs[0]]; + Shape outShape = output.shape(); + + success = padPrepare(input.shape(), + reinterpret_cast(paddings.buffer), + paddings.shape(), + &outShape) && + setInfoAndAllocateIfNeeded(&output, outShape) && + padGeneric(input.buffer, + input.shape(), + reinterpret_cast(paddings.buffer), + output.buffer, + outShape); + } + break; + case OperationType::SUB: + { + if (!allParametersPresent(3, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &in1 = mOperands[ins[0]]; + const RunTimeOperandInfo &in2 = mOperands[ins[1]]; + int32_t activation = getScalarData(mOperands[ins[2]]); + + RunTimeOperandInfo &out = mOperands[outs[0]]; + Shape outShape = out.shape(); + + ASSERT(in1.type == OperandType::TENSOR_FLOAT32); + { + success = subPrepare(in1.shape(), in2.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&out, outShape) && + subFloat32(reinterpret_cast(in1.buffer), in1.shape(), + reinterpret_cast(in2.buffer), in2.shape(), activation, + reinterpret_cast(out.buffer), outShape); + } + } + break; + case OperationType::DIV: + { + if (!allParametersPresent(3, 1)) + { + return ANEURALNETWORKS_BAD_DATA; + } + const RunTimeOperandInfo &in1 = mOperands[ins[0]]; + const RunTimeOperandInfo &in2 = mOperands[ins[1]]; + int32_t activation = getScalarData(mOperands[ins[2]]); + + RunTimeOperandInfo &out = mOperands[outs[0]]; + Shape outShape = out.shape(); + + ASSERT(in1.type == OperandType::TENSOR_FLOAT32); + { + success = divPrepare(in1.shape(), in2.shape(), &outShape) && + setInfoAndAllocateIfNeeded(&out, outShape) && + divFloat32(reinterpret_cast(in1.buffer), in1.shape(), + reinterpret_cast(in2.buffer), in2.shape(), activation, + reinterpret_cast(out.buffer), outShape); + } + } + break; + default: + NYI(getOperationName(operation.type)); + break; + } + if (!success) + { + LOG(ERROR) << getOperationName(operation.type) << " failed."; + return ANEURALNETWORKS_OP_FAILED; + } + + freeNoLongerUsedOperands(ins); + return ANEURALNETWORKS_NO_ERROR; +} diff --git a/compiler/ann-ref/src/Executor.h b/compiler/ann-ref/src/Executor.h new file mode 100644 index 00000000000..66dcca1165a --- /dev/null +++ b/compiler/ann-ref/src/Executor.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __EXECUTOR_H__ +#define __EXECUTOR_H__ + +#include "Model.h" + +#include "Shape.h" +#include "Request.h" + +#include + +// Information we maintain about each operand during execution that +// may change during execution. +struct RunTimeOperandInfo +{ + // TODO Storing the type here is redundant, as it won't change during execution. + OperandType type; + + // The type and dimensions of the operand. The dimensions can + // change at runtime. We include the type because it's useful + // to pass together with the dimension to the functions implementing + // the operators. + // + // Q: Is it possible?? + std::vector dimensions; + float scale; + int32_t zeroPoint; + + // Where the operand's data is stored. Check the corresponding + // location information in the model to figure out if this points + // to memory we have allocated for an temporary operand. + uint8_t *buffer; + // The length of the buffer. + uint32_t length; + + // Whether this is a temporary variable, a model input, a constant, etc. + OperandLifeTime lifetime; + + // Keeps track of how many operations have yet to make use + // of this temporary variable. When the count is decremented to 0, + // we free the buffer. For non-temporary variables, this count is + // always 0. + uint32_t numberOfUsesLeft; + + Shape shape() const + { + return Shape{.type = type, .dimensions = dimensions, .scale = scale, .offset = zeroPoint}; + } +}; + +// Used to keep a pointer to each of the memory pools +struct RunTimePoolInfo +{ + uint8_t *buffer; + + bool set(uint8_t *m) + { + buffer = m; + return true; + } +}; + +// This class is used to execute a model on the CPU. +class Executor +{ +public: + // Executes the model. The results will be stored at the locations + // specified in the constructor. + // The model must outlive the executor. We prevent it from being modified + // while this is executing. + int run(const Model &model, const Request &request, + const std::vector &modelPoolInfos, + const std::vector &requestPoolInfos); + +private: + bool initializeRunTimeInfo(const std::vector &modelPoolInfos, + const std::vector &requestPoolInfos); + // Runs one operation of the graph. + int executeOperation(const Operation &entry); + // Decrement the usage count for the operands listed. Frees the memory + // allocated for any temporary variable with a count of zero. + void freeNoLongerUsedOperands(const std::vector &inputs); + + // The model and the request that we'll execute. Only valid while run() + // is being executed. + const Model *mModel = nullptr; + const Request *mRequest = nullptr; + + // We're copying the list of all the dimensions from the model, as + // these may be modified when we run the operatins. Since we're + // making a full copy, the indexes used in the operand description + // stay valid. + // std::vector mDimensions; + // Runtime information about all the operands. + std::vector mOperands; +}; + +#endif // __CPU_EXECUTOR_H__ diff --git a/compiler/ann-ref/src/Logging.cpp b/compiler/ann-ref/src/Logging.cpp new file mode 100644 index 00000000000..4f849efaae6 --- /dev/null +++ b/compiler/ann-ref/src/Logging.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Logging.h" + +VLogging::VLogging() +{ + _enabled = false; +} + +VLogging &VLogging::access() +{ + static VLogging instance; + return instance; +} + +std::ostream &VLogging::stream() { return std::cout; } diff --git a/compiler/ann-ref/src/Logging.h b/compiler/ann-ref/src/Logging.h new file mode 100644 index 00000000000..1f81ad6e3ee --- /dev/null +++ b/compiler/ann-ref/src/Logging.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGGING_H__ +#define __LOGGING_H__ + +#include + +class VLogging +{ +public: + static VLogging &access(void); + bool enabled() const { return _enabled; } + std::ostream &stream(void); + +private: + VLogging(); + +private: + bool _enabled; +}; + +#define LOG(...) std::cout << std::endl +#define VLOG(...) \ + if (VLogging::access().enabled()) \ + (VLogging::access().stream() << std::endl) +#define NYI(module) std::cout << "NYI : '" << module << "' is not supported now." << std::endl; + +#endif // __LOGGING_H__ diff --git a/compiler/ann-ref/src/Macro.h b/compiler/ann-ref/src/Macro.h new file mode 100644 index 00000000000..829c15425a0 --- /dev/null +++ b/compiler/ann-ref/src/Macro.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __MACRO_H__ +#define __MACRO_H__ + +#define COUNT(X) (sizeof(X) / sizeof(X[0])) + +#endif // __MACRO_H__ diff --git a/compiler/ann-ref/src/Memory.cpp b/compiler/ann-ref/src/Memory.cpp new file mode 100644 index 00000000000..fd70f8db708 --- /dev/null +++ b/compiler/ann-ref/src/Memory.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#define LOG_TAG "Memory" + +#include "Memory.h" +#include "NeuralNetworks.h" // ANEURALNETWORKS_XXX + +#include + +MappedMemory::~MappedMemory() +{ + if (_base) + { + munmap(_base, _size); + } +} + +int MappedMemory::set(size_t size, int prot, int fd, size_t offset) +{ +#if 0 + if (fd < 0) + { + LOG(ERROR) << "ANeuralNetworksMemory_createFromFd invalid fd " << fd; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + if (size == 0 || fd < 0) + { + LOG(ERROR) << "Invalid size or fd"; + return ANEURALNETWORKS_BAD_DATA; + } + int dupfd = dup(fd); + if (dupfd == -1) + { + LOG(ERROR) << "Failed to dup the fd"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } +#endif + void * const base = mmap(nullptr, size, prot, MAP_PRIVATE, fd, offset); + + if (base == MAP_FAILED) + { + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + _base = static_cast(base); + _size = size; + + return ANEURALNETWORKS_NO_ERROR; +} + +int MappedMemory::getPointer(uint8_t **buffer) const +{ + *buffer = _base; + return ANEURALNETWORKS_NO_ERROR; +} + +bool MappedMemory::validateSize(uint32_t offset, uint32_t length) const +{ + return true; +} + +PrivateMemory::~PrivateMemory() +{ + if (_base) + { + delete[] _base; + } +} + +int PrivateMemory::create(uint32_t size) +{ + auto base = new uint8_t[size]; + + // TODO Check allocation failure + _base = base; + _size = size; + + return ANEURALNETWORKS_NO_ERROR; +} + +int PrivateMemory::getPointer(uint8_t **buffer) const +{ + *buffer = _base; + return ANEURALNETWORKS_NO_ERROR; +} + +bool PrivateMemory::validateSize(uint32_t offset, uint32_t length) const +{ + return true; +} diff --git a/compiler/ann-ref/src/Memory.h b/compiler/ann-ref/src/Memory.h new file mode 100644 index 00000000000..648b5c7d105 --- /dev/null +++ b/compiler/ann-ref/src/Memory.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __MEMORY_H__ +#define __MEMORY_H__ + +#include +#include + +// Represents a memory region. +struct Memory +{ + Memory() = default; + virtual ~Memory() = default; + + // Disallow copy semantics to ensure the runtime object can only be freed + // once. Copy semantics could be enabled if some sort of reference counting + // or deep-copy system for runtime objects is added later. + Memory(const Memory &) = delete; + Memory &operator=(const Memory &) = delete; + + // Returns a pointer to the underlying memory of this memory object. + virtual int getPointer(uint8_t **buffer) const = 0; + virtual bool validateSize(uint32_t offset, uint32_t length) const = 0; +}; + +class MappedMemory final : public Memory +{ +public: + MappedMemory() = default; + +public: + ~MappedMemory(); + +public: + // Create the native_handle based on input size, prot, and fd. + // Existing native_handle will be deleted, and mHidlMemory will wrap + // the newly created native_handle. + int set(size_t size, int prot, int fd, size_t offset); + +public: + int getPointer(uint8_t **buffer) const override; + bool validateSize(uint32_t offset, uint32_t length) const override; + +private: + uint8_t *_base = nullptr; + size_t _size = 0; +}; + +// Represents a memory region. +class AllocatedMemory : public Memory +{ +public: + AllocatedMemory() = default; + virtual ~AllocatedMemory() = default; + +public: + virtual int create(uint32_t size) = 0; + +public: + // Returns a pointer to the underlying memory of this memory object. + virtual int getPointer(uint8_t **buffer) const = 0; + virtual bool validateSize(uint32_t offset, uint32_t length) const = 0; +}; + +class PrivateMemory final : public AllocatedMemory +{ +public: + PrivateMemory() = default; + ~PrivateMemory(); + +public: + // Disallow copy semantics to ensure the runtime object can only be freed + // once. Copy semantics could be enabled if some sort of reference counting + // or deep-copy system for runtime objects is added later. + PrivateMemory(const PrivateMemory &) = delete; + PrivateMemory &operator=(const PrivateMemory &) = delete; + +public: + virtual int create(uint32_t size); + +public: + // Returns a pointer to the underlying memory of this memory object. + virtual int getPointer(uint8_t **buffer) const; + virtual bool validateSize(uint32_t offset, uint32_t length) const; + +private: + uint8_t *_base = nullptr; + size_t _size = 0; +}; + +#endif // __MEMORY_H__ diff --git a/compiler/ann-ref/src/MemoryTracker.cpp b/compiler/ann-ref/src/MemoryTracker.cpp new file mode 100644 index 00000000000..3c65149c641 --- /dev/null +++ b/compiler/ann-ref/src/MemoryTracker.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#define LOG_TAG "Memory" + +#include "NeuralNetworks.h" // For ANEURALNETWORKS_... +#include "MemoryTracker.h" + +#include "Logging.h" + +#include // It's for 'close' and 'dup' + // TODO-NNRT : Remove this if included another header including this. + +uint32_t MemoryTracker::add(const Memory *memory) +{ + VLOG(MODEL) << __func__ << " for " << memory; + // See if we already have this memory. If so, + // return its index. + auto i = mKnown.find(memory); + if (i != mKnown.end()) + { + return i->second; + } + VLOG(MODEL) << "It's new"; + // It's a new one. Save it an assign an index to it. + size_t next = mKnown.size(); + if (next > 0xFFFFFFFF) + { + LOG(ERROR) << "ANeuralNetworks more than 2^32 memories."; + return ANEURALNETWORKS_BAD_DATA; + } + uint32_t idx = static_cast(next); + mKnown[memory] = idx; + mMemories.push_back(memory); + return idx; +} diff --git a/compiler/ann-ref/src/MemoryTracker.h b/compiler/ann-ref/src/MemoryTracker.h new file mode 100644 index 00000000000..af687d18346 --- /dev/null +++ b/compiler/ann-ref/src/MemoryTracker.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __MEMORY_TRACKER_H__ +#define __MEMORY_TRACKER_H__ + +#include "Memory.h" + +#include +#include + +// A utility class to accumulate mulitple Memory objects and assign each +// a distinct index number, starting with 0. +// +// The user of this class is responsible for avoiding concurrent calls +// to this class from multiple threads. +class MemoryTracker +{ +public: + // Adds the memory, if it does not already exists. Returns its index. + // The memories should survive the tracker. + uint32_t add(const Memory *memory); + // Returns the number of memories contained. + uint32_t size() const { return static_cast(mKnown.size()); } + // Returns the ith memory. + const Memory *operator[](size_t i) const { return mMemories[i]; } + +private: + // The vector of Memory pointers we are building. + std::vector mMemories; + // A faster way to see if we already have a memory than doing find(). + std::unordered_map mKnown; +}; + +#endif // __MEMORY_TRACKER_H__ diff --git a/compiler/ann-ref/src/Model.h b/compiler/ann-ref/src/Model.h new file mode 100644 index 00000000000..dc6a0d3c975 --- /dev/null +++ b/compiler/ann-ref/src/Model.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +#include "Operand.h" +#include "Operation.h" + +#include +#include + +struct Model final { + std::vector operands; + std::vector operations; + + std::vector inputIndexes; + std::vector outputIndexes; + + std::vector operandValues; + + std::vector pools; +}; + +#endif // __MODEL_H__ diff --git a/compiler/ann-ref/src/ModelArgumentInfo.cpp b/compiler/ann-ref/src/ModelArgumentInfo.cpp new file mode 100644 index 00000000000..3c10cd0ead7 --- /dev/null +++ b/compiler/ann-ref/src/ModelArgumentInfo.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ModelArgumentInfo.h" +#include "NeuralNetworks.h" // For ANEURALNETWORKS_XXX +#include "Logging.h" +#include "Assert.h" + +// TODO-NNRT: Consider removing ModelArgumentInfo completely if it's not necessary +int ModelArgumentInfo::setFromPointer(const Operand &operand, + const ANeuralNetworksOperandType *type, void *data, + uint32_t length) +{ + if ((data == nullptr) != (length == 0)) + { + LOG(ERROR) << "Data pointer must be nullptr if and only if length is zero (data = " << data + << ", length = " << length << ")"; + return ANEURALNETWORKS_BAD_DATA; + } + if (data == nullptr) + { + state = ModelArgumentInfo::HAS_NO_VALUE; + } + else + { + int n = updateDimensionInfo(operand, type); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + uint32_t neededLength = sizeOfData(operand.type, dimensions); + if (neededLength != length) + { + LOG(ERROR) << "Setting argument with invalid length: " << length + << ", expected length: " << neededLength; + return ANEURALNETWORKS_BAD_DATA; + } + state = ModelArgumentInfo::POINTER; + } + buffer = data; + locationAndLength = {.poolIndex = 0, .offset = 0, .length = length}; + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelArgumentInfo::setFromMemory(const Operand &operand, const ANeuralNetworksOperandType *type, + uint32_t poolIndex, uint32_t offset, uint32_t length) +{ + int n = updateDimensionInfo(operand, type); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + uint32_t neededLength = sizeOfData(operand.type, dimensions); + if (neededLength != length) + { + LOG(ERROR) << "Setting argument with invalid length: " << length + << ", expected length: " << neededLength; + return ANEURALNETWORKS_BAD_DATA; + } + + state = ModelArgumentInfo::MEMORY; + locationAndLength = {.poolIndex = poolIndex, .offset = offset, .length = length}; + buffer = nullptr; + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelArgumentInfo::updateDimensionInfo(const Operand &operand, + const ANeuralNetworksOperandType *newType) +{ + ASSERT(dimensions.empty()); + if (newType == nullptr) + { + for (auto i : operand.dimensions) + { + if (i == 0) + { + LOG(ERROR) << "Setting input/output with unspecified dimensions"; + return ANEURALNETWORKS_BAD_DATA; + } + } + dimensions = operand.dimensions; + } + else + { + uint32_t count = newType->dimensionCount; + if (static_cast(newType->type) != operand.type || + count != operand.dimensions.size()) + { + LOG(ERROR) << "Setting input/output with incompatible types"; + return ANEURALNETWORKS_BAD_DATA; + } + dimensions = std::vector(count); + for (uint32_t i = 0; i < count; i++) + { + if (operand.dimensions[i] != 0 && operand.dimensions[i] != newType->dimensions[i]) + { + LOG(ERROR) << "Overriding a fully specified dimension is disallowed"; + return ANEURALNETWORKS_BAD_DATA; + } + else + { + dimensions[i] = newType->dimensions[i]; + } + } + } + return ANEURALNETWORKS_NO_ERROR; +} diff --git a/compiler/ann-ref/src/ModelArgumentInfo.h b/compiler/ann-ref/src/ModelArgumentInfo.h new file mode 100644 index 00000000000..5773da7431d --- /dev/null +++ b/compiler/ann-ref/src/ModelArgumentInfo.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __MODEL_ARGUMENT_INFO_H__ +#define __MODEL_ARGUMENT_INFO_H__ + +#include "NeuralNetworks.h" + +#include "Operand.h" + +#include + +struct ModelArgumentInfo +{ + // Whether the argument was specified as being in a Memory, as a pointer, + // has no value, or has not been specified. + // If POINTER then: + // locationAndLength.length is valid. + // dimensions is valid. + // buffer is valid + // If MEMORY then: + // locationAndLength.location.{poolIndex, offset, length} is valid. + // dimensions is valid. + enum + { + POINTER, + MEMORY, + HAS_NO_VALUE, + UNSPECIFIED + } state = UNSPECIFIED; + + DataLocation locationAndLength; + + std::vector dimensions; + void *buffer; + + int setFromPointer(const Operand &operand, const ANeuralNetworksOperandType *type, void *buffer, + uint32_t length); + int setFromMemory(const Operand &operand, const ANeuralNetworksOperandType *type, + uint32_t poolIndex, uint32_t offset, uint32_t length); + int updateDimensionInfo(const Operand &operand, const ANeuralNetworksOperandType *newType); +}; + +#endif // __MODEL_ARGUMENT_INFO_H__ diff --git a/compiler/ann-ref/src/ModelBuilder.cpp b/compiler/ann-ref/src/ModelBuilder.cpp new file mode 100644 index 00000000000..1f966bd2ebd --- /dev/null +++ b/compiler/ann-ref/src/ModelBuilder.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ModelBuilder.h" + +#include "CompilationBuilder.h" +#include "Validation.h" +#include "Logging.h" +#include "Assert.h" + +#include +#include + +static inline void setFromIntList(std::vector *vec, uint32_t count, const uint32_t *data) +{ + vec->resize(count); + for (uint32_t i = 0; i < count; i++) + { + (*vec)[i] = data[i]; + } +} + +// Returns the number of padding bytes needed to align data of the +// specified length. It aligns object of length: +// 2, 3 on a 2 byte boundary, +// 4+ on a 4 byte boundary. +// We may want to have different alignments for tensors. +// TODO: This is arbitrary, more a proof of concept. We need +// to determine what this should be. +uint32_t alignBytesNeeded(uint32_t index, size_t length) +{ + uint32_t pattern; + if (length < 2) + { + pattern = 0; // No alignment necessary + } + else if (length < 4) + { + pattern = 1; // Align on 2-byte boundary + } + else + { + pattern = 3; // Align on 4-byte boundary + } + uint32_t extra = (~(index - 1)) & pattern; + return extra; +} + + +// The maximum number of operands and operations that a model may have. +const uint32_t MAX_NUMBER_OF_OPERANDS = 0xFFFFFFFE; +const uint32_t MAX_NUMBER_OF_OPERATIONS = 0xFFFFFFFE; + +bool ModelBuilder::badState(const char *name) +{ + if (mCompletedModel) + { + LOG(ERROR) << "ANeuralNetworksModel_" << name << " can't modify after model finished"; + return true; + } + if (mInvalidModel) + { + LOG(ERROR) << "ANeuralNetworksModel_" << name << " can't modify an invalid model"; + return true; + } + return false; +} + +int ModelBuilder::addOperand(const ANeuralNetworksOperandType &type) +{ + if (badState("addOperand")) + { + return ANEURALNETWORKS_BAD_STATE; + } + + int n = validateOperandType(type, "ANeuralNetworksModel_addOperand", true); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + size_t idx = mOperands.size(); + if (idx >= MAX_NUMBER_OF_OPERANDS) + { + LOG(ERROR) << "ANeuralNetworksModel_addOperand exceed max operands"; + return ANEURALNETWORKS_BAD_DATA; + } + mOperands.resize(idx + 1); + auto &operand = mOperands[idx]; + operand.type = static_cast(type.type); + setFromIntList(&operand.dimensions, type.dimensionCount, type.dimensions); + operand.numberOfConsumers = 0; + operand.scale = type.scale; + operand.zeroPoint = type.zeroPoint; + operand.lifetime = OperandLifeTime::TEMPORARY_VARIABLE; + operand.location = {.poolIndex = 0, .offset = 0, .length = 0}; + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelBuilder::setOperandValue(uint32_t index, const void *buffer, size_t length) +{ + if (badState("setOperandValue")) + { + return ANEURALNETWORKS_BAD_STATE; + } + + VLOG(MODEL) << __func__ << " for operand " << index << " size " << length; + if (index >= operandCount()) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue setting operand " << index << " of " + << operandCount(); + return ANEURALNETWORKS_BAD_DATA; + } + Operand &operand = mOperands[index]; + if (buffer == nullptr) + { + if (length) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue buffer is nullptr but length is " + "not 0"; + return ANEURALNETWORKS_BAD_DATA; + } + operand.lifetime = OperandLifeTime::NO_VALUE; + // The location is unused and is set to zeros. + operand.location = {.poolIndex = 0, .offset = 0, .length = 0}; + } + else + { + if (length > 0xFFFFFFFF) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue value length of " << length + << " exceeds max size"; + return ANEURALNETWORKS_BAD_DATA; + } + uint32_t valueLength = static_cast(length); + uint32_t neededLength = sizeOfData(operand.type, operand.dimensions); + if (neededLength != valueLength) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue setting " << valueLength + << " bytes when needing " << neededLength; + return ANEURALNETWORKS_BAD_DATA; + } + if (valueLength <= ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES) + { + uint32_t existingSize = static_cast(mSmallOperandValues.size()); + uint32_t extraBytes = alignBytesNeeded(existingSize, valueLength); + mSmallOperandValues.resize(existingSize + extraBytes + valueLength); + operand.lifetime = OperandLifeTime::CONSTANT_COPY; + operand.location = { + .poolIndex = 0, .offset = existingSize + extraBytes, .length = neededLength}; + memcpy(&mSmallOperandValues[operand.location.offset], buffer, valueLength); + VLOG(MODEL) << "Copied small value to offset " << operand.location.offset; + } + else + { + VLOG(MODEL) << "Saving large value"; + operand.lifetime = OperandLifeTime::CONSTANT_REFERENCE; + // The values for poolIndex and offset will be set when the model is finished. + operand.location = {.poolIndex = 0, .offset = 0, .length = valueLength}; + // We keep track of the buffers. We'll allocate the shared memory only + // once we know the total size, to avoid needless copies. + mLargeOperandValues.push_back(LargeValue{.operandIndex = index, .buffer = buffer}); + } + } + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelBuilder::setOperandValueFromMemory(uint32_t index, const Memory *memory, uint32_t offset, + size_t length) +{ + VLOG(MODEL) << __func__ << " for operand " << index << " offset " << offset << " size " << length; + if (badState("setOperandValueFromMemory")) + { + return ANEURALNETWORKS_BAD_STATE; + } + + if (index >= operandCount()) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValueFromMemory setting operand " << index + << " of " << operandCount(); + return ANEURALNETWORKS_BAD_DATA; + } + Operand &operand = mOperands[index]; + uint32_t neededLength = sizeOfData(operand.type, operand.dimensions); + if (neededLength != length) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValueFromMemory setting " << length + << " bytes when needing " << neededLength; + return ANEURALNETWORKS_BAD_DATA; + } + if (!memory->validateSize(offset, length)) + { + return ANEURALNETWORKS_BAD_DATA; + } + // TODO validate does not exceed length of memory + operand.lifetime = OperandLifeTime::CONSTANT_REFERENCE; + operand.location = {.poolIndex = mMemories.add(memory), .offset = offset, .length = neededLength}; + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelBuilder::addOperation(OperationType type, uint32_t inputCount, const uint32_t *inputs, + uint32_t outputCount, const uint32_t *outputs) +{ + + if (badState("addOperation")) + { + return ANEURALNETWORKS_BAD_STATE; + } + + if (!validateOperationType(type)) + { + LOG(ERROR) << "ANeuralNetworksModel_addOperation invalid operations type " + << static_cast(type); + return ANEURALNETWORKS_BAD_DATA; + } + int n = validateOperandList(inputCount, inputs, operandCount(), + "ANeuralNetworksModel_addOperation inputs"); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + n = validateOperandList(outputCount, outputs, operandCount(), + "ANeuralNetworksModel_addOperation outputs"); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + + uint32_t operationIndex = operationCount(); + if (operationIndex >= MAX_NUMBER_OF_OPERATIONS) + { + LOG(ERROR) << "ANeuralNetworksModel_addOperation exceed max operations"; + return ANEURALNETWORKS_BAD_DATA; + } + mOperations.resize(operationIndex + 1); + auto &entry = mOperations[operationIndex]; + entry.type = type; + + setFromIntList(&entry.inputs, inputCount, inputs); + setFromIntList(&entry.outputs, outputCount, outputs); + for (uint32_t i : entry.inputs) + { + mOperands[i].numberOfConsumers++; + // TODO mOperands[i].consumers.push_back(operationIndex); + } + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelBuilder::identifyInputsAndOutputs(uint32_t inputCount, const uint32_t *inputs, + uint32_t outputCount, const uint32_t *outputs) +{ + if (badState("identifyInputsAndOutputs")) + { + return ANEURALNETWORKS_BAD_STATE; + } + + int n = validateOperandList(inputCount, inputs, operandCount(), + "ANeuralNetworksModel_identifyInputsAndOutputs inputs"); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + n = validateOperandList(outputCount, outputs, operandCount(), + "ANeuralNetworksModel_identifyInputsAndOutputs outputs"); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + + // Makes a copy of the index list, validates the arguments, and changes + // the lifetime info of the corresponding operand. + auto setArguments = [&](std::vector *indexVector, uint32_t indexCount, + const uint32_t *indexList, OperandLifeTime lifetime) -> bool { + indexVector->resize(indexCount); + for (uint32_t i = 0; i < indexCount; i++) + { + const uint32_t operandIndex = indexList[i]; + if (operandIndex >= mOperands.size()) + { + LOG(ERROR) << "ANeuralNetworksModel_identifyInputsAndOutputs Can't set input or output " + "to be " + << operandIndex << " as this exceeds the numbe of operands " << mOperands.size(); + return false; + } + (*indexVector)[i] = operandIndex; + Operand &operand = mOperands[operandIndex]; + if (operand.lifetime != OperandLifeTime::TEMPORARY_VARIABLE) + { + LOG(ERROR) << "ANeuralNetworksModel_identifyInputsAndOutputs Can't set operand " + << operandIndex + << " to be an input or output. Check that it's not a constant or " + "already an input or output"; + return false; + } + operand.lifetime = lifetime; + } + return true; + }; + + if (!setArguments(&mInputIndexes, inputCount, inputs, OperandLifeTime::MODEL_INPUT) || + !setArguments(&mOutputIndexes, outputCount, outputs, OperandLifeTime::MODEL_OUTPUT)) + { + return ANEURALNETWORKS_BAD_DATA; + } + + return ANEURALNETWORKS_NO_ERROR; +} + +int ModelBuilder::createCompilation(CompilationBuilder **compilation) +{ + if (!mCompletedModel || mInvalidModel) + { + LOG(ERROR) << "ANeuralNetworksCompilation_create passed an unfinished model"; + *compilation = nullptr; + return ANEURALNETWORKS_BAD_STATE; + } + *compilation = new CompilationBuilder(this); + return (*compilation ? ANEURALNETWORKS_NO_ERROR : ANEURALNETWORKS_OUT_OF_MEMORY); +} + +int ModelBuilder::finish() +{ + if (mCompletedModel) + { + LOG(ERROR) << "ANeuralNetworksModel_finish called more than once"; + return ANEURALNETWORKS_BAD_STATE; + } + if (mInvalidModel) + { + LOG(ERROR) << "ANeuralNetworksModel_finish called on an invalid model"; + return ANEURALNETWORKS_BAD_STATE; + } + + int n = copyLargeValuesToMemory(); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + + Model modelForValidation; + publish(&modelForValidation); + if (!validateModel(modelForValidation)) + { + LOG(ERROR) << "ANeuralNetworksModel_finish called on invalid model"; + mInvalidModel = true; + return ANEURALNETWORKS_BAD_DATA; + } + + // We sort the operations so that they will be in the appropriate + // order for a single-threaded, op at a time execution. + // TODO: we don't need this if we always run the partitioner. + sortIntoRunOrder(); + mCompletedModel = true; + return ANEURALNETWORKS_NO_ERROR; +} + +void ModelBuilder::sortIntoRunOrder() +{ + // Tracks the operations that can be executed. + std::vector opsReadyToRun; + std::vector runOrder; + + // Tracks how many inputs are needed for each operation to be ready to run. + std::multimap operandToOperations; + std::vector unknownInputCount(operationCount()); + for (uint32_t operationIndex = 0; operationIndex < operationCount(); operationIndex++) + { + uint32_t &count = unknownInputCount[operationIndex]; + count = 0; + for (uint32_t operandIndex : mOperations[operationIndex].inputs) + { + auto lifetime = mOperands[operandIndex].lifetime; + if (lifetime == OperandLifeTime::TEMPORARY_VARIABLE || + lifetime == OperandLifeTime::MODEL_OUTPUT) + { + count++; + operandToOperations.insert(std::pair(operandIndex, operationIndex)); + } + } + if (count == 0) + { + opsReadyToRun.push_back(operationIndex); + } + } + + while (opsReadyToRun.size() > 0) + { + // Execute the next op + int opIndex = opsReadyToRun.back(); + opsReadyToRun.pop_back(); + const Operation &operation = mOperations[opIndex]; + + runOrder.push_back(mOperations[opIndex]); + + // Mark all its outputs as known. + for (uint32_t operandIndex : operation.outputs) + { + auto range = operandToOperations.equal_range(operandIndex); + for (auto i = range.first; i != range.second; i++) + { + uint32_t &count = unknownInputCount[i->second]; + if (--count == 0) + { + opsReadyToRun.push_back(i->second); + } + } + } + } + mOperations = runOrder; +} + +void ModelBuilder::publish(Model *model) const +{ + model->operands = mOperands; + model->operations = mOperations; + model->inputIndexes = mInputIndexes; + model->outputIndexes = mOutputIndexes; + model->operandValues = mSmallOperandValues; + + uint32_t count = mMemories.size(); + model->pools.resize(count); + for (uint32_t i = 0; i < count; i++) + { + uint8_t *buffer; + mMemories[i]->getPointer(&buffer); + model->pools[i] = buffer; + } +} + +int ModelBuilder::copyLargeValuesToMemory() +{ + if (!mLargeOperandValues.empty()) + { + // Calculate the size of the shared memory needed for all the large values. + // Also sets the offset for each value within the memory. + size_t poolSize = 0; + for (LargeValue &l : mLargeOperandValues) + { + Operand &operand = mOperands[l.operandIndex]; + ASSERT(operand.lifetime == OperandLifeTime::CONSTANT_REFERENCE); + poolSize += alignBytesNeeded(poolSize, operand.location.length); + operand.location.offset = poolSize; + poolSize += operand.location.length; + } + + // Allocated the shared memory. + int n = mLargeValueMemory.create(poolSize); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + uint8_t *memoryPointer = nullptr; + n = mLargeValueMemory.getPointer(&memoryPointer); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + uint32_t poolIndex = mMemories.add(&mLargeValueMemory); + VLOG(MODEL) << "Allocated large value pool of size " << poolSize << " at index " << poolIndex; + + // Copy the values to this memory. + for (LargeValue &l : mLargeOperandValues) + { + Operand &operand = mOperands[l.operandIndex]; + operand.location.poolIndex = poolIndex; + memcpy(memoryPointer + operand.location.offset, l.buffer, operand.location.length); + } + } + return ANEURALNETWORKS_NO_ERROR; +} diff --git a/compiler/ann-ref/src/ModelBuilder.h b/compiler/ann-ref/src/ModelBuilder.h new file mode 100644 index 00000000000..ad50fad1d37 --- /dev/null +++ b/compiler/ann-ref/src/ModelBuilder.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __MODEL_BUILDER_H__ +#define __MODEL_BUILDER_H__ + +#include "NeuralNetworks.h" + +#include "Model.h" + +#include "Memory.h" +#include "MemoryTracker.h" + +#include +#include + +class CompilationBuilder; + +class ModelBuilder +{ +public: + virtual ~ModelBuilder() = default; + +public: + // Adds an operand to the model. + int addOperand(const ANeuralNetworksOperandType &type); + +public: + int setOperandValue(uint32_t index, const void *buffer, size_t length); + int setOperandValueFromMemory(uint32_t index, const Memory *memory, uint32_t offset, + size_t length); + +public: + int addOperation(OperationType type, uint32_t inputCount, const uint32_t *inputs, + uint32_t outputCount, const uint32_t *outputs); + +public: + int identifyInputsAndOutputs(uint32_t inputCount, const uint32_t *inputs, uint32_t outputCount, + const uint32_t *outputs); + +public: + int finish(); + bool isFinished() const { return mCompletedModel; } + +public: + int createCompilation(CompilationBuilder **compilation); + +public: + void publish(Model *model) const; + +public: + uint32_t operandCount() const + { + // We don't allow more than uint32_t worth of operands + return static_cast(mOperands.size()); + } + uint32_t operationCount() const + { + // We don't allow more than uint32_t worth of operations + return static_cast(mOperations.size()); + } + +public: + uint32_t inputCount() const { return static_cast(mInputIndexes.size()); } + uint32_t getInputOperandIndex(uint32_t i) const { return mInputIndexes[i]; } + const Operand &getInputOperand(uint32_t i) const { return mOperands[getInputOperandIndex(i)]; } + +public: + uint32_t outputCount() const { return static_cast(mOutputIndexes.size()); } + uint32_t getOutputOperandIndex(uint32_t i) const { return mOutputIndexes[i]; } + const Operand &getOutputOperand(uint32_t i) const { return mOperands[getOutputOperandIndex(i)]; } + +public: + const Operand &getOperand(uint32_t index) const { return mOperands[index]; } + const Operation &getOperation(uint32_t index) const { return mOperations[index]; } + +public: + const MemoryTracker &getMemories() const { return mMemories; } + const std::vector &getOperations() const { return mOperations; } + +private: + // Return true if either mCompleteModel or mInvalidModel is true. + bool badState(const char *name); + + // Sorts the operations to be in the correct order for single threaded + // node-at-a-time execution. + void sortIntoRunOrder(); + + // Copies the large values to a shared memory, if we have any. + int copyLargeValuesToMemory(); + +private: + // The operations of the graph. + std::vector mOperations; + // The description of the operands of the graph. + std::vector mOperands; + + // Specifies where to find the list of indexes identifying + // the inputs and outputs of the model. The offset is into + // the mOperandIndexes table. + std::vector mInputIndexes; + std::vector mOutputIndexes; + + MemoryTracker mMemories; + + // The value of the small operands that are defined at model + // creation time. + std::vector mSmallOperandValues; + + struct LargeValue + { + uint32_t operandIndex; + const void *buffer; + }; + // Operand index and buffer pointer for all the large operand values of this model. + std::vector mLargeOperandValues; + PrivateMemory mLargeValueMemory; + + // Once the model has been finished, we should not allow further + // modifications to the model. + mutable bool mCompletedModel = false; + + // Any invalid manipulation of the model will mark the model invalid. + // No further modifications are allowed to the model. + mutable bool mInvalidModel = false; +}; + +#endif // __MODEL_BUILDER_H__ diff --git a/compiler/ann-ref/src/NeuralNetworks.cpp b/compiler/ann-ref/src/NeuralNetworks.cpp new file mode 100644 index 00000000000..e43a8266727 --- /dev/null +++ b/compiler/ann-ref/src/NeuralNetworks.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "NeuralNetworks.h" + +#include "CompilationBuilder.h" +#include "ExecutionBuilder.h" +#include "ModelBuilder.h" +#include "Memory.h" + +#include "Logging.h" + +#include + +int ANeuralNetworksMemory_createFromFd(size_t size, int prot, int fd, size_t offset, + ANeuralNetworksMemory **memory) +{ + *memory = nullptr; + auto m = std::make_unique(); + if (m == nullptr) + { + return ANEURALNETWORKS_OUT_OF_MEMORY; + } + int n = m->set(size, prot, fd, offset); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + *memory = reinterpret_cast(m.release()); + return ANEURALNETWORKS_NO_ERROR; +} + +void ANeuralNetworksMemory_free(ANeuralNetworksMemory *memory) +{ + // No validation. Free of nullptr is valid. + Memory *m = reinterpret_cast(memory); + delete m; +} + +int ANeuralNetworksModel_create(ANeuralNetworksModel **model) +{ + if (!model) + { + LOG(ERROR) << "ANeuralNetworksModel_create passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = new ModelBuilder(); + if (m == nullptr) + { + *model = nullptr; + return ANEURALNETWORKS_OUT_OF_MEMORY; + } + *model = reinterpret_cast(m); + return ANEURALNETWORKS_NO_ERROR; +} + +void ANeuralNetworksModel_free(ANeuralNetworksModel *model) +{ + // No validation. Free of nullptr is valid. + ModelBuilder *m = reinterpret_cast(model); + delete m; +} + +int ANeuralNetworksModel_finish(ANeuralNetworksModel *model) +{ + if (!model) + { + LOG(ERROR) << "ANeuralNetworksModel_finish passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = reinterpret_cast(model); + return m->finish(); +} + +int ANeuralNetworksModel_addOperand(ANeuralNetworksModel *model, + const ANeuralNetworksOperandType *type) +{ + if (!model || !type) + { + LOG(ERROR) << "ANeuralNetworksModel_addOperand passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = reinterpret_cast(model); + return m->addOperand(*type); +} + +int ANeuralNetworksModel_setOperandValue(ANeuralNetworksModel *model, int32_t index, + const void *buffer, size_t length) +{ + if (!model || !buffer) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = reinterpret_cast(model); + return m->setOperandValue(index, buffer, length); +} + +int ANeuralNetworksModel_setOperandValueFromMemory(ANeuralNetworksModel *model, int32_t index, + const ANeuralNetworksMemory *memory, + size_t offset, size_t length) +{ + if (!model || !memory) + { + LOG(ERROR) << "ANeuralNetworksModel_setOperandValue passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + const Memory *mem = reinterpret_cast(memory); + ModelBuilder *m = reinterpret_cast(model); + return m->setOperandValueFromMemory(index, mem, offset, length); +} + +int ANeuralNetworksModel_addOperation(ANeuralNetworksModel *model, + ANeuralNetworksOperationType type, uint32_t inputCount, + const uint32_t *inputs, uint32_t outputCount, + const uint32_t *outputs) +{ + if (!model || !inputs || !outputs) + { + LOG(ERROR) << "ANeuralNetworksModel_addOperation passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = reinterpret_cast(model); + return m->addOperation(static_cast(type), inputCount, inputs, outputCount, + outputs); +} + +int ANeuralNetworksModel_identifyInputsAndOutputs(ANeuralNetworksModel *model, uint32_t inputCount, + const uint32_t *inputs, uint32_t outputCount, + const uint32_t *outputs) +{ + if (!model || !inputs || !outputs) + { + LOG(ERROR) << ("ANeuralNetworksModel_identifyInputsAndOutputs passed a nullptr"); + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ModelBuilder *m = reinterpret_cast(model); + return m->identifyInputsAndOutputs(inputCount, inputs, outputCount, outputs); +} + +int ANeuralNetworksCompilation_create(ANeuralNetworksModel *model, + ANeuralNetworksCompilation **compilation) +{ + if (!model || !compilation) + { + LOG(ERROR) << "ANeuralNetworksCompilation_create passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + ModelBuilder *m = reinterpret_cast(model); + CompilationBuilder *c = nullptr; + int result = m->createCompilation(&c); + *compilation = reinterpret_cast(c); + return result; +} + +void ANeuralNetworksCompilation_free(ANeuralNetworksCompilation *compilation) +{ + // No validation. Free of nullptr is valid. + // TODO specification says that a compilation-in-flight can be deleted + CompilationBuilder *c = reinterpret_cast(compilation); + delete c; +} + +int ANeuralNetworksCompilation_setPreference(ANeuralNetworksCompilation *compilation, + int32_t preference) +{ + if (!compilation) + { + LOG(ERROR) << "ANeuralNetworksCompilation_setPreference passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + // NOTE Ignore preference + return ANEURALNETWORKS_NO_ERROR; +} + +int ANeuralNetworksCompilation_finish(ANeuralNetworksCompilation *compilation) +{ + if (!compilation) + { + LOG(ERROR) << "ANeuralNetworksCompilation_finish passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + CompilationBuilder *c = reinterpret_cast(compilation); + return c->finish(); +} + +int ANeuralNetworksExecution_create(ANeuralNetworksCompilation *compilation, + ANeuralNetworksExecution **execution) +{ + if (!compilation || !execution) + { + LOG(ERROR) << "ANeuralNetworksExecution_create passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + CompilationBuilder *c = reinterpret_cast(compilation); + ExecutionBuilder *r = nullptr; + int result = c->createExecution(&r); + *execution = reinterpret_cast(r); + return result; +} + +void ANeuralNetworksExecution_free(ANeuralNetworksExecution *execution) +{ + // TODO specification says that an execution-in-flight can be deleted + // No validation. Free of nullptr is valid. + ExecutionBuilder *r = reinterpret_cast(execution); + delete r; +} + +int ANeuralNetworksExecution_setInput(ANeuralNetworksExecution *execution, int32_t index, + const ANeuralNetworksOperandType *type, const void *buffer, + size_t length) +{ + if (!execution) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInput passed execution with a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + if (!buffer && length != 0) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInput passed buffer with a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + ExecutionBuilder *r = reinterpret_cast(execution); + return r->setInput(index, type, buffer, length); +} + +int ANeuralNetworksExecution_setInputFromMemory(ANeuralNetworksExecution *execution, int32_t index, + const ANeuralNetworksOperandType *type, + const ANeuralNetworksMemory *memory, size_t offset, + size_t length) +{ + if (!execution || !memory) + { + LOG(ERROR) << "ANeuralNetworksExecution_setInputFromMemory passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + const Memory *m = reinterpret_cast(memory); + ExecutionBuilder *r = reinterpret_cast(execution); + return r->setInputFromMemory(index, type, m, offset, length); +} + +int ANeuralNetworksExecution_setOutput(ANeuralNetworksExecution *execution, int32_t index, + const ANeuralNetworksOperandType *type, void *buffer, + size_t length) +{ + if (!execution || !buffer) + { + LOG(ERROR) << "ANeuralNetworksExecution_setOutput passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + ExecutionBuilder *r = reinterpret_cast(execution); + return r->setOutput(index, type, buffer, length); +} + +int ANeuralNetworksExecution_setOutputFromMemory(ANeuralNetworksExecution *execution, int32_t index, + const ANeuralNetworksOperandType *type, + const ANeuralNetworksMemory *memory, size_t offset, + size_t length) +{ + if (!execution || !memory) + { + LOG(ERROR) << "ANeuralNetworksExecution_setOutputFromMemory passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + ExecutionBuilder *r = reinterpret_cast(execution); + const Memory *m = reinterpret_cast(memory); + return r->setOutputFromMemory(index, type, m, offset, length); +} + +int ANeuralNetworksExecution_startCompute(ANeuralNetworksExecution *execution, + ANeuralNetworksEvent **event) +{ + if (!execution || !event) + { + LOG(ERROR) << "ANeuralNetworksExecution_startCompute passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + // TODO validate the rest + + ExecutionBuilder *r = reinterpret_cast(execution); + + // Dynamically allocate an sp to wrap an ExecutionCallback, seen in the NN + // API as an abstract event object. The sp object is + // returned when the execution has been successfully launched, otherwise a + // nullptr is returned. The sp is used for ref-counting purposes. Without + // it, the HIDL service could attempt to communicate with a dead callback + // object. + *event = nullptr; + + int n = r->startCompute(); + if (n != ANEURALNETWORKS_NO_ERROR) + { + return n; + } + *event = reinterpret_cast(new int); + return ANEURALNETWORKS_NO_ERROR; +} + +int ANeuralNetworksEvent_wait(ANeuralNetworksEvent *event) +{ + if (event == nullptr) + { + LOG(ERROR) << "ANeuralNetworksEvent_wait passed a nullptr"; + return ANEURALNETWORKS_UNEXPECTED_NULL; + } + + return ANEURALNETWORKS_NO_ERROR; +} + +void ANeuralNetworksEvent_free(ANeuralNetworksEvent *event) +{ + // No validation. Free of nullptr is valid. + if (event) + { + int *e = reinterpret_cast(event); + delete e; + } +} diff --git a/compiler/ann-ref/src/Operand.h b/compiler/ann-ref/src/Operand.h new file mode 100644 index 00000000000..870a0564493 --- /dev/null +++ b/compiler/ann-ref/src/Operand.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OPERAND_H__ +#define __OPERAND_H__ + +#include "OperandType.h" + +#include +#include + +enum class OperandLifeTime : int32_t { + TEMPORARY_VARIABLE = 0, + MODEL_INPUT = 1, + MODEL_OUTPUT = 2, + CONSTANT_COPY = 3, + CONSTANT_REFERENCE = 4, + NO_VALUE = 5, +}; + +struct DataLocation final { + uint32_t poolIndex; + uint32_t offset; + uint32_t length; +}; + +struct Operand final { + OperandType type; + float scale; + int32_t zeroPoint; + + std::vector dimensions; + + DataLocation location; + + uint32_t numberOfConsumers; + OperandLifeTime lifetime; +}; + +// Returns the amount of space needed to store a value of the dimensions and +// type of this operand. +inline uint32_t sizeOfData(const Operand &operand) +{ + return sizeOfData(operand.type, operand.dimensions); +} + +#endif // __OPERAND_H__ diff --git a/compiler/ann-ref/src/OperandType.cpp b/compiler/ann-ref/src/OperandType.cpp new file mode 100644 index 00000000000..9f75fcc5452 --- /dev/null +++ b/compiler/ann-ref/src/OperandType.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "OperandType.h" +#include "Macro.h" + +const char *kTypeNames[] = { + "FLOAT32", "INT32", "UINT32", "TENSOR_FLOAT32", "TENSOR_INT32", "TENSOR_QUANT8_ASYMM", +}; + +static_assert(COUNT(kTypeNames) == kNumberOfDataTypes, "kTypeNames is incorrect"); + +const uint32_t kSizeOfDataType[]{ + 4, // ANEURALNETWORKS_FLOAT32 + 4, // ANEURALNETWORKS_INT32 + 4, // ANEURALNETWORKS_UINT32 + 4, // ANEURALNETWORKS_TENSOR_FLOAT32 + 4, // ANEURALNETWORKS_TENSOR_INT32 + 1 // ANEURALNETWORKS_TENSOR_SYMMETRICAL_QUANT8 +}; + +static_assert(COUNT(kSizeOfDataType) == kNumberOfDataTypes, "kSizeOfDataType is incorrect"); + +const char *getOperandTypeName(OperandType type) +{ + uint32_t n = static_cast(type); + return kTypeNames[n]; +} + +uint32_t sizeOfData(OperandType type, const std::vector &dimensions) +{ + int n = static_cast(type); + + uint32_t size = kSizeOfDataType[n]; + + for (auto d : dimensions) + { + size *= d; + } + return size; +} diff --git a/compiler/ann-ref/src/OperandType.h b/compiler/ann-ref/src/OperandType.h new file mode 100644 index 00000000000..3dfd2329b0e --- /dev/null +++ b/compiler/ann-ref/src/OperandType.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OPERAND_TYPES_H__ +#define __OPERAND_TYPES_H__ + +#include +#include + +enum class OperandType : int32_t { + FLOAT32 = 0, + INT32 = 1, + UINT32 = 2, + TENSOR_FLOAT32 = 3, + TENSOR_INT32 = 4, + TENSOR_QUANT8_ASYMM = 5, +}; + +// The number of data types (OperandCode) defined in NeuralNetworks.h. +const int kNumberOfDataTypes = 6; + +// Returns the name of the operand type in ASCII. +const char *getOperandTypeName(OperandType type); + +// Returns the amount of space needed to store a value of the specified +// dimensions and type. +uint32_t sizeOfData(OperandType type, const std::vector &dimensions); + +#endif // __OPERAND_TYPES_H__ diff --git a/compiler/ann-ref/src/OperandType.probe.cpp b/compiler/ann-ref/src/OperandType.probe.cpp new file mode 100644 index 00000000000..2caffdeebb8 --- /dev/null +++ b/compiler/ann-ref/src/OperandType.probe.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "OperandType.h" +#include "NeuralNetworks.h" + +static_assert(static_cast(OperandType::FLOAT32) == ANEURALNETWORKS_FLOAT32, + "FLOAT32 != ANEURALNETWORKS_FLOAT32"); +static_assert(static_cast(OperandType::INT32) == ANEURALNETWORKS_INT32, + "INT32 != ANEURALNETWORKS_INT32"); +static_assert(static_cast(OperandType::UINT32) == ANEURALNETWORKS_UINT32, + "UINT32 != ANEURALNETWORKS_UINT32"); + +static_assert(static_cast(OperandType::TENSOR_FLOAT32) == ANEURALNETWORKS_TENSOR_FLOAT32, + "TENSOR_FLOAT32 != ANEURALNETWORKS_TENSOR_FLOAT32"); +static_assert(static_cast(OperandType::TENSOR_QUANT8_ASYMM) == + ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, + "TENSOR_QUANT8_ASYMM != ANEURALNETWORKS_TENSOR_QUANT8_ASYMM"); diff --git a/compiler/ann-ref/src/Operation.h b/compiler/ann-ref/src/Operation.h new file mode 100644 index 00000000000..37f6a872723 --- /dev/null +++ b/compiler/ann-ref/src/Operation.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OPERATION_H__ +#define __OPERATION_H__ + +#include "OperationType.h" + +#include +#include + +struct Operation final { + OperationType type; + std::vector inputs; + std::vector outputs; +}; + +#endif // __OPERATION_H__ diff --git a/compiler/ann-ref/src/OperationType.cpp b/compiler/ann-ref/src/OperationType.cpp new file mode 100644 index 00000000000..f938b4d1cd8 --- /dev/null +++ b/compiler/ann-ref/src/OperationType.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "OperationType.h" +#include "Macro.h" + +const char *kOperationNames[kNumberOfOperationTypes] = { + "ADD", + "AVERAGE_POOL", + "CONCATENATION", + "CONV", + "DEPTHWISE_CONV", + "DEPTH_TO_SPACE", + "DEQUANTIZE", + "EMBEDDING_LOOKUP", + "FLOOR", + "FULLY_CONNECTED", + "HASHTABLE_LOOKUP", + "L2_NORMALIZATION", + "L2_POOL", + "LOCAL_RESPONSE_NORMALIZATION", + "LOGISTIC", + "LSH_PROJECTION", + "LSTM", + "MAX_POOL", + "MUL", + "RELU", + "RELU1", + "RELU6", + "RESHAPE", + "RESIZE_BILINEAR", + "RNN", + "SOFTMAX", + "SPACE_TO_DEPTH", + "SVDF", + "TANH", + "BATCH_TO_SPACE_ND", // V1_1, will not be merged till V1_1 is finalized + "DIV", + "MEAN", // V1_1, will not be merged till V1_1 is finalized + "PAD", // V1_1, will not be merged till V1_1 is finalized + "SPACE_TO_BATCH_ND", // V1_1, will not be merged till V1_1 is finalized + "SQUEEZE", // V1_1, will not be merged till V1_1 is finalized + "STRIDED_SLICE", + "SUB", +}; + +static_assert(COUNT(kOperationNames) == kNumberOfOperationTypes, "kOperationNames is incorrect"); + +const char *getOperationName(OperationType type) +{ + uint32_t n = static_cast(type); + return kOperationNames[n]; +} diff --git a/compiler/ann-ref/src/OperationType.h b/compiler/ann-ref/src/OperationType.h new file mode 100644 index 00000000000..fc66eeeabd0 --- /dev/null +++ b/compiler/ann-ref/src/OperationType.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OPERATION_TYPE_H__ +#define __OPERATION_TYPE_H__ + +#include + +enum class OperationType : int32_t { + ADD = 0, + AVERAGE_POOL_2D = 1, + CONCATENATION = 2, + CONV_2D = 3, + DEPTHWISE_CONV_2D = 4, + DEPTH_TO_SPACE = 5, + DEQUANTIZE = 6, + EMBEDDING_LOOKUP = 7, + FLOOR = 8, + FULLY_CONNECTED = 9, + HASHTABLE_LOOKUP = 10, + L2_NORMALIZATION = 11, + L2_POOL_2D = 12, + LOCAL_RESPONSE_NORMALIZATION = 13, + LOGISTIC = 14, + LSH_PROJECTION = 15, + LSTM = 16, + MAX_POOL_2D = 17, + MUL = 18, + RELU = 19, + RELU1 = 20, + RELU6 = 21, + RESHAPE = 22, + RESIZE_BILINEAR = 23, + RNN = 24, + SOFTMAX = 25, + SPACE_TO_DEPTH = 26, + SVDF = 27, + TANH = 28, + DIV = 30, + PAD = 32, + STRIDED_SLICE = 35, + SUB = 36, + OEM_OPERATION = 10000, +}; + +// The number of operation types (OperationCode) defined in NeuralNetworks.h. +const int kNumberOfOperationTypes = 37; + +// Returns the name of the operation in ASCII. +const char *getOperationName(OperationType opCode); + +#endif // __OPERATION_TYPE_H__ diff --git a/compiler/ann-ref/src/OperationType.probe.cpp b/compiler/ann-ref/src/OperationType.probe.cpp new file mode 100644 index 00000000000..c9886f35bd6 --- /dev/null +++ b/compiler/ann-ref/src/OperationType.probe.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "OperationType.h" +#include "NeuralNetworks.h" + +static_assert(static_cast(OperationType::ADD) == ANEURALNETWORKS_ADD, + "OperationType::ADD != ANEURALNETWORKS_ADD"); +static_assert(static_cast(OperationType::AVERAGE_POOL_2D) == + ANEURALNETWORKS_AVERAGE_POOL_2D, + "OperationType::AVERAGE_POOL_2D != ANEURALNETWORKS_AVERAGE_POOL_2D"); +static_assert(static_cast(OperationType::CONV_2D) == ANEURALNETWORKS_CONV_2D, + "OperationType::CONV_2D != ANEURALNETWORKS_CONV_2D"); +static_assert(static_cast(OperationType::DEPTHWISE_CONV_2D) == + ANEURALNETWORKS_DEPTHWISE_CONV_2D, + "OperationType::DEPTHWISE_CONV_2D != ANEURALNETWORKS_DEPTHWISE_CONV_2D"); +static_assert(static_cast(OperationType::DEPTH_TO_SPACE) == ANEURALNETWORKS_DEPTH_TO_SPACE, + "OperationType::DEPTH_TO_SPACE != ANEURALNETWORKS_DEPTH_TO_SPACE"); +static_assert(static_cast(OperationType::DEQUANTIZE) == ANEURALNETWORKS_DEQUANTIZE, + "OperationType::DEQUANTIZE != ANEURALNETWORKS_DEQUANTIZE"); +static_assert(static_cast(OperationType::EMBEDDING_LOOKUP) == + ANEURALNETWORKS_EMBEDDING_LOOKUP, + "OperationType::EMBEDDING_LOOKUP != ANEURALNETWORKS_EMBEDDING_LOOKUP"); +static_assert(static_cast(OperationType::FLOOR) == ANEURALNETWORKS_FLOOR, + "OperationType::FLOOR != ANEURALNETWORKS_FLOOR"); +static_assert(static_cast(OperationType::FULLY_CONNECTED) == + ANEURALNETWORKS_FULLY_CONNECTED, + "OperationType::FULLY_CONNECTED != ANEURALNETWORKS_FULLY_CONNECTED"); +static_assert(static_cast(OperationType::HASHTABLE_LOOKUP) == + ANEURALNETWORKS_HASHTABLE_LOOKUP, + "OperationType::HASHTABLE_LOOKUP != ANEURALNETWORKS_HASHTABLE_LOOKUP"); +static_assert(static_cast(OperationType::L2_NORMALIZATION) == + ANEURALNETWORKS_L2_NORMALIZATION, + "OperationType::L2_NORMALIZATION != ANEURALNETWORKS_L2_NORMALIZATION"); +static_assert(static_cast(OperationType::L2_POOL_2D) == ANEURALNETWORKS_L2_POOL_2D, + "OperationType::L2_POOL_2D != ANEURALNETWORKS_L2_POOL_2D"); +static_assert(static_cast(OperationType::LOCAL_RESPONSE_NORMALIZATION) == + ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION, + "OperationType::LOCAL_RESPONSE_NORMALIZATION != " + "ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION"); +static_assert(static_cast(OperationType::LOGISTIC) == ANEURALNETWORKS_LOGISTIC, + "OperationType::LOGISTIC != ANEURALNETWORKS_LOGISTIC"); +static_assert(static_cast(OperationType::LSH_PROJECTION) == ANEURALNETWORKS_LSH_PROJECTION, + "OperationType::LSH_PROJECTION != ANEURALNETWORKS_LSH_PROJECTION"); +static_assert(static_cast(OperationType::LSTM) == ANEURALNETWORKS_LSTM, + "OperationType::LSTM != ANEURALNETWORKS_LSTM"); +static_assert(static_cast(OperationType::MAX_POOL_2D) == ANEURALNETWORKS_MAX_POOL_2D, + "OperationType::MAX_POOL_2D != ANEURALNETWORKS_MAX_POOL_2D"); +static_assert(static_cast(OperationType::MUL) == ANEURALNETWORKS_MUL, + "OperationType::MUL != ANEURALNETWORKS_MUL"); +static_assert(static_cast(OperationType::RELU) == ANEURALNETWORKS_RELU, + "OperationType::RELU != ANEURALNETWORKS_RELU"); +static_assert(static_cast(OperationType::RELU1) == ANEURALNETWORKS_RELU1, + "OperationType::RELU1 != ANEURALNETWORKS_RELU1"); +static_assert(static_cast(OperationType::RELU6) == ANEURALNETWORKS_RELU6, + "OperationType::RELU6 != ANEURALNETWORKS_RELU6"); +static_assert(static_cast(OperationType::RESHAPE) == ANEURALNETWORKS_RESHAPE, + "OperationType::RESHAPE != ANEURALNETWORKS_RESHAPE"); +static_assert(static_cast(OperationType::RESIZE_BILINEAR) == + ANEURALNETWORKS_RESIZE_BILINEAR, + "OperationType::RESIZE_BILINEAR != ANEURALNETWORKS_RESIZE_BILINEAR"); +static_assert(static_cast(OperationType::RNN) == ANEURALNETWORKS_RNN, + "OperationType::RNN != ANEURALNETWORKS_RNN"); +static_assert(static_cast(OperationType::SOFTMAX) == ANEURALNETWORKS_SOFTMAX, + "OperationType::SOFTMAX != ANEURALNETWORKS_SOFTMAX"); +static_assert(static_cast(OperationType::SPACE_TO_DEPTH) == ANEURALNETWORKS_SPACE_TO_DEPTH, + "OperationType::SPACE_TO_DEPTH != ANEURALNETWORKS_SPACE_TO_DEPTH"); +static_assert(static_cast(OperationType::SVDF) == ANEURALNETWORKS_SVDF, + "OperationType::SVDF != ANEURALNETWORKS_SVDF"); +static_assert(static_cast(OperationType::TANH) == ANEURALNETWORKS_TANH, + "OperationType::TANH != ANEURALNETWORKS_TANH"); diff --git a/compiler/ann-ref/src/Probe.cpp b/compiler/ann-ref/src/Probe.cpp new file mode 100644 index 00000000000..3a085a19d9c --- /dev/null +++ b/compiler/ann-ref/src/Probe.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "NeuralNetworks.h" + +// Make sure the constants defined in the header files have not changed values. +// IMPORTANT: When adding new values, update kNumberOfDataTypes or kNumberOfDataTypesOEM +// in Utils.h. +static_assert(ANEURALNETWORKS_FLOAT32 == 0, "ANEURALNETWORKS_FLOAT32 has changed"); +static_assert(ANEURALNETWORKS_INT32 == 1, "ANEURALNETWORKS_INT32 has changed"); +static_assert(ANEURALNETWORKS_UINT32 == 2, "ANEURALNETWORKS_UINT32 has changed"); +static_assert(ANEURALNETWORKS_TENSOR_FLOAT32 == 3, "ANEURALNETWORKS_TENSOR_FLOAT32 has changed"); +static_assert(ANEURALNETWORKS_TENSOR_INT32 == 4, "ANEURALNETWORKS_TENSOR_INT32 has changed"); +static_assert(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM == 5, + "ANEURALNETWORKS_TENSOR_QUANT8_ASYMM has changed"); + +// IMPORTANT: When adding new values, update kNumberOfOperationTypes or +// kNumberOfOperationTypesOEM kNumberOfOperationTypesEx in Utils.h. +static_assert(ANEURALNETWORKS_ADD == 0, "ANEURALNETWORKS_ADD has changed"); +static_assert(ANEURALNETWORKS_AVERAGE_POOL_2D == 1, "ANEURALNETWORKS_AVERAGE_POOL_2D has changed"); +static_assert(ANEURALNETWORKS_CONCATENATION == 2, "ANEURALNETWORKS_CONCATENATION has changed"); +static_assert(ANEURALNETWORKS_CONV_2D == 3, "ANEURALNETWORKS_CONV_2D has changed"); +static_assert(ANEURALNETWORKS_DEPTHWISE_CONV_2D == 4, + "ANEURALNETWORKS_DEPTHWISE_CONV_2D has changed"); +static_assert(ANEURALNETWORKS_DEPTH_TO_SPACE == 5, "ANEURALNETWORKS_DEPTH_TO_SPACE has changed"); +static_assert(ANEURALNETWORKS_DEQUANTIZE == 6, "ANEURALNETWORKS_DEQUANTIZE has changed"); +static_assert(ANEURALNETWORKS_EMBEDDING_LOOKUP == 7, + "ANEURALNETWORKS_EMBEDDING_LOOKUP has changed"); +static_assert(ANEURALNETWORKS_FLOOR == 8, "ANEURALNETWORKS_FLOOR has changed"); +static_assert(ANEURALNETWORKS_FULLY_CONNECTED == 9, "ANEURALNETWORKS_FULLY_CONNECTED has changed"); +static_assert(ANEURALNETWORKS_HASHTABLE_LOOKUP == 10, + "ANEURALNETWORKS_HASHTABLE_LOOKUP has changed"); +static_assert(ANEURALNETWORKS_L2_NORMALIZATION == 11, + "ANEURALNETWORKS_L2_NORMALIZATION has changed"); +static_assert(ANEURALNETWORKS_L2_POOL_2D == 12, "ANEURALNETWORKS_L2_POOL has changed"); +static_assert(ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION == 13, + "ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION has changed"); +static_assert(ANEURALNETWORKS_LOGISTIC == 14, "ANEURALNETWORKS_LOGISTIC has changed"); +static_assert(ANEURALNETWORKS_LSH_PROJECTION == 15, "ANEURALNETWORKS_LSH_PROJECTION has changed"); +static_assert(ANEURALNETWORKS_LSTM == 16, "ANEURALNETWORKS_LSTM has changed"); +static_assert(ANEURALNETWORKS_MAX_POOL_2D == 17, "ANEURALNETWORKS_MAX_POOL has changed"); +static_assert(ANEURALNETWORKS_MUL == 18, "ANEURALNETWORKS_MUL has changed"); +static_assert(ANEURALNETWORKS_RELU == 19, "ANEURALNETWORKS_RELU has changed"); +static_assert(ANEURALNETWORKS_RELU1 == 20, "ANEURALNETWORKS_RELU1 has changed"); +static_assert(ANEURALNETWORKS_RELU6 == 21, "ANEURALNETWORKS_RELU6 has changed"); +static_assert(ANEURALNETWORKS_RESHAPE == 22, "ANEURALNETWORKS_RESHAPE has changed"); +static_assert(ANEURALNETWORKS_RESIZE_BILINEAR == 23, "ANEURALNETWORKS_RESIZE_BILINEAR has changed"); +static_assert(ANEURALNETWORKS_RNN == 24, "ANEURALNETWORKS_RNN has changed"); +static_assert(ANEURALNETWORKS_SOFTMAX == 25, "ANEURALNETWORKS_SOFTMAX has changed"); +static_assert(ANEURALNETWORKS_SPACE_TO_DEPTH == 26, "ANEURALNETWORKS_SPACE_TO_DEPTH has changed"); +static_assert(ANEURALNETWORKS_SVDF == 27, "ANEURALNETWORKS_SVDF has changed"); +static_assert(ANEURALNETWORKS_TANH == 28, "ANEURALNETWORKS_TANH has changed"); + +static_assert(ANEURALNETWORKS_FUSED_NONE == 0, "ANEURALNETWORKS_FUSED_NONE has changed"); +static_assert(ANEURALNETWORKS_FUSED_RELU == 1, "ANEURALNETWORKS_FUSED_RELU has changed"); +static_assert(ANEURALNETWORKS_FUSED_RELU1 == 2, "ANEURALNETWORKS_FUSED_RELU1 has changed"); +static_assert(ANEURALNETWORKS_FUSED_RELU6 == 3, "ANEURALNETWORKS_FUSED_RELU6 has changed"); + +static_assert(ANEURALNETWORKS_PREFER_LOW_POWER == 0, + "ANEURALNETWORKS_PREFER_LOW_POWER has changed"); +static_assert(ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER == 1, + "ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER has changed"); +static_assert(ANEURALNETWORKS_PREFER_SUSTAINED_SPEED == 2, + "ANEURALNETWORKS_PREFER_SUSTAINED_SPEED has changed"); + +static_assert(ANEURALNETWORKS_NO_ERROR == 0, "ANEURALNETWORKS_NO_ERROR has changed"); +static_assert(ANEURALNETWORKS_OUT_OF_MEMORY == 1, "ANEURALNETWORKS_OUT_OF_MEMORY has changed"); +static_assert(ANEURALNETWORKS_INCOMPLETE == 2, "ANEURALNETWORKS_INCOMPLETE has changed"); +static_assert(ANEURALNETWORKS_UNEXPECTED_NULL == 3, "ANEURALNETWORKS_UNEXPECTED_NULL has changed"); +static_assert(ANEURALNETWORKS_BAD_DATA == 4, "ANEURALNETWORKS_BAD_DATA has changed"); +static_assert(ANEURALNETWORKS_OP_FAILED == 5, "ANEURALNETWORKS_OP_FAILED has changed"); +static_assert(ANEURALNETWORKS_BAD_STATE == 6, "ANEURALNETWORKS_BAD_STATE has changed"); + +static_assert(ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES == 128, + "ANEURALNETWORKS_MAX_SIZE_OF_IMMEDIATELY_COPIED_VALUES has changed"); diff --git a/compiler/ann-ref/src/Request.h b/compiler/ann-ref/src/Request.h new file mode 100644 index 00000000000..49f74fdf517 --- /dev/null +++ b/compiler/ann-ref/src/Request.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __REQUEST_H__ +#define __REQUEST_H__ + +#include +#include + +struct RequestArgument final { + bool hasNoValue; + DataLocation location; + std::vector dimensions; +}; + +struct Request final { + std::vector inputs; + std::vector outputs; +}; + +#endif // __REQUEST_H__ diff --git a/compiler/ann-ref/src/Shape.cpp b/compiler/ann-ref/src/Shape.cpp new file mode 100644 index 00000000000..37a54c2137e --- /dev/null +++ b/compiler/ann-ref/src/Shape.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Shape.h" + +#include // For 'size_t' + +bool SameShape(const Shape &in1, const Shape &in2) +{ + if (in1.type != in2.type || in1.dimensions.size() != in2.dimensions.size()) + { + return false; + } + for (size_t i = 0; i < in1.dimensions.size(); i++) + { + if (in1.dimensions[i] != in2.dimensions[i]) + { + return false; + } + } + return true; +} + +bool SetShape(const Shape &in, Shape *out) +{ + if (in.type != out->type || in.dimensions.size() != out->dimensions.size()) + { + return false; + } + out->dimensions = in.dimensions; + return true; +} + +uint32_t getNumberOfElements(const Shape &shape) +{ + uint32_t count = 1; + for (size_t i = 0; i < shape.dimensions.size(); i++) + { + count *= shape.dimensions[i]; + } + return count; +} + +uint32_t getNumberOfDimensions(const Shape &shape) { return shape.dimensions.size(); } + +uint32_t getSizeOfDimension(const Shape &shape, uint32_t dimensionIdx) +{ + if (dimensionIdx >= shape.dimensions.size()) + { + // TODO, log the error + return 0; + } + return shape.dimensions[dimensionIdx]; +} diff --git a/compiler/ann-ref/src/Shape.h b/compiler/ann-ref/src/Shape.h new file mode 100644 index 00000000000..2e3d92e5086 --- /dev/null +++ b/compiler/ann-ref/src/Shape.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __SHAPE_H__ +#define __SHAPE_H__ + +#include "OperandType.h" + +#include +#include + +// The type and dimensions of an operand. +struct Shape +{ + OperandType type; + std::vector dimensions; + float scale; + int32_t offset; +}; + +// Verifies that the two shapes are the same. +bool SameShape(const Shape &in1, const Shape &in2); + +// Sets out to the same shape as in. +bool SetShape(const Shape &in, Shape *out); + +// Return the total number of elements, i.e. all the dimensions multiplied +// together. For a scalar, returns one. +uint32_t getNumberOfElements(const Shape &shape); +uint32_t getNumberOfDimensions(const Shape &shape); +uint32_t getSizeOfDimension(const Shape &shape, uint32_t dimensionIdx); + +#endif // __SHAPE_H__ diff --git a/compiler/ann-ref/src/Validation.cpp b/compiler/ann-ref/src/Validation.cpp new file mode 100644 index 00000000000..679b14a9a52 --- /dev/null +++ b/compiler/ann-ref/src/Validation.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Validation.h" +#include "Macro.h" +#include "Assert.h" + +static inline bool validCode(uint32_t codeCount, uint32_t code) +{ + return (code < codeCount); +} + +int validateOperationType(const OperationType &type) +{ + return validCode(kNumberOfOperationTypes, static_cast(type)); +} + +// Validates the type. The used dimensions can be underspecified. +int validateOperandType(const ANeuralNetworksOperandType &type, const char *tag, bool allowPartial) +{ + if (!allowPartial) + { + for (uint32_t i = 0; i < type.dimensionCount; i++) + { + if (type.dimensions[i] == 0) + { + LOG(ERROR) << tag << " OperandType invalid dimensions[" << i + << "] = " << type.dimensions[i]; + return ANEURALNETWORKS_BAD_DATA; + } + } + } + if (!validCode(kNumberOfDataTypes, type.type)) + { + LOG(ERROR) << tag << " OperandType invalid type " << type.type; + return ANEURALNETWORKS_BAD_DATA; + } + if (type.type == ANEURALNETWORKS_TENSOR_QUANT8_ASYMM) + { + if (type.zeroPoint < 0 || type.zeroPoint > 255) + { + LOG(ERROR) << tag << " OperandType invalid zeroPoint " << type.zeroPoint; + return ANEURALNETWORKS_BAD_DATA; + } + if (type.scale < 0.f) + { + LOG(ERROR) << tag << " OperandType invalid scale " << type.scale; + return ANEURALNETWORKS_BAD_DATA; + } + } + + // TODO-NNRT : add 'type.type == ANEURALNETWORKS_OEM_SCALAR' later. + // OEM operaters are not supported now. + if (type.type == ANEURALNETWORKS_FLOAT32 || type.type == ANEURALNETWORKS_INT32 || + type.type == ANEURALNETWORKS_UINT32) + { + if (type.dimensionCount != 0 || type.dimensions != nullptr) + { + LOG(ERROR) << tag << " Invalid dimensions for scalar type"; + return ANEURALNETWORKS_BAD_DATA; + } + } + + return ANEURALNETWORKS_NO_ERROR; +} + +int validateOperandList(uint32_t count, const uint32_t *list, uint32_t operandCount, + const char *tag) +{ + for (uint32_t i = 0; i < count; i++) + { + if (list[i] >= operandCount) + { + LOG(ERROR) << tag << " invalid operand index at " << i << " = " << list[i] + << ", operandCount " << operandCount; + return ANEURALNETWORKS_BAD_DATA; + } + } + return ANEURALNETWORKS_NO_ERROR; +} + +static bool validOperandIndexes(const std::vector indexes, size_t operandCount) +{ + for (uint32_t i : indexes) + { + if (i >= operandCount) + { + LOG(ERROR) << "Index out of range " << i << "/" << operandCount; + return false; + } + } + return true; +} + +static bool validOperands(const std::vector &operands, const std::vector &operandValues) +{ + for (auto &operand : operands) + { + if (!validCode(kNumberOfDataTypes, static_cast(operand.type))) + { + LOG(ERROR) << "Invalid operand type "; + return false; + } + /* TODO validate dim with type + if (!validOperandIndexes(operand.dimensions, mDimensions)) { + return false; + } + */ + switch (operand.lifetime) + { + case OperandLifeTime::CONSTANT_COPY: + if (operand.location.offset + operand.location.length > operandValues.size()) + { + LOG(ERROR) << "OperandValue location out of range. Starts at " << operand.location.offset + << ", length " << operand.location.length << ", max " << operandValues.size(); + return false; + } + break; + case OperandLifeTime::TEMPORARY_VARIABLE: + case OperandLifeTime::MODEL_INPUT: + case OperandLifeTime::MODEL_OUTPUT: + case OperandLifeTime::NO_VALUE: + if (operand.location.offset != 0 || operand.location.length != 0) + { + LOG(ERROR) << "Unexpected offset " << operand.location.offset << " or length " + << operand.location.length << " for runtime location."; + return false; + } + break; + case OperandLifeTime::CONSTANT_REFERENCE: +#if 0 + if (operand.location.poolIndex >= poolCount) + { + LOG(ERROR) << "Invalid poolIndex " << operand.location.poolIndex << "/" << poolCount; + return false; + } +#endif + break; + // TODO: Validate that we are within the pool. + default: + LOG(ERROR) << "Invalid lifetime"; + return false; + } + } + return true; +} + +static bool validOperations(const std::vector &operations, size_t operandCount) +{ + for (auto &op : operations) + { + if (!validCode(kNumberOfOperationTypes, static_cast(op.type))) + { + LOG(ERROR) << "Invalid operation type "; + return false; + } + if (!validOperandIndexes(op.inputs, operandCount) || + !validOperandIndexes(op.outputs, operandCount)) + { + return false; + } + } + return true; +} + +// TODO doublecheck +bool validateModel(const Model &model) +{ + const size_t operandCount = model.operands.size(); + return (validOperands(model.operands, model.operandValues) && + validOperations(model.operations, operandCount) && + validOperandIndexes(model.inputIndexes, operandCount) && + validOperandIndexes(model.outputIndexes, operandCount)); +} + +bool validRequestArguments(const std::vector &arguments, + const std::vector &operandIndexes, + const std::vector &operands, size_t poolCount, const char *type) +{ + const size_t argumentCount = arguments.size(); + if (argumentCount != operandIndexes.size()) + { + LOG(ERROR) << "Request specifies " << argumentCount << " " << type << "s but the model has " + << operandIndexes.size(); + return false; + } + for (size_t argumentIndex = 0; argumentIndex < argumentCount; argumentIndex++) + { + const RequestArgument &argument = arguments[argumentIndex]; + const uint32_t operandIndex = operandIndexes[argumentIndex]; + const Operand &operand = operands[operandIndex]; + if (argument.hasNoValue) + { + if (argument.location.poolIndex != 0 || argument.location.offset != 0 || + argument.location.length != 0 || argument.dimensions.size() != 0) + { + LOG(ERROR) << "Request " << type << " " << argumentIndex + << " has no value yet has details."; + return false; + } + } + if (argument.location.poolIndex >= poolCount) + { + LOG(ERROR) << "Request " << type << " " << argumentIndex << " has an invalid poolIndex " + << argument.location.poolIndex << "/" << poolCount; + return false; + } + // TODO: Validate that we are within the pool. + uint32_t rank = argument.dimensions.size(); + if (rank > 0) + { + if (rank != operand.dimensions.size()) + { + LOG(ERROR) << "Request " << type << " " << argumentIndex << " has number of dimensions (" + << rank << ") different than the model's (" << operand.dimensions.size() << ")"; + return false; + } + for (size_t i = 0; i < rank; i++) + { + if (argument.dimensions[i] != operand.dimensions[i] && operand.dimensions[i] != 0) + { + LOG(ERROR) << "Request " << type << " " << argumentIndex << " has dimension " << i + << " of " << operand.dimensions[i] << " different than the model's " + << operand.dimensions[i]; + return false; + } + if (argument.dimensions[i] == 0) + { + LOG(ERROR) << "Request " << type << " " << argumentIndex << " has dimension " << i + << " of zero"; + return false; + } + } + } + } + return true; +} + +// TODO doublecheck +bool validateRequest(const Request &request, const Model &model) +{ + //const size_t poolCount = request.pools.size(); + const size_t poolCount = 0; + return (validRequestArguments(request.inputs, model.inputIndexes, model.operands, poolCount, + "input") && + validRequestArguments(request.outputs, model.outputIndexes, model.operands, poolCount, + "output")); +} + diff --git a/compiler/ann-ref/src/Validation.h b/compiler/ann-ref/src/Validation.h new file mode 100644 index 00000000000..dab426af404 --- /dev/null +++ b/compiler/ann-ref/src/Validation.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __VALIDATION_H__ +#define __VALIDATION_H__ + +#include "OperationType.h" +#include "Model.h" +#include "Request.h" +#include "NeuralNetworks.h" + +int validateOperationType(const OperationType &); +int validateOperandType(const ANeuralNetworksOperandType &type, const char *tag, bool allowPartial); +int validateOperandList(uint32_t count, const uint32_t *list, uint32_t operandCount, + const char *tag); + +bool validateModel(const Model &model); +bool validateRequest(const Request &request, const Model &model); + +#endif // __VALIDATION_H__ diff --git a/compiler/ann-ref/src/ops/Add.cpp b/compiler/ann-ref/src/ops/Add.cpp new file mode 100644 index 00000000000..0b826f05dec --- /dev/null +++ b/compiler/ann-ref/src/ops/Add.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Add.h" +#include "Assert.h" + +bool addPrepare(const Shape &in1, const Shape &in2, Shape *out) +{ + ASSERT(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4); + ASSERT(in1.type == in2.type); + if (SameShape(in1, in2)) + { + return SetShape(in1, out); + } + else + { + // BroadcastAdd needed + uint32_t numberOfDims1 = getNumberOfDimensions(in1); + uint32_t numberOfDims2 = getNumberOfDimensions(in2); + uint32_t maxDims = std::max(numberOfDims1, numberOfDims2); + out->dimensions = std::vector(maxDims); + for (uint32_t i = 1; i <= maxDims; i++) + { + uint32_t dim1 = 1; + if (i <= numberOfDims1) + { + dim1 = getSizeOfDimension(in1, numberOfDims1 - i); + } + uint32_t dim2 = 1; + if (i <= numberOfDims2) + { + dim2 = getSizeOfDimension(in2, numberOfDims2 - i); + } + if (dim1 != dim2 && dim1 != 1 && dim2 != 1) + { + LOG(ERROR) << "Dimensions mismatch for BroadcastAdd"; + return false; + } + out->dimensions[maxDims - i] = std::max(dim1, dim2); + } + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Add.float.cpp b/compiler/ann-ref/src/ops/Add.float.cpp new file mode 100644 index 00000000000..ce825d43df2 --- /dev/null +++ b/compiler/ann-ref/src/ops/Add.float.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Add.float.h" + +#include "internal/Array.h" +#include "internal/NDArray.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +template +void Add(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + MatchingArraySize(input1_dims, 3, input2_dims, 3, output_dims, 3); + MatchingArraySize(input1_dims, 2, input2_dims, 2, output_dims, 2); + MatchingArraySize(input1_dims, 1, input2_dims, 1, output_dims, 1); + MatchingArraySize(input1_dims, 0, input2_dims, 0, output_dims, 0); + DCHECK(IsPackedWithoutStrides(input1_dims)); + DCHECK(IsPackedWithoutStrides(input2_dims)); + DCHECK(IsPackedWithoutStrides(output_dims)); + + int i = 0; + const int size = input1_dims.sizes[3] * input1_dims.strides[3]; + + for (; i < size; i++) + { + auto x = input1_data[i] + input2_data[i]; + output_data[i] = ActivationFunction(x); + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// TODO: We can implement BroadcastAdd on buffers of arbitrary +// dimensionality if the runtime code does a single loop over one dimension +// that handles broadcasting as the base case. The code generator would then +// generate max(D1, D2) nested for loops. +// TODO: BroadcastAdd is intentionally duplicated from +// reference_ops.h. Once an optimized version is implemented and NdArrayDesc +// is no longer referenced in this file, move NdArrayDesc from types.h to +// reference_ops.h. +template +void BroadcastAdd(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + NdArrayDesc<4> desc1; + NdArrayDesc<4> desc2; + NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); + + // In Tensorflow, the dimensions are canonically named (batch_number, row, + // col, channel), with extents (batches, height, width, depth), with the + // trailing dimension changing most rapidly (channels has the smallest stride, + // typically 1 element). + // + // In generated C code, we store arrays with the dimensions reversed. The + // first dimension has smallest stride. + // + // We name our variables by their Tensorflow convention, but generate C code + // nesting loops such that the innermost loop has the smallest stride for the + // best cache behavior. + for (int b = 0; b < ArraySize(output_dims, 3); ++b) + { + for (int y = 0; y < ArraySize(output_dims, 2); ++y) + { + for (int x = 0; x < ArraySize(output_dims, 1); ++x) + { + for (int c = 0; c < ArraySize(output_dims, 0); ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(input1_data[SubscriptToIndex(desc1, c, x, y, b)] + + input2_data[SubscriptToIndex(desc2, c, x, y, b)]); + } + } + } + } +} + +bool addFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut) +{ + bool needBroadcast = !SameShape(shape1, shape2); + +#define ANDROID_NN_NORMAL_ADD(activation) \ + Add(in1, convertShapeToDims(shape1), \ + in2, convertShapeToDims(shape2), \ + out, convertShapeToDims(shapeOut)) + +#define ANDROID_NN_BROADCAST_ADD(activation) \ + BroadcastAdd( \ + in1, convertShapeToDims(shape1), in2, convertShapeToDims(shape2), out, \ + convertShapeToDims(shapeOut)) + + if (needBroadcast) + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_BROADCAST_ADD) + } + else + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_NORMAL_ADD) + } + +#undef ANDROID_NN_NORMAL_ADD +#undef ANDROID_NN_BROADCAST_ADD + return true; +} diff --git a/compiler/ann-ref/src/ops/Add.float.h b/compiler/ann-ref/src/ops/Add.float.h new file mode 100644 index 00000000000..3657a045dae --- /dev/null +++ b/compiler/ann-ref/src/ops/Add.float.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_ADD_FLOAT_H__ +#define __OP_ADD_FLOAT_H__ + +#include "Shape.h" + +#include + +bool addFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut); + +#endif // __OP_ADD_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Add.h b/compiler/ann-ref/src/ops/Add.h new file mode 100644 index 00000000000..c6751fc001f --- /dev/null +++ b/compiler/ann-ref/src/ops/Add.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_ADD_H__ +#define __OP_ADD_H__ + +#include "Shape.h" + +bool addPrepare(const Shape &in1, const Shape &in2, Shape *out1); + +#endif // __OP_ADD_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/AvgPool2D.cpp b/compiler/ann-ref/src/ops/AvgPool2D.cpp new file mode 100644 index 00000000000..cd9fcff662f --- /dev/null +++ b/compiler/ann-ref/src/ops/AvgPool2D.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "AvgPool2D.h" + +#include "internal/Pooling.h" + +bool averagePoolPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output) +{ + return genericPoolingPrepare(input, padding_left, padding_right, padding_top, padding_bottom, + stride_width, stride_height, filter_width, filter_height, + output); +} diff --git a/compiler/ann-ref/src/ops/AvgPool2D.float.cpp b/compiler/ann-ref/src/ops/AvgPool2D.float.cpp new file mode 100644 index 00000000000..21d3e977c34 --- /dev/null +++ b/compiler/ann-ref/src/ops/AvgPool2D.float.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "AvgPool2D.float.h" + +#include "internal/Array.h" +#include "internal/Matrix.h" +#include "internal/FeatureMap.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +// From optimized_ops.h in TensorFlow Lite +template +void AveragePool(const float *input_data, const Dims<4> &input_dims, int stride_width, + int stride_height, int pad_width, int pad_height, int kwidth, int kheight, + float *output_data, const Dims<4> &output_dims) +{ + const int batches = MatchingArraySize(input_dims, 3, output_dims, 3); + const int input_height = ArraySize(input_dims, 2); + const int input_width = ArraySize(input_dims, 1); + const int output_height = ArraySize(output_dims, 2); + const int output_width = ArraySize(output_dims, 1); + const int depth = MatchingArraySize(input_dims, 0, output_dims, 0); + + const auto in_mat = MapAsMatrixWithFirstDimAsRows(input_data, input_dims); + auto out_mat = MapAsMatrixWithFirstDimAsRows(output_data, output_dims); + // TODO: get rid of the dynamic memory allocation here! + Eigen::VectorXf out_count(out_mat.cols()); + out_count.setZero(); + // Prefill the output to 0. + out_mat.setZero(); + for (int b = 0; b < batches; ++b) + { + for (int h = 0; h < input_height; ++h) + { + for (int w = 0; w < input_width; ++w) + { + // (h_start, h_end) * (w_start, w_end) is the range that the input + // vector projects to. + int hpad = h + pad_height; + int wpad = w + pad_width; + int h_start = (hpad < kheight) ? 0 : (hpad - kheight) / stride_height + 1; + int h_end = std::min(hpad / stride_height + 1, output_height); + int w_start = (wpad < kwidth) ? 0 : (wpad - kwidth) / stride_width + 1; + int w_end = std::min(wpad / stride_width + 1, output_width); + // compute elementwise sum + for (int ph = h_start; ph < h_end; ++ph) + { + for (int pw = w_start; pw < w_end; ++pw) + { + int out_offset = NodeOffset(b, ph, pw, output_height, output_width); + out_mat.col(out_offset) += in_mat.col(NodeOffset(b, h, w, input_height, input_width)); + out_count(out_offset)++; + } + } + } + } + } + // Divide the output by the actual number of elements being averaged over + DCHECK_GT(out_count.minCoeff(), 0); + out_mat.array().rowwise() /= out_count.transpose().array(); + + for (int b = 0; b < batches; ++b) + { + for (int y = 0; y < output_height; ++y) + { + for (int x = 0; x < output_width; ++x) + { + for (int c = 0; c < depth; ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(output_data[Offset(output_dims, c, x, y, b)]); + } + } + } + } +} + +#define ANDROID_NN_POOLING_PARAMETERS \ + uint32_t height = getSizeOfDimension(inputShape, 1); \ + uint32_t width = getSizeOfDimension(inputShape, 2); \ + uint32_t outHeight = getSizeOfDimension(outputShape, 1); \ + uint32_t outWidth = getSizeOfDimension(outputShape, 2); \ + \ + uint32_t paddingHeight = (uint32_t)padding_top; \ + uint32_t paddingWidth = (uint32_t)padding_left; + +bool averagePoolFloat32(const float *inputData, const Shape &inputShape, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, int32_t filter_width, + int32_t filter_height, int32_t activation, float *outputData, + const Shape &outputShape) +{ + + ANDROID_NN_POOLING_PARAMETERS + +#define ANDROID_NN_AVERAGE_POOL(activation) \ + AveragePool( \ + inputData, convertShapeToDims(inputShape), stride_width, stride_height, paddingWidth, \ + paddingHeight, filter_width, filter_height, outputData, convertShapeToDims(outputShape)) + + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_AVERAGE_POOL) +#undef ANDROID_NN_AVERAGE_POOL + + return true; +} + +#undef ANDROID_NN_POOLING_PARAMETERS diff --git a/compiler/ann-ref/src/ops/AvgPool2D.float.h b/compiler/ann-ref/src/ops/AvgPool2D.float.h new file mode 100644 index 00000000000..b980e004b16 --- /dev/null +++ b/compiler/ann-ref/src/ops/AvgPool2D.float.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_AVG_POOL_2D_FLOAT_H__ +#define __OP_AVG_POOL_2D_FLOAT_H__ + +#include "Shape.h" + +#include + +bool averagePoolFloat32(const float *inputData, const Shape &inputShape, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, int32_t filter_width, + int32_t filter_height, int32_t activation, float *outputData, + const Shape &outputShape); + +#endif // __OP_AVG_POOL_2D_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/AvgPool2D.h b/compiler/ann-ref/src/ops/AvgPool2D.h new file mode 100644 index 00000000000..c8638553171 --- /dev/null +++ b/compiler/ann-ref/src/ops/AvgPool2D.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_AVG_POOL_2D_H__ +#define __OP_AVG_POOL_2D_H__ + +#include "Shape.h" + +#include + +bool averagePoolPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output); + +#endif // __OP_AVG_POOL_2D_H__ diff --git a/compiler/ann-ref/src/ops/Concatenation.cpp b/compiler/ann-ref/src/ops/Concatenation.cpp new file mode 100644 index 00000000000..6bfe640b53f --- /dev/null +++ b/compiler/ann-ref/src/ops/Concatenation.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Concatenation.h" +#include "Assert.h" + +bool concatenationPrepare(const std::vector &inputShapes, int32_t axis, Shape *output) +{ + + int num_inputs = inputShapes.size(); + OperandType input_type = inputShapes[0].type; + uint32_t num_dimensions = getNumberOfDimensions(inputShapes[0]); + + ASSERT(axis >= 0); + ASSERT(axis < (int32_t)num_dimensions); + + int sum_axis = getSizeOfDimension(inputShapes[0], axis); + for (int i = 1; i < num_inputs; ++i) + { + ASSERT(getNumberOfDimensions(inputShapes[i]) == num_dimensions); + ASSERT(inputShapes[i].type == inputShapes[0].type); + if (input_type == OperandType::TENSOR_QUANT8_ASYMM) + { + ASSERT(inputShapes[0].offset == inputShapes[i].offset); + ASSERT(inputShapes[0].scale == inputShapes[i].scale); + } + for (int d = 0; d < (int32_t)num_dimensions; ++d) + { + if (d == axis) + { + sum_axis += getSizeOfDimension(inputShapes[i], axis); + } + else + { + ASSERT(getSizeOfDimension(inputShapes[0], d) == + getSizeOfDimension(inputShapes[i], d)); + } + } + } + + output->type = input_type; + output->dimensions = inputShapes[0].dimensions; + output->dimensions[axis] = sum_axis; + + if (input_type == OperandType::TENSOR_QUANT8_ASYMM) + { + ASSERT(inputShapes[0].offset == output->offset); + ASSERT(inputShapes[0].scale == output->scale); + } + + return true; +} diff --git a/compiler/ann-ref/src/ops/Concatenation.float.cpp b/compiler/ann-ref/src/ops/Concatenation.float.cpp new file mode 100644 index 00000000000..ac32aa0ffce --- /dev/null +++ b/compiler/ann-ref/src/ops/Concatenation.float.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Concatenation.float.h" + +#include "internal/Array.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" + +// From optimized_ops.h in TensorFlow Lite +template +void Concatenation(int concat_dim, const Scalar *const *input_data, + const Dims<4> *const *input_dims, int inputs_count, Scalar *output_data, + const Dims<4> &output_dims) +{ + DCHECK_GT(inputs_count, 1); + int concat_size = 0; + for (int i = 0; i < inputs_count; i++) + { + for (int j = 0; j < 4; j++) + { + if (j != concat_dim) + { + MatchingArraySize(*input_dims[i], j, output_dims, j); + } + } + concat_size += ArraySize(*input_dims[i], concat_dim); + } + DCHECK_EQ(concat_size, ArraySize(output_dims, concat_dim)); + DCHECK(IsPackedWithoutStrides(output_dims)); + // for now we dont have a model with a Concatenation + // with fused activation function. + DCHECK(Ac == FusedActivationFunctionType::kNone); + int outer_size = 1; + for (int i = concat_dim + 1; i < 4; i++) + { + outer_size *= output_dims.sizes[i]; + } + Scalar *output_ptr = output_data; + for (int k = 0; k < outer_size; k++) + { + for (int i = 0; i < inputs_count; ++i) + { + const int copy_size = input_dims[i]->sizes[concat_dim] * input_dims[i]->strides[concat_dim]; + memcpy(output_ptr, input_data[i] + k * copy_size, copy_size * sizeof(Scalar)); + output_ptr += copy_size; + } + } +} + +bool concatenationFloat32(const std::vector &inputDataPtrs, + const std::vector &inputShapes, int32_t axis, float *outputData, + const Shape &outputShape) +{ + int num_inputs = inputShapes.size(); + std::vector *> inputDimsPtr(num_inputs); + std::vector> inputDims(num_inputs); + for (int i = 0; i < num_inputs; i++) + { + inputDims[i] = convertShapeToDims(inputShapes[i]); + inputDimsPtr[i] = &inputDims[i]; + } + + Concatenation( + getNumberOfDimensions(outputShape) - axis - 1, inputDataPtrs.data(), inputDimsPtr.data(), + num_inputs, outputData, convertShapeToDims(outputShape)); + + return true; +} diff --git a/compiler/ann-ref/src/ops/Concatenation.float.h b/compiler/ann-ref/src/ops/Concatenation.float.h new file mode 100644 index 00000000000..65bca18801d --- /dev/null +++ b/compiler/ann-ref/src/ops/Concatenation.float.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_CONCATENATION_FLOAT_H__ +#define __OP_CONCATENATION_FLOAT_H__ + +#include "Shape.h" + +#include +#include + +bool concatenationFloat32(const std::vector &inputDataPtrs, + const std::vector &inputShapes, int32_t axis, float *outputData, + const Shape &outputShape); + +#endif // __OP_CONCATENATION_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Concatenation.h b/compiler/ann-ref/src/ops/Concatenation.h new file mode 100644 index 00000000000..b92071e4526 --- /dev/null +++ b/compiler/ann-ref/src/ops/Concatenation.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_CONCATENATION_H__ +#define __OP_CONCATENATION_H__ + +#include "Shape.h" + +#include +#include + +bool concatenationPrepare(const std::vector &inputShapes, int32_t axis, Shape *output); + +#endif // __OP_CONCATENATION_H__ diff --git a/compiler/ann-ref/src/ops/Conv2D.cpp b/compiler/ann-ref/src/ops/Conv2D.cpp new file mode 100644 index 00000000000..ef4407e0094 --- /dev/null +++ b/compiler/ann-ref/src/ops/Conv2D.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Conv2D.h" +#include "Assert.h" + +#include "internal/Spatial.h" + +bool convPrepare(const Shape &input, const Shape &filter, const Shape &bias, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, Shape *output) +{ + ASSERT(input.type == filter.type); + if (input.type == OperandType::TENSOR_QUANT8_ASYMM) + { + ASSERT(bias.type == OperandType::TENSOR_INT32); + } + else + { + ASSERT(input.type == bias.type); + } + ASSERT(getNumberOfDimensions(input) == 4); + ASSERT(getNumberOfDimensions(filter) == 4); + ASSERT(getNumberOfDimensions(bias) == 1); + + ASSERT(getSizeOfDimension(filter, 0) == getSizeOfDimension(bias, 0)); + ASSERT(getSizeOfDimension(filter, 3) == getSizeOfDimension(input, 3)); + + uint32_t channels_out = getSizeOfDimension(filter, 0); + uint32_t width = getSizeOfDimension(input, 2); + uint32_t height = getSizeOfDimension(input, 1); + uint32_t filterWidth = getSizeOfDimension(filter, 2); + uint32_t filterHeight = getSizeOfDimension(filter, 1); + uint32_t batches = getSizeOfDimension(input, 0); + + uint32_t outWidth = computeOutSize(width, filterWidth, stride_width, padding_left, padding_right); + uint32_t outHeight = + computeOutSize(height, filterHeight, stride_height, padding_top, padding_bottom); + + output->type = input.type; + output->dimensions = {batches, outHeight, outWidth, channels_out}; + return true; +} diff --git a/compiler/ann-ref/src/ops/Conv2D.float.cpp b/compiler/ann-ref/src/ops/Conv2D.float.cpp new file mode 100644 index 00000000000..b47fcce27f5 --- /dev/null +++ b/compiler/ann-ref/src/ops/Conv2D.float.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Conv2D.float.h" + +#include "internal/Spatial.h" +#include "internal/Array.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/GEMM.h" +#include "internal/ActivationUtils.h" + +// From optimized_ops.h in TensorFlow Lite +template +inline void ExtractPatchIntoBufferColumn(const Dims<4> &input_dims, int w, int h, int b, + int kheight, int kwidth, int stride_width, + int stride_height, int pad_width, int pad_height, + int in_width, int in_height, int in_depth, + int single_buffer_length, int buffer_id, const T *in_data, + T *conv_buffer_data, uint8 byte_zero) +{ + // This chunk of code reshapes all the inputs corresponding to + // output (b, h, w) to a column vector in conv_buffer(:, buffer_id). + const int kwidth_times_indepth = kwidth * in_depth; + const int inwidth_times_indepth = in_width * in_depth; + const int ih_ungated_start = h * stride_height - pad_height; + const int ih_ungated_end = (ih_ungated_start + kheight); + const int ih_end = std::min(ih_ungated_end, in_height); + const int iw_ungated_start = w * stride_width - pad_width; + const int iw_ungated_end = (iw_ungated_start + kwidth); + const int iw_end = std::min(iw_ungated_end, in_width); + // If the patch is off the edge of the input image, skip writing those rows + // and columns from the patch into the output array. + const int h_offset = std::max(0, -ih_ungated_start); + const int w_offset = std::max(0, -iw_ungated_start); + const int ih_start = std::max(0, ih_ungated_start); + const int iw_start = std::max(0, iw_ungated_start); + const int single_row_num = std::min(kwidth - w_offset, in_width - iw_start) * in_depth; + const int output_row_offset = (buffer_id * single_buffer_length); + int out_offset = output_row_offset + (h_offset * kwidth + w_offset) * in_depth; + int in_offset = Offset(input_dims, 0, iw_start, ih_start, b); + + // Express all of the calculations as padding around the input patch. + const int top_padding = h_offset; + const int bottom_padding = (ih_ungated_end - ih_end); + const int left_padding = w_offset; + const int right_padding = (iw_ungated_end - iw_end); + assert(single_row_num == ((kwidth - (left_padding + right_padding)) * in_depth)); + + // Write out zeroes to the elements representing the top rows of the input + // patch that are off the edge of the input image. + if (top_padding > 0) + { + const int top_row_elements = (top_padding * kwidth * in_depth); + memset(conv_buffer_data + output_row_offset, byte_zero, (top_row_elements * sizeof(T))); + } + + // If the patch is on the interior of the input image horizontally, just copy + // over the rows sequentially, otherwise add zero padding at the start or end. + if ((left_padding == 0) && (right_padding == 0)) + { + for (int ih = ih_start; ih < ih_end; ++ih) + { + memcpy(conv_buffer_data + out_offset, in_data + in_offset, single_row_num * sizeof(T)); + out_offset += kwidth_times_indepth; + in_offset += inwidth_times_indepth; + } + } + else + { + for (int ih = ih_start; ih < ih_end; ++ih) + { + if (left_padding > 0) + { + const int left_start = (out_offset - (left_padding * in_depth)); + memset(conv_buffer_data + left_start, byte_zero, (left_padding * in_depth * sizeof(T))); + } + memcpy(conv_buffer_data + out_offset, in_data + in_offset, single_row_num * sizeof(T)); + if (right_padding > 0) + { + const int right_start = (out_offset + single_row_num); + memset(conv_buffer_data + right_start, byte_zero, (right_padding * in_depth * sizeof(T))); + } + out_offset += kwidth_times_indepth; + in_offset += inwidth_times_indepth; + } + } + + // If the bottom of the patch falls off the input image, pad the values + // representing those input rows with zeroes. + if (bottom_padding > 0) + { + const int bottom_row_elements = (bottom_padding * kwidth * in_depth); + const int bottom_start = + output_row_offset + ((top_padding + (ih_end - ih_start)) * kwidth * in_depth); + memset(conv_buffer_data + bottom_start, byte_zero, (bottom_row_elements * sizeof(T))); + } +} + +template +void Im2col(const T *input_data, const Dims<4> &input_dims, int stride_width, int stride_height, + int pad_width, int pad_height, int kheight, int kwidth, uint8 byte_zero, T *output_data, + const Dims<4> &output_dims) +{ + DCHECK(IsPackedWithoutStrides(input_dims)); + DCHECK(IsPackedWithoutStrides(output_dims)); + const int batches = MatchingArraySize(input_dims, 3, output_dims, 3); + const int input_depth = ArraySize(input_dims, 0); + const int input_width = ArraySize(input_dims, 1); + const int input_height = ArraySize(input_dims, 2); + const int output_depth = ArraySize(output_dims, 0); + const int output_width = ArraySize(output_dims, 1); + const int output_height = ArraySize(output_dims, 2); + + int buffer_id = 0; + // Loop over the output nodes. + for (int b = 0; b < batches; ++b) + { + for (int h = 0; h < output_height; ++h) + { + for (int w = 0; w < output_width; ++w) + { + ExtractPatchIntoBufferColumn(input_dims, w, h, b, kheight, kwidth, stride_width, + stride_height, pad_width, pad_height, input_width, + input_height, input_depth, output_depth, buffer_id, input_data, + output_data, byte_zero); + ++buffer_id; + } + } + } +} + +// From optimized_ops.h in TensorFlow Lite +template +void Conv(const float *input_data, const Dims<4> &input_dims, const float *filter_data, + const Dims<4> &filter_dims, const float *bias_data, const Dims<4> &bias_dims, + int stride_width, int stride_height, int pad_width, int pad_height, float *output_data, + const Dims<4> &output_dims, float *im2col_data, const Dims<4> &im2col_dims) +{ + (void)im2col_data; + (void)im2col_dims; + + const float *gemm_input_data = nullptr; + const Dims<4> *gemm_input_dims = nullptr; + const int filter_width = ArraySize(filter_dims, 1); + const int filter_height = ArraySize(filter_dims, 2); + const bool need_im2col = + stride_width != 1 || stride_height != 1 || filter_width != 1 || filter_height != 1; + if (need_im2col) + { + DCHECK(im2col_data); + Im2col(input_data, input_dims, stride_width, stride_height, pad_width, pad_height, + filter_height, filter_width, 0, im2col_data, im2col_dims); + gemm_input_data = im2col_data; + gemm_input_dims = &im2col_dims; + } + else + { +#if 0 // TODO-NNRT : Check if it needs, 'im2col_data' seems to be always not null. + DCHECK(!im2col_data); +#endif + gemm_input_data = input_data; + gemm_input_dims = &input_dims; + } + + const auto im2col_matrix_map = MapAsMatrixWithFirstDimAsRows(gemm_input_data, *gemm_input_dims); + const auto filter_matrix_map = MapAsMatrixWithLastDimAsCols(filter_data, filter_dims); + auto output_matrix_map = MapAsMatrixWithFirstDimAsRows(output_data, output_dims); + + Gemm(filter_matrix_map.transpose(), im2col_matrix_map, &output_matrix_map); + + AddBiasAndEvalActivationFunction(bias_data, bias_dims, output_data, output_dims); +} + +// If possible we will use this static buffer for the tensor. +static constexpr int kStaticBufferSize = 1605632; +static char static_scratch_buffer[kStaticBufferSize]; + +#define ANDROID_NN_CONV_PARAMETERS(Type) \ + uint32_t height = getSizeOfDimension(inputShape, 1); \ + uint32_t width = getSizeOfDimension(inputShape, 2); \ + uint32_t filterHeight = getSizeOfDimension(filterShape, 1); \ + uint32_t filterWidth = getSizeOfDimension(filterShape, 2); \ + uint32_t outHeight = getSizeOfDimension(outputShape, 1); \ + uint32_t outWidth = getSizeOfDimension(outputShape, 2); \ + uint32_t inDepth = getSizeOfDimension(inputShape, 3); \ + \ + uint32_t paddingHeight = (uint32_t)padding_top; \ + uint32_t paddingWidth = (uint32_t)padding_left; \ + \ + Dims<4> im2colDim; \ + im2colDim.sizes[3] = (int)getSizeOfDimension(outputShape, 0); \ + im2colDim.sizes[2] = (int)getSizeOfDimension(outputShape, 1); \ + im2colDim.sizes[1] = (int)getSizeOfDimension(outputShape, 2); \ + im2colDim.sizes[0] = (int)inDepth * filterHeight * filterWidth; \ + \ + im2colDim.strides[0] = 1; \ + for (int i = 1; i < 4; i++) \ + { \ + im2colDim.strides[i] = im2colDim.strides[i - 1] * im2colDim.sizes[i - 1]; \ + } \ + \ + Type *im2colData = nullptr; \ + int im2colByteSize = sizeof(Type); \ + for (int i = 0; i < 4; i++) \ + { \ + im2colByteSize *= im2colDim.sizes[i]; \ + } \ + if (im2colByteSize <= kStaticBufferSize) \ + { \ + im2colData = reinterpret_cast(static_scratch_buffer); \ + } \ + else \ + { \ + im2colData = new (std::nothrow) Type[im2colByteSize / sizeof(Type)]; \ + } + +bool convFloat32(const float *inputData, const Shape &inputShape, const float *filterData, + const Shape &filterShape, const float *biasData, const Shape &biasShape, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + int32_t activation, float *outputData, const Shape &outputShape) +{ + + ANDROID_NN_CONV_PARAMETERS(float) + +#define ANDROID_NN_CONV(activation) \ + Conv( \ + inputData, convertShapeToDims(inputShape), filterData, convertShapeToDims(filterShape), \ + biasData, convertShapeToDims(biasShape), stride_width, stride_height, paddingWidth, \ + paddingHeight, outputData, convertShapeToDims(outputShape), im2colData, im2colDim) + + ANDROID_NN_MACRO_DISPATCH_WITH_DELETE(ANDROID_NN_CONV) +#undef ANDROID_NN_CONV + + if (im2colByteSize > kStaticBufferSize) + { + delete[] im2colData; + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Conv2D.float.h b/compiler/ann-ref/src/ops/Conv2D.float.h new file mode 100644 index 00000000000..620263fc333 --- /dev/null +++ b/compiler/ann-ref/src/ops/Conv2D.float.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_CONV_2D_FLOAT_H__ +#define __OP_CONV_2D_FLOAT_H__ + +#include "Shape.h" + +#include + +bool convFloat32(const float *inputData, const Shape &inputShape, const float *filterData, + const Shape &filterShape, const float *biasData, const Shape &biasShape, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + int32_t activation, float *outputData, const Shape &outputShape); + +#endif // __OP_CONV_2D_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Conv2D.h b/compiler/ann-ref/src/ops/Conv2D.h new file mode 100644 index 00000000000..7dc1e34246f --- /dev/null +++ b/compiler/ann-ref/src/ops/Conv2D.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_CONV_2D_H__ +#define __OP_CONV_2D_H__ + +#include "Shape.h" + +#include + +bool convPrepare(const Shape &input, const Shape &filter, const Shape &bias, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, Shape *output); + +#endif // __OP_CONV_2D_H__ diff --git a/compiler/ann-ref/src/ops/DepthwiseConv2D.cpp b/compiler/ann-ref/src/ops/DepthwiseConv2D.cpp new file mode 100644 index 00000000000..4692564e751 --- /dev/null +++ b/compiler/ann-ref/src/ops/DepthwiseConv2D.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "DepthwiseConv2D.h" +#include "Assert.h" + +#include "internal/Spatial.h" + +bool depthwiseConvPrepare(const Shape &input, const Shape &filter, const Shape &bias, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + Shape *output) +{ + ASSERT(input.type == filter.type); + if (input.type == OperandType::TENSOR_QUANT8_ASYMM) + { + ASSERT(bias.type == OperandType::TENSOR_INT32); + } + else + { + ASSERT(input.type == bias.type); + } + ASSERT(getNumberOfDimensions(input) == 4); + ASSERT(getNumberOfDimensions(filter) == 4); + ASSERT(getNumberOfDimensions(bias) == 1); + + ASSERT(getSizeOfDimension(filter, 3) == getSizeOfDimension(bias, 0)); + + uint32_t channels_out = getSizeOfDimension(filter, 3); + uint32_t width = getSizeOfDimension(input, 2); + uint32_t height = getSizeOfDimension(input, 1); + uint32_t filterWidth = getSizeOfDimension(filter, 2); + uint32_t filterHeight = getSizeOfDimension(filter, 1); + uint32_t batches = getSizeOfDimension(input, 0); + + uint32_t outWidth = computeOutSize(width, filterWidth, stride_width, padding_left, padding_right); + uint32_t outHeight = + computeOutSize(height, filterHeight, stride_height, padding_top, padding_bottom); + + output->type = input.type; + output->dimensions = {batches, outHeight, outWidth, channels_out}; + return true; +} diff --git a/compiler/ann-ref/src/ops/DepthwiseConv2D.float.cpp b/compiler/ann-ref/src/ops/DepthwiseConv2D.float.cpp new file mode 100644 index 00000000000..936b24ec789 --- /dev/null +++ b/compiler/ann-ref/src/ops/DepthwiseConv2D.float.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "DepthwiseConv2D.float.h" +#include "Assert.h" + +#include "internal/Spatial.h" +#include "internal/Array.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +#include // 'memcpy' + +namespace optimized_ops +{ + +// Implementation of float DepthwiseConv + +template +struct FloatDepthwiseConvKernel +{ +}; + +// From optimized_ops.h in TensorFlow Lite +// +// Accumulates the effect of one row of the filter, on a segment of one row +// of the output, accessing the corresponding one row of the input. +template +void FloatDepthwiseConvAccumRow(int stride, int input_depth, int input_width, + const float *input_data, int pad_width, int depth_multiplier, + int filter_width, const float *filter_data, int out_x_buffer_start, + int out_x_buffer_end, int output_depth, float *acc_buffer) +{ + // Sanity check parameters. This is important in particular to ensure + // that we keep the number of template instantiations minimal, so we don't + // increase binary size unnecessarily. + static_assert(kFixedDepthMultiplier || !kFixedInputDepth, ""); + static_assert(kFixedInputDepth || kAllowStrided, ""); + DCHECK(stride == 1 || kAllowStrided); + if (kFixedInputDepth) + { + DCHECK_EQ(input_depth, kFixedInputDepth); + } + if (kFixedDepthMultiplier) + { + DCHECK_EQ(depth_multiplier, kFixedDepthMultiplier); + } + DCHECK_EQ(output_depth, input_depth * depth_multiplier); + const int input_ptr_increment = stride * input_depth; + const float *filter_base_ptr = filter_data; + for (int filter_x = 0; filter_x < filter_width; ++filter_x) + { + // For the current (filter_x, filter_y) point in the filter, + // compute the boundaries of the corresponding output row segment. + int out_x_loop_start_unclampled = 0; + int out_x_loop_end_unclampled = 0; + if (kAllowStrided) + { + if (stride == 2) + { + out_x_loop_start_unclampled = (pad_width - filter_x + 1) / 2; + out_x_loop_end_unclampled = (pad_width + input_width - filter_x + 1) / 2; + } + else if (stride == 4) + { + out_x_loop_start_unclampled = (pad_width - filter_x + 3) / 4; + out_x_loop_end_unclampled = (pad_width + input_width - filter_x + 3) / 4; + } + else + { + out_x_loop_start_unclampled = (pad_width - filter_x + stride - 1) / stride; + out_x_loop_end_unclampled = (pad_width + input_width - filter_x + stride - 1) / stride; + } + } + else + { + out_x_loop_start_unclampled = pad_width - filter_x; + out_x_loop_end_unclampled = pad_width + input_width - filter_x; + } + // The kernel will have to iterate on the segment of the + // output row that starts at out_x_loop_start and out_x_loop_end. + const int out_x_loop_start = std::max(out_x_buffer_start, out_x_loop_start_unclampled); + const int out_x_loop_end = std::min(out_x_buffer_end, out_x_loop_end_unclampled); + + float *acc_buffer_ptr = acc_buffer + (out_x_loop_start - out_x_buffer_start) * output_depth; + const int in_x_origin = (out_x_loop_start * stride) - pad_width + filter_x; + const float *input_ptr = input_data + in_x_origin * input_depth; + const int num_output_pixels = out_x_loop_end - out_x_loop_start; + FloatDepthwiseConvKernel::Run( + num_output_pixels, input_depth, depth_multiplier, input_ptr, input_ptr_increment, + filter_base_ptr, acc_buffer_ptr); + filter_base_ptr += output_depth; + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// generic fallback of FloatDepthwiseConvAccumRow, portable, non-templatized. +inline void FloatDepthwiseConvAccumRowGeneric(int stride, int input_depth, int input_width, + const float *input_data, int pad_width, + int depth_multiplier, int filter_width, + const float *filter_data, int out_x_buffer_start, + int out_x_buffer_end, int output_depth, + float *acc_buffer) +{ + const float *filter_base_ptr = filter_data; + for (int filter_x = 0; filter_x < filter_width; ++filter_x) + { + const int out_x_loop_start = + std::max(out_x_buffer_start, (pad_width - filter_x + stride - 1) / stride); + const int out_x_loop_end = + std::min(out_x_buffer_end, (pad_width + input_width - filter_x + stride - 1) / stride); + + float *acc_buffer_ptr = acc_buffer + (out_x_loop_start - out_x_buffer_start) * output_depth; + const int in_x_origin = (out_x_loop_start * stride) - pad_width + filter_x; + const float *input_ptr = input_data + in_x_origin * input_depth; + const int input_ptr_increment = (stride - 1) * input_depth; + for (int out_x = out_x_loop_start; out_x < out_x_loop_end; out_x++) + { + const float *filter_ptr = filter_base_ptr; + for (int ic = 0; ic < input_depth; ++ic) + { + const float input_val = *input_ptr++; + for (int m = 0; m < depth_multiplier; m++) + { + const float filter_val = *filter_ptr++; + *acc_buffer_ptr++ += filter_val * input_val; + } + } + input_ptr += input_ptr_increment; + } + filter_base_ptr += output_depth; + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// Initializes the accumulator buffer with bias values. +inline void DepthwiseConvInitAccBuffer(int num_output_pixels, int output_depth, + const float *bias_data, float *acc_buffer) +{ + for (int i = 0; i < num_output_pixels; i++) + { + memcpy(acc_buffer + i * output_depth, bias_data, sizeof(acc_buffer[0]) * output_depth); + } +} + +// From optimized_ops.h in TensorFlow Lite +template +void DepthwiseConv(const float *input_data, const Dims<4> &input_dims, const float *filter_data, + const Dims<4> &filter_dims, const float *bias_data, const Dims<4> &bias_dims, + int stride_width, int stride_height, int pad_width, int pad_height, + int depth_multiplier, float *output_data, const Dims<4> &output_dims) +{ + static_assert( + Ac == FusedActivationFunctionType::kNone || Ac == FusedActivationFunctionType::kRelu || + Ac == FusedActivationFunctionType::kRelu6 || Ac == FusedActivationFunctionType::kRelu1, + ""); + const int batches = MatchingArraySize(input_dims, 3, output_dims, 3); + const int output_depth = MatchingArraySize(filter_dims, 0, output_dims, 0); + const int input_height = ArraySize(input_dims, 2); + const int input_width = ArraySize(input_dims, 1); + const int input_depth = ArraySize(input_dims, 0); + const int filter_height = ArraySize(filter_dims, 2); + const int filter_width = ArraySize(filter_dims, 1); + const int output_height = ArraySize(output_dims, 2); + const int output_width = ArraySize(output_dims, 1); +#if 0 // TODO-NNRT : Check if assertion is needed, output depth some times not equal to input * + // depthmultiplier + DCHECK(output_depth == input_depth * depth_multiplier); +#endif + + static const int kAccBufferMaxSize = 1024; + float acc_buffer[kAccBufferMaxSize]; + DCHECK_GE(kAccBufferMaxSize, output_depth); + const int kOutputPixelsInAccBuffer = kAccBufferMaxSize / output_depth; + const int kAccBufferActualSize = kOutputPixelsInAccBuffer * output_depth; + DCHECK_LE(kOutputPixelsInAccBuffer * output_depth, kAccBufferActualSize); + DCHECK_LE(kAccBufferActualSize, kAccBufferMaxSize); + DCHECK_GE(kOutputPixelsInAccBuffer, 1); + + // row_accum_func will point to the core accumulation function to be used + // for this DepthwiseConv op. + auto *row_accum_func = FloatDepthwiseConvAccumRowGeneric; + + const int kMaxFixedDepthMultiplier = 16; + int fixed_depth_multiplier = 0; + if (depth_multiplier <= kMaxFixedDepthMultiplier) + { + fixed_depth_multiplier = depth_multiplier; + } + // kMaxUnrolling is the max number of output values that we aim to handle + // in one unrolled iteration of the inner loop. For practical performance + // reasons, it is limited by the number of available registers. We could + // fine-tune it depending on the architecture, but that's not worth doing + // since this whole code is not very optimized to begin with. The + // present value reflects what's realistic on ARM 32bit NEON with 16 128-bit + // vector registers. + const int kMaxUnrolling = 8; + int fixed_input_depth = 0; + if (fixed_depth_multiplier && input_depth * fixed_depth_multiplier <= kMaxUnrolling) + { + fixed_input_depth = input_depth; + } + + // Now that we have determined row_accum_func, we can start work. + float *output_ptr = output_data; + for (int b = 0; b < batches; ++b) + { + for (int out_y = 0; out_y < output_height; ++out_y) + { + const int in_y_origin = (out_y * stride_height) - pad_height; + const int filter_y_start = std::max(0, -in_y_origin); + const int filter_y_end = std::min(filter_height, input_height - in_y_origin); + for (int out_x_buffer_start = 0; out_x_buffer_start < output_width; + out_x_buffer_start += kOutputPixelsInAccBuffer) + { + const int out_x_buffer_end = + std::min(output_width, out_x_buffer_start + kOutputPixelsInAccBuffer); + // We call a 'pixel' a group of activation that share all but the + // 'depth'/'channel' coordinate. num_output_pixels is the number of + // output pixels that we will accumulate in this loop iteration. + const int num_output_pixels = out_x_buffer_end - out_x_buffer_start; + // Initialize our local accumulator with the bias values, so we don't + // have to add them later. + DepthwiseConvInitAccBuffer(num_output_pixels, output_depth, bias_data, acc_buffer); + // Accumulation loop. Most of the time should be spent in here. + for (int filter_y = filter_y_start; filter_y < filter_y_end; ++filter_y) + { + const int in_y = in_y_origin + filter_y; + row_accum_func(stride_width, input_depth, input_width, + input_data + in_y * input_dims.strides[2] + b * input_dims.strides[3], + pad_width, depth_multiplier, filter_width, + filter_data + filter_y * filter_dims.strides[2], out_x_buffer_start, + out_x_buffer_end, output_depth, acc_buffer); + } + // Finished accumulating. Now store to destination. + const int num_output_values = output_depth * num_output_pixels; + int i = 0; + // Handle leftover values, one by one. This is very slow. + for (; i < num_output_values; i++) + { + float acc = acc_buffer[i]; + if (Ac == FusedActivationFunctionType::kRelu) + { + acc = std::max(0.f, acc); + } + else if (Ac == FusedActivationFunctionType::kRelu6) + { + acc = std::max(0.f, std::min(6.f, acc)); + } + else if (Ac == FusedActivationFunctionType::kRelu1) + { + acc = std::max(-1.f, std::min(1.f, acc)); + } + *output_ptr++ = acc; + } + } + } + } +} + +} // namespace optimized_ops + +#define ANDROID_NN_DEPTHWISE_CONV_PARAMETERS \ + uint32_t height = getSizeOfDimension(inputShape, 1); \ + uint32_t width = getSizeOfDimension(inputShape, 2); \ + uint32_t filterHeight = getSizeOfDimension(filterShape, 1); \ + uint32_t filterWidth = getSizeOfDimension(filterShape, 2); \ + uint32_t outHeight = getSizeOfDimension(outputShape, 1); \ + uint32_t outWidth = getSizeOfDimension(outputShape, 2); \ + \ + uint32_t paddingHeight = (uint32_t)padding_top; \ + uint32_t paddingWidth = (uint32_t)padding_left; + +bool depthwiseConvFloat32(const float *inputData, const Shape &inputShape, const float *filterData, + const Shape &filterShape, const float *biasData, const Shape &biasShape, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + int32_t depth_multiplier, int32_t activation, float *outputData, + const Shape &outputShape) +{ + + ANDROID_NN_DEPTHWISE_CONV_PARAMETERS + +#define ANDROID_NN_DEPTHWISE_CONV(activation) \ + optimized_ops::DepthwiseConv( \ + inputData, convertShapeToDims(inputShape), filterData, convertShapeToDims(filterShape), \ + biasData, convertShapeToDims(biasShape), stride_width, stride_height, paddingWidth, \ + paddingHeight, depth_multiplier, outputData, convertShapeToDims(outputShape)) + + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_DEPTHWISE_CONV) +#undef ANDROID_NN_DEPTHWISE_CONV + + return true; +} diff --git a/compiler/ann-ref/src/ops/DepthwiseConv2D.float.h b/compiler/ann-ref/src/ops/DepthwiseConv2D.float.h new file mode 100644 index 00000000000..3fbfeae673c --- /dev/null +++ b/compiler/ann-ref/src/ops/DepthwiseConv2D.float.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_DEPTHWISE_CONV_2D_FLOAT_H__ +#define __OP_DEPTHWISE_CONV_2D_FLOAT_H__ + +#include "Shape.h" + +#include + +bool depthwiseConvFloat32(const float *inputData, const Shape &inputShape, const float *filterData, + const Shape &filterShape, const float *biasData, const Shape &biasShape, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + int32_t depth_multiplier, int32_t activation, float *outputData, + const Shape &outputShape); + +#endif // __OP_DEPTHWISE_CONV_2D_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/DepthwiseConv2D.h b/compiler/ann-ref/src/ops/DepthwiseConv2D.h new file mode 100644 index 00000000000..13f52021956 --- /dev/null +++ b/compiler/ann-ref/src/ops/DepthwiseConv2D.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_DEPTHWISE_CONV_2D_H__ +#define __OP_DEPTHWISE_CONV_2D_H__ + +#include "Shape.h" + +#include + +bool depthwiseConvPrepare(const Shape &input, const Shape &filter, const Shape &bias, + int32_t padding_left, int32_t padding_right, int32_t padding_top, + int32_t padding_bottom, int32_t stride_width, int32_t stride_height, + Shape *output); + +#endif // __OP_DEPTHWISE_CONV_2D_H__ diff --git a/compiler/ann-ref/src/ops/Div.cpp b/compiler/ann-ref/src/ops/Div.cpp new file mode 100644 index 00000000000..250e72b1d24 --- /dev/null +++ b/compiler/ann-ref/src/ops/Div.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Div.h" +#include "Assert.h" + +bool divPrepare(const Shape &in1, const Shape &in2, Shape *out) +{ + ASSERT(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4); + ASSERT(in1.type == in2.type); + if (SameShape(in1, in2)) + { + return SetShape(in1, out); + } + else + { + // Broadcast needed + uint32_t numberOfDims1 = getNumberOfDimensions(in1); + uint32_t numberOfDims2 = getNumberOfDimensions(in2); + uint32_t maxDims = std::max(numberOfDims1, numberOfDims2); + out->dimensions = std::vector(maxDims); + for (uint32_t i = 1; i <= maxDims; i++) + { + uint32_t dim1 = 1; + if (i <= numberOfDims1) + { + dim1 = getSizeOfDimension(in1, numberOfDims1 - i); + } + uint32_t dim2 = 1; + if (i <= numberOfDims2) + { + dim2 = getSizeOfDimension(in2, numberOfDims2 - i); + } + if (dim1 != dim2 && dim1 != 1 && dim2 != 1) + { + LOG(ERROR) << "Dimensions mismatch for BroadcastDiv"; + return false; + } + out->dimensions[maxDims - i] = std::max(dim1, dim2); + } + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Div.float.cpp b/compiler/ann-ref/src/ops/Div.float.cpp new file mode 100644 index 00000000000..a1a39e5466a --- /dev/null +++ b/compiler/ann-ref/src/ops/Div.float.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Div.float.h" + +#include "internal/Array.h" +#include "internal/NDArray.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +template +void Div(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + MatchingArraySize(input1_dims, 3, input2_dims, 3, output_dims, 3); + MatchingArraySize(input1_dims, 2, input2_dims, 2, output_dims, 2); + MatchingArraySize(input1_dims, 1, input2_dims, 1, output_dims, 1); + MatchingArraySize(input1_dims, 0, input2_dims, 0, output_dims, 0); + DCHECK(IsPackedWithoutStrides(input1_dims)); + DCHECK(IsPackedWithoutStrides(input2_dims)); + DCHECK(IsPackedWithoutStrides(output_dims)); + + const int size = input1_dims.sizes[3] * input1_dims.strides[3]; + + for (int i = 0; i < size; i++) + { + auto x = input1_data[i] / input2_data[i]; + output_data[i] = ActivationFunction(x); + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// TODO: We can implement BroadcastDiv on buffers of arbitrary +// dimensionality if the runtime code does a single loop over one dimension +// that handles broadcasting as the base case. The code generator would then +// generate max(D1, D2) nested for loops. +// TODO: BroadcastDiv is intentionally duplicated from +// reference_ops.h. Once an optimized version is implemented and NdArrayDesc +// is no longer referenced in this file, move NdArrayDesc from types.h to +// reference_ops.h. +template +void BroadcastDiv(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + NdArrayDesc<4> desc1; + NdArrayDesc<4> desc2; + NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); + + // In Tensorflow, the dimensions are canonically named (batch_number, row, + // col, channel), with extents (batches, height, width, depth), with the + // trailing dimension changing most rapidly (channels has the smallest stride, + // typically 1 element). + // + // In generated C code, we store arrays with the dimensions reversed. The + // first dimension has smallest stride. + // + // We name our variables by their Tensorflow convention, but generate C code + // nesting loops such that the innermost loop has the smallest stride for the + // best cache behavior. + for (int b = 0; b < ArraySize(output_dims, 3); ++b) + { + for (int y = 0; y < ArraySize(output_dims, 2); ++y) + { + for (int x = 0; x < ArraySize(output_dims, 1); ++x) + { + for (int c = 0; c < ArraySize(output_dims, 0); ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(input1_data[SubscriptToIndex(desc1, c, x, y, b)] / + input2_data[SubscriptToIndex(desc2, c, x, y, b)]); + } + } + } + } +} + +bool divFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut) +{ + bool needBroadcast = !SameShape(shape1, shape2); + +#define ANDROID_NN_NORMAL_DIV(activation) \ + Div(in1, convertShapeToDims(shape1), \ + in2, convertShapeToDims(shape2), \ + out, convertShapeToDims(shapeOut)) + +#define ANDROID_NN_BROADCAST_DIV(activation) \ + BroadcastDiv( \ + in1, convertShapeToDims(shape1), \ + in2, convertShapeToDims(shape2), \ + out, convertShapeToDims(shapeOut)) + + if (needBroadcast) + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_BROADCAST_DIV) + } + else + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_NORMAL_DIV) + } + +#undef ANDROID_NN_NORMAL_ADD +#undef ANDROID_NN_BROADCAST_ADD + return true; +} diff --git a/compiler/ann-ref/src/ops/Div.float.h b/compiler/ann-ref/src/ops/Div.float.h new file mode 100644 index 00000000000..a2aa7e1a903 --- /dev/null +++ b/compiler/ann-ref/src/ops/Div.float.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_DIV_FLOAT_H__ +#define __OP_DIV_FLOAT_H__ + +#include "Shape.h" + +#include + +bool divFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut); + +#endif // __OP_DIV_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Div.h b/compiler/ann-ref/src/ops/Div.h new file mode 100644 index 00000000000..5eb98a3f205 --- /dev/null +++ b/compiler/ann-ref/src/ops/Div.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_DIV_H__ +#define __OP_DIV_H__ + +#include "Shape.h" + +bool divPrepare(const Shape &in1, const Shape &in2, Shape *out); + +#endif // __OP_DIV_H__ diff --git a/compiler/ann-ref/src/ops/FullyConnected.cpp b/compiler/ann-ref/src/ops/FullyConnected.cpp new file mode 100644 index 00000000000..d21389e7e2c --- /dev/null +++ b/compiler/ann-ref/src/ops/FullyConnected.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "FullyConnected.h" +#include "Assert.h" + +#if 0 +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/GEMM.h" +#include "internal/ActivationUtils.h" +#endif + +bool fullyConnectedPrepare(const Shape &input, const Shape &weights, const Shape &bias, + Shape *output) +{ + // Check all the parameters of tensor match within themselves and match the + // input configuration. + ASSERT(input.type == weights.type); + if (input.type == OperandType::TENSOR_QUANT8_ASYMM) + { + ASSERT(bias.type == OperandType::TENSOR_INT32); + } + else + { + ASSERT(input.type == bias.type); + } + ASSERT(getNumberOfDimensions(input) >= 2); + uint32_t input_size = getNumberOfElements(input); + uint32_t num_units = getSizeOfDimension(weights, 0); + + // modified to resolve Coverity 118949 (Apr 25, 2018) by hyunsik.yoon + // Original Code: + // uint32_t batch_size = input_size / getSizeOfDimension(weights, 1); + // + // Coverity Detection: Division by zero + // + // Code below is modified code + + uint32_t shape_size = getSizeOfDimension(weights, 1); + if (shape_size == 0) + { + return false; + } + + uint32_t batch_size = input_size / shape_size; + + ASSERT(getSizeOfDimension(bias, 0) == num_units); + ASSERT(getSizeOfDimension(weights, 1) * batch_size == input_size); + ASSERT(getNumberOfDimensions(weights) == 2); + + output->type = input.type; + output->dimensions = {batch_size, num_units}; + + return true; +} diff --git a/compiler/ann-ref/src/ops/FullyConnected.float.cpp b/compiler/ann-ref/src/ops/FullyConnected.float.cpp new file mode 100644 index 00000000000..4d12382caac --- /dev/null +++ b/compiler/ann-ref/src/ops/FullyConnected.float.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "FullyConnected.float.h" +#include "Assert.h" + +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/GEMM.h" +#include "internal/ActivationUtils.h" + +// From optimized_ops.h in TensorFlow Lite +template +void FullyConnected(const float *input_data, const Dims<4> &input_dims, const float *weights_data, + const Dims<4> &weights_dims, const float *bias_data, const Dims<4> &bias_dims, + float *output_data, const Dims<4> &output_dims) +{ + // TODO(b/62193649): this convoluted shape computation (determining + // input_rows from the weights_dims, then MapAsMatrixWithGivenNumberOfRows) + // is because the current --variable_batch hack consists in overwriting the + // 3rd dimension with the runtime batch size, as we don't keep track for each + // array of which dimension is the batch dimension in it. + // When that is fixed, this should become: + // const auto input_matrix_map = + // MapAsMatrixWithFirstDimAsRows(input_data, input_dims); + const int input_rows = ArraySize(weights_dims, 0); + const auto input_matrix_map = + MapAsMatrixWithGivenNumberOfRows(input_data, input_dims, input_rows); + const auto filter_matrix_map = MapAsMatrixWithFirstDimAsRows(weights_data, weights_dims); + auto output_matrix_map = MapAsMatrixWithFirstDimAsRows(output_data, output_dims); + + Gemm(filter_matrix_map.transpose(), input_matrix_map, &output_matrix_map); + AddBiasAndEvalActivationFunction(bias_data, bias_dims, output_data, output_dims); +} + +bool fullyConnectedFloat32(const float *inputData, const Shape &inputShape, + const float *weightsData, const Shape &weightsShape, + const float *biasData, const Shape &biasShape, int32_t activation, + float *outputData, const Shape &outputShape) +{ + +#define ANDROID_NN_FULLY_CONNECTED(activation) \ + FullyConnected( \ + inputData, convertShapeToDims(inputShape), weightsData, convertShapeToDims(weightsShape), \ + biasData, convertShapeToDims(biasShape), outputData, convertShapeToDims(outputShape)) + + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_FULLY_CONNECTED) +#undef ANDROID_NN_FULLY_CONNECTED + return true; +} diff --git a/compiler/ann-ref/src/ops/FullyConnected.float.h b/compiler/ann-ref/src/ops/FullyConnected.float.h new file mode 100644 index 00000000000..3412fdb060f --- /dev/null +++ b/compiler/ann-ref/src/ops/FullyConnected.float.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_FULLY_CONNECTED_FLOAT_H__ +#define __OP_FULLY_CONNECTED_FLOAT_H__ + +#include "Shape.h" + +#include + +bool fullyConnectedFloat32(const float *inputData, const Shape &inputShape, const float *weights, + const Shape &weightsShape, const float *biasData, const Shape &biasShape, + int32_t activation, float *outputData, const Shape &outputShape); + +#endif // __OP_FULLY_CONNECTED_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/FullyConnected.h b/compiler/ann-ref/src/ops/FullyConnected.h new file mode 100644 index 00000000000..985fd7ec271 --- /dev/null +++ b/compiler/ann-ref/src/ops/FullyConnected.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_FULLY_CONNECTED_H__ +#define __OP_FULLY_CONNECTED_H__ + +#include "Shape.h" + +#include + +bool fullyConnectedPrepare(const Shape &input, const Shape &weights, const Shape &bias, + Shape *output); + +#endif // __OP_FULLY_CONNECTED_H__ diff --git a/compiler/ann-ref/src/ops/MaxPool2D.cpp b/compiler/ann-ref/src/ops/MaxPool2D.cpp new file mode 100644 index 00000000000..405afbbdcc8 --- /dev/null +++ b/compiler/ann-ref/src/ops/MaxPool2D.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "MaxPool2D.h" + +#include "internal/Pooling.h" + +bool maxPoolPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output) +{ + return genericPoolingPrepare(input, padding_left, padding_right, padding_top, padding_bottom, + stride_width, stride_height, filter_width, filter_height, + output); +} diff --git a/compiler/ann-ref/src/ops/MaxPool2D.float.cpp b/compiler/ann-ref/src/ops/MaxPool2D.float.cpp new file mode 100644 index 00000000000..d49b6aad8fb --- /dev/null +++ b/compiler/ann-ref/src/ops/MaxPool2D.float.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "MaxPool2D.float.h" + +#include "internal/Array.h" +#include "internal/Matrix.h" +#include "internal/FeatureMap.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +// From optimized_ops.h in TensorFlow Lite +template +void MaxPool(const float *input_data, const Dims<4> &input_dims, int stride_width, + int stride_height, int pad_width, int pad_height, int kwidth, int kheight, + float *output_data, const Dims<4> &output_dims) +{ + const int batches = MatchingArraySize(input_dims, 3, output_dims, 3); + const int input_height = ArraySize(input_dims, 2); + const int input_width = ArraySize(input_dims, 1); + const int output_height = ArraySize(output_dims, 2); + const int output_width = ArraySize(output_dims, 1); + const int depth = MatchingArraySize(input_dims, 0, output_dims, 0); + + const auto in_mat = MapAsMatrixWithFirstDimAsRows(input_data, input_dims); + auto out_mat = MapAsMatrixWithFirstDimAsRows(output_data, output_dims); + // Prefill the output to minimum representable float value + out_mat.setConstant(std::numeric_limits::lowest()); + for (int b = 0; b < batches; ++b) + { + for (int h = 0; h < input_height; ++h) + { + for (int w = 0; w < input_width; ++w) + { + // (h_start, h_end) * (w_start, w_end) is the range that the input + // vector projects to. + int hpad = h + pad_height; + int wpad = w + pad_width; + int h_start = (hpad < kheight) ? 0 : (hpad - kheight) / stride_height + 1; + int h_end = std::min(hpad / stride_height + 1, output_height); + int w_start = (wpad < kwidth) ? 0 : (wpad - kwidth) / stride_width + 1; + int w_end = std::min(wpad / stride_width + 1, output_width); + // compute elementwise sum + for (int ph = h_start; ph < h_end; ++ph) + { + for (int pw = w_start; pw < w_end; ++pw) + { + int out_offset = NodeOffset(b, ph, pw, output_height, output_width); + out_mat.col(out_offset) = + out_mat.col(out_offset) + .cwiseMax(in_mat.col(NodeOffset(b, h, w, input_height, input_width))); + } + } + } + } + } + + for (int b = 0; b < batches; ++b) + { + for (int y = 0; y < output_height; ++y) + { + for (int x = 0; x < output_width; ++x) + { + for (int c = 0; c < depth; ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(output_data[Offset(output_dims, c, x, y, b)]); + } + } + } + } +} + +#define ANDROID_NN_POOLING_PARAMETERS \ + uint32_t height = getSizeOfDimension(inputShape, 1); \ + uint32_t width = getSizeOfDimension(inputShape, 2); \ + uint32_t outHeight = getSizeOfDimension(outputShape, 1); \ + uint32_t outWidth = getSizeOfDimension(outputShape, 2); \ + \ + uint32_t paddingHeight = (uint32_t)padding_top; \ + uint32_t paddingWidth = (uint32_t)padding_left; + +bool maxPoolFloat32(const float *inputData, const Shape &inputShape, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, int32_t filter_width, + int32_t filter_height, int32_t activation, float *outputData, + const Shape &outputShape) +{ + + ANDROID_NN_POOLING_PARAMETERS + +#define ANDROID_NN_MAX_POOL(activation) \ + MaxPool( \ + inputData, convertShapeToDims(inputShape), stride_width, stride_height, paddingWidth, \ + paddingHeight, filter_width, filter_height, outputData, convertShapeToDims(outputShape)) + + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_MAX_POOL) +#undef ANDROID_NN_MAX_POOL + + return true; +} + +#undef ANDROID_NN_POOLING_PARAMETERS diff --git a/compiler/ann-ref/src/ops/MaxPool2D.float.h b/compiler/ann-ref/src/ops/MaxPool2D.float.h new file mode 100644 index 00000000000..fd320f3b46e --- /dev/null +++ b/compiler/ann-ref/src/ops/MaxPool2D.float.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_MAX_POOL_2D_FLOAT_H__ +#define __OP_MAX_POOL_2D_FLOAT_H__ + +#include "Shape.h" + +#include + +bool maxPoolFloat32(const float *inputData, const Shape &inputShape, int32_t padding_left, + int32_t padding_right, int32_t padding_top, int32_t padding_bottom, + int32_t stride_width, int32_t stride_height, int32_t filter_width, + int32_t filter_height, int32_t activation, float *outputData, + const Shape &outputShape); + +#endif // __OP_MAX_POOL_2D_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/MaxPool2D.h b/compiler/ann-ref/src/ops/MaxPool2D.h new file mode 100644 index 00000000000..e15a030bba1 --- /dev/null +++ b/compiler/ann-ref/src/ops/MaxPool2D.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_MAX_POOL_2D_H__ +#define __OP_MAX_POOL_2D_H__ + +#include "Shape.h" + +#include + +bool maxPoolPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output); + +#endif // __OP_MAX_POOL_2D_H__ diff --git a/compiler/ann-ref/src/ops/Mul.cpp b/compiler/ann-ref/src/ops/Mul.cpp new file mode 100644 index 00000000000..03ea9383a3a --- /dev/null +++ b/compiler/ann-ref/src/ops/Mul.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Mul.h" +#include "Assert.h" + +bool mulPrepare(const Shape &in1, const Shape &in2, Shape *out) +{ + ASSERT(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4); + ASSERT(in1.type == in2.type); + if (SameShape(in1, in2)) + { + return SetShape(in1, out); + } + else + { + // Broadcast needed + uint32_t numberOfDims1 = getNumberOfDimensions(in1); + uint32_t numberOfDims2 = getNumberOfDimensions(in2); + uint32_t maxDims = std::max(numberOfDims1, numberOfDims2); + out->dimensions = std::vector(maxDims); + for (uint32_t i = 1; i <= maxDims; i++) + { + uint32_t dim1 = 1; + if (i <= numberOfDims1) + { + dim1 = getSizeOfDimension(in1, numberOfDims1 - i); + } + uint32_t dim2 = 1; + if (i <= numberOfDims2) + { + dim2 = getSizeOfDimension(in2, numberOfDims2 - i); + } + if (dim1 != dim2 && dim1 != 1 && dim2 != 1) + { + LOG(ERROR) << "Dimensions mismatch for BroadcastAdd"; + return false; + } + out->dimensions[maxDims - i] = std::max(dim1, dim2); + } + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Mul.float.cpp b/compiler/ann-ref/src/ops/Mul.float.cpp new file mode 100644 index 00000000000..8a6f039d462 --- /dev/null +++ b/compiler/ann-ref/src/ops/Mul.float.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Mul.float.h" + +#include "internal/Array.h" +#include "internal/NDArray.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +template +void Mul(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + MatchingArraySize(input1_dims, 3, input2_dims, 3, output_dims, 3); + MatchingArraySize(input1_dims, 2, input2_dims, 2, output_dims, 2); + MatchingArraySize(input1_dims, 1, input2_dims, 1, output_dims, 1); + MatchingArraySize(input1_dims, 0, input2_dims, 0, output_dims, 0); + DCHECK(IsPackedWithoutStrides(input1_dims)); + DCHECK(IsPackedWithoutStrides(input2_dims)); + DCHECK(IsPackedWithoutStrides(output_dims)); + + int i = 0; + const int size = input1_dims.sizes[3] * input1_dims.strides[3]; + + for (; i < size; i++) + { + auto x = input1_data[i] * input2_data[i]; + output_data[i] = ActivationFunction(x); + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// TODO: We can implement BroadcastMul on buffers of arbitrary +// dimensionality if the runtime code does a single loop over one dimension +// that handles broadcasting as the base case. The code generator would then +// generate max(D1, D2) nested for loops. +// TODO: BroadcastMul is intentionally duplicated from +// reference_ops.h. Once an optimized version is implemented and NdArrayDesc +// is no longer referenced in this file, move NdArrayDesc from types.h to +// reference_ops.h. +template +void BroadcastMul(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + NdArrayDesc<4> desc1; + NdArrayDesc<4> desc2; + NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); + + // In Tensorflow, the dimensions are canonically named (batch_number, row, + // col, channel), with extents (batches, height, width, depth), with the + // trailing dimension changing most rapidly (channels has the smallest stride, + // typically 1 element). + // + // In generated C code, we store arrays with the dimensions reversed. The + // first dimension has smallest stride. + // + // We name our variables by their Tensorflow convention, but generate C code + // nesting loops such that the innermost loop has the smallest stride for the + // best cache behavior. + for (int b = 0; b < ArraySize(output_dims, 3); ++b) + { + for (int y = 0; y < ArraySize(output_dims, 2); ++y) + { + for (int x = 0; x < ArraySize(output_dims, 1); ++x) + { + for (int c = 0; c < ArraySize(output_dims, 0); ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(input1_data[SubscriptToIndex(desc1, c, x, y, b)] * + input2_data[SubscriptToIndex(desc2, c, x, y, b)]); + } + } + } + } +} + +bool mulFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut) +{ + bool needBroadcast = !SameShape(shape1, shape2); + +#define ANDROID_NN_NORMAL_MUL(activation) \ + Mul(in1, convertShapeToDims(shape1), \ + in2, convertShapeToDims(shape2), \ + out, convertShapeToDims(shapeOut)) + +#define ANDROID_NN_BROADCAST_MUL(activation) \ + BroadcastMul( \ + in1, convertShapeToDims(shape1), in2, convertShapeToDims(shape2), out, \ + convertShapeToDims(shapeOut)) + + if (needBroadcast) + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_BROADCAST_MUL) + } + else + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_NORMAL_MUL) + } + +#undef ANDROID_NN_NORMAL_ADD +#undef ANDROID_NN_BROADCAST_ADD + return true; +} diff --git a/compiler/ann-ref/src/ops/Mul.float.h b/compiler/ann-ref/src/ops/Mul.float.h new file mode 100644 index 00000000000..bb6b9410bec --- /dev/null +++ b/compiler/ann-ref/src/ops/Mul.float.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_MUL_FLOAT_H__ +#define __OP_MUL_FLOAT_H__ + +#include "Shape.h" + +#include + +bool mulFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut); + +#endif // __OP_MUL_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Mul.h b/compiler/ann-ref/src/ops/Mul.h new file mode 100644 index 00000000000..ed808062bdb --- /dev/null +++ b/compiler/ann-ref/src/ops/Mul.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_MUL_H__ +#define __OP_MUL_H__ + +#include "Shape.h" + +bool mulPrepare(const Shape &in1, const Shape &in2, Shape *out1); + +#endif // __OP_MUL_H__ diff --git a/compiler/ann-ref/src/ops/Pad.cpp b/compiler/ann-ref/src/ops/Pad.cpp new file mode 100644 index 00000000000..91741762d7f --- /dev/null +++ b/compiler/ann-ref/src/ops/Pad.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Pad.h" +#include "Assert.h" +#include "Logging.h" + +#include "internal/Dims.h" + +#include +#include // For 'memset' + +bool padPrepare(const Shape& input, const int32_t* paddingsData, const Shape& paddingsShape, + Shape* output) +{ + // Currently only 4D tensors are supported. + uint32_t numInputDims = getNumberOfDimensions(input); + ASSERT(numInputDims == 4); + + // paddings need to be provided as a 2-D int32 tensor. + ASSERT(paddingsShape.type == OperandType::TENSOR_INT32); + ASSERT(getNumberOfDimensions(paddingsShape) == 2); + ASSERT(getSizeOfDimension(paddingsShape, 0) == numInputDims); + ASSERT(getSizeOfDimension(paddingsShape, 1) == 2); + + std::vector outDims(numInputDims); + for (uint32_t i = 0; i < numInputDims; ++i) + { + int32_t beforePadding = *paddingsData++; + int32_t afterPadding = *paddingsData++; + // Pad value has to be greater than equal to 0. + ASSERT(beforePadding >= 0 && afterPadding >= 0); + outDims[i] = beforePadding + getSizeOfDimension(input, i) + afterPadding; + } + output->type = input.type; + output->dimensions = outDims; + output->offset = input.offset; + output->scale = input.scale; + + return true; +} + +namespace +{ + +// From optimized_ops.h in TensorFlow Lite +template +inline void Pad(const T* input_data, const Dims<4>& input_dims, + const std::vector& left_paddings, + const std::vector& right_paddings, T* output_data, + const Dims<4>& output_dims) { + const int output_batch = ArraySize(output_dims, 3); + const int output_height = ArraySize(output_dims, 2); + const int output_width = ArraySize(output_dims, 1); + const int output_depth = ArraySize(output_dims, 0); + + const int left_b_padding = left_paddings[3]; + const int left_h_padding = left_paddings[2]; + const int left_w_padding = left_paddings[1]; + const int left_d_padding = left_paddings[0]; + + const int right_b_padding = right_paddings[3]; + const int right_h_padding = right_paddings[2]; + const int right_w_padding = right_paddings[1]; + const int right_d_padding = right_paddings[0]; + + const int input_depth = ArraySize(input_dims, 0); + + if (left_b_padding != 0) + { + memset(output_data, 0, left_b_padding * output_height * output_width * output_depth * + sizeof(T)); + } + for (int out_b = left_b_padding; out_b < output_batch - right_b_padding; ++out_b) + { + if (left_h_padding != 0) + { + memset(output_data + Offset(output_dims, 0, 0, 0, out_b), 0, + left_h_padding * output_width * output_depth * sizeof(T)); + } + for (int out_h = left_h_padding; out_h < output_height - right_h_padding; ++out_h) + { + if (left_w_padding != 0) + { + memset(output_data + Offset(output_dims, 0, 0, out_h, out_b), 0, + left_w_padding * output_depth * sizeof(T)); + } + for (int out_w = left_w_padding; out_w < output_width - right_w_padding; ++out_w) + { + if (left_d_padding != 0) + { + memset(output_data + Offset(output_dims, 0, out_w, out_h, out_b), 0, + left_d_padding * sizeof(T)); + } + + T* out = output_data + + Offset(output_dims, left_d_padding, out_w, out_h, out_b); + const T* in = + input_data + Offset(input_dims, 0, out_w - left_w_padding, + out_h - left_h_padding, out_b - left_b_padding); + memcpy(out, in, input_depth * sizeof(T)); + + if (right_d_padding != 0) + { + memset( + output_data + Offset(output_dims, output_depth - right_d_padding, + out_w, out_h, out_b), + 0, right_d_padding * sizeof(T)); + } + } + if (right_w_padding != 0) + { + memset( + output_data + Offset(output_dims, 0, output_width - right_w_padding, + out_h, out_b), + 0, right_w_padding * output_depth * sizeof(T)); + } + } + if (right_h_padding != 0) + { + memset(output_data + Offset(output_dims, 0, 0, + output_height - right_h_padding, out_b), + 0, right_h_padding * output_width * output_depth * sizeof(T)); + } + } + if (right_b_padding != 0) + { + memset(output_data + + Offset(output_dims, 0, 0, 0, output_batch - right_b_padding), + 0, + right_b_padding * output_height * output_width * output_depth * + sizeof(T)); + } +} + +} // namespace + +bool padGeneric(const uint8_t* inputData, const Shape& inputShape, const int32_t* paddings, + uint8_t* outputData, const Shape& outputShape) +{ + int32_t numInputDims = static_cast(getNumberOfDimensions(inputShape)); + + std::vector beforePadding; + std::vector afterPadding; + // The lower level implementation expects the paddings in the reverse order. + for (int32_t i = numInputDims - 1; i >= 0; --i) + { + beforePadding.push_back(paddings[i * 2]); + afterPadding.push_back(paddings[i * 2 + 1]); + } + + if (inputShape.type == OperandType::TENSOR_FLOAT32) + { + ::Pad(reinterpret_cast(inputData), + convertShapeToDims(inputShape), + beforePadding, afterPadding, + reinterpret_cast(outputData), + convertShapeToDims(outputShape)); + } + else if (inputShape.type == OperandType::TENSOR_QUANT8_ASYMM) + { + ::Pad(reinterpret_cast(inputData), + convertShapeToDims(inputShape), + beforePadding, afterPadding, + reinterpret_cast(outputData), + convertShapeToDims(outputShape)); + } + else + { + LOG(ERROR) << "Unsupported data type"; + return false; + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Pad.h b/compiler/ann-ref/src/ops/Pad.h new file mode 100644 index 00000000000..542ab896288 --- /dev/null +++ b/compiler/ann-ref/src/ops/Pad.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_PAD_H__ +#define __OP_PAD_H__ + +#include "Shape.h" + +#include + +bool padPrepare(const Shape& input, const int32_t* paddingsData, const Shape& paddingsShape, + Shape* output); + +bool padGeneric(const uint8_t* inputData, const Shape& inputShape, const int32_t* paddings, + uint8_t* outputData, const Shape& outputShape); + +#endif // __OP_PAD_H__ diff --git a/compiler/ann-ref/src/ops/ReLU.cpp b/compiler/ann-ref/src/ops/ReLU.cpp new file mode 100644 index 00000000000..334291ae5ea --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ReLU.h" + +#include "internal/Elementwise.h" + +bool reluPrepare(const Shape &input, Shape *output) +{ + return genericActivationPrepare(input, output); +} diff --git a/compiler/ann-ref/src/ops/ReLU.float.cpp b/compiler/ann-ref/src/ops/ReLU.float.cpp new file mode 100644 index 00000000000..df170e48e8e --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU.float.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ReLU.float.h" + +#include + +bool reluFloat32(const float *inputData, const Shape &inputShape, float *outputData, + const Shape &outputShape) +{ + int numElements = getNumberOfElements(inputShape); + for (int i = 0; i < numElements; i++, inputData++, outputData++) + { + *outputData = std::max(0.f, *inputData); + } + return true; +} diff --git a/compiler/ann-ref/src/ops/ReLU.float.h b/compiler/ann-ref/src/ops/ReLU.float.h new file mode 100644 index 00000000000..4c6cf383337 --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU.float.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_RELU_FLOAT_H__ +#define __OP_RELU_FLOAT_H__ + +#include "Shape.h" + +bool reluFloat32(const float *inputData, const Shape &inputShape, float *outputData, + const Shape &outputShape); + +#endif // __OP_RELU_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/ReLU.h b/compiler/ann-ref/src/ops/ReLU.h new file mode 100644 index 00000000000..4b329fb8df6 --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_RELU_H__ +#define __OP_RELU_H__ + +#include "Shape.h" + +bool reluPrepare(const Shape &input, Shape *output); + +#endif // __OP_RELU_H__ diff --git a/compiler/ann-ref/src/ops/ReLU6.cpp b/compiler/ann-ref/src/ops/ReLU6.cpp new file mode 100644 index 00000000000..acaa58bda19 --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU6.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ReLU6.h" + +#include "internal/Elementwise.h" + +bool relu6Prepare(const Shape &input, Shape *output) +{ + return genericActivationPrepare(input, output); +} diff --git a/compiler/ann-ref/src/ops/ReLU6.float.cpp b/compiler/ann-ref/src/ops/ReLU6.float.cpp new file mode 100644 index 00000000000..b8aa790b59e --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU6.float.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "ReLU6.float.h" + +#include + +bool relu6Float32(const float *inputData, const Shape &inputShape, float *outputData, + const Shape &outputShape) +{ + int numElements = getNumberOfElements(inputShape); + for (int i = 0; i < numElements; i++, inputData++, outputData++) + { + *outputData = std::min(std::max(0.f, *inputData),6.f); + } + return true; +} diff --git a/compiler/ann-ref/src/ops/ReLU6.float.h b/compiler/ann-ref/src/ops/ReLU6.float.h new file mode 100644 index 00000000000..06c421a0b4e --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU6.float.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_RELU6_FLOAT_H__ +#define __OP_RELU6_FLOAT_H__ + +#include "Shape.h" + +bool relu6Float32(const float *inputData, const Shape &inputShape, float *outputData, + const Shape &outputShape); + +#endif // __OP_RELU6_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/ReLU6.h b/compiler/ann-ref/src/ops/ReLU6.h new file mode 100644 index 00000000000..625db4b6ed4 --- /dev/null +++ b/compiler/ann-ref/src/ops/ReLU6.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_RELU6_H__ +#define __OP_RELU6_H__ + +#include "Shape.h" + +bool relu6Prepare(const Shape &input, Shape *output); + +#endif // __OP_RELU6_H__ diff --git a/compiler/ann-ref/src/ops/Reshape.cpp b/compiler/ann-ref/src/ops/Reshape.cpp new file mode 100644 index 00000000000..a88e81ae46c --- /dev/null +++ b/compiler/ann-ref/src/ops/Reshape.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Reshape.h" +#include "Operand.h" +#include "Assert.h" + +#include + +bool reshapePrepare(const Shape &input, const int32_t *targetDims, const int32_t targetDimsSize, + Shape *output) +{ + // Reshape allows one of the targetDims components to have the + // special -1 value, meaning it will be calculated automatically based on the + // input. Here we calculate what that dimension should be so that the number + // of output elements in the same as the number of input elements. + int32_t numInputElements = (int32_t)getNumberOfElements(input); + + std::vector outDims(targetDimsSize); + int32_t numOutputElements = 1; + int32_t strechDim = -1; + for (int32_t i = 0; i < targetDimsSize; ++i) + { + int32_t value = targetDims[i]; + if (value == -1) + { + ASSERT(strechDim == -1); + strechDim = i; + } + else + { + numOutputElements *= value; + outDims[i] = (uint32_t)value; + } + } + if (strechDim != -1) + { + int32_t strechValue = numInputElements / numOutputElements; + outDims[strechDim] = (uint32_t)strechValue; + numOutputElements *= strechValue; + } + + ASSERT(numInputElements == numOutputElements); + + output->type = input.type; + output->dimensions = outDims; + output->offset = input.offset; + output->scale = input.scale; + + return true; +} + +bool reshapeGeneric(const void *inputData, const Shape &inputShape, void *outputData, + const Shape &outputShape) +{ + size_t count = sizeOfData(inputShape.type, inputShape.dimensions); + memcpy(outputData, inputData, count); + return true; +} diff --git a/compiler/ann-ref/src/ops/Reshape.h b/compiler/ann-ref/src/ops/Reshape.h new file mode 100644 index 00000000000..47609ff3c27 --- /dev/null +++ b/compiler/ann-ref/src/ops/Reshape.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_RESHAPE_H__ +#define __OP_RESHAPE_H__ + +#include "Shape.h" + +#include + +bool reshapePrepare(const Shape &input, const int32_t *targetDims, const int32_t targetDimsSize, + Shape *output); + +bool reshapeGeneric(const void *inputData, const Shape &inputShape, void *outputData, + const Shape &outputShape); + +#endif // __OP_RESHAPE_H__ diff --git a/compiler/ann-ref/src/ops/Softmax.cpp b/compiler/ann-ref/src/ops/Softmax.cpp new file mode 100644 index 00000000000..9e904463697 --- /dev/null +++ b/compiler/ann-ref/src/ops/Softmax.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Softmax.h" + +#include "internal/Elementwise.h" + +bool softmaxPrepare(const Shape &input, Shape *output) +{ + return genericActivationPrepare(input, output); +} diff --git a/compiler/ann-ref/src/ops/Softmax.float.cpp b/compiler/ann-ref/src/ops/Softmax.float.cpp new file mode 100644 index 00000000000..31c29c0c689 --- /dev/null +++ b/compiler/ann-ref/src/ops/Softmax.float.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Softmax.float.h" +#include "Logging.h" + +#include "internal/Array.h" +#include "internal/Matrix.h" + +// From optimized_ops.h in TensorFlow Lite +inline void Softmax(const float *input_data, const Dims<4> &input_dims, float beta, + float *output_data, const Dims<4> &output_dims) +{ + MatchingArraySize(input_dims, 3, output_dims, 3); + MatchingArraySize(input_dims, 2, output_dims, 2); + MatchingArraySize(input_dims, 1, output_dims, 1); + MatchingArraySize(input_dims, 0, output_dims, 0); + + const auto in_mat = MapAsMatrixWithFirstDimAsRows(input_data, input_dims); + auto out_mat = MapAsMatrixWithFirstDimAsRows(output_data, output_dims); + // Compute the exponential first, removing the max coefficient for numerical + // stability. + out_mat = (in_mat.rowwise() - in_mat.colwise().maxCoeff()).array() * beta; + // We are separating out the exp function so that exp can be vectorized. + out_mat = out_mat.array().exp(); + // Normalize to get the activations. + Eigen::Array scale = out_mat.array().colwise().sum().inverse(); + out_mat.array().rowwise() *= scale; +} + +bool softmaxFloat32(const float *inputData, const Shape &inputShape, const float beta, + float *outputData, const Shape &outputShape) +{ + Dims<4> dim; + if (getNumberOfDimensions(inputShape) == 2) + { + uint32_t batch_size = getSizeOfDimension(inputShape, 0); + uint32_t input_size = getNumberOfElements(inputShape) / batch_size; + + Shape shapeIn4D; + shapeIn4D.dimensions = {batch_size, 1, 1, input_size}; + dim = convertShapeToDims(shapeIn4D); + } + else if (getNumberOfDimensions(inputShape) == 4) + { + dim = convertShapeToDims(inputShape); + } + else + { + LOG(ERROR) << "only 2D and 4D tensors supported"; + return false; + } + + Softmax(inputData, dim, beta, outputData, dim); + return true; +} diff --git a/compiler/ann-ref/src/ops/Softmax.float.h b/compiler/ann-ref/src/ops/Softmax.float.h new file mode 100644 index 00000000000..227b6580740 --- /dev/null +++ b/compiler/ann-ref/src/ops/Softmax.float.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_SOFTMAX_FLOAT_H__ +#define __OP_SOFTMAX_FLOAT_H__ + +#include "Shape.h" + +#include + +bool softmaxFloat32(const float *inputData, const Shape &inputShape, const float beta, + float *outputData, const Shape &outputShape); + +#endif // __OP_SOFTMAX_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Softmax.h b/compiler/ann-ref/src/ops/Softmax.h new file mode 100644 index 00000000000..a1e2e9c1b66 --- /dev/null +++ b/compiler/ann-ref/src/ops/Softmax.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_SOFTMAX_H__ +#define __OP_SOFTMAX_H__ + +#include "Shape.h" + +#include + +bool softmaxPrepare(const Shape &input, Shape *output); + +#endif // __OP_SOFTMAX_H__ diff --git a/compiler/ann-ref/src/ops/Sub.cpp b/compiler/ann-ref/src/ops/Sub.cpp new file mode 100644 index 00000000000..accda912737 --- /dev/null +++ b/compiler/ann-ref/src/ops/Sub.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Sub.h" +#include "Assert.h" + +bool subPrepare(const Shape &in1, const Shape &in2, Shape *out) +{ + ASSERT(getNumberOfDimensions(in1) <= 4 && getNumberOfDimensions(in2) <= 4); + ASSERT(in1.type == in2.type); + if (SameShape(in1, in2)) + { + return SetShape(in1, out); + } + else + { + // BroadcastSub needed + uint32_t numberOfDims1 = getNumberOfDimensions(in1); + uint32_t numberOfDims2 = getNumberOfDimensions(in2); + uint32_t maxDims = std::max(numberOfDims1, numberOfDims2); + out->dimensions = std::vector(maxDims); + for (uint32_t i = 1; i <= maxDims; i++) + { + uint32_t dim1 = 1; + if (i <= numberOfDims1) + { + dim1 = getSizeOfDimension(in1, numberOfDims1 - i); + } + uint32_t dim2 = 1; + if (i <= numberOfDims2) + { + dim2 = getSizeOfDimension(in2, numberOfDims2 - i); + } + if (dim1 != dim2 && dim1 != 1 && dim2 != 1) + { + LOG(ERROR) << "Dimensions mismatch for BroadcastSub"; + return false; + } + out->dimensions[maxDims - i] = std::max(dim1, dim2); + } + } + return true; +} diff --git a/compiler/ann-ref/src/ops/Sub.float.cpp b/compiler/ann-ref/src/ops/Sub.float.cpp new file mode 100644 index 00000000000..deb5d9855a3 --- /dev/null +++ b/compiler/ann-ref/src/ops/Sub.float.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Sub.float.h" + +#include "internal/Array.h" +#include "internal/NDArray.h" +#include "internal/Matrix.h" +#include "internal/Fused.h" +#include "internal/ActivationUtils.h" + +template +void Sub(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + MatchingArraySize(input1_dims, 3, input2_dims, 3, output_dims, 3); + MatchingArraySize(input1_dims, 2, input2_dims, 2, output_dims, 2); + MatchingArraySize(input1_dims, 1, input2_dims, 1, output_dims, 1); + MatchingArraySize(input1_dims, 0, input2_dims, 0, output_dims, 0); + DCHECK(IsPackedWithoutStrides(input1_dims)); + DCHECK(IsPackedWithoutStrides(input2_dims)); + DCHECK(IsPackedWithoutStrides(output_dims)); + + int i = 0; + const int size = input1_dims.sizes[3] * input1_dims.strides[3]; + + for (; i < size; i++) + { + auto x = input1_data[i] - input2_data[i]; + output_data[i] = ActivationFunction(x); + } +} + +// From optimized_ops.h in TensorFlow Lite +// +// TODO: We can implement BroadcastSub on buffers of arbitrary +// dimensionality if the runtime code does a single loop over one dimension +// that handles broadcasting as the base case. The code generator would then +// generate max(D1, D2) nested for loops. +// TODO: BroadcastSub is intentionally duplicated from +// reference_ops.h. Once an optimized version is implemented and NdArrayDesc +// is no longer referenced in this file, move NdArrayDesc from types.h to +// reference_ops.h. +template +void BroadcastSub(const float *input1_data, const Dims<4> &input1_dims, const float *input2_data, + const Dims<4> &input2_dims, float *output_data, const Dims<4> &output_dims) +{ + NdArrayDesc<4> desc1; + NdArrayDesc<4> desc2; + NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2); + + // In Tensorflow, the dimensions are canonically named (batch_number, row, + // col, channel), with extents (batches, height, width, depth), with the + // trailing dimension changing most rapidly (channels has the smallest stride, + // typically 1 element). + // + // In generated C code, we store arrays with the dimensions reversed. The + // first dimension has smallest stride. + // + // We name our variables by their Tensorflow convention, but generate C code + // nesting loops such that the innermost loop has the smallest stride for the + // best cache behavior. + for (int b = 0; b < ArraySize(output_dims, 3); ++b) + { + for (int y = 0; y < ArraySize(output_dims, 2); ++y) + { + for (int x = 0; x < ArraySize(output_dims, 1); ++x) + { + for (int c = 0; c < ArraySize(output_dims, 0); ++c) + { + output_data[Offset(output_dims, c, x, y, b)] = + ActivationFunction(input1_data[SubscriptToIndex(desc1, c, x, y, b)] - + input2_data[SubscriptToIndex(desc2, c, x, y, b)]); + } + } + } + } +} + +bool subFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut) +{ + bool needBroadcast = !SameShape(shape1, shape2); + +#define ANDROID_NN_NORMAL_SUB(activation) \ + Sub(in1, convertShapeToDims(shape1), \ + in2, convertShapeToDims(shape2), \ + out, convertShapeToDims(shapeOut)) + +#define ANDROID_NN_BROADCAST_SUB(activation) \ + BroadcastSub( \ + in1, convertShapeToDims(shape1), in2, convertShapeToDims(shape2), out, \ + convertShapeToDims(shapeOut)) + + if (needBroadcast) + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_BROADCAST_SUB) + } + else + { + ANDROID_NN_MACRO_DISPATCH(ANDROID_NN_NORMAL_SUB) + } + +#undef ANDROID_NN_NORMAL_SUB +#undef ANDROID_NN_BROADCAST_SUB + return true; +} diff --git a/compiler/ann-ref/src/ops/Sub.float.h b/compiler/ann-ref/src/ops/Sub.float.h new file mode 100644 index 00000000000..d494f757615 --- /dev/null +++ b/compiler/ann-ref/src/ops/Sub.float.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_SUB_FLOAT_H__ +#define __OP_SUB_FLOAT_H__ + +#include "Shape.h" + +#include + +bool subFloat32(const float *in1, const Shape &shape1, const float *in2, const Shape &shape2, + int32_t activation, float *out, const Shape &shapeOut); + +#endif // __OP_SUB_FLOAT_H__ diff --git a/compiler/ann-ref/src/ops/Sub.h b/compiler/ann-ref/src/ops/Sub.h new file mode 100644 index 00000000000..d3626205b9a --- /dev/null +++ b/compiler/ann-ref/src/ops/Sub.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __OP_SUB_H__ +#define __OP_SUB_H__ + +#include "Shape.h" + +bool subPrepare(const Shape &in1, const Shape &in2, Shape *out1); + +#endif // __OP_SUB_H__ diff --git a/compiler/ann-ref/src/ops/internal/ActivationUtils.h b/compiler/ann-ref/src/ops/internal/ActivationUtils.h new file mode 100644 index 00000000000..9d413c6a4e6 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/ActivationUtils.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __ACTIVATION_UTILS_H__ +#define __ACTIVATION_UTILS_H__ + +#include "Logging.h" + +#define ANDROID_NN_MACRO_DISPATCH_INTERNAL(macro) \ + case (int32_t)FusedActivationFunc::NONE: \ + macro(kNone); \ + break; \ + case (int32_t)FusedActivationFunc::RELU: \ + macro(kRelu); \ + break; \ + case (int32_t)FusedActivationFunc::RELU1: \ + macro(kRelu1); \ + break; \ + case (int32_t)FusedActivationFunc::RELU6: \ + macro(kRelu6); \ + break; + +#define ANDROID_NN_MACRO_DISPATCH(macro) \ + switch (activation) \ + { \ + ANDROID_NN_MACRO_DISPATCH_INTERNAL(macro) \ + default: \ + LOG(ERROR) << "Unsupported fused activation function type"; \ + return false; \ + } + +#define ANDROID_NN_MACRO_DISPATCH_WITH_DELETE(macro) \ + switch (activation) \ + { \ + ANDROID_NN_MACRO_DISPATCH_INTERNAL(macro) \ + default: \ + LOG(ERROR) << "Unsupported fused activation function type"; \ + if (im2colByteSize > kStaticBufferSize) \ + { \ + delete[] im2colData; \ + } \ + return false; \ + } + +#endif // __ACTIVATION_UTILS_H__ diff --git a/compiler/ann-ref/src/ops/internal/Array.h b/compiler/ann-ref/src/ops/internal/Array.h new file mode 100644 index 00000000000..49a3e771b37 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Array.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#ifndef __ARRAY_H__ +#define __ARRAY_H__ + +#include "Shape.h" +#include "Dims.h" + +#include "Macro.h" + +// From types.h in TensorFlow Lite +// +// Get common array size, DCHECKing that they all agree. +template +int MatchingArraySize(const ArrayType1 &array1, int index1, const ArrayType2 &array2, int index2) +{ + DCHECK_EQ(ArraySize(array1, index1), ArraySize(array2, index2)); + return ArraySize(array1, index1); +} + +// From types.h in TensorFlow Lite +template +int MatchingArraySize(const ArrayType1 &array1, int index1, const ArrayType2 &array2, int index2, + Args... args) +{ + DCHECK_EQ(ArraySize(array1, index1), ArraySize(array2, index2)); + return MatchingArraySize(array1, index1, args...); +} + +#endif // __ARRAY_H__ diff --git a/compiler/ann-ref/src/ops/internal/Dims.h b/compiler/ann-ref/src/ops/internal/Dims.h new file mode 100644 index 00000000000..2b3aaa65ac6 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Dims.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#ifndef __DIMS_H__ +#define __DIMS_H__ + +#include "Shape.h" +#include "Macro.h" + +template struct Dims +{ + int sizes[N]; + int strides[N]; +}; + +inline Dims<4> convertShapeToDims(const Shape &shape) +{ + Dims<4> dims; + for (int i = 0; i < 4; i++) + { + dims.sizes[i] = 1; + } + + if (shape.dimensions.size() == 1) + { + dims.sizes[0] = (int)getSizeOfDimension(shape, 0); + } + else + { + for (int i = 0; i < 4; i++) + { + int src = (int)shape.dimensions.size() - i - 1; + if (src >= 0) + { + dims.sizes[i] = (int)getSizeOfDimension(shape, src); + } + } + } + + dims.strides[0] = 1; + for (int i = 1; i < 4; i++) + { + dims.strides[i] = dims.strides[i - 1] * dims.sizes[i - 1]; + } + return dims; +} + +// From types.h in TensorFlow Lite +inline int Offset(const Dims<4> &dims, int i0, int i1, int i2, int i3) +{ + DCHECK(i0 >= 0 && i0 < dims.sizes[0]); + DCHECK(i1 >= 0 && i1 < dims.sizes[1]); + DCHECK(i2 >= 0 && i2 < dims.sizes[2]); + DCHECK(i3 >= 0 && i3 < dims.sizes[3]); + return i0 * dims.strides[0] + i1 * dims.strides[1] + i2 * dims.strides[2] + i3 * dims.strides[3]; +} + +// From types.h in TensorFlow Lite +// +// Get array size, DCHECKing that the dim index is in range. +template int ArraySize(const Dims &array, int index) +{ + DCHECK(index >= 0 && index < N); + return array.sizes[index]; +} + +// From types.h in TensorFlow Lite +template inline int FlatSize(const Dims &dims) +{ + int flat_size = 1; + for (int i = 0; i < N; ++i) + { + flat_size *= dims.sizes[i]; + } + return flat_size; +} + +// From types.h in TensorFlow Lite +inline int RequiredBufferSizeForDims(const Dims<4> &dims) +{ + int max_offset = 0; + for (int i = 0; i < 4; i++) + { + max_offset += (dims.sizes[i] - 1) * dims.strides[i]; + } + return max_offset + 1; +} + +// From types.h in TensorFlow Lite +// +// Flat size calculation, checking that dimensions match with one or more other +// arrays. +template inline int MatchingFlatSize(const Dims &dims, const Dims &check_dims_0) +{ + for (int i = 0; i < N; ++i) + { + DCHECK_EQ(ArraySize(dims, i), ArraySize(check_dims_0, i)); + } + return FlatSize(dims); +} + +// From types.h in TensorFlow Lite +template +inline int MatchingFlatSize(const Dims &dims, const Dims &check_dims_0, + const Dims &check_dims_1) +{ + for (int i = 0; i < N; ++i) + { + DCHECK_EQ(ArraySize(dims, i), ArraySize(check_dims_0, i)); + } + return MatchingFlatSize(dims, check_dims_1); +} + +// From types.h in TensorFlow Lite +template +inline int MatchingFlatSize(const Dims &dims, const Dims &check_dims_0, + const Dims &check_dims_1, const Dims &check_dims_2) +{ + for (int i = 0; i < N; ++i) + { + DCHECK_EQ(ArraySize(dims, i), ArraySize(check_dims_0, i)); + } + return FlatSize(dims, check_dims_1, check_dims_2); +} + +// From types.h in TensorFlow Lite +template +inline int MatchingFlatSize(const Dims &dims, const Dims &check_dims_0, + const Dims &check_dims_1, const Dims &check_dims_2, + const Dims &check_dims_3) +{ + for (int i = 0; i < N; ++i) + { + DCHECK_EQ(ArraySize(dims, i), ArraySize(check_dims_0, i)); + } + return FlatSize(dims, check_dims_1, check_dims_2, check_dims_3); +} + +// From types.h in TensorFlow Lite +template bool IsPackedWithoutStrides(const Dims &dims) +{ + int expected_stride = 1; + for (int d = 0; d < N; d++) + { + if (dims.strides[d] != expected_stride) + return false; + expected_stride *= dims.sizes[d]; + } + return true; +} + +#endif // __DIMS_H__ diff --git a/compiler/ann-ref/src/ops/internal/Elementwise.cpp b/compiler/ann-ref/src/ops/internal/Elementwise.cpp new file mode 100644 index 00000000000..5615e309d62 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Elementwise.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Elementwise.h" +#include "Assert.h" + +bool genericActivationPrepare(const Shape &input, Shape *output) +{ + ASSERT(getNumberOfDimensions(input) <= 4); + return SetShape(input, output); +} diff --git a/compiler/ann-ref/src/ops/internal/Elementwise.h b/compiler/ann-ref/src/ops/internal/Elementwise.h new file mode 100644 index 00000000000..732f9b8a292 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Elementwise.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __ELEMENTWISE_H__ +#define __ELEMENTWISE_H__ + +#include "Shape.h" + +bool genericActivationPrepare(const Shape &input, Shape *output); + +#endif // __ELEMENTWISE_H__ diff --git a/compiler/ann-ref/src/ops/internal/FeatureMap.h b/compiler/ann-ref/src/ops/internal/FeatureMap.h new file mode 100644 index 00000000000..e4d323f62ed --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/FeatureMap.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __FEATURE_MAP_H__ +#define __FEATURE_MAP_H__ + +inline int NodeOffset(int b, int h, int w, int height, int width) +{ + return (b * height + h) * width + w; +} + +#endif // __FEATURE_MAP_H__ diff --git a/compiler/ann-ref/src/ops/internal/Fused.cpp b/compiler/ann-ref/src/ops/internal/Fused.cpp new file mode 100644 index 00000000000..c50b9dea027 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Fused.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Fused.h" +#include "NeuralNetworks.h" + +static_assert(static_cast(FusedActivationFunc::NONE) == ANEURALNETWORKS_FUSED_NONE, + "FusedActivationFunc::NONE != ANEURALNETWORKS_FUSED_NONE"); +static_assert(static_cast(FusedActivationFunc::RELU) == ANEURALNETWORKS_FUSED_RELU, + "FusedActivationFunc::RELU != ANEURALNETWORKS_FUSED_RELU"); +static_assert(static_cast(FusedActivationFunc::RELU1) == ANEURALNETWORKS_FUSED_RELU1, + "FusedActivationFunc::RELU1 != ANEURALNETWORKS_FUSED_RELU1"); +static_assert(static_cast(FusedActivationFunc::RELU6) == ANEURALNETWORKS_FUSED_RELU6, + "FusedActivationFunc::RELU6 != ANEURALNETWORKS_FUSED_RELU6"); diff --git a/compiler/ann-ref/src/ops/internal/Fused.h b/compiler/ann-ref/src/ops/internal/Fused.h new file mode 100644 index 00000000000..fccd72cc3de --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Fused.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __FUSED_H__ +#define __FUSED_H__ + +#include "Dims.h" + +#include + +enum class FusedActivationFunc : int32_t { + NONE = 0, + RELU = 1, + RELU1 = 2, + RELU6 = 3, +}; + +enum class FusedActivationFunctionType +{ + kNone, + kRelu6, + kRelu1, + kRelu +}; + +template struct ActivationFunctionImpl; + +template <> struct ActivationFunctionImpl +{ + static float Eval(float x) { return x; } +}; + +template <> struct ActivationFunctionImpl +{ + static float Eval(float x) { return x < 0.f ? 0.f : x; } +}; + +template <> struct ActivationFunctionImpl +{ + static float Eval(float x) { return x > 1.f ? 1.f : x < -1.f ? -1.f : x; } +}; + +template <> struct ActivationFunctionImpl +{ + static float Eval(float x) { return x > 6.f ? 6.f : x < 0.f ? 0.f : x; } +}; + +template float ActivationFunction(float x) +{ + return ActivationFunctionImpl::Eval(x); +} + +template +void AddBiasAndEvalActivationFunction(const float *bias_data, const Dims<4> &bias_dims, + float *array_data, const Dims<4> &array_dims) +{ + const int bias_size = bias_dims.sizes[3] * bias_dims.strides[3]; + const int array_size = array_dims.sizes[3] * array_dims.strides[3]; + DCHECK_EQ((array_size % bias_size), 0); + for (int array_offset = 0; array_offset < array_size; array_offset += bias_size) + { + for (int i = 0; i < bias_size; i++) + { + array_data[array_offset + i] = + ActivationFunction(array_data[array_offset + i] + bias_data[i]); + } + } +} + +#endif // __FUSED_H__ diff --git a/compiler/ann-ref/src/ops/internal/GEMM.h b/compiler/ann-ref/src/ops/internal/GEMM.h new file mode 100644 index 00000000000..e94b35855e2 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/GEMM.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __GEMM_H__ +#define __GEMM_H__ + +#include "Eigen/Core" + +template +void Gemm(const Eigen::MatrixBase &lhs, const Eigen::MatrixBase &rhs, + Eigen::MatrixBase *result) +{ + if (rhs.cols() == 1) + { + result->col(0).noalias() = lhs * rhs.col(0); + } + else + { + result->noalias() = lhs * rhs; + } +} + + +#endif // __GEMM_H__ diff --git a/compiler/ann-ref/src/ops/internal/Macro.h b/compiler/ann-ref/src/ops/internal/Macro.h new file mode 100644 index 00000000000..b80a748bbd6 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Macro.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __COMPATIBILITY_H__ +#define __COMPATIBILITY_H__ + +#include +#include + +#ifndef DCHECK +#define DCHECK(condition) assert((condition)) +#endif + +#ifndef DCHECK_EQ +#define DCHECK_EQ(x, y) assert((x) == (y)) +#endif + +#ifndef DCHECK_GE +#define DCHECK_GE(x, y) assert((x) >= (y)) +#endif + +#ifndef DCHECK_GT +#define DCHECK_GT(x, y) assert((x) > (y)) +#endif + +#ifndef DCHECK_LE +#define DCHECK_LE(x, y) assert((x) <= (y)) +#endif + +#ifndef DCHECK_LT +#define DCHECK_LT(x, y) assert((x) < (y)) +#endif + +#ifndef CHECK_EQ +#define CHECK_EQ(x, y) assert((x) == (y)) +#endif + +using uint8 = std::uint8_t; +using int16 = std::int16_t; +using uint16 = std::uint16_t; +using int32 = std::int32_t; +using uint32 = std::uint32_t; + +#endif // __COMPATIBILITY_H__ diff --git a/compiler/ann-ref/src/ops/internal/Matrix.h b/compiler/ann-ref/src/ops/internal/Matrix.h new file mode 100644 index 00000000000..71b1fc5d716 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Matrix.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2017 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#ifndef __MATRIX_H__ +#define __MATRIX_H__ + +#include "Dims.h" +#include "Eigen/Core" + +// From optimized_ops.h in TensorFlow Lite +// +// Make a local VectorMap typedef allowing to map a float array +// as a Eigen vector expression. The std::conditional here is to +// construct the suitable Eigen type for the constness of the +// data. Indeed, for const data, we need to produce +// Eigen::Map> +// and not the more straightforward +// Eigen::Map> +template +using VectorMap = typename std::conditional< + std::is_const::value, + Eigen::Map::type, Eigen::Dynamic, 1>>, + Eigen::Map>>::type; + +template VectorMap MapAsVector(Scalar *data, const Dims &dims) +{ + const int size = RequiredBufferSizeForDims(dims); + return VectorMap(data, size, 1); +} + +// From optimized_ops.h in TensorFlow Lite +// +// Make a local VectorMap typedef allowing to map a float array +// as a Eigen matrix expression. The same explanation as for VectorMap +// above also applies here. +template +using MatrixMap = typename std::conditional< + std::is_const::value, + Eigen::Map::type, Eigen::Dynamic, + Eigen::Dynamic>>, + Eigen::Map>>::type; + +// From optimized_ops.h in TensorFlow Lite +template +MatrixMap MapAsMatrixWithFirstDimAsRows(Scalar *data, const Dims &dims) +{ + const int rows = dims.sizes[0]; + int cols = 1; + for (int d = 1; d < N; d++) + { + cols *= dims.sizes[d]; + } + return MatrixMap(data, rows, cols); +} + +// From optimized_ops.h in TensorFlow Lite +template +MatrixMap MapAsMatrixWithLastDimAsCols(Scalar *data, const Dims &dims) +{ + const int cols = dims.sizes[N - 1]; + int rows = 1; + for (int d = 0; d < N - 1; d++) + { + rows *= dims.sizes[d]; + } + return MatrixMap(data, rows, cols); +} + +// From optimized_ops.h in TensorFlow Lite +template +using ArrayMap = typename std::conditional< + std::is_const::value, + Eigen::Map::type, Eigen::Dynamic, + Eigen::Dynamic>>, + Eigen::Map>>::type; + +// From optimized_ops.h in TensorFlow Lite +template +ArrayMap MapAsArrayWithFirstDimAsRows(Scalar *data, const Dims &dims) +{ + const int rows = dims.sizes[0]; + int cols = 1; + for (int d = 1; d < N; d++) + { + cols *= dims.sizes[d]; + } + return ArrayMap(data, rows, cols); +} + +// From optimized_ops.h in TensorFlow Lite +// +// TODO(b/62193649): this function is only needed as long +// as we have the --variable_batch hack. +template +MatrixMap MapAsMatrixWithGivenNumberOfRows(Scalar *data, const Dims &dims, int rows) +{ + int cols = 1; + bool matched_rows = false; + for (int d = 0; d < N; d++) + { + cols *= dims.sizes[d]; + if (cols == rows) + { + matched_rows = true; + cols = 1; + } + } + DCHECK(matched_rows); + return MatrixMap(data, rows, cols); +} + +#endif // __MATRIX_H__ diff --git a/compiler/ann-ref/src/ops/internal/NDArray.h b/compiler/ann-ref/src/ops/internal/NDArray.h new file mode 100644 index 00000000000..14b16046971 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/NDArray.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright (C) 2017 The Android Open Source Project + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#ifndef __ND_ARRAY_H__ +#define __ND_ARRAY_H__ + +#include "Dims.h" +#include "Macro.h" + +// From optimized_ops.h in TensorFlow Lite +// +// DO NOT USE THIS STRUCT FOR NEW FUNCTIONALITY BEYOND IMPLEMENTING ELEMENT-WISE +// BROADCASTING. +// +// NdArrayDesc describes the shape and memory layout of an N-dimensional +// rectangular array of numbers. +// +// NdArrayDesc is basically identical to Dims defined in types.h. +// However, as Dims is to be deprecated, this class exists as an adaptor +// to enable simple unoptimized implementations of element-wise broadcasting +// operations. +template struct NdArrayDesc +{ + // The "extent" of each dimension. Indices along dimension d must be in the + // half-open interval [0, extents[d]). + int extents[N]; + + // The number of *elements* (not bytes) between consecutive indices of each + // dimension. + int strides[N]; +}; + +// From optimized_ops.h in TensorFlow Lite +// +// DO NOT USE THIS FUNCTION FOR NEW FUNCTIONALITY BEYOND IMPLEMENTING +// ELEMENT-WISE BROADCASTING. +// +// Same as Offset(), except takes as NdArrayDesc instead of Dims. +inline int SubscriptToIndex(const NdArrayDesc<4> &desc, int i0, int i1, int i2, int i3) +{ + DCHECK(i0 >= 0 && i0 < desc.extents[0]); + DCHECK(i1 >= 0 && i1 < desc.extents[1]); + DCHECK(i2 >= 0 && i2 < desc.extents[2]); + DCHECK(i3 >= 0 && i3 < desc.extents[3]); + return i0 * desc.strides[0] + i1 * desc.strides[1] + i2 * desc.strides[2] + i3 * desc.strides[3]; +} + +// From optimized_ops.h in TensorFlow Lite +// +// Given the dimensions of the operands for an element-wise binary broadcast, +// adjusts them so that they can be directly iterated over with simple loops. +// Returns the adjusted dims as instances of NdArrayDesc in 'desc0_out' and +// 'desc1_out'. 'desc0_out' and 'desc1_out' cannot be nullptr. +// +// This function assumes that the two input shapes are compatible up to +// broadcasting and the shorter one has already been prepended with 1s to be the +// same length. E.g., if shape0 is (1, 16, 16, 64) and shape1 is (1, 64), +// shape1 must already have been prepended to be (1, 1, 1, 64). Recall that +// Dims refer to shapes in reverse order. In this case, input0_dims will be +// (64, 16, 16, 1) and input1_dims will be (64, 1, 1, 1). +// +// When two shapes are compatible up to broadcasting, for each dimension d, +// the input extents are either equal, or one of them is 1. +// +// This function performs the following for each dimension d: +// - If the extents are equal, then do nothing since the loop that walks over +// both of the input arrays is correct. +// - Otherwise, one (and only one) of the extents must be 1. Say extent0 is 1 +// and extent1 is e1. Then set extent0 to e1 and stride0 *to 0*. This allows +// array0 to be referenced *at any index* in dimension d and still access the +// same slice. +template +inline void +NdArrayDescsForElementwiseBroadcast(const Dims &input0_dims, const Dims &input1_dims, + NdArrayDesc *desc0_out, NdArrayDesc *desc1_out) +{ + DCHECK(desc0_out != nullptr); + DCHECK(desc1_out != nullptr); + + // Copy dims to desc. + for (int i = 0; i < N; ++i) + { + desc0_out->extents[i] = input0_dims.sizes[i]; + desc0_out->strides[i] = input0_dims.strides[i]; + desc1_out->extents[i] = input1_dims.sizes[i]; + desc1_out->strides[i] = input1_dims.strides[i]; + } + + // Walk over each dimension. If the extents are equal do nothing. + // Otherwise, set the desc with extent 1 to have extent equal to the other and + // stride 0. + for (int i = 0; i < N; ++i) + { + const int extent0 = ArraySize(input0_dims, i); + const int extent1 = ArraySize(input1_dims, i); + if (extent0 != extent1) + { + if (extent0 == 1) + { + desc0_out->strides[i] = 0; + desc0_out->extents[i] = extent1; + } + else + { + DCHECK_EQ(extent1, 1); + desc1_out->strides[i] = 0; + desc1_out->extents[i] = extent0; + } + } + } +} + +inline int NodeOffset(int b, int h, int w, int height, int width) +{ + return (b * height + h) * width + w; +} + +#endif // __ND_ARRAY_H__ diff --git a/compiler/ann-ref/src/ops/internal/Pooling.cpp b/compiler/ann-ref/src/ops/internal/Pooling.cpp new file mode 100644 index 00000000000..a3b8cf3269e --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Pooling.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#include "Pooling.h" +#include "Spatial.h" + +#include "Assert.h" + +bool genericPoolingPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output) +{ + ASSERT(getNumberOfDimensions(input) == 4); + + uint32_t batches = getSizeOfDimension(input, 0); + uint32_t width = getSizeOfDimension(input, 2); + uint32_t height = getSizeOfDimension(input, 1); + uint32_t channels_out = getSizeOfDimension(input, 3); + + uint32_t outWidth = + computeOutSize(width, filter_width, stride_width, padding_left, padding_right); + uint32_t outHeight = + computeOutSize(height, filter_height, stride_height, padding_top, padding_bottom); + + output->type = input.type; + output->dimensions = {batches, outHeight, outWidth, channels_out}; + return true; +} diff --git a/compiler/ann-ref/src/ops/internal/Pooling.h b/compiler/ann-ref/src/ops/internal/Pooling.h new file mode 100644 index 00000000000..c55bc16cdb5 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Pooling.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __POOLING_H__ +#define __POOLING_H__ + +#include "Shape.h" + +#include + +bool genericPoolingPrepare(const Shape &input, int32_t padding_left, int32_t padding_right, + int32_t padding_top, int32_t padding_bottom, int32_t stride_width, + int32_t stride_height, int32_t filter_width, int32_t filter_height, + Shape *output); + + +#endif // __POOLING_H__ diff --git a/compiler/ann-ref/src/ops/internal/Spatial.h b/compiler/ann-ref/src/ops/internal/Spatial.h new file mode 100644 index 00000000000..6b8f0c11f30 --- /dev/null +++ b/compiler/ann-ref/src/ops/internal/Spatial.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * 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. + */ + +#ifndef __SPATIAL_H__ +#define __SPATIAL_H__ + +#include + +inline uint32_t computeOutSize(uint32_t imageSize, uint32_t filterSize, uint32_t stride, + uint32_t paddingHead, uint32_t paddingTail) +{ + return (imageSize - filterSize + stride + paddingHead + paddingTail) / stride; +} + +#endif // __SPATIAL_H__ diff --git a/compiler/bino/CMakeLists.txt b/compiler/bino/CMakeLists.txt new file mode 100644 index 00000000000..519eecdc838 --- /dev/null +++ b/compiler/bino/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(bino INTERFACE) +target_include_directories(bino INTERFACE include) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +file(GLOB_RECURSE TESTS "tests/*.cpp") + +GTest_AddTest(bino_test ${TESTS}) +target_link_libraries(bino_test bino) diff --git a/compiler/bino/README.md b/compiler/bino/README.md new file mode 100644 index 00000000000..9d58c725d4d --- /dev/null +++ b/compiler/bino/README.md @@ -0,0 +1,5 @@ +# bino + +Let's manipulate std::pair values with UNIX pipe-like syntax. + +**NOTE** The _bino_ originates from a binocular telescope. diff --git a/compiler/bino/include/bino.h b/compiler/bino/include/bino.h new file mode 100644 index 00000000000..fc22d12851e --- /dev/null +++ b/compiler/bino/include/bino.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __BINO_H__ +#define __BINO_H__ + +#include + +namespace bino +{ + +template class UniformTransform +{ +public: + UniformTransform(Callable &&cb) : f{std::forward(cb)} + { + // DO NOTHING + } + +public: + template + auto operator()(const std::pair &p) const + -> decltype(std::make_pair(std::declval()(p.first), + std::declval()(p.second))) + { + return std::make_pair(f(p.first), f(p.second)); + } + +private: + Callable f; +}; + +template UniformTransform transform_both(Callable &&f) +{ + return UniformTransform{std::forward(f)}; +} + +// TODO Implement transform_both(f, g) +// TODO Implement transform_first(f) +// TODO Implement transform_second(f) + +} // namespace bino + +#endif // __BINO_H__ diff --git a/compiler/bino/tests/Functional.tests.cpp b/compiler/bino/tests/Functional.tests.cpp new file mode 100644 index 00000000000..14dde6a45cd --- /dev/null +++ b/compiler/bino/tests/Functional.tests.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * Let's test functionals in "bino". + * + * NOTE The tests in this file assume that operator overloading works well. + */ + +#include "bino.h" + +#include + +TEST(FunctionalTests, transform_both_uniform) +{ + auto inc = [](int n) { return n + 1; }; + auto f = bino::transform_both(inc); + auto res = f(std::make_pair(1, 3)); + + ASSERT_EQ(res.first, 2); + ASSERT_EQ(res.second, 4); +} diff --git a/compiler/caffe2circle/CMakeLists.txt b/compiler/caffe2circle/CMakeLists.txt new file mode 100644 index 00000000000..eaf54170504 --- /dev/null +++ b/compiler/caffe2circle/CMakeLists.txt @@ -0,0 +1,16 @@ +if(NOT TARGET mir_caffe_importer) + return() +endif() + +if(NOT TARGET mir2loco) + return() +endif() + +if(NOT TARGET exo) + return() +endif() + +message(STATUS "Build caffe2circle: TRUE") + +add_executable(caffe2circle src/caffe2circle.cpp) +target_link_libraries(caffe2circle PRIVATE mir_caffe_importer mir2loco exo) diff --git a/compiler/caffe2circle/README.md b/compiler/caffe2circle/README.md new file mode 100644 index 00000000000..fe9ea26dd09 --- /dev/null +++ b/compiler/caffe2circle/README.md @@ -0,0 +1,3 @@ +# caffe2circle + +_caffe2circle_ is a Caffe-to-Circle model converter. diff --git a/compiler/caffe2circle/requires.cmake b/compiler/caffe2circle/requires.cmake new file mode 100644 index 00000000000..cc05edd84f2 --- /dev/null +++ b/compiler/caffe2circle/requires.cmake @@ -0,0 +1,3 @@ +require("mir-onnx-importer") +require("mir2loco") +require("exo") diff --git a/compiler/caffe2circle/src/caffe2circle.cpp b/compiler/caffe2circle/src/caffe2circle.cpp new file mode 100644 index 00000000000..fb09c0a1c4d --- /dev/null +++ b/compiler/caffe2circle/src/caffe2circle.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include +#include + +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 3) + { + std::cerr << "Usage: caffe2circle \n"; + return EXIT_FAILURE; + } + + const char *caffe_path = argv[1]; + const char *circle_path = argv[2]; + + std::unique_ptr mir_graph = mir_caffe::importModelFromBinaryFile(caffe_path); + std::unique_ptr loco_graph = mir2loco::Transformer().transform(mir_graph.get()); + exo::CircleExporter(loco_graph.get()).dumpToFile(circle_path); + return EXIT_SUCCESS; +} diff --git a/compiler/caffegen/CMakeLists.txt b/compiler/caffegen/CMakeLists.txt new file mode 100644 index 00000000000..334174dcd0f --- /dev/null +++ b/compiler/caffegen/CMakeLists.txt @@ -0,0 +1,14 @@ +nnas_find_package(Caffe QUIET) + +if(NOT Caffe_FOUND) + return() +endif(NOT Caffe_FOUND) + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(caffegen ${SOURCES}) +target_link_libraries(caffegen stdex) +target_link_libraries(caffegen cli) +# NOTE "Caffe" package provides both caffe and caffeproto target +# NOTE "caffeproto" is linked to "caffe" +target_link_libraries(caffegen caffe) diff --git a/compiler/caffegen/README.md b/compiler/caffegen/README.md new file mode 100644 index 00000000000..c322721b3cc --- /dev/null +++ b/compiler/caffegen/README.md @@ -0,0 +1,45 @@ +# caffegen + +`caffegen` is a tool for generating caffe model and decoding binary file of caffe model + +## How caffegen works + +Some of commands in `caffegen` use standard input for reading data and standard output for exporting result. +In this case, we strongly recommand you to use pipe, not copy & paste the content of file itself. + +Otherwise, `caffegen` use arguments to pass some directories. + +## Supported command + +Basically, caffgen command is used as `caffegen [COMMAND]` and there are four `COMMAND` types. + - init : initialize parameters using prototxt. + - encode : make a binary file(caffemodel) using initialized data + - decode : decode a binary file(caffemodel) and reproduce the initialized data + - merge : copy the trained weights from a caffemodel into a prototxt file + +## How to use each command + +1. Init (Using stdin and stdout) + - `./build/compiler/caffegen/caffegen init` + - Type the prototxt by yourself + - Then you can get the result on the shell. + - `cat ./res/BVLCCaffeTests/Convolution_000/test.prototxt | ./build/compiler/caffegen/caffegen init` + - Prototxt will be automatically passed + - Then you can get the result on the shell. + +2. Encode (Using stdin and stdout) + - `./build/compiler/caffegen/caffegen encode` + - Type the initialized data by yourself + - Then you can get the result on the shell. + - `cat ./res/BVLCCaffeTests/Convolution_000/test.prototxt | ./build/compiler/caffegen/caffegen init | ./build/compiler/caffegen/caffegen encode > Convolution_000.caffemodel` + - The initialized data will be automatically passed + - The encoded result will be automatically saved in caffemodel file + +3. Decode (Using stdin and stdout) + - `cat Convolution_000.caffemodel | ./build/compiler/caffegen/caffegen decode` + - Caffemodel file will be automatically passed + - Then you can get the result on the shell + +4. Merge (Using arguments) + - `./build/compiler/caffegen/caffegen merge ./res/BVLCCaffeTests/Convolution_000/test.prototxt Convolution_000.caffemodel` + - `./build/compiler/caffegen/caffegen merge ./res/BVLCCaffeTests/Convolution_000/test.prototxt Convolution_000.caffemodel > Convolution_000.merged` diff --git a/compiler/caffegen/src/DecodeCommand.cpp b/compiler/caffegen/src/DecodeCommand.cpp new file mode 100644 index 00000000000..02d044ed3f7 --- /dev/null +++ b/compiler/caffegen/src/DecodeCommand.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DecodeCommand.h" + +#include + +#include +#include +#include + +#include + +int DecodeCommand::run(int, const char *const *) const +{ + caffe::NetParameter param; + + // Load binary from standard input + google::protobuf::io::IstreamInputStream is{&std::cin}; + google::protobuf::io::CodedInputStream coded_is{&is}; + + if (!param.ParseFromCodedStream(&coded_is)) + { + std::cerr << "ERROR: Failed to parse caffemodel" << std::endl; + return 255; + } + + // Write text into standard output + google::protobuf::io::OstreamOutputStream os{&std::cout}; + google::protobuf::TextFormat::Print(param, &os); + + return 0; +} diff --git a/compiler/caffegen/src/DecodeCommand.h b/compiler/caffegen/src/DecodeCommand.h new file mode 100644 index 00000000000..4b43b465b7a --- /dev/null +++ b/compiler/caffegen/src/DecodeCommand.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DECODE_COMMAND_H__ +#define __DECODE_COMMAND_H__ + +#include + +struct DecodeCommand final : public cli::Command +{ + int run(int argc, const char *const *argv) const override; +}; + +#endif // __DECODE_COMMAND_H__ diff --git a/compiler/caffegen/src/Driver.cpp b/compiler/caffegen/src/Driver.cpp new file mode 100644 index 00000000000..81b01e6f191 --- /dev/null +++ b/compiler/caffegen/src/Driver.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "InitCommand.h" +#include "EncodeCommand.h" +#include "DecodeCommand.h" +#include "MergeCommand.h" + +#include +#include + +#include +#include + +using stdex::make_unique; + +int main(int argc, char **argv) +{ + cli::App app{argv[0]}; + + // all receive data from stdin + app.insert("init", make_unique()); + app.insert("encode", make_unique()); + app.insert("decode", make_unique()); + // takes 2 args: prototxt model and caffemodel weights in that order + app.insert("merge", make_unique()); + + return app.run(argc - 1, argv + 1); +} diff --git a/compiler/caffegen/src/EncodeCommand.cpp b/compiler/caffegen/src/EncodeCommand.cpp new file mode 100644 index 00000000000..4b35030bd7d --- /dev/null +++ b/compiler/caffegen/src/EncodeCommand.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EncodeCommand.h" + +#include + +#include +#include +#include + +#include + +int EncodeCommand::run(int, const char *const *) const +{ + caffe::NetParameter param; + + // Load text from standard input + google::protobuf::io::IstreamInputStream is{&std::cin}; + + if (!google::protobuf::TextFormat::Parse(&is, ¶m)) + { + std::cerr << "ERROR: Failed to parse prototxt" << std::endl; + return 255; + } + + // Write binary into standard output + google::protobuf::io::OstreamOutputStream os{&std::cout}; + google::protobuf::io::CodedOutputStream coded_os{&os}; + + if (!param.SerializeToCodedStream(&coded_os)) + { + std::cerr << "ERROR: Failed to serialize" << std::endl; + return 255; + } + + return 0; +} diff --git a/compiler/caffegen/src/EncodeCommand.h b/compiler/caffegen/src/EncodeCommand.h new file mode 100644 index 00000000000..1115c236322 --- /dev/null +++ b/compiler/caffegen/src/EncodeCommand.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCODE_COMMAND_H__ +#define __ENCODE_COMMAND_H__ + +#include + +struct EncodeCommand final : public cli::Command +{ + int run(int argc, const char *const *argv) const override; +}; + +#endif // __ENCODE_COMMAND_H__ diff --git a/compiler/caffegen/src/InitCommand.cpp b/compiler/caffegen/src/InitCommand.cpp new file mode 100644 index 00000000000..fd5b8a467b2 --- /dev/null +++ b/compiler/caffegen/src/InitCommand.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "InitCommand.h" + +#include +#include +#include + +#include +#include +#include + +#include + +int InitCommand::run(int, const char *const *) const +{ + // Read prototxt from standard input + ::caffe::NetParameter in; + { + google::protobuf::io::IstreamInputStream is{&std::cin}; + if (!google::protobuf::TextFormat::Parse(&is, &in)) + { + std::cerr << "ERROR: Failed to parse prototxt" << std::endl; + return 255; + } + } + + // Upgrade prototxt if necessary + if (::caffe::NetNeedsUpgrade(in)) + { + if (!::caffe::UpgradeNetAsNeeded("", &in)) + { + std::cerr << "ERROR: Failed to upgrade prototxt" << std::endl; + return 255; + } + } + + ::caffe::Net net(in); + + // Extract initialized parameters + ::caffe::NetParameter out; + { + net.ToProto(&out); + } + + // Write initialized parameters to standard output + google::protobuf::io::OstreamOutputStream os{&std::cout}; + google::protobuf::TextFormat::Print(out, &os); + + return 0; +} diff --git a/compiler/caffegen/src/InitCommand.h b/compiler/caffegen/src/InitCommand.h new file mode 100644 index 00000000000..8d86a195f05 --- /dev/null +++ b/compiler/caffegen/src/InitCommand.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __INIT_COMMAND_H__ +#define __INIT_COMMAND_H__ + +#include + +struct InitCommand final : public cli::Command +{ + int run(int argc, const char *const *argv) const override; +}; + +#endif // __INIT_COMMAND_H__ diff --git a/compiler/caffegen/src/MergeCommand.cpp b/compiler/caffegen/src/MergeCommand.cpp new file mode 100644 index 00000000000..4e1d863bc66 --- /dev/null +++ b/compiler/caffegen/src/MergeCommand.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MergeCommand.h" + +#include +#include + +#include +#include +#include + +#include +#include + +int MergeCommand::run(int argc, const char *const *argv) const +{ + if (argc != 2) + { + std::cerr << "ERROR: this command requires exactly 2 arguments" << std::endl; + return 254; + } + + std::string model_file = argv[0]; + std::string trained_file = argv[1]; + + // Load the network + caffe::Net caffe_test_net(model_file, caffe::TEST); + // Load the weights + caffe_test_net.CopyTrainedLayersFrom(trained_file); + + caffe::NetParameter net_param; + caffe_test_net.ToProto(&net_param); + + // Write binary with initialized params into standard output + google::protobuf::io::OstreamOutputStream os(&std::cout); + google::protobuf::io::CodedOutputStream coded_os{&os}; + + if (!net_param.SerializeToCodedStream(&coded_os)) + { + std::cerr << "ERROR: Failed to serialize" << std::endl; + return 255; + } + return 0; +} diff --git a/compiler/caffegen/src/MergeCommand.h b/compiler/caffegen/src/MergeCommand.h new file mode 100644 index 00000000000..e0134626cac --- /dev/null +++ b/compiler/caffegen/src/MergeCommand.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __MERGE_COMMAND_H__ +#define __MERGE_COMMAND_H__ + +#include + +/** + * @brief Takes .prototxt and .caffemodel filenames from ARGV + * and fills the model with trained weights. + * The resulting binary model with weights to be consumed by nnc is printed to StdOut + * @return error code + */ +struct MergeCommand final : public cli::Command +{ + int run(int argc, const char *const *argv) const override; +}; + +#endif //__MERGE_COMMAND_H__ diff --git a/compiler/circle-inspect/CMakeLists.txt b/compiler/circle-inspect/CMakeLists.txt new file mode 100644 index 00000000000..222f8cb1a4b --- /dev/null +++ b/compiler/circle-inspect/CMakeLists.txt @@ -0,0 +1,13 @@ +if(NOT TARGET mio_circle) + return() +endif(NOT TARGET mio_circle) + +set(DRIVER "driver/Driver.cpp") + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(circle-inspect ${DRIVER} ${SOURCES}) +target_include_directories(circle-inspect PRIVATE src) +target_link_libraries(circle-inspect mio_circle) +target_link_libraries(circle-inspect safemain) +target_link_libraries(circle-inspect stdex) diff --git a/compiler/circle-inspect/README.md b/compiler/circle-inspect/README.md new file mode 100644 index 00000000000..1f76c8ede30 --- /dev/null +++ b/compiler/circle-inspect/README.md @@ -0,0 +1,22 @@ +# circle-inspect + +_circle-inspect_ allows users to retrieve various information from a Circle model file + +## Information to inspect + +Operators with `--operators` +- show operator codes one line at a time in execution order + +Example +``` +$ circle-inspect --operators model.circle +``` + +Result +``` +RESHAPE +DEPTHWISE_CONV_2D +ADD +``` + +To get the count of specific operator, use other tools like sort, uniq, etc. diff --git a/compiler/circle-inspect/driver/Driver.cpp b/compiler/circle-inspect/driver/Driver.cpp new file mode 100644 index 00000000000..d23cd0f8b3a --- /dev/null +++ b/compiler/circle-inspect/driver/Driver.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Model.h" +#include "Dump.h" + +#include + +#include +#include +#include +#include +#include + +using OptionHook = std::function(void)>; + +int entry(int argc, char **argv) +{ + if (argc < 3) + { + std::cerr << "ERROR: Failed to parse arguments" << std::endl; + std::cerr << std::endl; + std::cerr << "USAGE: " << argv[0] << " [options] [circle]" << std::endl; + std::cerr << " --operators : dump operators in circle file" << std::endl; + std::cerr << " --conv2d_weight : dump Conv2D series weight operators in circle file" + << std::endl; + return 255; + } + + // Simple argument parser (based on map) + std::map argparse; + + argparse["--operators"] = [&](void) { + // dump all operators + return std::move(stdex::make_unique()); + }; + + argparse["--conv2d_weight"] = [&](void) { + // dump Conv2D, DepthwiseConv2D weight operators + return std::move(stdex::make_unique()); + }; + + std::vector> dumps; + + for (int n = 1; n < argc - 1; ++n) + { + const std::string tag{argv[n]}; + + auto it = argparse.find(tag); + if (it == argparse.end()) + { + std::cerr << "Option '" << tag << "' is not supported" << std::endl; + return 255; + } + auto dump = it->second(); + assert(dump != nullptr); + dumps.push_back(std::move(dump)); + } + + std::string model_file = argv[argc - 1]; + + // Load Circle model from a circle file + auto model = circleinspect::load_circle(model_file); + if (model == nullptr) + { + std::cerr << "ERROR: Failed to load circle '" << model_file << "'" << std::endl; + return 255; + } + + const circle::Model *circlemodel = model->model(); + if (circlemodel == nullptr) + { + std::cerr << "ERROR: Failed to load circle '" << model_file << "'" << std::endl; + return 255; + } + + for (auto &dump : dumps) + { + dump->run(std::cout, circlemodel); + } + + return 0; +} diff --git a/compiler/circle-inspect/requires.cmake b/compiler/circle-inspect/requires.cmake new file mode 100644 index 00000000000..b090dbd4d75 --- /dev/null +++ b/compiler/circle-inspect/requires.cmake @@ -0,0 +1,3 @@ +require("mio-circle") +require("safemain") +require("stdex") diff --git a/compiler/circle-inspect/src/Dump.cpp b/compiler/circle-inspect/src/Dump.cpp new file mode 100644 index 00000000000..fbc092b8998 --- /dev/null +++ b/compiler/circle-inspect/src/Dump.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Dump.h" +#include "Reader.h" + +#include + +namespace circleinspect +{ + +void DumpOperators::run(std::ostream &os, const circle::Model *model) +{ + circleinspect::Reader reader(model); + + assert(reader.num_subgraph() == 1); + reader.select_subgraph(0); + + auto ops = reader.operators(); + + // dump operators + for (uint32_t i = 0; i < ops->Length(); ++i) + { + const auto op = ops->Get(i); + + auto op_name = reader.opcode_name(op); + + os << op_name << std::endl; + } +} + +} // namespace circleinspect + +namespace +{ + +const circle::Operator *operator_match_output(circleinspect::Reader &reader, const int32_t tensor) +{ + auto ops = reader.operators(); + + for (uint32_t i = 0; i < ops->Length(); ++i) + { + const auto op = ops->Get(i); + + const std::vector &outputs = circleinspect::as_index_vector(op->outputs()); + + for (auto output : outputs) + { + if (output == tensor) + return op; + } + } + return nullptr; +} + +size_t tensor_buffer_size(circleinspect::Reader &reader, const int32_t tensor_id) +{ + auto tensors = reader.tensors(); + + if (tensor_id < 0 || tensor_id >= tensors->Length()) + { + throw std::runtime_error("Invalid Tensor ID"); + } + + auto tensor = tensors->Get(tensor_id); + auto buffer_id = tensor->buffer(); + + size_t size = reader.buffer_info(buffer_id, nullptr); + + return size; +} + +} // namespace + +namespace circleinspect +{ + +void DumpConv2DWeight::run(std::ostream &os, const circle::Model *model) +{ + circleinspect::Reader reader(model); + + assert(reader.num_subgraph() == 1); + reader.select_subgraph(0); + + auto ops = reader.operators(); + + // dump Conv2D, DepthwiseConv2D and its weight input operator + for (uint32_t i = 0; i < ops->Length(); ++i) + { + const auto op = ops->Get(i); + auto bc = reader.builtin_code(op); + + if (bc == circle::BuiltinOperator_CONV_2D || bc == circle::BuiltinOperator_DEPTHWISE_CONV_2D) + { + const std::vector &inputs = circleinspect::as_index_vector(op->inputs()); + if (inputs.size() < 2) + { + throw std::runtime_error("Operator has invalid input"); + } + auto weight_input = inputs[1]; // Tensor ID of weight input + + const auto op_weight = operator_match_output(reader, weight_input); + const auto buffer_size = tensor_buffer_size(reader, weight_input); + + std::string weight_op_name = "?"; + + if (op_weight == nullptr && buffer_size > 0) + { + weight_op_name = "CONST"; + } + else if (op_weight != nullptr) + { + weight_op_name = reader.opcode_name(op_weight); + } + + auto op_name = reader.opcode_name(op); + os << op_name << "," << weight_op_name << std::endl; + } + } +} + +} // namespace circleinspect diff --git a/compiler/circle-inspect/src/Dump.h b/compiler/circle-inspect/src/Dump.h new file mode 100644 index 00000000000..6afba83b367 --- /dev/null +++ b/compiler/circle-inspect/src/Dump.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DUMP_H__ +#define __DUMP_H__ + +#include + +#include + +namespace circleinspect +{ + +class DumpInterface +{ +public: + virtual ~DumpInterface() = default; + +public: + virtual void run(std::ostream &os, const circle::Model *model) = 0; +}; + +class DumpOperators final : public DumpInterface +{ +public: + DumpOperators() = default; + +public: + void run(std::ostream &os, const circle::Model *model); +}; + +class DumpConv2DWeight final : public DumpInterface +{ +public: + DumpConv2DWeight() = default; + +public: + void run(std::ostream &os, const circle::Model *model); +}; + +} // namespace circleinspect + +#endif // __DUMP_H__ diff --git a/compiler/circle-inspect/src/Model.cpp b/compiler/circle-inspect/src/Model.cpp new file mode 100644 index 00000000000..fec1eb99e03 --- /dev/null +++ b/compiler/circle-inspect/src/Model.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Model.h" + +#include +#include +#include +#include + +namespace +{ + +class MemoryMappedModel final : public circleinspect::Model +{ +public: + /** + * @require fd and data SHOULD be valid + */ + explicit MemoryMappedModel(int fd, void *data, size_t size) : _fd{fd}, _data{data}, _size{size} + { + // DO NOTHING + } + +public: + ~MemoryMappedModel() + { + munmap(_data, _size); + close(_fd); + } + +public: + MemoryMappedModel(const MemoryMappedModel &) = delete; + MemoryMappedModel(MemoryMappedModel &&) = delete; + +public: + const ::circle::Model *model(void) const override { return ::circle::GetModel(_data); } + +private: + int _fd = -1; + void *_data = nullptr; + size_t _size = 0; +}; + +class FileDescriptor final +{ +public: + FileDescriptor(int value) : _value{value} + { + // DO NOTHING + } + +public: + // NOTE Copy is not allowed + FileDescriptor(const FileDescriptor &) = delete; + +public: + // NOTE Move is allowed + FileDescriptor(FileDescriptor &&fd) { _value = fd.release(); } + +public: + ~FileDescriptor() + { + if (_value != -1) + { + // Close on descturction + close(_value); + } + } + +public: + int value(void) const { return _value; } + +public: + int release(void) + { + auto res = _value; + _value = -1; + return res; + } + +private: + int _value = -1; +}; + +} // namespace + +namespace circleinspect +{ + +std::unique_ptr load_circle(const std::string &path) +{ + FileDescriptor fd = open(path.c_str(), O_RDONLY); + + if (fd.value() == -1) + { + // Return nullptr on open failure + return nullptr; + } + + struct stat st; + if (fstat(fd.value(), &st) == -1) + { + // Return nullptr on fstat failure + return nullptr; + } + + auto size = st.st_size; + auto data = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd.value(), 0); + + if (data == MAP_FAILED) + { + // Return nullptr on mmap failure + return nullptr; + } + + // Check if file is a valid Flatbuffer file + const uint8_t *u8data = reinterpret_cast(data); + flatbuffers::Verifier verifier{u8data, size}; + if (!circle::VerifyModelBuffer(verifier)) + { + munmap(data, size); + close(fd.release()); + return nullptr; + } + + return std::unique_ptr{new MemoryMappedModel(fd.release(), data, size)}; +} + +} // namespace circleinspect diff --git a/compiler/circle-inspect/src/Model.h b/compiler/circle-inspect/src/Model.h new file mode 100644 index 00000000000..8206ed364e5 --- /dev/null +++ b/compiler/circle-inspect/src/Model.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +#include + +#include + +namespace circleinspect +{ + +struct Model +{ + virtual ~Model() = default; + + virtual const ::circle::Model *model(void) const = 0; +}; + +/** + * @brief Load Circle model (as a raw Model) from a given path + * + * @note May return a nullptr + */ +std::unique_ptr load_circle(const std::string &path); + +} // namespace circleinspect + +#endif // __MODEL_H__ diff --git a/compiler/circle-inspect/src/Reader.cpp b/compiler/circle-inspect/src/Reader.cpp new file mode 100644 index 00000000000..dbbc7c75eac --- /dev/null +++ b/compiler/circle-inspect/src/Reader.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Reader.h" + +#include +#include + +namespace circleinspect +{ + +bool is_valid(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (circle::BuiltinOperator_MIN <= code && code <= circle::BuiltinOperator_MAX); +} + +bool is_custom(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (code == circle::BuiltinOperator_CUSTOM); +} + +std::string opcode_name(const circle::OperatorCode *opcode) +{ + assert(opcode); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid)"; + return oss.str(); + } + + if (is_custom(opcode)) + { + if (!opcode->custom_code()) + return "(invalid custom)"; + + return opcode->custom_code()->c_str(); + } + + circle::BuiltinOperator code = opcode->builtin_code(); + return circle::EnumNameBuiltinOperator(code); +} + +const char *tensor_type(const circle::Tensor *tensor) +{ + return circle::EnumNameTensorType(tensor->type()); +} + +const char *tensor_name(const circle::Tensor *tensor) +{ + static const char *kEmptyTensorName = "(noname)"; + + auto name = tensor->name(); + if (name) + return name->c_str(); + + return kEmptyTensorName; +} + +Reader::Reader(const circle::Model *model) +{ + _subgraphs = model->subgraphs(); + _buffers = model->buffers(); + + auto opcodes = model->operator_codes(); + for (const ::circle::OperatorCode *opcode : *opcodes) + { + _op_codes.push_back(opcode); + } +} + +size_t Reader::buffer_info(uint32_t buf_idx, const uint8_t **buff_data) +{ + if (buff_data != nullptr) + { + *buff_data = nullptr; + } + + if (buf_idx == 0) + return 0; + + if (auto *buffer = (*_buffers)[buf_idx]) + { + if (auto *array = buffer->data()) + { + if (size_t size = array->size()) + { + if (buff_data != nullptr) + { + *buff_data = reinterpret_cast(array->data()); + } + return size; + } + } + } + + return 0; +} + +circle::BuiltinOperator Reader::builtin_code(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + return opcode->builtin_code(); +} + +std::string Reader::opcode_name(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid: " << index << ")"; + return oss.str(); + } + + return circleinspect::opcode_name(opcode); +} + +bool Reader::select_subgraph(uint32_t sgindex) +{ + _tensors = nullptr; + _operators = nullptr; + + _inputs.clear(); + _outputs.clear(); + + if (_subgraphs->Length() <= sgindex) + { + assert(false); + return false; + } + + const circle::SubGraph *subgraph = (*_subgraphs)[sgindex]; + + _tensors = subgraph->tensors(); + _operators = subgraph->operators(); + + _inputs = as_index_vector(subgraph->inputs()); + _outputs = as_index_vector(subgraph->outputs()); + + return true; +} + +} // namespace circleinspect diff --git a/compiler/circle-inspect/src/Reader.h b/compiler/circle-inspect/src/Reader.h new file mode 100644 index 00000000000..b5a99df3f13 --- /dev/null +++ b/compiler/circle-inspect/src/Reader.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __READER_H__ +#define __READER_H__ + +#include + +#include +#include +#include + +namespace circleinspect +{ + +template std::vector as_index_vector(const flatbuffers::Vector *flat_array) +{ + std::vector ret(flat_array->Length()); + for (uint32_t i = 0; i < flat_array->Length(); i++) + { + ret[i] = flat_array->Get(i); + } + return ret; +} + +bool is_valid(const circle::OperatorCode *opcode); +bool is_custom(const circle::OperatorCode *opcode); +std::string opcode_name(const circle::OperatorCode *opcode); +const char *tensor_type(const circle::Tensor *tensor); +const char *tensor_name(const circle::Tensor *tensor); + +/** + * @brief Loads Circle file and provides helpers to access attributes + */ +class Reader +{ +private: + using CircleSubGraphs_t = flatbuffers::Vector>; + using CircleBuffers_t = flatbuffers::Vector>; + using CircleTensors_t = flatbuffers::Vector>; + using CircleOperators_t = flatbuffers::Vector>; + +public: + Reader(const circle::Model *model); + + Reader() = delete; + +public: + const std::vector &opcodes() { return _op_codes; } + const CircleBuffers_t *buffers() { return _buffers; } + const CircleTensors_t *tensors() { return _tensors; } + const CircleOperators_t *operators() { return _operators; } + const std::vector &inputs() const { return _inputs; } + const std::vector &outputs() const { return _outputs; } + + uint32_t num_subgraph() const { return _subgraphs->Length(); } + + size_t buffer_info(uint32_t buf_idx, const uint8_t **buff_data); + circle::BuiltinOperator builtin_code(const circle::Operator *op) const; + std::string opcode_name(const circle::Operator *op) const; + +public: + bool select_subgraph(uint32_t subgraph); + +private: + const CircleSubGraphs_t *_subgraphs{nullptr}; + const CircleBuffers_t *_buffers{nullptr}; + const CircleTensors_t *_tensors{nullptr}; + const CircleOperators_t *_operators{nullptr}; + + std::vector _op_codes; + std::vector _inputs; + std::vector _outputs; +}; + +} // namespace circleinspect + +#endif // __READER_H__ diff --git a/compiler/circle-verify/CMakeLists.txt b/compiler/circle-verify/CMakeLists.txt new file mode 100644 index 00000000000..2e19951e1c8 --- /dev/null +++ b/compiler/circle-verify/CMakeLists.txt @@ -0,0 +1,12 @@ +if(NOT TARGET mio_circle) + return() +endif(NOT TARGET mio_circle) + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(circle-verify ${SOURCES}) +target_include_directories(circle-verify PRIVATE src) +target_link_libraries(circle-verify mio_circle) +target_link_libraries(circle-verify safemain) +target_link_libraries(circle-verify cwrap) +target_link_libraries(circle-verify stdex) diff --git a/compiler/circle-verify/README.md b/compiler/circle-verify/README.md new file mode 100644 index 00000000000..1eda8a99e75 --- /dev/null +++ b/compiler/circle-verify/README.md @@ -0,0 +1,23 @@ +# circle-verify + +_circle-verify_ allows users to verify Circle models. + +## Usage + +Provide _circle_ file as a parameter to verify validity. + +``` +$ circle-verify circlefile.circle +``` + +Result for valid file +``` +[ RUN ] Check circlefile.circle +[ PASS ] Check circlefile.circle +``` + +Result for invalid file +``` +[ RUN ] Check circlefile.circle +[ FAIL ] Check circlefile.circle +``` diff --git a/compiler/circle-verify/requires.cmake b/compiler/circle-verify/requires.cmake new file mode 100644 index 00000000000..2509b693167 --- /dev/null +++ b/compiler/circle-verify/requires.cmake @@ -0,0 +1,4 @@ +require("mio-circle") +require("safemain") +require("cwrap") +require("stdex") diff --git a/compiler/circle-verify/src/Driver.cpp b/compiler/circle-verify/src/Driver.cpp new file mode 100644 index 00000000000..ad13e504fae --- /dev/null +++ b/compiler/circle-verify/src/Driver.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "VerifyFlatBuffers.h" + +#include + +#include +#include + +int entry(int argc, char **argv) +{ + if (argc != 2) + { + std::cerr << "ERROR: Failed to parse arguments" << std::endl; + std::cerr << std::endl; + std::cerr << "USAGE: " << argv[0] << " [circle]" << std::endl; + return 255; + } + auto verifier = stdex::make_unique(); + + std::string model_file = argv[argc - 1]; + + std::cout << "[ RUN ] Check " << model_file << std::endl; + + auto result = verifier->run(model_file); + + if (result == 0) + { + std::cout << "[ PASS ] Check " << model_file << std::endl; + } + else + { + std::cout << "[ FAIL ] Check " << model_file << std::endl; + } + + return result; +} diff --git a/compiler/circle-verify/src/Model.cpp b/compiler/circle-verify/src/Model.cpp new file mode 100644 index 00000000000..efac1210db5 --- /dev/null +++ b/compiler/circle-verify/src/Model.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Model.h" + +#include + +#include +#include +#include +#include + +namespace +{ + +class MemoryMappedModel final : public ModelData +{ +public: + /** + * @require fd and data SHOULD be valid + */ + explicit MemoryMappedModel(int fd, void *data, size_t size) : _fd{fd}, _data{data}, _size{size} + { + // DO NOTHING + } + +public: + ~MemoryMappedModel() + { + munmap(_data, _size); + close(_fd); + } + +public: + MemoryMappedModel(const MemoryMappedModel &) = delete; + MemoryMappedModel(MemoryMappedModel &&) = delete; + +public: + const void *data(void) const override { return _data; }; + const size_t size(void) const override { return _size; }; + +private: + int _fd = -1; + void *_data = nullptr; + size_t _size = 0; +}; + +} // namespace + +std::unique_ptr load_modeldata(const std::string &path) +{ + cwrap::Fildes fd(open(path.c_str(), O_RDONLY)); + + if (fd.get() == -1) + { + // Return nullptr on open failure + return nullptr; + } + + struct stat st; + if (fstat(fd.get(), &st) == -1) + { + // Return nullptr on fstat failure + return nullptr; + } + + auto size = st.st_size; + auto data = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd.get(), 0); + + if (data == MAP_FAILED) + { + // Return nullptr on mmap failure + return nullptr; + } + + return std::unique_ptr{new MemoryMappedModel(fd.release(), data, size)}; +} diff --git a/compiler/circle-verify/src/Model.h b/compiler/circle-verify/src/Model.h new file mode 100644 index 00000000000..e1bd839712e --- /dev/null +++ b/compiler/circle-verify/src/Model.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __MODEL_H__ +#define __MODEL_H__ + +#include +#include + +struct ModelData +{ + virtual ~ModelData() = default; + + virtual const void *data(void) const = 0; + virtual const size_t size(void) const = 0; +}; + +/** + * @brief Load Circle model (as a raw data) from a given path + * + * @note May return a nullptr + */ +std::unique_ptr load_modeldata(const std::string &path); + +#endif // __MODEL_H__ diff --git a/compiler/circle-verify/src/VerifyFlatBuffers.cpp b/compiler/circle-verify/src/VerifyFlatBuffers.cpp new file mode 100644 index 00000000000..36b16685f01 --- /dev/null +++ b/compiler/circle-verify/src/VerifyFlatBuffers.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "VerifyFlatBuffers.h" + +#include "Model.h" + +#include + +int VerifyFlatbuffers::run(const std::string &model_file) +{ + auto modeldata = load_modeldata(model_file); + + const uint8_t *data = reinterpret_cast(modeldata->data()); + flatbuffers::Verifier verifier{data, modeldata->size()}; + + if (!circle::VerifyModelBuffer(verifier)) + { + return -1; + } + + return 0; +} diff --git a/compiler/circle-verify/src/VerifyFlatBuffers.h b/compiler/circle-verify/src/VerifyFlatBuffers.h new file mode 100644 index 00000000000..c301b5b10ab --- /dev/null +++ b/compiler/circle-verify/src/VerifyFlatBuffers.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __VERIFY_FLATBUFFERS_H__ +#define __VERIFY_FLATBUFFERS_H__ + +#include +#include + +class VerifyFlatbuffers +{ +public: + VerifyFlatbuffers() = default; + +public: + int run(const std::string &model_file); +}; + +#endif // __VERIFY_FLATBUFFERS_H__ diff --git a/compiler/circledump/CMakeLists.txt b/compiler/circledump/CMakeLists.txt new file mode 100644 index 00000000000..a117e72857c --- /dev/null +++ b/compiler/circledump/CMakeLists.txt @@ -0,0 +1,14 @@ +if(NOT TARGET mio_circle) + return() +endif(NOT TARGET mio_circle) + +set(DRIVER "driver/Driver.cpp") + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(circledump ${DRIVER} ${SOURCES}) +target_include_directories(circledump PRIVATE include) +target_link_libraries(circledump mio_circle) +target_link_libraries(circledump safemain) +target_link_libraries(circledump stdex) +target_link_libraries(circledump flatbuffers) diff --git a/compiler/circledump/README.md b/compiler/circledump/README.md new file mode 100644 index 00000000000..686e918ac3f --- /dev/null +++ b/compiler/circledump/README.md @@ -0,0 +1,71 @@ +# circledump + +### What is this? + +circledump is a tool that dumps binary circle file into human readable text to console. + +circledump is implemented with C++ not python. We can do the same thing much easier +with python but this tool doesn't need to install TensorFlow python package. + +Schema for FlatBuffer used is from TensorFlow v1.13.1 release. + +### Design philosophy + +Make the code simple. + +### To do + +- Print weight values other than uint8_t +- Add more operators + +### How to use + +Command argument format: +``` +circledump circle_file +``` + +Example output of dump `readme.circle` file +``` +Dump: readme.circle + +Data Format: +CHANNEL_LAST (NHWC for 2d, NDHWC for 3d data) + +Operator Codes: [order] OpCodeName (OpCode Enum) +[0] CONV_2D (code: 3) + +Buffers: B(index) (length) values, if any +B(0) (0) +B(1) (8) 0x94 0x5b 0x95 0xbf 0x42 0xa4 0x52 0xbf ... +B(2) (4) 0xcd 0xcc 0x8c 0x3f + +Operands: T(tensor index) TYPE (shape) B(buffer index) OperandName +T(0) FLOAT32 (1, 3, 3, 2) B(0) ifm +T(1) FLOAT32 (1, 1, 1, 2) B(1) ker +T(2) FLOAT32 (1) B(2) bias +T(3) FLOAT32 (1, 3, 3, 1) B(0) ofm + +Operators: O(operator index) OpCodeName + Option(values) ... <-- depending on OpCode + I T(tensor index) OperandName <-- as input + O T(tensor index) OperandName <-- as output +O(0) CONV_2D + Padding(1) Stride.W(1) Stride.H(1) Activation(0) + I T(0) ifm + I T(1) ker + I T(2) bias + O T(3) ofm + +Inputs/Outputs: I(input)/O(output) T(tensor index) OperandName +I T(0) ifm +I T(1) ker +O T(3) ofm +``` + +### Dependency + +- mio-circle +- safemain +- stdex +- FlatBuffers diff --git a/compiler/circledump/driver/Driver.cpp b/compiler/circledump/driver/Driver.cpp new file mode 100644 index 00000000000..8ed88e1d84b --- /dev/null +++ b/compiler/circledump/driver/Driver.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include + +#include + +int entry(int argc, char **argv) +{ + if (argc != 2) + { + std::cerr << "ERROR: Failed to parse arguments" << std::endl; + std::cerr << std::endl; + std::cerr << "USAGE: " << argv[0] << " [circle]" << std::endl; + return 255; + } + + // Load Circle model from a circle file + std::unique_ptr model = circleread::load_circle(argv[1]); + if (model == nullptr) + { + std::cerr << "ERROR: Failed to load circle '" << argv[1] << "'" << std::endl; + return 255; + } + + const circle::Model *circlemodel = model->model(); + if (circlemodel == nullptr) + { + std::cerr << "ERROR: Failed to load circle '" << argv[1] << "'" << std::endl; + return 255; + } + + std::cout << "Dump: " << argv[1] << std::endl << std::endl; + + std::cout << circlemodel << std::endl; + + return 0; +} diff --git a/compiler/circledump/include/circledump/Dump.h b/compiler/circledump/include/circledump/Dump.h new file mode 100644 index 00000000000..a129458f416 --- /dev/null +++ b/compiler/circledump/include/circledump/Dump.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLEDUMP_DUMP_H__ +#define __CIRCLEDUMP_DUMP_H__ + +#include + +#include + +namespace circledump +{ + +void dump_model(std::ostream &os, const circle::Model *model); +} + +std::ostream &operator<<(std::ostream &os, const circle::Model *model); + +#endif // __CIRCLEDUMP_DUMP_H__ diff --git a/compiler/circledump/include/circleread/Model.h b/compiler/circledump/include/circleread/Model.h new file mode 100644 index 00000000000..234db8b4c74 --- /dev/null +++ b/compiler/circledump/include/circleread/Model.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLEREAD_MODEL_H__ +#define __CIRCLEREAD_MODEL_H__ + +#include + +#include + +namespace circleread +{ + +struct Model +{ + virtual ~Model() = default; + + virtual const ::circle::Model *model(void) const = 0; +}; + +/** + * @brief Load Circle model (as a raw Model) from a given path + * + * @note May return a nullptr + */ +std::unique_ptr load_circle(const std::string &path); + +} // namespace circleread + +#endif // __CIRCLEREAD_MODEL_H__ diff --git a/compiler/circledump/requires.cmake b/compiler/circledump/requires.cmake new file mode 100644 index 00000000000..b090dbd4d75 --- /dev/null +++ b/compiler/circledump/requires.cmake @@ -0,0 +1,3 @@ +require("mio-circle") +require("safemain") +require("stdex") diff --git a/compiler/circledump/src/Dump.cpp b/compiler/circledump/src/Dump.cpp new file mode 100644 index 00000000000..3b7a249be78 --- /dev/null +++ b/compiler/circledump/src/Dump.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include "Read.h" +#include "OpPrinter.h" + +#include + +#include // min +#include // setfill + +namespace circledump +{ + +void dump_buffer(std::ostream &os, const uint8_t *buffer, size_t size, size_t amount) +{ + std::ios_base::fmtflags saveflags(os.flags()); + + bool second = false; + bool ellipsis = amount > 0 && size > 4; + size_t count = ellipsis ? std::min(size, amount) : size; + + for (size_t i = 0; i < count; i++) + { + if (second) + { + os << " "; + } + + os << std::showbase << std::setfill('0') << std::setw(2); + os << std::hex << (uint32_t)buffer[i]; + + second = true; + } + if (ellipsis) + { + os << " ..."; + } + + os.flags(saveflags); +} + +void dump_vector(std::ostream &os, const std::vector &vs) +{ + uint32_t seq = 0; + for (auto &v : vs) + { + if (seq) + os << ", "; + os << v; + seq++; + } +} + +std::ostream &operator<<(std::ostream &os, const std::vector &vect) +{ + circledump::dump_vector(os, vect); + return os; +} + +template void dump_fbvect(std::ostream &os, const flatbuffers::Vector *fbvect) +{ + if (fbvect == nullptr) + return; + + bool ellipsis = (fbvect->size() > 4); + auto limit_size = ellipsis ? 4 : fbvect->size(); + + if (ellipsis) + { + os << "(" << fbvect->size() << ") "; + } + for (uint32_t q = 0; q < limit_size; q++) + { + if (q) + os << ", "; + os << fbvect->Get(q); + } + if (ellipsis) + { + os << " ... "; + } +} + +template +std::ostream &operator<<(std::ostream &os, const flatbuffers::Vector *fbvect) +{ + dump_fbvect(os, fbvect); + return os; +} + +void dump_model(std::ostream &os, const circle::Model *model) +{ + circleread::Reader reader(model); + + assert(reader.num_subgraph() == 1); + reader.select_subgraph(0); + + auto opcodes = reader.opcodes(); + auto buffers = reader.buffers(); + auto tensors = reader.tensors(); + auto operators = reader.operators(); + auto data_format = reader.data_format(); + + // dump data_format + os << "Data Format:" << std::endl; + if (data_format == circle::DataFormat::DataFormat_CHANNELS_LAST) + { + os << "CHANNEL_LAST (NHWC for 2d, NDHWC for 3d data)" << std::endl; + } + else if (data_format == circle::DataFormat::DataFormat_CHANNELS_FIRST) + { + os << "CHANNEL_FIRST (NCHW for 2d, NCDHW for 3d data)" << std::endl; + } + os << std::endl; + + // dump operator_codes + os << "Operator Codes: [order] OpCodeName (OpCode Enum)" << std::endl; + int32_t opcode_index = 0; + for (auto opcode : opcodes) + { + circle::BuiltinOperator op_code = opcode->builtin_code(); + auto op_name = circleread::opcode_name(opcode); + + os << "[" << opcode_index << "] " << op_name << " (code: " << op_code << ")" << std::endl; + + opcode_index++; + } + os << std::endl; + + // dump buffer + os << "Buffers: B(index) (length) values, if any" << std::endl; + for (uint32_t i = 0; i < buffers->Length(); ++i) + { + const uint8_t *buff_data; + size_t size = reader.buffer_info(i, &buff_data); + + os << "B(" << i << ") (" << size << ") "; + if (buff_data != nullptr) + { + dump_buffer(os, buff_data, size, 16); + } + os << std::endl; + } + os << std::endl; + + // dump operands(tensors) + os << "Operands: T(tensor index) TYPE (shape) B(buffer index) OperandName" << std::endl; + for (uint32_t i = 0; i < tensors->Length(); ++i) + { + // TODO refactor to some better structure + auto tensor = tensors->Get(i); + std::vector dims = {-1}; + + if (tensor->shape()) + dims = circleread::as_index_vector(tensor->shape()); + + os << "T(" << i << ") " << circleread::tensor_type(tensor) << " "; + os << "(" << dims << ") "; + os << "B(" << tensor->buffer() << ") "; + os << circleread::tensor_name(tensor) << std::endl; + + if (auto q_params = tensor->quantization()) + { + if ((q_params->min() && q_params->max()) || (q_params->scale() && q_params->zero_point())) + { + std::string strquantiz = " Quantization: "; + std::string strqindent(strquantiz.size(), ' '); + os << strquantiz; + + if (q_params->min()) + { + os << "min(" << q_params->min() << ") "; + if (q_params->min()->size() > 1) + os << std::endl << strqindent; + } + if (q_params->max()) + { + os << "max(" << q_params->max() << ") "; + if (q_params->max()->size() > 1) + os << std::endl << strqindent; + } + if (q_params->scale()) + { + os << "scale(" << q_params->scale() << ") "; + if (q_params->scale()->size() > 1) + os << std::endl << strqindent; + } + if (q_params->zero_point()) + os << "zeropt(" << q_params->zero_point() << ") "; + + os << std::endl; + } + } + } + os << std::endl; + + // dump operators + os << "Operators: O(operator index) OpCodeName " << std::endl; + os << " Option(values) ... <-- depending on OpCode" << std::endl; + os << " I T(tensor index) OperandName <-- as input" << std::endl; + os << " O T(tensor index) OperandName <-- as output" << std::endl; + for (uint32_t i = 0; i < operators->Length(); ++i) + { + const auto op = operators->Get(i); + circle::BuiltinOperator builtincode = reader.builtin_code(op); + + const std::vector &inputs = circleread::as_index_vector(op->inputs()); + const std::vector &outputs = circleread::as_index_vector(op->outputs()); + auto op_name = reader.opcode_name(op); + + os << "O(" << i << ") " << op_name << " "; + os << std::endl; + + if (auto op_prn = OpPrinterRegistry::get().lookup(builtincode)) + { + op_prn->options(op, os); + } + + for (auto input : inputs) + { + os << " I T(" << input << ") "; + if (input >= 0) + { + auto tensor = tensors->Get(input); + os << circleread::tensor_name(tensor); + } + os << std::endl; + } + for (auto output : outputs) + { + os << " O T(" << output << ") "; + if (output >= 0) + { + auto tensor = tensors->Get(output); + os << circleread::tensor_name(tensor); + } + os << std::endl; + } + } + os << std::endl; + + // dump network inputs/outputs + os << "Inputs/Outputs: I(input)/O(output) T(tensor index) OperandName" << std::endl; + + for (const auto input : reader.inputs()) + { + auto tensor = tensors->Get(input); + std::string name = circleread::tensor_name(tensor); + os << "I T(" << input << ") " << name << std::endl; + } + + for (const auto output : reader.outputs()) + { + auto tensor = tensors->Get(output); + std::string name = circleread::tensor_name(tensor); + os << "O T(" << output << ") " << name << std::endl; + } +} + +} // namespace circledump + +std::ostream &operator<<(std::ostream &os, const circle::Model *model) +{ + circledump::dump_model(os, model); + return os; +} diff --git a/compiler/circledump/src/Load.cpp b/compiler/circledump/src/Load.cpp new file mode 100644 index 00000000000..ec91ed18999 --- /dev/null +++ b/compiler/circledump/src/Load.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include +#include +#include + +namespace +{ + +class MemoryMappedModel final : public circleread::Model +{ +public: + /** + * @require fd and data SHOULD be valid + */ + explicit MemoryMappedModel(int fd, void *data, size_t size) : _fd{fd}, _data{data}, _size{size} + { + // DO NOTHING + } + +public: + ~MemoryMappedModel() + { + munmap(_data, _size); + close(_fd); + } + +public: + MemoryMappedModel(const MemoryMappedModel &) = delete; + MemoryMappedModel(MemoryMappedModel &&) = delete; + +public: + const ::circle::Model *model(void) const override { return ::circle::GetModel(_data); } + +private: + int _fd = -1; + void *_data = nullptr; + size_t _size = 0; +}; + +class FileDescriptor final +{ +public: + FileDescriptor(int value) : _value{value} + { + // DO NOTHING + } + +public: + // NOTE Copy is not allowed + FileDescriptor(const FileDescriptor &) = delete; + +public: + // NOTE Move is allowed + FileDescriptor(FileDescriptor &&fd) { _value = fd.release(); } + +public: + ~FileDescriptor() + { + if (_value != -1) + { + // Close on descturction + close(_value); + } + } + +public: + int value(void) const { return _value; } + +public: + int release(void) + { + auto res = _value; + _value = -1; + return res; + } + +private: + int _value = -1; +}; + +} // namespace + +namespace circleread +{ + +std::unique_ptr load_circle(const std::string &path) +{ + FileDescriptor fd = open(path.c_str(), O_RDONLY); + + if (fd.value() == -1) + { + // Return nullptr on open failure + return nullptr; + } + + struct stat st; + if (fstat(fd.value(), &st) == -1) + { + // Return nullptr on fstat failure + return nullptr; + } + + auto size = st.st_size; + auto data = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd.value(), 0); + + if (data == MAP_FAILED) + { + // Return nullptr on mmap failure + return nullptr; + } + + return std::unique_ptr{new MemoryMappedModel(fd.release(), data, size)}; +} + +} // namespace circleread diff --git a/compiler/circledump/src/OpPrinter.cpp b/compiler/circledump/src/OpPrinter.cpp new file mode 100644 index 00000000000..903678a9af3 --- /dev/null +++ b/compiler/circledump/src/OpPrinter.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "OpPrinter.h" +#include "Read.h" + +#include + +#include + +using stdex::make_unique; + +namespace circledump +{ + +// TODO move to some header +std::ostream &operator<<(std::ostream &os, const std::vector &vect); + +// TODO Re-arrange in alphabetical order + +class AddPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *params = op->builtin_options_as_AddOptions()) + { + os << " "; + os << "Activation(" << params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class Conv2DPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto conv_params = op->builtin_options_as_Conv2DOptions()) + { + os << " "; + os << "Padding(" << conv_params->padding() << ") "; + os << "Stride.W(" << conv_params->stride_w() << ") "; + os << "Stride.H(" << conv_params->stride_h() << ") "; + os << "Activation(" << conv_params->fused_activation_function() << ")"; + os << std::endl; + } + } +}; + +class DivPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *params = op->builtin_options_as_DivOptions()) + { + os << " "; + os << "Activation(" << params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class Pool2DPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto pool_params = op->builtin_options_as_Pool2DOptions()) + { + os << " "; + os << "Padding(" << pool_params->padding() << ") "; + os << "Stride.W(" << pool_params->stride_w() << ") "; + os << "Stride.H(" << pool_params->stride_h() << ") "; + os << "Filter.W(" << pool_params->filter_width() << ") "; + os << "Filter.H(" << pool_params->filter_height() << ") "; + os << "Activation(" << pool_params->fused_activation_function() << ")"; + os << std::endl; + } + } +}; + +class ConcatenationPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *concatenation_params = op->builtin_options_as_ConcatenationOptions()) + { + os << " "; + os << "Activation(" << concatenation_params->fused_activation_function() << ") "; + os << "Axis(" << concatenation_params->axis() << ")"; + os << std::endl; + } + } +}; + +class ReshapePrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *reshape_params = op->builtin_options_as_ReshapeOptions()) + { + auto new_shape = circleread::as_index_vector(reshape_params->new_shape()); + os << " "; + os << "NewShape(" << new_shape << ")"; + os << std::endl; + } + } +}; + +class DepthwiseConv2DPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto conv_params = op->builtin_options_as_DepthwiseConv2DOptions()) + { + os << " "; + os << "Padding(" << conv_params->padding() << ") "; + os << "Stride.W(" << conv_params->stride_w() << ") "; + os << "Stride.H(" << conv_params->stride_h() << ") "; + os << "DepthMultiplier(" << conv_params->depth_multiplier() << ") "; + os << "Dilation.W(" << conv_params->dilation_w_factor() << ") "; + os << "Dilation.H(" << conv_params->dilation_h_factor() << ")"; + os << "Activation(" << conv_params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class FullyConnectedPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *params = op->builtin_options_as_FullyConnectedOptions()) + { + os << " "; + os << "WeightFormat(" + << "..." + << ") "; // TODO implement this + os << "Activation(" << params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class MulPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *params = op->builtin_options_as_MulOptions()) + { + os << " "; + os << "Activation(" << params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class SoftmaxPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *softmax_params = op->builtin_options_as_SoftmaxOptions()) + { + os << " "; + os << "Beta(" << softmax_params->beta() << ")"; + os << std::endl; + } + } +}; + +class SubPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (auto *params = op->builtin_options_as_SubOptions()) + { + os << " "; + os << "Activation(" << params->fused_activation_function() << ") "; + os << std::endl; + } + } +}; + +class CustomOpPrinter : public OpPrinter +{ +public: + void options(const circle::Operator *op, std::ostream &os) const override + { + if (op->custom_options_format() != circle::CustomOptionsFormat::CustomOptionsFormat_FLEXBUFFERS) + { + os << " "; + os << "Unknown custom option format"; + return; + } + + const flatbuffers::Vector *option_buf = op->custom_options(); + + if (option_buf == nullptr || option_buf->size() == 0) + { + os << "No attrs found." << std::endl; + return; + } + + // printing attrs + // attrs of custom ops are encoded in flexbuffer format + auto attr_map = flexbuffers::GetRoot(option_buf->data(), option_buf->size()).AsMap(); + + os << " "; + auto keys = attr_map.Keys(); + for (int i = 0; i < keys.size(); i++) + { + auto key = keys[i].ToString(); + os << key << "(" << attr_map[key].ToString() << ") "; + } + + // Note: attr in "Shape" type does not seem to be converted by circle_convert. + // When the converted circle file (with custom op) is opened with hexa editory, + // attrs names can be found but attr name in "Shape" type is not found. + + os << std::endl; + } +}; + +OpPrinterRegistry::OpPrinterRegistry() +{ + _op_map[circle::BuiltinOperator_ADD] = make_unique(); + _op_map[circle::BuiltinOperator_AVERAGE_POOL_2D] = make_unique(); + _op_map[circle::BuiltinOperator_CONCATENATION] = make_unique(); + _op_map[circle::BuiltinOperator_CONV_2D] = make_unique(); + _op_map[circle::BuiltinOperator_DEPTHWISE_CONV_2D] = make_unique(); + _op_map[circle::BuiltinOperator_DIV] = make_unique(); + _op_map[circle::BuiltinOperator_FULLY_CONNECTED] = make_unique(); + _op_map[circle::BuiltinOperator_MAX_POOL_2D] = make_unique(); + _op_map[circle::BuiltinOperator_MUL] = make_unique(); + // There is no Option for Pad + // There is no Option for ReLU and ReLU6 + _op_map[circle::BuiltinOperator_RESHAPE] = make_unique(); + _op_map[circle::BuiltinOperator_SOFTMAX] = make_unique(); + _op_map[circle::BuiltinOperator_SUB] = make_unique(); + _op_map[circle::BuiltinOperator_CUSTOM] = make_unique(); +} + +} // namespace circledump diff --git a/compiler/circledump/src/OpPrinter.h b/compiler/circledump/src/OpPrinter.h new file mode 100644 index 00000000000..6b978a4c7ea --- /dev/null +++ b/compiler/circledump/src/OpPrinter.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLEDUMP_OPPRINTER_H__ +#define __CIRCLEDUMP_OPPRINTER_H__ + +#include + +#include +#include + +namespace circledump +{ + +class OpPrinter +{ +public: + virtual void options(const circle::Operator *, std::ostream &) const {}; +}; + +class OpPrinterRegistry +{ +public: + OpPrinterRegistry(); + +public: + const OpPrinter *lookup(circle::BuiltinOperator op) const + { + if (_op_map.find(op) == _op_map.end()) + return nullptr; + + return _op_map.at(op).get(); + } + +public: + static OpPrinterRegistry &get() + { + static OpPrinterRegistry me; + return me; + } + +private: + std::map> _op_map; +}; + +} // namespace circledump + +#endif // __CIRCLEDUMP_OPPRINTER_H__ diff --git a/compiler/circledump/src/Read.cpp b/compiler/circledump/src/Read.cpp new file mode 100644 index 00000000000..a0ba86778d0 --- /dev/null +++ b/compiler/circledump/src/Read.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Read.h" + +#include +#include + +namespace circleread +{ + +bool is_valid(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (circle::BuiltinOperator_MIN <= code && code <= circle::BuiltinOperator_MAX); +} + +bool is_custom(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (code == circle::BuiltinOperator_CUSTOM); +} + +std::string opcode_name(const circle::OperatorCode *opcode) +{ + assert(opcode); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid)"; + return oss.str(); + } + + if (is_custom(opcode)) + { + if (!opcode->custom_code()) + return "(invalid custom)"; + + std::string custom_op = "CUSTOM("; + custom_op += opcode->custom_code()->c_str(); + custom_op += ")"; + return custom_op; + } + + circle::BuiltinOperator code = opcode->builtin_code(); + return circle::EnumNameBuiltinOperator(code); +} + +const char *tensor_type(const circle::Tensor *tensor) +{ + return circle::EnumNameTensorType(tensor->type()); +} + +const char *tensor_name(const circle::Tensor *tensor) +{ + static const char *kEmptyTensorName = "(noname)"; + + auto name = tensor->name(); + if (name) + return name->c_str(); + + return kEmptyTensorName; +} + +Reader::Reader(const circle::Model *model) +{ + _subgraphs = model->subgraphs(); + _buffers = model->buffers(); + + auto opcodes = model->operator_codes(); + for (const ::circle::OperatorCode *opcode : *opcodes) + { + _op_codes.push_back(opcode); + } +} + +size_t Reader::buffer_info(uint32_t buf_idx, const uint8_t **buff_data) +{ + *buff_data = nullptr; + + if (buf_idx == 0) + return 0; + + if (auto *buffer = (*_buffers)[buf_idx]) + { + if (auto *array = buffer->data()) + { + if (size_t size = array->size()) + { + *buff_data = reinterpret_cast(array->data()); + return size; + } + } + } + + return 0; +} + +circle::BuiltinOperator Reader::builtin_code(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + return opcode->builtin_code(); +} + +std::string Reader::opcode_name(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid: " << index << ")"; + return oss.str(); + } + + return circleread::opcode_name(opcode); +} + +bool Reader::select_subgraph(uint32_t sgindex) +{ + _tensors = nullptr; + _operators = nullptr; + + _inputs.clear(); + _outputs.clear(); + + if (_subgraphs->Length() <= sgindex) + { + assert(false); + return false; + } + + const circle::SubGraph *subgraph = (*_subgraphs)[sgindex]; + + _tensors = subgraph->tensors(); + _operators = subgraph->operators(); + _data_format = subgraph->data_format(); + + _inputs = as_index_vector(subgraph->inputs()); + _outputs = as_index_vector(subgraph->outputs()); + + return true; +} + +} // namespace circleread diff --git a/compiler/circledump/src/Read.h b/compiler/circledump/src/Read.h new file mode 100644 index 00000000000..b80e77e6279 --- /dev/null +++ b/compiler/circledump/src/Read.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLEREAD_READ_H__ +#define __CIRCLEREAD_READ_H__ + +#include + +#include +#include +#include + +namespace circleread +{ + +template std::vector as_index_vector(const flatbuffers::Vector *flat_array) +{ + std::vector ret(flat_array->Length()); + for (uint32_t i = 0; i < flat_array->Length(); i++) + { + ret[i] = flat_array->Get(i); + } + return ret; +} + +bool is_valid(const circle::OperatorCode *opcode); +bool is_custom(const circle::OperatorCode *opcode); +std::string opcode_name(const circle::OperatorCode *opcode); +const char *tensor_type(const circle::Tensor *tensor); +const char *tensor_name(const circle::Tensor *tensor); + +/** + * @brief Loads Circle file and provides helpers to access attributes + */ +class Reader +{ +private: + using CircleSubGraphs_t = flatbuffers::Vector>; + using CircleBuffers_t = flatbuffers::Vector>; + using CircleTensors_t = flatbuffers::Vector>; + using CircleOperators_t = flatbuffers::Vector>; + +public: + Reader(const circle::Model *model); + + Reader() = delete; + +public: + const std::vector &opcodes() { return _op_codes; } + const CircleBuffers_t *buffers() { return _buffers; } + const CircleTensors_t *tensors() { return _tensors; } + const CircleOperators_t *operators() { return _operators; } + const std::vector &inputs() const { return _inputs; } + const std::vector &outputs() const { return _outputs; } + const circle::DataFormat &data_format() const { return _data_format; } + + uint32_t num_subgraph() const { return _subgraphs->Length(); } + + size_t buffer_info(uint32_t buf_idx, const uint8_t **buff_data); + circle::BuiltinOperator builtin_code(const circle::Operator *op) const; + std::string opcode_name(const circle::Operator *op) const; + +public: + bool select_subgraph(uint32_t subgraph); + +private: + const CircleSubGraphs_t *_subgraphs{nullptr}; + const CircleBuffers_t *_buffers{nullptr}; + const CircleTensors_t *_tensors{nullptr}; + const CircleOperators_t *_operators{nullptr}; + + std::vector _op_codes; + std::vector _inputs; + std::vector _outputs; + circle::DataFormat _data_format; +}; + +} // namespace circleread + +#endif // __CIRCLEREAD_READ_H__ diff --git a/compiler/cli/CMakeLists.txt b/compiler/cli/CMakeLists.txt new file mode 100644 index 00000000000..22948fff94d --- /dev/null +++ b/compiler/cli/CMakeLists.txt @@ -0,0 +1,15 @@ +list(APPEND SOURCES "src/App.cpp") +list(APPEND TESTS "src/App.test.cpp") + +add_library(cli ${SOURCES}) +target_include_directories(cli PUBLIC include) + +nnas_find_package(GTest QUIET) + +if(NOT GTest_FOUND) + return() +endif(NOT GTest_FOUND) + +GTest_AddTEst(cli_test ${TESTS}) +target_link_libraries(cli_test cli) +target_link_libraries(cli_test stdex) diff --git a/compiler/cli/README.md b/compiler/cli/README.md new file mode 100644 index 00000000000..6095c73bf6d --- /dev/null +++ b/compiler/cli/README.md @@ -0,0 +1,13 @@ +# cli + +`cli` is a CLI (Command Line Interface) application framework. + +# Background + +Many tools in `nncc` are command-line interface (CLI) applications. They generally need to handle command line parameters. +`cli` was written to reduce code duplication across such applications. + + +# How to use + +Please refer to `cli/src/App.test.cpp` for an example. diff --git a/compiler/cli/include/cli/App.h b/compiler/cli/include/cli/App.h new file mode 100644 index 00000000000..61554e933e7 --- /dev/null +++ b/compiler/cli/include/cli/App.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CLI_APP_H__ +#define __CLI_APP_H__ + +#include "Command.h" + +#include +#include +#include + +namespace cli +{ + +class App +{ +public: + explicit App(const std::string &name); + +public: + App &insert(const std::string &tag, std::unique_ptr &&command); + +public: + int run(int argc, const char *const *argv) const; + +private: + void usage(std::ostream &os) const; + +private: + const std::string _name; + std::map> _commands; +}; + +} // namespace cli + +#endif // __APP_H__ diff --git a/compiler/cli/include/cli/Command.h b/compiler/cli/include/cli/Command.h new file mode 100644 index 00000000000..2e264f9ef86 --- /dev/null +++ b/compiler/cli/include/cli/Command.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CLI_COMMAND_H__ +#define __CLI_COMMAND_H__ + +namespace cli +{ + +struct Command +{ + virtual ~Command() = default; + + virtual int run(int argc, const char *const *argv) const = 0; +}; + +} // namespace cli + +#endif // __CLI_COMMAND_H__ diff --git a/compiler/cli/include/cli/FunctionCommand.h b/compiler/cli/include/cli/FunctionCommand.h new file mode 100644 index 00000000000..58565309930 --- /dev/null +++ b/compiler/cli/include/cli/FunctionCommand.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CLI_FUNCTION_COMMAND_H__ +#define __CLI_FUNCTION_COMMAND_H__ + +#include + +namespace cli +{ + +class FunctionCommand final : public Command +{ +public: + // NOTE The use of pure funtion pointer here is intended to disallow variable capture + using Entry = int (*)(int argc, const char *const *argv); + +public: + FunctionCommand(const Entry &entry) : _entry{entry} + { + // DO NOTHING + } + +public: + int run(int argc, const char *const *argv) const override { return _entry(argc, argv); }; + +private: + Entry const _entry; +}; + +} // namespace cli + +#endif // __CLI_FUNCTION_COMMAND_H__ diff --git a/compiler/cli/src/App.cpp b/compiler/cli/src/App.cpp new file mode 100644 index 00000000000..5052f682a3c --- /dev/null +++ b/compiler/cli/src/App.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "cli/App.h" + +#include +#include + +namespace cli +{ + +App::App(const std::string &name) : _name{name} +{ + // DO NOTHING +} + +App &App::insert(const std::string &tag, std::unique_ptr &&command) +{ + assert(_commands.find(tag) == _commands.end()); + + _commands[tag] = std::move(command); + + return (*this); +} + +int App::run(int argc, const char *const *argv) const +{ + if (argc < 1) + { + std::cerr << "ERROR: COMMAND is not provided" << std::endl; + usage(std::cerr); + return 255; + } + + const std::string command{argv[0]}; + + auto it = _commands.find(command); + + if (it == _commands.end()) + { + std::cerr << "ERROR: '" << command << "' is not a valid command" << std::endl; + usage(std::cerr); + return 255; + } + + return it->second->run(argc - 1, argv + 1); +} + +void App::usage(std::ostream &os) const +{ + os << std::endl; + os << "USAGE: " << _name << " [COMMAND] ..." << std::endl; + os << std::endl; + os << "SUPPORTED COMMANDS:" << std::endl; + for (auto it = _commands.begin(); it != _commands.end(); ++it) + { + os << " " << it->first << std::endl; + } +} + +} // namespace cli diff --git a/compiler/cli/src/App.test.cpp b/compiler/cli/src/App.test.cpp new file mode 100644 index 00000000000..fe2d441794f --- /dev/null +++ b/compiler/cli/src/App.test.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "cli/App.h" + +#include + +#include + +class RecordCommand final : public cli::Command +{ +public: + RecordCommand(int ret, std::string &out) : _ret{ret}, _out(out) + { + // DO NOTHING + } + +public: + int run(int argc, const char *const *argv) const override + { + _out += std::to_string(argc); + + for (int n = 0; n < argc; ++n) + { + _out += ";"; + _out += argv[n]; + } + + return _ret; + } + +private: + int const _ret; + std::string &_out; +}; + +TEST(APP, run) +{ + cli::App app("test"); + + std::string args; + app.insert("record", stdex::make_unique(3, args)); + + const char *argv[] = {"record", "hello", "world"}; + + int ret = app.run(3, argv); + + ASSERT_EQ(ret, 3); + ASSERT_EQ(args, "2;hello;world"); +} diff --git a/compiler/coco/CMakeLists.txt b/compiler/coco/CMakeLists.txt new file mode 100644 index 00000000000..4be53e8a809 --- /dev/null +++ b/compiler/coco/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(core) +add_subdirectory(generic) diff --git a/compiler/coco/README.md b/compiler/coco/README.md new file mode 100644 index 00000000000..cfef8bafee1 --- /dev/null +++ b/compiler/coco/README.md @@ -0,0 +1,3 @@ +# coco + +_coco_ is an experimental coarse-grained intermediate representation (IR) for NN compilers. diff --git a/compiler/coco/core/CMakeLists.txt b/compiler/coco/core/CMakeLists.txt new file mode 100644 index 00000000000..8c6844733c1 --- /dev/null +++ b/compiler/coco/core/CMakeLists.txt @@ -0,0 +1,25 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(coco_core SHARED ${SOURCES}) +target_include_directories(coco_core PUBLIC include) +# NOTE Some coco_core PUBLIC headers include angkor headers +target_link_libraries(coco_core PUBLIC angkor) +target_link_libraries(coco_core PRIVATE pepper_assert) +target_link_libraries(coco_core PRIVATE stdex) +# Let's apply nncc common compile options +# NOTE This will enable strict compilation (warnings as error). +# Please refer to top-level CMakeLists.txt for details +target_link_libraries(coco_core PRIVATE nncc_common) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is required for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(coco_core_test ${TESTS}) +target_link_libraries(coco_core_test coco_core) +target_link_libraries(coco_core_test stdex) diff --git a/compiler/coco/core/include/coco/ADT/DLinkedList.h b/compiler/coco/core/include/coco/ADT/DLinkedList.h new file mode 100644 index 00000000000..e3c27504105 --- /dev/null +++ b/compiler/coco/core/include/coco/ADT/DLinkedList.h @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_ADT_DLINKED_LIST_H__ +#define __COCO_ADT_DLINKED_LIST_H__ + +#include +#include + +namespace coco +{ + +// **CAUTION** Child SHOULD inherit DLinkedList::Node +template struct DLinkedList +{ + /// @brief A hook for Child-Join event + static void joined(Parent *, Child *); + /// @brief A hook for Child-Leave event + static void leaving(Parent *, Child *); + + class Head + { + public: + Head(Parent *parent) : _parent{parent} + { + _head = nullptr; + _tail = nullptr; + } + + public: + Head(const Head &) = delete; + Head(Head &&) = delete; + + public: + Child *head(void) const { return _head; } + Child *tail(void) const { return _tail; } + + public: + bool empty(void) const + { + if (_head == nullptr) + { + assert(_head == _tail); + return true; + } + + assert(_head != nullptr); + assert(_tail != nullptr); + return false; + } + + public: + void enlist(Child *child) + { + assert((child->prev() == nullptr) || (child->prev()->parent() == _parent)); + assert((child->next() == nullptr) || (child->next()->parent() == _parent)); + + if (empty()) + { + _head = child; + _tail = child; + } + else + { + if (child->next() == _head) + { + // _child is a new head + assert(child->prev() == nullptr); + _head = child; + } + + if (child->prev() == _tail) + { + // _child is a new tail + assert(child->next() == nullptr); + _tail = child; + } + } + + // Update parent-child relation + child->parent(_parent); + + // Notify Child-Joining event + joined(_parent, child); + } + + public: + void delist(Child *child) + { + assert(child->parent() == _parent); + assert(!empty()); + + // Notify Child-Leaving event + leaving(_parent, child); + + if (child == _head) + { + _head = child->next(); + } + + if (child == _tail) + { + _tail = child->prev(); + } + + // Update parent-child relation + child->parent(nullptr); + } + + public: + void prepend(Child *child) + { + if (empty()) + { + enlist(child); + } + else + { + child->insertBefore(_head); + } + } + + public: + void append(Child *child) + { + if (empty()) + { + enlist(child); + } + else + { + child->insertAfter(_tail); + } + } + + private: + Parent *const _parent; + + private: + Child *_head; + Child *_tail; + }; + + // NOTE Client SHOULD implement this static method + static Head *head(Parent *); + + class Node + { + public: + friend class Head; + + public: + Node() + { + static_assert(std::is_base_of::value, + "Type `Child` must be subclass of `Node`."); + + _prev = nullptr; + _next = nullptr; + } + + public: + virtual ~Node() + { + // Each Child should unlink itself on destruction + // + // NOTE detach invokes "leaving" hook which may access the internal of each Child, + // so it is not safe to invoke detach here + assert(parent() == nullptr); + } + + public: + Parent *parent(void) const { return _parent; } + + private: + Child *curr(void) { return reinterpret_cast(this); } + const Child *curr(void) const { return reinterpret_cast(this); } + + public: + Child *prev(void) const { return _prev; } + Child *next(void) const { return _next; } + + public: + void insertBefore(Node *next) + { + assert(next != nullptr); + assert(next->parent() != nullptr); + assert(head(next->parent()) != nullptr); + + assert(_prev == nullptr); + assert(_next == nullptr); + + // Update the link of the current node + _prev = next->prev(); + _next = next->curr(); + + if (auto prev = next->prev()) + { + prev->_next = curr(); + } + next->_prev = curr(); + + // Update parent-child relation + assert(parent() == nullptr); + head(next->parent())->enlist(curr()); + assert(parent() == next->parent()); + } + + public: + void insertAfter(Node *prev) + { + assert(prev != nullptr); + assert(prev->parent() != nullptr); + assert(head(prev->parent()) != nullptr); + + assert(_prev == nullptr); + assert(_next == nullptr); + + // Update the link of the current node + _prev = prev->curr(); + _next = prev->next(); + + // Update the link of the sibling nodes + if (auto next = prev->next()) + { + next->_prev = curr(); + } + prev->_next = curr(); + + // Update parent-child relation + assert(parent() == nullptr); + head(prev->parent())->enlist(curr()); + assert(parent() == prev->parent()); + }; + + public: + void detach(void) + { + // Update parent-child relation + assert(parent() != nullptr); + assert(head(parent()) != nullptr); + head(parent())->delist(curr()); + assert(parent() == nullptr); + + // Update the link of sibling nodes + if (prev()) + { + prev()->_next = next(); + } + + if (next()) + { + next()->_prev = prev(); + } + + // Update the link of the current node + _prev = nullptr; + _next = nullptr; + } + + private: + // WARN Do NOT invoke this method outside Head::enlist + void parent(Parent *p) { _parent = p; } + + private: + // WARN Do NOT modify this field inside Node. + Parent *_parent = nullptr; + Child *_prev; + Child *_next; + }; +}; + +} // namespace coco + +#endif // __COCO_ADT_DLINKED_LIST_H__ diff --git a/compiler/coco/core/include/coco/ADT/PtrList.h b/compiler/coco/core/include/coco/ADT/PtrList.h new file mode 100644 index 00000000000..37fead72861 --- /dev/null +++ b/compiler/coco/core/include/coco/ADT/PtrList.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_ADT_PTR_LIST_H__ +#define __COCO_ADT_PTR_LIST_H__ + +#include + +#include + +namespace coco +{ + +template class PtrList +{ +public: + PtrList() = default; + +public: + PtrList(const PtrList &) = delete; + PtrList(PtrList &&) = delete; + +public: + virtual ~PtrList() = default; + +public: + uint32_t size(void) const { return _ptrs.size(); } + +public: + T *at(uint32_t n) const { return _ptrs.at(n); } + +public: + void insert(T *ptr) { _ptrs.emplace_back(ptr); } + +private: + std::vector _ptrs; +}; + +} // namespace coco + +#endif // __COCO_ADT_PTR_LIST_H__ diff --git a/compiler/coco/core/include/coco/ADT/PtrManager.h b/compiler/coco/core/include/coco/ADT/PtrManager.h new file mode 100644 index 00000000000..2b254c70a07 --- /dev/null +++ b/compiler/coco/core/include/coco/ADT/PtrManager.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_ADT_PTR_MANAGER_H__ +#define __COCO_ADT_PTR_MANAGER_H__ + +#include + +#include +#include + +namespace coco +{ + +template class PtrManager +{ +public: + /// @brief Return the number of managed objects + uint32_t size(void) const { return _ptrs.size(); } + +public: + T *at(uint32_t n) const { return _ptrs.at(n).get(); } + +protected: + template U *take(std::unique_ptr &&o) + { + auto res = o.get(); + _ptrs.emplace_back(std::move(o)); + return res; + } + +protected: + std::unique_ptr release(T *ptr) + { + for (auto it = _ptrs.begin(); it != _ptrs.end(); ++it) + { + if (it->get() == ptr) + { + std::unique_ptr res = std::move(*it); + _ptrs.erase(it); + return res; + } + } + + throw std::invalid_argument{"ptr"}; + } + +private: + std::vector> _ptrs; +}; + +} // namespace coco + +#endif // __COCO_ADT_PTR_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR.h b/compiler/coco/core/include/coco/IR.h new file mode 100644 index 00000000000..aa7ad57273a --- /dev/null +++ b/compiler/coco/core/include/coco/IR.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_H__ +#define __COCO_IR_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/Object.h" +#include "coco/IR/FeatureLayouts.h" +#include "coco/IR/KernelLayouts.h" + +#include "coco/IR/Op.h" +#include "coco/IR/Instr.h" +#include "coco/IR/Block.h" + +#include "coco/IR/Input.h" +#include "coco/IR/Output.h" + +#include "coco/IR/Module.h" + +#endif // __COCO_IR_H__ diff --git a/compiler/coco/core/include/coco/IR/Arg.h b/compiler/coco/core/include/coco/IR/Arg.h new file mode 100644 index 00000000000..fc451a231b3 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Arg.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_ARG_H__ +#define __COCO_IR_ARG_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/ElemID.h" + +#include +#include +#include + +#include +#include + +namespace coco +{ + +/** + * @brief Base class for NN model arguments (Input/Output) + */ +class Arg +{ +public: + explicit Arg(const nncc::core::ADT::tensor::Shape &shape); + +public: + virtual ~Arg() = default; + +public: + const nncc::core::ADT::tensor::Shape &shape(void) const { return _shape; } + +public: + const std::string &name(void) const { return _name; } + void name(const std::string &s) { _name = s; } + +protected: + virtual void onTake(Bag *) { return; } + virtual void onRelease(Bag *) { return; } + +public: + Bag *bag(void) const { return _bag; } + void bag(Bag *); + +public: + ElemID &at(const nncc::core::ADT::tensor::Index &); + const ElemID &at(const nncc::core::ADT::tensor::Index &) const; + +public: + void reorder(const nncc::core::ADT::tensor::Layout &l); + template void reorder(void) { reorder(LayoutImpl{}); } + +private: + nncc::core::ADT::tensor::Shape const _shape; + +private: + std::string _name; + +private: + Bag *_bag; + std::vector _map; +}; + +} // namespace coco + +#endif // __COCO_IR_ARG_H__ diff --git a/compiler/coco/core/include/coco/IR/Bag.h b/compiler/coco/core/include/coco/IR/Bag.h new file mode 100644 index 00000000000..1c86899d7ec --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Bag.h @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BAG_H__ +#define __COCO_IR_BAG_H__ + +#include "coco/IR/Entity.h" +#include "coco/IR/ObjectSet.h" +#include "coco/IR/DepSet.h" +#include "coco/IR/ReadSet.h" +#include "coco/IR/UpdateSet.h" +#include "coco/IR/Input.forward.h" +#include "coco/IR/Output.forward.h" +#include "coco/IR/Locatable.h" + +#include + +#include + +namespace coco +{ + +/** + * @brief A collection of (abstracted) elements of the same type + * + * When there are N elements in a bag, we refer to N as the size of this bag, and every + * element in a bag has a unique numeric ID whose range is [0, N). + * + * NOTE 'Bag' is not a container (such as std::vector). 'Bag' just assures that there are + * N elements. It does not state about its value. + * + * NOTE coco IR treats Bag as virtual memory allocation + */ +class Bag final : public Entity +{ +public: + struct Updater : public Locatable + { + virtual ~Updater() = default; + }; + + using UpdaterSet = std::set; + + struct Reader : public Locatable + { + virtual ~Reader() = default; + }; + + using ReaderSet = std::set; + +public: + friend class Dep; + friend class Read; + friend class Update; + friend class Input; + friend class Output; + +public: + explicit Bag(uint32_t size); + +public: + ~Bag(); + +public: + uint32_t size(void) const; + +public: + bool isInput(void) const; + bool isOutput(void) const; + +public: + /// @brief Return the set of Dep links that point to this bag + const DepSet *deps(void) const; + /// @brief Return the set of Read links that point to this bag + const ReadSet *reads(void) const; + /// @brief Return the set of Update links that point to this bag + const UpdateSet *updates(void) const; + +public: + /// @brief Return a valid pointer if this bag is marked as an input of the model + Input *input(void) const { return _input; } + /// @brief Return a valid pointer if this bag is marked as an output of the model + Output *output(void) const { return _output; } + +public: + /** + * @brief Replace all the occurence of a bag (except those in Input/Output) with another bag + * + * NOTE reaplceWith(b) works correctly only when b is neither Input nor Output + */ + void replaceWith(Bag *b); + + /** + * @brief Replace all the occurence of a bag in Object with another bag + * + * NOTE Unlike replaceWith(b), replaceAllDepsWith(b) has no restriction + */ + void replaceAllDepsWith(Bag *); + +private: + // "mutable_" prefix is deliberately introduced below to avoid resolution issue. + // + // Let's assume that two "deps" are overloaded in Bag as follows: + // class Bag + // { + // private: + // DepSet *deps(void); <-- 1 + // public: + // const DepSet *deps(void) const; <-- 2 + // }; + // + // C++ compiler tries to invoke method 1 unless a bag itself is const. Thus, any "deps" calls + // over non-const bags except those calls from friend classes will introduce build error. + + // WARN Only Dep is allowed to access this method + DepSet *mutable_deps(void) { return &_deps; } + // WARN Only Read is allowed to access this method + ReadSet *mutable_reads(void) { return &_reads; } + // WARN Only Update is allowed to access this method + UpdateSet *mutable_updates(void) { return &_updates; } + +private: + // WARN Only Input is allowed to access this method + void input(Input *i) { _input = i; } + // WARN Only Output is allowed to access this method + void output(Output *o) { _output = o; } + +private: + uint32_t _size; + + /** @brief Links to dependent Object(s) */ + DepSet _deps; + /** @brief Direct reads (not through Object) */ + ReadSet _reads; + /** @brief Direct updates (not through Object) */ + UpdateSet _updates; + + Input *_input = nullptr; + Output *_output = nullptr; +}; + +/// @brief Return a set of objects that depends on a given bag +ObjectSet dependent_objects(const Bag *); +/// @brief Return a set of readers that reads a given bag +Bag::ReaderSet readers(const Bag *); +/// @brief Return a set of updaters that updates a given bag +Bag::UpdaterSet updaters(const Bag *); + +} // namespace coco + +#endif // __COCO_IR_BAG_H__ diff --git a/compiler/coco/core/include/coco/IR/BagManager.h b/compiler/coco/core/include/coco/IR/BagManager.h new file mode 100644 index 00000000000..6ba644101d6 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/BagManager.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BAG_MANAGER_H__ +#define __COCO_IR_BAG_MANAGER_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class BagManager final : public PtrManager, public EntityBuilder +{ +public: + BagManager(Module *m = nullptr) { module(m); } + +public: + Bag *create(uint32_t size); + +public: + /** + * @brief Destroy (= deallocate) a Bag entity + * + * NOTE A Bag SHOULD BE detached from IR before destruction + */ + void destroy(Bag *b); +}; + +} // namespace coco + +#endif // __COCO_IR_BAG_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Block.forward.h b/compiler/coco/core/include/coco/IR/Block.forward.h new file mode 100644 index 00000000000..6d179314197 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Block.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BLOCK_FORWARD_H__ +#define __COCO_IR_BLOCK_FORWARD_H__ + +namespace coco +{ + +class Block; + +} // namespace coco + +#endif // __COCO_IR_BLOCK_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Block.h b/compiler/coco/core/include/coco/IR/Block.h new file mode 100644 index 00000000000..1bb3f47c762 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Block.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BLOCK_H__ +#define __COCO_IR_BLOCK_H__ + +#include "coco/IR/Module.forward.h" +#include "coco/IR/Block.forward.h" +#include "coco/IR/BlockIndex.h" +#include "coco/IR/Instr.h" +#include "coco/IR/Entity.h" + +#include "coco/ADT/DLinkedList.h" + +namespace coco +{ + +using BlockList = DLinkedList::Head; + +/** + * @brief A unit of (grouped) instructions + * + * Block allows backend to manage a set of instructions as one unit, which is useful for H/W that + * has a restriction on code size + */ +class Block final : public DLinkedList::Node, public Entity +{ +public: + friend void DLinkedList::joined(Module *, Block *); + friend void DLinkedList::leaving(Module *, Block *); + +public: + Block() : _instr{this} + { + // DO NOTHING + } + +public: + Block(const Block &) = delete; + Block(Block &&) = delete; + +public: + ~Block() + { + if (parent()) + { + detach(); + } + } + +public: + InstrList *instr(void) { return &_instr; } + const InstrList *instr(void) const { return &_instr; } + +public: + const BlockIndex &index(void) const { return _index; } + +private: + BlockIndex _index; + DLinkedList::Head _instr; +}; + +} // namespace coco + +#endif // __COCO_IR_BLOCK_H__ diff --git a/compiler/coco/core/include/coco/IR/BlockIndex.h b/compiler/coco/core/include/coco/IR/BlockIndex.h new file mode 100644 index 00000000000..7deabf4889f --- /dev/null +++ b/compiler/coco/core/include/coco/IR/BlockIndex.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BLOCK_INDEX_H__ +#define __COCO_IR_BLOCK_INDEX_H__ + +#include + +namespace coco +{ + +/** + * @brief A BlockIndex denotes the index of a block in a block list + */ +class BlockIndex final +{ +private: + static const uint32_t undefined = 0xffffffff; + +public: + BlockIndex() : _value{undefined} + { + // DO NOTHING + } + +public: + BlockIndex(uint32_t value) { set(value); } + +public: + bool valid(void) const { return _value != undefined; } + +public: + uint32_t value(void) const { return _value; } + +public: + void set(uint32_t value); + void reset(void) { _value = undefined; } + +private: + uint32_t _value; +}; + +static inline bool operator<(const BlockIndex &lhs, const BlockIndex &rhs) +{ + return lhs.value() < rhs.value(); +} + +} // namespace coco + +#endif // __COCO_IR_BLOCK_INDEX_H__ diff --git a/compiler/coco/core/include/coco/IR/BlockManager.h b/compiler/coco/core/include/coco/IR/BlockManager.h new file mode 100644 index 00000000000..f81f1f22b5a --- /dev/null +++ b/compiler/coco/core/include/coco/IR/BlockManager.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_BLOCK_MANAGER_H__ +#define __COCO_IR_BLOCK_MANAGER_H__ + +#include "coco/IR/Block.h" +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class BlockManager final : public PtrManager, public EntityBuilder +{ +public: + BlockManager(Module *m = nullptr) { module(m); } + +public: + Block *create(void); + +public: + /** + * @brief Free 'Block' object + * + * NOTE Block SHOULD be detached from any list before it is destructed + */ + void destroy(Block *); +}; + +} // namespace coco + +#endif // __COCO_IR_BLOCK_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Def.forward.h b/compiler/coco/core/include/coco/IR/Def.forward.h new file mode 100644 index 00000000000..93878c65817 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Def.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DEF_FORWARD_H__ +#define __COCO_IR_DEF_FORWARD_H__ + +namespace coco +{ + +class Def; + +} // namespace coco + +#endif // __COCO_IR_DEF_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Def.h b/compiler/coco/core/include/coco/IR/Def.h new file mode 100644 index 00000000000..d9b1567e5f6 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Def.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DEF_H__ +#define __COCO_IR_DEF_H__ + +#include "coco/IR/Object.h" + +namespace coco +{ + +class Def final +{ +public: + Def(Object::Producer *producer) : _producer{producer} + { + // DO NOTHING + } + +public: + ~Def() { value(nullptr); } + +public: + Object *value(void) const { return _value; } + +public: + void value(Object *value); + +public: + Object::Producer *producer(void) const { return _producer; } + +private: + Object *_value = nullptr; + Object::Producer *_producer = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_DEF_H__ diff --git a/compiler/coco/core/include/coco/IR/Dep.forward.h b/compiler/coco/core/include/coco/IR/Dep.forward.h new file mode 100644 index 00000000000..596ee3126b5 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Dep.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DEP_FORWARD_H__ +#define __COCO_IR_DEP_FORWARD_H__ + +namespace coco +{ + +class Dep; + +} // namespace coco + +#endif // __COCO_IR_DEP_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Dep.h b/compiler/coco/core/include/coco/IR/Dep.h new file mode 100644 index 00000000000..645c3befebd --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Dep.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DEP_H__ +#define __COCO_IR_DEP_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/Object.forward.h" + +namespace coco +{ + +/** + * @brief A Dep represents the edge between a Bag and its dependent Object + * + * WARNING A Dep will update dependent Object set (stored BagInfo) only when + * users properly initialize object and link values. + */ +class Dep final +{ +public: + Dep() = default; + +public: + Dep(const Dep &) = delete; + Dep(Dep &&) = delete; + +public: + ~Dep(); + +public: + Bag *bag(void) const { return _bag; } + void bag(Bag *); + +public: + Object *object(void) const { return _object; } + void object(Object *object) { _object = object; } + +private: + Bag *_bag = nullptr; + Object *_object = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_DEP_H__ diff --git a/compiler/coco/core/include/coco/IR/DepSet.h b/compiler/coco/core/include/coco/IR/DepSet.h new file mode 100644 index 00000000000..c4e2df979d4 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/DepSet.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DEP_SET_H__ +#define __COCO_IR_DEP_SET_H__ + +#include "coco/IR/Dep.forward.h" + +#include + +namespace coco +{ + +using DepSet = std::set; + +} // namespace coco + +#endif // __COCO_IR_DEP_SET_H__ diff --git a/compiler/coco/core/include/coco/IR/ElemID.h b/compiler/coco/core/include/coco/IR/ElemID.h new file mode 100644 index 00000000000..7065d13ebb3 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/ElemID.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_ELEM_ID_H__ +#define __COCO_IR_ELEM_ID_H__ + +#include + +namespace coco +{ + +class ElemID final +{ +public: + ElemID() : _value{0xffffffff} + { + // DO NOTHING + } + +public: + explicit ElemID(uint32_t value) : _value{value} + { + // DO NOTHING + } + +public: + uint32_t value(void) const { return _value; } + +private: + uint32_t _value; +}; + +bool operator==(const ElemID &lhs, const ElemID &rhs); +bool operator<(const ElemID &lhs, const ElemID &rhs); + +} // namespace coco + +#endif // __COCO_IR_ELEM_ID_H__ diff --git a/compiler/coco/core/include/coco/IR/Entity.h b/compiler/coco/core/include/coco/IR/Entity.h new file mode 100644 index 00000000000..4bf9df6513e --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Entity.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_ENTITY_H__ +#define __COCO_IR_ENTITY_H__ + +#include "coco/IR/Module.forward.h" + +namespace coco +{ + +/** + * @brief A base class for IR entities + * + * NOTE Each IR entity has a link to a module that it belongs to + */ +class Entity +{ +public: + friend class EntityBuilder; + +public: + virtual ~Entity() = default; + +public: + Module *module(void) const { return _module; } + +private: + // WARN Only EntityBuilder is allowed to access this method + void module(Module *m) { _module = m; } + +private: + Module *_module = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_ENTITY_H__ diff --git a/compiler/coco/core/include/coco/IR/EntityBuilder.h b/compiler/coco/core/include/coco/IR/EntityBuilder.h new file mode 100644 index 00000000000..161f3f29438 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/EntityBuilder.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_ENTITY_BUILDER_H__ +#define __COCO_IR_ENTITY_BUILDER_H__ + +#include "coco/IR/Entity.h" +#include "coco/IR/Module.forward.h" + +namespace coco +{ + +/** + * @brief A base class for IR entity builders + * + * NOTE Only EntityBuilder is allowed to update module field of each Entity + */ +class EntityBuilder +{ +public: + virtual ~EntityBuilder() = default; + +protected: + Module *module(void) const { return _module; } + + void module(Module *m) { _module = m; } + void modulize(Entity *entity) const { entity->module(_module); } + +private: + Module *_module = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_ENTITY_BUILDER_H__ diff --git a/compiler/coco/core/include/coco/IR/EntityManager.h b/compiler/coco/core/include/coco/IR/EntityManager.h new file mode 100644 index 00000000000..e76dec7aa46 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/EntityManager.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_ENTITY_MANAGER_H__ +#define __COCO_IR_ENTITY_MANAGER_H__ + +#include "coco/IR/BagManager.h" +#include "coco/IR/ObjectManager.h" + +#include "coco/IR/OpManager.h" +#include "coco/IR/InstrManager.h" +#include "coco/IR/BlockManager.h" + +#include "coco/IR/InputManager.h" +#include "coco/IR/OutputManager.h" + +namespace coco +{ + +/** + * @brief Meta (lifetime) manager interface + * + * EntityManager is referred as meta manager as it is a gateway to other + * managers. + */ +struct EntityManager +{ + virtual ~EntityManager() = default; + + virtual BagManager *bag(void) = 0; + virtual const BagManager *bag(void) const = 0; + + virtual ObjectManager *object(void) = 0; + virtual const ObjectManager *object(void) const = 0; + + virtual OpManager *op(void) = 0; + virtual const OpManager *op(void) const = 0; + + virtual InstrManager *instr(void) = 0; + virtual const InstrManager *instr(void) const = 0; + + virtual BlockManager *block(void) = 0; + virtual const BlockManager *block(void) const = 0; + + virtual InputManager *input(void) = 0; + virtual const InputManager *input(void) const = 0; + + virtual OutputManager *output(void) = 0; + virtual const OutputManager *output(void) const = 0; +}; + +} // namespace coco + +#endif // __COCO_IR_ENTITY_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/FeatureLayout.h b/compiler/coco/core/include/coco/IR/FeatureLayout.h new file mode 100644 index 00000000000..63f02c8bad2 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/FeatureLayout.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_FEATURE_LAYOUT_H__ +#define __COCO_IR_FEATURE_LAYOUT_H__ + +#include "coco/IR/ElemID.h" +#include "coco/IR/FeatureShape.h" + +namespace coco +{ + +/** + * @brief A FeatureLayout connects each feature index to a Bag element + * + * NOTE FeatureLayout is an immutable interface + */ +struct FeatureLayout +{ + struct ID + { + virtual ~ID() = default; + }; + + virtual ~FeatureLayout() = default; + + virtual const ID *id(void) const = 0; + + virtual const FeatureShape &shape(void) const = 0; + + uint32_t batch(void) const { return shape().batch(); } + uint32_t depth(void) const { return shape().depth(); } + uint32_t height(void) const { return shape().height(); } + uint32_t width(void) const { return shape().width(); } + + virtual ElemID at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const = 0; +}; + +} // namespace coco + +#endif // __COCO_IR_FEATURE_LAYOUT_H__ diff --git a/compiler/coco/core/include/coco/IR/FeatureLayouts.h b/compiler/coco/core/include/coco/IR/FeatureLayouts.h new file mode 100644 index 00000000000..23b9c491927 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/FeatureLayouts.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_FEATURE_LAYOUTS_H__ +#define __COCO_IR_FEATURE_LAYOUTS_H__ + +#include "coco/IR/FeatureLayout.h" + +#include + +#include +#include + +namespace coco +{ +namespace FeatureLayouts +{ + +/** + * @brief BCHW Feature Layout + */ +class BCHW final : public FeatureLayout +{ +private: + BCHW(const FeatureShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + static const FeatureLayout::ID *uid(void); + const FeatureLayout::ID *id(void) const override { return uid(); } + + const FeatureShape &shape(void) const override { return _shape; } + + ElemID at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const override; + +private: + FeatureShape _shape; + +public: + static std::unique_ptr create(const nncc::core::ADT::feature::Shape &shape); +}; + +/** + * @brief BHWC Feature Layout + */ +class BHWC : public coco::FeatureLayout +{ +private: + BHWC(const FeatureShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + static const FeatureLayout::ID *uid(void); + const FeatureLayout::ID *id(void) const override { return uid(); } + + const FeatureShape &shape(void) const override { return _shape; } + + coco::ElemID at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const override; + +private: + FeatureShape _shape; + +public: + static std::unique_ptr create(const nncc::core::ADT::feature::Shape &shape); + static std::unique_ptr create(const FeatureShape &shape); +}; + +/** + * @brief BC (Channel-wise Channel-major) Feature Layout + * + * 1. A layout is said to be channel-wise if the following holds: + * + * For each pair of valid feature index I and J, + * at(I) == at(J) if batch(I) == batch(J) and channel(I) == channel(J) + * + * 2. A layout is said to be channel-major if the followings hold: + * + * For each pair of valid feature index I and J, + * at(I) + 1 == at(J) if batch(I) == batch(J) and channel(I) + 1 == channel(J) + * + * For each pair of valid feature index I and J, + * at(I) + 1 == at(J) if batch(I) + 1 == batch(J), channel(I) == depth - 1, and channel(J) == 0 + */ +class BC : public coco::FeatureLayout +{ +private: + BC(const FeatureShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + static const FeatureLayout::ID *uid(void); + const FeatureLayout::ID *id(void) const override { return uid(); } + + const FeatureShape &shape(void) const override { return _shape; } + + coco::ElemID at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const override; + +private: + FeatureShape _shape; + +public: + static std::unique_ptr create(const nncc::core::ADT::feature::Shape &shape); +}; + +/** + * @brief Generic Feature Layout + */ +class Generic final : public FeatureLayout +{ +private: + Generic(const FeatureShape &shape); + +public: + static const FeatureLayout::ID *uid(void); + const FeatureLayout::ID *id(void) const override { return uid(); } + + const FeatureShape &shape(void) const override { return _shape; } + + ElemID &at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col); + ElemID at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const override; + + void reorder(const nncc::core::ADT::feature::Layout &l); + +private: + uint32_t offset(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const; + +private: + FeatureShape _shape; + +private: + std::vector _content; + +public: + static std::unique_ptr create(const nncc::core::ADT::feature::Shape &shape); +}; + +} // namespace FeatureLayouts +} // namespace coco + +#endif // __COCO_IR_FEATURE_LAYOUTS_H__ diff --git a/compiler/coco/core/include/coco/IR/FeatureObject.forward.h b/compiler/coco/core/include/coco/IR/FeatureObject.forward.h new file mode 100644 index 00000000000..41477e85395 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/FeatureObject.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_FEATURE_OBJECT_FORWARD_H__ +#define __COCO_IR_FEATURE_OBJECT_FORWARD_H__ + +namespace coco +{ + +class FeatureObject; + +} // namespace coco + +#endif // __COCO_IR_FEATURE_OBJECT_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/FeatureObject.h b/compiler/coco/core/include/coco/IR/FeatureObject.h new file mode 100644 index 00000000000..f4244d9bec9 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/FeatureObject.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_FEATURE_OBJECT_H__ +#define __COCO_IR_FEATURE_OBJECT_H__ + +#include "coco/IR/Object.h" +#include "coco/IR/FeatureShape.h" +#include "coco/IR/FeatureLayout.h" +#include "coco/IR/ElemID.h" + +#include + +#include + +namespace coco +{ + +/** + * @brief FeatureMap values (used in CNN) + */ +class FeatureObject final : public Object +{ +public: + FeatureObject() = default; + +public: + ~FeatureObject(); + +public: + Object::Kind kind(void) const override { return Object::Kind::Feature; } + +public: + FeatureObject *asFeature(void) override { return this; } + const FeatureObject *asFeature(void) const override { return this; } + +public: + const FeatureShape &shape(void) const; + +public: + const FeatureLayout *layout(void) const { return _layout.get(); } + void layout(std::unique_ptr &&l) { _layout = std::move(l); } + +private: + std::unique_ptr _layout; +}; + +} // namespace coco + +#endif // __COCO_IR_FEATURE_OBJECT_H__ diff --git a/compiler/coco/core/include/coco/IR/FeatureShape.h b/compiler/coco/core/include/coco/IR/FeatureShape.h new file mode 100644 index 00000000000..015fc709d40 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/FeatureShape.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_FEATURE_SHAPE_H__ +#define __COCO_IR_FEATURE_SHAPE_H__ + +#include + +namespace coco +{ + +/** + * @brief The shape of a feature map + * + * TODO Implement coco's own FeatureShape without "nncc::core::ADT::feature::Shape" + */ +class FeatureShape : public nncc::core::ADT::feature::Shape +{ +public: + FeatureShape(uint32_t depth, uint32_t height, uint32_t width) + : Shape{depth, height, width}, _batch{1} + { + // DO NOTHING + } + + FeatureShape(uint32_t batch, uint32_t depth, uint32_t height, uint32_t width) + : Shape{depth, height, width}, _batch{batch} + { + // DO NOTHING + } + + FeatureShape(const nncc::core::ADT::feature::Shape &shape) : Shape{shape}, _batch{1} + { + // DO NOTHING + } + +public: + uint32_t batch(void) const { return _batch; } + +private: + uint32_t _batch; +}; + +static inline bool operator==(const FeatureShape &lhs, const FeatureShape &rhs) +{ + return (lhs.batch() == rhs.batch()) && (lhs.depth() == rhs.depth()) && + (lhs.height() == rhs.height()) && (lhs.width() == rhs.width()); +} + +static inline bool operator!=(const FeatureShape &lhs, const FeatureShape &rhs) +{ + return !(lhs == rhs); +} + +} // namespace coco + +#endif // __COCO_IR_FEATURE_SHAPE_H__ diff --git a/compiler/coco/core/include/coco/IR/Input.forward.h b/compiler/coco/core/include/coco/IR/Input.forward.h new file mode 100644 index 00000000000..4b529cddf4a --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Input.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INPUT_FORWARD_H__ +#define __COCO_IR_INPUT_FORWARD_H__ + +namespace coco +{ + +class Input; + +} // namespace coco + +#endif // __COCO_IR_INPUT_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Input.h b/compiler/coco/core/include/coco/IR/Input.h new file mode 100644 index 00000000000..ef8e88c9d34 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Input.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INPUT_H__ +#define __COCO_IR_INPUT_H__ + +#include "coco/IR/Arg.h" +#include "coco/IR/Entity.h" + +#include +#include + +#include +#include + +namespace coco +{ + +class Input final : public Arg, public Entity +{ +public: + Input(const nncc::core::ADT::tensor::Shape &shape); + +private: + void onTake(Bag *) override; + void onRelease(Bag *) override; +}; + +} // namespace coco + +#endif // __COCO_IR_INPUT_H__ diff --git a/compiler/coco/core/include/coco/IR/InputList.h b/compiler/coco/core/include/coco/IR/InputList.h new file mode 100644 index 00000000000..cd6337a5ae7 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/InputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INPUT_LIST_H__ +#define __COCO_IR_INPUT_LIST_H__ + +#include "coco/IR/Input.h" + +#include "coco/ADT/PtrList.h" + +namespace coco +{ + +using InputList = PtrList; + +} // namespace coco + +#endif // __COCO_IR_INPUT_LIST_H__ diff --git a/compiler/coco/core/include/coco/IR/InputManager.h b/compiler/coco/core/include/coco/IR/InputManager.h new file mode 100644 index 00000000000..bfbd712b561 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/InputManager.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INPUT_MANAGER_H__ +#define __COCO_IR_INPUT_MANAGER_H__ + +#include "coco/IR/Input.h" +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class InputManager final : public PtrManager, public EntityBuilder +{ +public: + InputManager(Module *m = nullptr) { module(m); } + +public: + Input *create(const nncc::core::ADT::tensor::Shape &); +}; + +} // namespace coco + +#endif // __COCO_IR_INPUT_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Instr.forward.h b/compiler/coco/core/include/coco/IR/Instr.forward.h new file mode 100644 index 00000000000..4043970db65 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Instr.forward.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INSTR_FORWARD_H__ +#define __COCO_IR_INSTR_FORWARD_H__ + +namespace coco +{ + +// WARNING This header should be aligned with Instr.h +class Instr; + +} // namespace coco + +#endif // __COCO_IR_INSTR_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Instr.h b/compiler/coco/core/include/coco/IR/Instr.h new file mode 100644 index 00000000000..fc1cc332dd6 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Instr.h @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INSTR_H__ +#define __COCO_IR_INSTR_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/Block.forward.h" +#include "coco/IR/Instr.forward.h" +#include "coco/IR/InstrIndex.h" +#include "coco/IR/Entity.h" + +#include "coco/ADT/DLinkedList.h" + +#include +#include + +namespace coco +{ + +#define INSTR(Name) class Name; +#include "coco/IR/Instr.lst" +#undef INSTR + +using InstrList = coco::DLinkedList::Head; + +/** + * @brief Base interface on explicit computation steps in coco IR + * + * NOTE Input/output is explicit in Instr, but implicit in Op + * NOTE Instr is may (not always) be a combination of multiple NN operations + * + * One may find a set of supported instructions from "Instrs.h" + * + * >> How to add a new base instruction in coco IR << + * + * To introduce a new instruction (whose name is INS), + * 1. Append "INSTR(INS)" to "Instr.lst" + * 2. Declare class INS which inherits Instr class in "Instrs.h" + * NOTE This class SHOULD be default constructible + * + */ +class Instr : public coco::DLinkedList::Node, public Entity +{ +public: + friend void DLinkedList::joined(Block *, Instr *); + friend void DLinkedList::leaving(Block *, Instr *); + +public: + Instr() = default; + +public: + Instr(const Instr &) = delete; + Instr(Instr &&) = delete; + +public: + virtual ~Instr() + { + if (parent()) + { + // NOTE It is safe to invoke detach here (although "Instr" is not a final class) + // as "leaving" hook accesses only the internal of "Instr" class + detach(); + } + } + +public: +#define INSTR(Name) \ + virtual Name *as##Name(void) { return nullptr; } \ + virtual const Name *as##Name(void) const { return nullptr; } +#include "coco/IR/Instr.lst" +#undef INSTR + +public: + /** + * @brief Instr visitor interface + * + * WARN Use this interface only for coco-internal classes + * (to minimize changes upon Instr extension) + */ + template struct IVisitor + { + virtual ~IVisitor() = default; + +#define INSTR(Name) virtual T visit(const Name *) = 0; +#include "coco/IR/Instr.lst" +#undef INSTR + }; + + template struct Visitor : public IVisitor + { + virtual ~Visitor() = default; + +#define INSTR(Name) \ + T visit(const Name *) override { throw std::runtime_error{"NYI"}; } +#include "coco/IR/Instr.lst" +#undef INSTR + }; + +public: + template T accept(IVisitor *v) const + { +#define INSTR(Name) \ + if (auto ins = as##Name()) \ + { \ + return v->visit(ins); \ + } +#include "coco/IR/Instr.lst" +#undef INSTR + throw std::runtime_error{"unreachable"}; + } + + template T accept(IVisitor &v) const { return accept(&v); } + template T accept(IVisitor &&v) const { return accept(&v); } + +public: + const InstrIndex &index(void) const { return _index; } + +private: + InstrIndex _index; +}; + +/** + * @brief Return true if a given instruction is of T type + * + * @note "ins" cannot be a null pointer + */ +template bool isa(const Instr *ins) +{ + assert(ins != nullptr); + return dynamic_cast(ins) != nullptr; +} + +/** + * @brief Cast as a derived instruction + * + * @note "safe_cast(ins)" returns a null pointer if "ins" is not of T type + * @note "safe_cast(ins)" returns a null pointer if "ins" is a null pointer + */ +template T *safe_cast(Instr *ins) +{ + // NOTE dynamic_cast(nullptr) returns nullptr + return dynamic_cast(ins); +} + +} // namespace coco + +#endif // __COCO_IR_INSTR_H__ diff --git a/compiler/coco/core/include/coco/IR/Instr.lst b/compiler/coco/core/include/coco/IR/Instr.lst new file mode 100644 index 00000000000..f13a65bf2d8 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Instr.lst @@ -0,0 +1,9 @@ +#ifndef INSTR +#error Define INSTR first +#endif // INSTR + +// INSTR(Name) + +INSTR(Eval) +INSTR(Shuffle) +INSTR(Copy) diff --git a/compiler/coco/core/include/coco/IR/InstrIndex.h b/compiler/coco/core/include/coco/IR/InstrIndex.h new file mode 100644 index 00000000000..a61d97cadd6 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/InstrIndex.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INSTR_INDEX_H__ +#define __COCO_IR_INSTR_INDEX_H__ + +#include + +namespace coco +{ + +/** + * @brief A InstrIndex denotes the index of an instruction in an instruction list + */ +class InstrIndex final +{ +private: + static const uint32_t undefined = 0xffffffff; + +public: + InstrIndex() : _value{undefined} + { + // DO NOTHING + } + +public: + InstrIndex(uint32_t value) { set(value); } + +public: + bool valid(void) const { return _value != undefined; } + +public: + uint32_t value(void) const { return _value; } + +public: + void set(uint32_t value); + void reset(void) { _value = undefined; } + +private: + uint32_t _value; +}; + +static inline bool operator<(const InstrIndex &lhs, const InstrIndex &rhs) +{ + return lhs.value() < rhs.value(); +} + +} // namespace coco + +#endif // __COCO_IR_INSTR_INDEX_H__ diff --git a/compiler/coco/core/include/coco/IR/InstrManager.h b/compiler/coco/core/include/coco/IR/InstrManager.h new file mode 100644 index 00000000000..537467ae24a --- /dev/null +++ b/compiler/coco/core/include/coco/IR/InstrManager.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INSTR_MANAGER_H__ +#define __COCO_IR_INSTR_MANAGER_H__ + +#include "coco/IR/Instr.h" +#include "coco/IR/Instrs.h" + +#include "coco/IR/Op.forward.h" + +#include "coco/IR/Bag.h" + +#include "coco/IR/Object.forward.h" + +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class InstrManager final : public PtrManager, public EntityBuilder +{ +public: + InstrManager(Module *m = nullptr) { module(m); } + +public: + template Ins *create(void); + +public: + /** + * @brief Destroy (= deallocate) an Instr instance + * + * NOTE destroy(ins) WILL NOT update ins->parent(). An Instruction SHOULD BE detacted from a + * module before destroy call + */ + void destroy(Instr *); +}; + +// +// Every instruction class SHOULD be default constructible +// +template Ins *InstrManager::create(void) +{ + auto ins = new Ins; + modulize(ins); + return take(std::unique_ptr(ins)); +} + +} // namespace coco + +#endif // __COCO_IR_INSTR_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Instrs.h b/compiler/coco/core/include/coco/IR/Instrs.h new file mode 100644 index 00000000000..9245443e974 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Instrs.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_INSTRS_H__ +#define __COCO_IR_INSTRS_H__ + +#include "coco/IR/Instr.h" + +#include "coco/IR/ElemID.h" + +#include "coco/IR/Bag.h" +#include "coco/IR/Object.h" + +#include "coco/IR/Def.h" +#include "coco/IR/Use.h" +#include "coco/IR/Read.h" +#include "coco/IR/Update.h" + +#include "coco/IR/Step.h" + +#include + +namespace coco +{ + +/** + * @brief Evaluate an Object from a given Op + */ +class Eval final : public Instr, public Object::Producer +{ +public: + explicit Eval(); + +public: + Eval *asEval(void) override { return this; } + const Eval *asEval(void) const override { return this; } + +public: + Instr *loc(void) override { return this; } + +public: + Object *out(void) const { return _out.value(); } + void out(Object *obj) { _out.value(obj); } + +public: + Op *op(void) const { return _step.op(); } + void op(Op *op) { _step.op(op); } + +private: + Def _out; + Step _step; +}; + +/** + * @brief Index-wise element transfer between two objects + * + * Given two objects "src" and "dst" of the same kind/shape, "copy(src, dst)" + * denotes index-wise element transfer. + * + * For example, the following pseudo-code describes "copy(src, dat)" + * when both src and dst are a feature map of the shape B x C x H x W: + * + * for each valid index b, ch, row, col: + * load the "src->at(b, ch, row, col)"-th element from bag(src) + * store it as the "dst->at(b, ch, row, col)"-th element of bag(dst) + * + * In principle, "copy" is unnecessary as it is always possible to rewrite "copy" + * as a "shuffle" below. However, "shuffle"-based optimization is too heavy as it + * requires much of iterations. + */ +class Copy final : public Instr, public Object::Producer, public Object::Consumer +{ +public: + Copy() : _from{this}, _into{this} + { + // DO NOTHING + } + +public: + Copy *asCopy(void) override { return this; } + const Copy *asCopy(void) const override { return this; } + +public: + Instr *loc(void) override { return this; } + +public: + Object *from(void) const { return _from.value(); } + void from(Object *o) { _from.value(o); } + +public: + Object *into(void) const { return _into.value(); } + void into(Object *o) { _into.value(o); } + +private: + Use _from; + Def _into; +}; + +/** + * @brief Generic element transfer + */ +class Shuffle final : public Instr, public Bag::Reader, public Bag::Updater +{ +public: + Shuffle() : _from{this}, _into{this} + { + // DO NOTHING + } + +public: + Shuffle *asShuffle(void) override { return this; } + const Shuffle *asShuffle(void) const override { return this; } + +public: + Instr *loc(void) override { return this; } + +public: + Bag *from(void) const { return _from.bag(); } + void from(Bag *bag); + +public: + Bag *into(void) const { return _into.bag(); } + void into(Bag *); + +public: + /** + * @brief Return the number of Element-wise transfers + * + * NOTE size() SHOULD BE identical to range().size() + */ + uint32_t size(void) const; + + /// @brief Return a set of elements in the destination bag that Shuffle will update + std::set range(void) const; + +public: + /// @brief Return true if a given elem is updated after execution + bool defined(const ElemID &dst) const { return _content.find(dst) != _content.end(); } + +public: + /** + * Let M be the return of at(N). This means that N-th element in the destination + * bag will be filled with the value of M-th element in the source bag. + * + * NOTE at(n) may be undefined on partial shuffle + */ + const ElemID &at(const ElemID &dst) const { return _content.at(dst); } + +public: + void insert(const ElemID &from, const ElemID &into); + +private: + Read _from; + Update _into; + +private: + std::map _content; +}; + +} // namespace coco + +#endif // __COCO_IR_INSTRS_H__ diff --git a/compiler/coco/core/include/coco/IR/KernelLayout.h b/compiler/coco/core/include/coco/IR/KernelLayout.h new file mode 100644 index 00000000000..49aaf1a81d7 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/KernelLayout.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_KERNEL_LAYOUT_H__ +#define __COCO_IR_KERNEL_LAYOUT_H__ + +#include "coco/IR/ElemID.h" + +#include + +namespace coco +{ + +/** + * @brief A KernelLayout connectes each kernel index to an element (in a bag) + * + * NOTE KernelLayout is an immutable interface + */ +struct KernelLayout +{ + struct ID + { + virtual ~ID() = default; + }; + + virtual ~KernelLayout() = default; + + /** + * @brief Return the identifier of each layout + * + * REQUIRED + * + * Given l1 and l2 of KernelLayout * type, + * typeid(*l1) == typeif(*l2) SHOULD hold if l1->id() == l2->id() holds. + */ + virtual const ID *id(void) const = 0; + + virtual const nncc::core::ADT::kernel::Shape &shape(void) const = 0; + + virtual ElemID at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const = 0; +}; + +} // namespace coco + +#endif // __COCO_IR_KERNEL_LAYOUT_H__ diff --git a/compiler/coco/core/include/coco/IR/KernelLayouts.h b/compiler/coco/core/include/coco/IR/KernelLayouts.h new file mode 100644 index 00000000000..0a04cf1636f --- /dev/null +++ b/compiler/coco/core/include/coco/IR/KernelLayouts.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_KERNEL_LAYOUTS_H__ +#define __COCO_IR_KERNEL_LAYOUTS_H__ + +#include "coco/IR/KernelLayout.h" + +#include + +#include +#include + +namespace coco +{ +namespace KernelLayouts +{ + +/** + * @brief NCHW Kernel Layout + */ +class NCHW final : public KernelLayout +{ +private: + NCHW(const nncc::core::ADT::kernel::Shape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + static const KernelLayout::ID *uid(void); + const KernelLayout::ID *id(void) const override { return uid(); } + + const nncc::core::ADT::kernel::Shape &shape(void) const override { return _shape; } + + ElemID at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const override; + +private: + nncc::core::ADT::kernel::Shape _shape; + +public: + static std::unique_ptr create(const nncc::core::ADT::kernel::Shape &shape); +}; + +/** + * @brief NHWC Kernel Layout + */ +class NHWC final : public KernelLayout +{ +private: + NHWC(const nncc::core::ADT::kernel::Shape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + static const KernelLayout::ID *uid(void); + const KernelLayout::ID *id(void) const override { return uid(); } + + const nncc::core::ADT::kernel::Shape &shape(void) const override { return _shape; } + + ElemID at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const override; + +private: + nncc::core::ADT::kernel::Shape _shape; + +public: + static std::unique_ptr create(const nncc::core::ADT::kernel::Shape &shape); +}; + +/** + * @brief Generic Kernel Layout + */ +class Generic final : public KernelLayout +{ +private: + Generic(const nncc::core::ADT::kernel::Shape &shape); + +public: + static const KernelLayout::ID *uid(void); + const KernelLayout::ID *id(void) const override { return uid(); } + + const nncc::core::ADT::kernel::Shape &shape(void) const override { return _shape; } + + ElemID &at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col); + ElemID at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const override; + + void reorder(const nncc::core::ADT::kernel::Layout &l); + template void reorder(void) { reorder(LayoutImpl{}); } + +private: + nncc::core::ADT::kernel::Shape _shape; + +private: + std::vector _content; + +public: + static std::unique_ptr create(const nncc::core::ADT::kernel::Shape &shape); +}; + +} // namespace KernelLayouts +} // namespace coco + +#endif // __COCO_IR_KERNEL_LAYOUTS_H__ diff --git a/compiler/coco/core/include/coco/IR/KernelObject.forward.h b/compiler/coco/core/include/coco/IR/KernelObject.forward.h new file mode 100644 index 00000000000..10fbac4ca2c --- /dev/null +++ b/compiler/coco/core/include/coco/IR/KernelObject.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_KERNEL_OBJECT_FORWARD_H__ +#define __COCO_IR_KERNEL_OBJECT_FORWARD_H__ + +namespace coco +{ + +class KernelObject; + +} // namespace coco + +#endif // __COCO_IR_KERNEL_OBJECT_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/KernelObject.h b/compiler/coco/core/include/coco/IR/KernelObject.h new file mode 100644 index 00000000000..2ec0cee0bad --- /dev/null +++ b/compiler/coco/core/include/coco/IR/KernelObject.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_KERNEL_OBJECT_H__ +#define __COCO_IR_KERNEL_OBJECT_H__ + +#include "coco/IR/Object.h" +#include "coco/IR/KernelLayout.h" +#include "coco/IR/ElemID.h" + +#include +#include + +namespace coco +{ + +/** + * @brief Convolution Kernel (in CNN) values + */ +class KernelObject final : public Object +{ +public: + KernelObject() = default; + explicit KernelObject(const nncc::core::ADT::kernel::Shape &shape); + +public: + virtual ~KernelObject(); + +public: + Object::Kind kind(void) const override { return Object::Kind::Kernel; } + +public: + KernelObject *asKernel(void) override { return this; } + const KernelObject *asKernel(void) const override { return this; } + +public: + const nncc::core::ADT::kernel::Shape &shape(void) const; + +public: + ElemID at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const; + +public: + const KernelLayout *layout(void) const { return _layout.get(); } + void layout(std::unique_ptr &&l) { _layout = std::move(l); } + +private: + std::unique_ptr _layout; +}; + +} // namespace coco + +#endif // __COCO_IR_KERNEL_OBJECT_H__ diff --git a/compiler/coco/core/include/coco/IR/Locatable.h b/compiler/coco/core/include/coco/IR/Locatable.h new file mode 100644 index 00000000000..b80a4a3602c --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Locatable.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_LOCATABLE_H__ +#define __COCO_IR_LOCATABLE_H__ + +#include "coco/IR/Instr.forward.h" + +namespace coco +{ + +/** + * @brief Return the associated instruction if exists. + */ +struct Locatable +{ + virtual ~Locatable() = default; + + virtual Instr *loc(void) = 0; +}; + +} // namespace coco + +#endif // __COCO_IR_LOCATABLE_H__ diff --git a/compiler/coco/core/include/coco/IR/Module.forward.h b/compiler/coco/core/include/coco/IR/Module.forward.h new file mode 100644 index 00000000000..94f8cc7d2a6 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Module.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_MODULE_FORWARD_H__ +#define __COCO_IR_MODULE_FORWARD_H__ + +namespace coco +{ + +class Module; + +} // namespace coco + +#endif // __COCO_IR_MODULE_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Module.h b/compiler/coco/core/include/coco/IR/Module.h new file mode 100644 index 00000000000..9eb0b248b1d --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Module.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_MODULE_H__ +#define __COCO_IR_MODULE_H__ + +#include "coco/IR/EntityManager.h" +#include "coco/IR/Block.h" +#include "coco/IR/InputList.h" +#include "coco/IR/OutputList.h" + +#include + +namespace coco +{ + +/** + * @brief Top-level element of coco IR which represents a neural network + */ +class Module +{ +public: + Module() = default; + +public: + Module(const Module &) = delete; + Module(Module &&) = delete; + +public: + virtual ~Module() = default; + +public: + virtual EntityManager *entity(void) = 0; + virtual const EntityManager *entity(void) const = 0; + +public: + virtual BlockList *block(void) = 0; + virtual const BlockList *block(void) const = 0; + +public: + virtual InputList *input(void) = 0; + virtual const InputList *input(void) const = 0; + +public: + virtual OutputList *output(void) = 0; + virtual const OutputList *output(void) const = 0; + +public: + static std::unique_ptr create(void); +}; + +} // namespace coco + +#endif // __COCO_IR_MODULE_H__ diff --git a/compiler/coco/core/include/coco/IR/Object.forward.h b/compiler/coco/core/include/coco/IR/Object.forward.h new file mode 100644 index 00000000000..d9a6c0422f7 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Object.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OBJECT_FORWARD_H__ +#define __COCO_IR_OBJECT_FORWARD_H__ + +namespace coco +{ + +class Object; + +} // namespace coco + +#endif // __COCO_IR_OBJECT_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Object.h b/compiler/coco/core/include/coco/IR/Object.h new file mode 100644 index 00000000000..617e8a1985b --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Object.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OBJECT_H__ +#define __COCO_IR_OBJECT_H__ + +#include "coco/IR/Entity.h" +#include "coco/IR/Bag.h" +#include "coco/IR/Dep.h" +#include "coco/IR/Def.forward.h" +#include "coco/IR/UseSet.h" + +#include "coco/IR/FeatureObject.forward.h" +#include "coco/IR/KernelObject.forward.h" + +#include + +namespace coco +{ + +/** + * @brief Base interface on all typed NN values + */ +class Object : public Entity +{ +public: + friend class Def; + friend class Use; + +public: + enum class Kind + { + Unknown, + Feature, + Kernel, + }; + +public: + struct Producer : public Bag::Updater + { + virtual ~Producer() = default; + }; + + struct Consumer : public Bag::Reader + { + virtual ~Consumer() = default; + }; + + using ConsumerSet = std::set; + +public: + Object(); + +public: + virtual ~Object() = default; + +public: + virtual Kind kind(void) const { return Kind::Unknown; } + +public: + coco::Bag *bag(void) const { return _dep.bag(); } + void bag(coco::Bag *bag) { _dep.bag(bag); } + +public: + virtual FeatureObject *asFeature(void) { return nullptr; } + virtual const FeatureObject *asFeature(void) const { return nullptr; } + + virtual KernelObject *asKernel(void) { return nullptr; } + virtual const KernelObject *asKernel(void) const { return nullptr; } + +public: + Def *def(void) const; + const UseSet *uses(void) const; + +private: + /** + * @brief Update the link to a producer + * + * WARN Only Def class is allowed to access this method + */ + void def(Def *d); + + // NOTE "mutable_" prefix is introduced to avoid resolution issue similarly as in Bag + // WARN Only Use class is allowed to access this method + UseSet *mutable_uses(void); + +private: + Dep _dep; + Def *_def = nullptr; + UseSet _uses; +}; + +/** + * @brief Check whether a given object is of type T + * + * The example below shows how to use this "isa" helper: + * auto obj = new FeatureObject{}; + * + * if (isa()) + * { + * std::cout << "FeatureObject" << std::endl; + * } + */ +template bool isa(const Object *); + +/** + * @brief Cast a generic object as a specific one + * + * "cast(o)" accepts only a valid object pointer "o" that "isa(o)" holds + * - Then, "cast(o)" always returns a valid object pointer. + */ +template T *cast(Object *); + +/** + * @brief Cast a generic object as a specific one + * + * Unlike "cast", "safe_cast" accepts any object pointer + * - "safe_cast(nullptr)" returns "nullptr" + * - "safe_cast(o)" returns "nullptr" if "isa(o)" does not hold + */ +template T *safe_cast(Object *); + +/// @brief Return the producer of a given object if it exists +Object::Producer *producer(const Object *); + +/// @brief Return a set of consumers of a given object. +Object::ConsumerSet consumers(const Object *); + +} // namespace coco + +#endif // __COCO_IR_OBJECT_H__ diff --git a/compiler/coco/core/include/coco/IR/ObjectManager.h b/compiler/coco/core/include/coco/IR/ObjectManager.h new file mode 100644 index 00000000000..a05b724ce1a --- /dev/null +++ b/compiler/coco/core/include/coco/IR/ObjectManager.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OBJECT_MANAGER_H__ +#define __COCO_IR_OBJECT_MANAGER_H__ + +#include "coco/IR/Object.h" +#include "coco/IR/FeatureShape.h" +#include "coco/IR/FeatureObject.h" +#include "coco/IR/KernelObject.forward.h" +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +#include + +namespace coco +{ + +class ObjectManager final : public PtrManager, public EntityBuilder +{ +public: + ObjectManager(Module *m = nullptr) { module(m); } + +public: + template T *create(void); + +public: + /** + * @brief Destroy (= deallocate) an Object entity + * + * NOTE An Object SHOULD HAVE NO DEF & USES to be destructed + * NOTE An Object WILL BE unlinked from its dependent bag (if has) on destruction + */ + void destroy(Object *o); +}; + +} // namespace coco + +#endif // __COCO_IR_OBJECT_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/ObjectSet.h b/compiler/coco/core/include/coco/IR/ObjectSet.h new file mode 100644 index 00000000000..d97781996ba --- /dev/null +++ b/compiler/coco/core/include/coco/IR/ObjectSet.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OBJECT_SET_H__ +#define __COCO_IR_OBJECT_SET_H__ + +#include "coco/IR/Object.forward.h" + +#include + +namespace coco +{ + +using ObjectSet = std::set; + +} // namespace coco + +#endif // __COCO_IR_OBJECT_SET_H__ diff --git a/compiler/coco/core/include/coco/IR/Op.forward.h b/compiler/coco/core/include/coco/IR/Op.forward.h new file mode 100644 index 00000000000..9ba3c94e3f9 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Op.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OP_FORWARD_H__ +#define __COCO_IR_OP_FORWARD_H__ + +namespace coco +{ + +struct Op; + +} // namespace coco + +#endif // __COCO_IR_OP_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Op.h b/compiler/coco/core/include/coco/IR/Op.h new file mode 100644 index 00000000000..090527e2fd8 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Op.h @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @file Op.h + * @brief This header file declares "Op" class and several traits related with "Op" + */ +#ifndef __COCO_IR_OP_H__ +#define __COCO_IR_OP_H__ + +#include "coco/IR/Object.forward.h" +#include "coco/IR/Instr.forward.h" +#include "coco/IR/Step.forward.h" +#include "coco/IR/Part.h" +#include "coco/IR/Entity.h" + +#include + +#include + +namespace coco +{ + +#define OP(Name) class Name; +#include "coco/IR/Op.lst" +#undef OP + +/** + * @brief Base interface on all supported NN operations + */ +struct Op : public Entity +{ + friend class Step; + friend class Part; + + virtual ~Op(); + + /** + * @brief Return the number of arguments (# of child Ops) + */ + virtual uint32_t arity(void) const = 0; + + /** + * @brief Return N-th argument + * + * @note The behavior of arg(n) is defined only when n < artiy() + */ + virtual Op *arg(uint32_t n) const = 0; + + /** + * @brief Return a set of object(s) used during execution + * + * NOTE There is no 'def' method as Op is not allowed to define a new object + */ + virtual std::set uses(void) const = 0; + +#define OP(Name) \ + virtual Name *as##Name(void) { return nullptr; } \ + virtual const Name *as##Name(void) const { return nullptr; } +#include "coco/IR/Op.lst" +#undef OP + + /** + * @brief Op visitor interface + * + * WARN Use this interface only for coco-internal classes + * (to minimize changes upon Op extension) + */ + template struct IVisitor + { + virtual ~IVisitor() = default; + +#define OP(Name) virtual T visit(const Name *) = 0; +#include "coco/IR/Op.lst" +#undef OP + }; + + template struct Visitor : public IVisitor + { + virtual ~Visitor() = default; + +#define OP(Name) \ + T visit(const Name *) override { throw std::runtime_error{"NYI"}; } +#include "coco/IR/Op.lst" +#undef OP + }; + + template T accept(IVisitor *v) const + { +#define OP(Name) \ + if (auto op = as##Name()) \ + { \ + return v->visit(op); \ + } +#include "coco/IR/Op.lst" +#undef OP + throw std::runtime_error{"unreachable"}; + } + + template T accept(IVisitor &v) const { return accept(&v); } + template T accept(IVisitor &&v) const { return accept(&v); } + +public: + /** + * @brief Op mutator interface + * + * WARN Use this interface only for coco-internal classes + * (to minimize changes upon Instr extension) + */ + struct IMutator + { + virtual ~IMutator() = default; + +#define OP(Name) virtual void mutate(Name *) = 0; +#include "coco/IR/Op.lst" +#undef OP + }; + + struct Mutator : public IMutator + { + virtual ~Mutator() = default; + +#define OP(Name) \ + void mutate(Name *) override { throw std::runtime_error{"NYI"}; } +#include "coco/IR/Op.lst" +#undef OP + }; + + void accept(IMutator *m) + { +#define OP(Name) \ + if (auto op = as##Name()) \ + { \ + return m->mutate(op); \ + } +#include "coco/IR/Op.lst" +#undef OP + throw std::runtime_error{"unreachable"}; + } + + void accept(IMutator &m) { return accept(&m); } + void accept(IMutator &&m) { return accept(&m); } + +public: + Instr *parent(void) const; + + /// @brief Return a pointer to the parent Op + Op *up(void) const; + +private: + /** + * @brief A link to Instr from Op + * + * WARN Update this field only through Step + */ + Step *_step = nullptr; + + /** + * @brief A link to a parent Op + * + * WARN Update this field only through Part + * NOTE An "Op" CANNOT have a link to a parent Op if it is linked to an "Instr" + */ + Part *_part = nullptr; +}; + +/** + * @brief Op with a single argument + */ +class UnaryOp : public Op +{ +public: + explicit UnaryOp(); + +public: + UnaryOp(const UnaryOp &) = delete; + UnaryOp(UnaryOp &&) = delete; + +public: + virtual ~UnaryOp() = default; + +public: + uint32_t arity(void) const final; + Op *arg(uint32_t n) const final; + + std::set uses(void) const final; + +public: + Op *arg(void) const { return _arg.child(); } + void arg(Op *arg) { _arg.child(arg); } + +private: + /// @brief Link to Op's argument + Part _arg; +}; + +/** + * @brief Op with two arguments + */ +class BinaryOp : public Op +{ +public: + explicit BinaryOp(); + +public: + BinaryOp(const BinaryOp &) = delete; + BinaryOp(BinaryOp &&) = delete; + +public: + virtual ~BinaryOp() = default; + +public: + uint32_t arity(void) const final; + Op *arg(uint32_t n) const final; + + std::set uses(void) const final; + +public: + Op *left(void) const { return _left.child(); } + void left(Op *op) { _left.child(op); } + +public: + Op *right(void) const { return _right.child(); } + void right(Op *op) { _right.child(op); } + +private: + /// @brief Left-hand side (LHS) argument + Part _left; + /// @brief Right-hand side (RHS) argument + Part _right; +}; + +/** + * @brief Return the root Op from a given Op node + * + * @note root(op) == op holds for a root op + */ +Op *root(Op *); + +} // namespace coco + +#endif // __COCO_IR_OP_H__ diff --git a/compiler/coco/core/include/coco/IR/Op.lst b/compiler/coco/core/include/coco/IR/Op.lst new file mode 100644 index 00000000000..a3028bde212 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Op.lst @@ -0,0 +1,19 @@ +#ifndef OP +#error OP should be defined before including this file +#endif // OP + +// OP(Name) + +OP(Load) +OP(Conv2D) +OP(MaxPool2D) +OP(AvgPool2D) +OP(PadF) +OP(ReLU) +OP(ReLU6) +OP(Add) +OP(Sqrt) +OP(Sub) +OP(Mul) +OP(Div) +OP(ConcatF) diff --git a/compiler/coco/core/include/coco/IR/OpManager.h b/compiler/coco/core/include/coco/IR/OpManager.h new file mode 100644 index 00000000000..2c88867def5 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/OpManager.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OP_MANAGER_H__ +#define __COCO_IR_OP_MANAGER_H__ + +#include "coco/IR/Op.h" +#include "coco/IR/Ops.h" + +#include "coco/IR/Instr.forward.h" + +#include "coco/IR/Object.forward.h" + +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class OpManager final : public PtrManager, public EntityBuilder +{ +public: + OpManager(Module *m = nullptr) { module(m); } + +public: + ~OpManager(); + +public: + template T *create(void); + +public: + /** + * @brief Destroy (= deallocate) a Op instance + * + * NOTE destroy(op) WILL NOT update op->parent(). Client SHOULD detach op before destroy(op) call + */ + void destroy(Op *); + + /** + * @brief Destroy a Op tree + * + * @require op->parent() == nullptr && op->up() == nullptr + */ + void destroy_all(Op *); +}; + +} // namespace coco + +#endif // __COCO_IR_OP_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Ops.h b/compiler/coco/core/include/coco/IR/Ops.h new file mode 100644 index 00000000000..01ac92b7f60 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Ops.h @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OPS_H__ +#define __COCO_IR_OPS_H__ + +#include "coco/IR/Op.h" +#include "coco/IR/Object.h" +#include "coco/IR/KernelObject.h" + +#include "coco/IR/Use.h" +#include "coco/IR/Part.h" + +#include "coco/IR/Padding2D.h" +#include "coco/IR/Stride2D.h" +#include "coco/IR/Window2D.h" + +namespace coco +{ + +/** + * @brief Load an Object + */ +class Load final : public Op, public Object::Consumer +{ +public: + explicit Load(); + +public: + Load(const Load &) = delete; + Load(Load &&) = delete; + +public: + uint32_t arity(void) const final; + Op *arg(uint32_t n) const final; + + std::set uses(void) const override; + +public: + Load *asLoad(void) override { return this; } + const Load *asLoad(void) const override { return this; } + +public: + Instr *loc(void) override { return parent(); } + +public: + void object(Object *o) { _obj.value(o); } + Object *object(void) const { return _obj.value(); } + +private: + Use _obj; +}; + +/** + * @brief 2D Convolution over 3D Feature Map with 4D kernel + * + * NOTE IFM and OFM are implicit. Only 4D kernel is explicit in this class + * TODO Decide source code layout policy and extract this class if necessary + */ +class Conv2D : public Op, public Object::Consumer +{ +public: + explicit Conv2D(); + +public: + uint32_t arity(void) const final; + Op *arg(uint32_t n) const final; + + std::set uses(void) const override; + +public: + Conv2D *asConv2D(void) override { return this; } + const Conv2D *asConv2D(void) const override { return this; } + +public: + Instr *loc(void) override { return parent(); } + +private: + Use _ker; + +public: + Op *arg(void) const { return _arg.child(); } + void arg(Op *arg) { _arg.child(arg); } + +public: + KernelObject *ker(void) const; + void ker(KernelObject *ker); + +public: + /** + * @brief Divide an input and kernel (= convolution filter) into G independent groups + * + * Given an input of shape(Ic, Ih, Iw), a kernel of shape(Kn, Kc, Kh, Kw), and group G, + * Conv2D is identical to G independent convolutions over G inputs of shape(Ic / G, Ih, Iw) + * and a kernel of shape(Kn / G, Kc, Kh, Kw) followed by concatenation. + * + * REQUIRED + * - "Ic" SHOULD BE a multiple of "G" + * - "Kc" SHOULD BE identical to "Ic /G" + * + * NOTE Depthwise convolution is a special case of group convolution where Ic == G. + */ + uint32_t group(void) const { return _group; } + void group(uint32_t g) { _group = g; } + +public: + Padding2D *pad(void) { return &_pad; } + const Padding2D *pad(void) const { return &_pad; } + +public: + Stride2D *stride(void) { return &_stride; } + const Stride2D *stride(void) const { return &_stride; } + +private: + uint32_t _group = 1; + + Padding2D _pad; + Stride2D _stride; + +private: + /// @brief Link to an argument of Conv2D operation (= IFM) + Part _arg; +}; + +/** + * @brief 2D Max Pooling + */ +class MaxPool2D final : public UnaryOp +{ +public: + explicit MaxPool2D() = default; + +public: + MaxPool2D(const MaxPool2D &) = delete; + MaxPool2D(MaxPool2D &&) = delete; + +public: + MaxPool2D *asMaxPool2D(void) override { return this; } + const MaxPool2D *asMaxPool2D(void) const override { return this; } + +public: + Window2D *window(void) { return &_window; } + const Window2D *window(void) const { return &_window; } + +public: + Stride2D *stride(void) { return &_stride; } + const Stride2D *stride(void) const { return &_stride; } + +public: + Padding2D *pad(void) { return &_pad; } + const Padding2D *pad(void) const { return &_pad; } + +private: + Window2D _window; + Stride2D _stride; + Padding2D _pad; +}; + +/** + * @brief 2D Average Pooling + */ +class AvgPool2D final : public UnaryOp +{ +public: + enum class Divisor + { + Unknown, + // Use the number of elements in each receptive field as a divisor + Static, + // Use the number of valid (non-padding) elements in each receptive field as a divisor + PaddingExcluded + }; + +public: + explicit AvgPool2D() = default; + +public: + AvgPool2D(const AvgPool2D &) = delete; + AvgPool2D(AvgPool2D &&) = delete; + +public: + AvgPool2D *asAvgPool2D(void) override { return this; } + const AvgPool2D *asAvgPool2D(void) const override { return this; } + +public: + Divisor divisor(void) const { return _divisor; } + void divisor(const Divisor &divisor) { _divisor = divisor; } + +public: + Window2D *window(void) { return &_window; } + const Window2D *window(void) const { return &_window; } + +public: + Padding2D *pad(void) { return &_pad; } + const Padding2D *pad(void) const { return &_pad; } + +public: + Stride2D *stride(void) { return &_stride; } + const Stride2D *stride(void) const { return &_stride; } + +private: + Divisor _divisor = Divisor::Unknown; + + Window2D _window; + Stride2D _stride; + Padding2D _pad; +}; + +/** + * @brief Introduce padding area + */ +class PadF final : public UnaryOp +{ +public: + explicit PadF() = default; + +public: + PadF(const PadF &) = delete; + PadF(PadF &&) = delete; + +public: + PadF *asPadF(void) override { return this; } + const PadF *asPadF(void) const override { return this; } + +public: + Padding2D *pad(void) { return &_pad; } + const Padding2D *pad(void) const { return &_pad; } + +private: + Padding2D _pad; +}; + +/** + * @brief Apply ReLU over elements + */ +class ReLU final : public UnaryOp +{ +public: + explicit ReLU() = default; + +public: + ReLU(const ReLU &) = delete; + ReLU(ReLU &&) = delete; + +public: + ReLU *asReLU(void) override { return this; } + const ReLU *asReLU(void) const override { return this; } +}; + +/** + * @brief Apply ReLU6 over elements + * @note ReLU6 is subject to change + */ +class ReLU6 final : public UnaryOp +{ +public: + explicit ReLU6() = default; + +public: + ReLU6(const ReLU6 &) = delete; + ReLU6(ReLU6 &&) = delete; + +public: + ReLU6 *asReLU6(void) override { return this; } + const ReLU6 *asReLU6(void) const override { return this; } +}; + +/** + * @brief Element-wise addition + * + * Add(L, R) is valid only when L and R have identical kind/shape/dtype + */ +class Add final : public BinaryOp +{ +public: + explicit Add() = default; + +public: + Add(const Add &) = delete; + Add(Add &&) = delete; + +public: + Add *asAdd(void) override { return this; } + const Add *asAdd(void) const override { return this; } +}; + +/** + * @brief Element-wise subtraction + * + * Sub(L, R) is valid only when L and R have identical kind/shape/dtype + */ +class Sub final : public BinaryOp +{ +public: + explicit Sub() = default; + +public: + Sub(const Sub &) = delete; + Sub(Sub &&) = delete; + +public: + Sub *asSub(void) override { return this; } + const Sub *asSub(void) const override { return this; } +}; + +/** + * @brief Element-wise multiplication + * + * Mul(L, R) is valid only when L and R have identical kind/shape/dtype + */ +class Mul final : public BinaryOp +{ +public: + explicit Mul() = default; + +public: + Mul(const Mul &) = delete; + Mul(Mul &&) = delete; + +public: + Mul *asMul(void) override { return this; } + const Mul *asMul(void) const override { return this; } +}; + +/** + * @brief Element-wise division + * + * Div(L, R) is valid only when L and R have identical kind/shape/dtype + */ +class Div final : public BinaryOp +{ +public: + explicit Div() = default; + +public: + Div(const Div &) = delete; + Div(Div &&) = delete; + +public: + Div *asDiv(void) override { return this; } + const Div *asDiv(void) const override { return this; } +}; + +/** + * @brief Concatenate two feature maps + * + * ConcatF(L, R) requires + */ +class ConcatF final : public BinaryOp +{ +public: + enum class Axis + { + Unknown = 0, + Batch = 1, + Depth = 2, + Height = 3, + Width = 4, + }; + +public: + explicit ConcatF() = default; + +public: + ConcatF(const ConcatF &) = delete; + ConcatF(ConcatF &&) = delete; + +public: + ConcatF *asConcatF(void) override { return this; } + const ConcatF *asConcatF(void) const override { return this; } + +public: + const Axis &axis(void) const { return _axis; } + void axis(const Axis &axis) { _axis = axis; } + +private: + Axis _axis = Axis::Unknown; +}; + +/** + * @brief Apply Sqrt over elements + */ +class Sqrt final : public UnaryOp +{ +public: + explicit Sqrt() = default; + +public: + Sqrt(const Sqrt &) = delete; + Sqrt(Sqrt &&) = delete; + +public: + Sqrt *asSqrt(void) override { return this; } + const Sqrt *asSqrt(void) const override { return this; } +}; + +} // namesapce coco + +#endif // __COCO_IR_OPS_H__ diff --git a/compiler/coco/core/include/coco/IR/Output.forward.h b/compiler/coco/core/include/coco/IR/Output.forward.h new file mode 100644 index 00000000000..f011400c0a4 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Output.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OUTPUT_FORWARD_H__ +#define __COCO_IR_OUTPUT_FORWARD_H__ + +namespace coco +{ + +class Output; + +} // namespace coco + +#endif // __COCO_IR_OUTPUT_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Output.h b/compiler/coco/core/include/coco/IR/Output.h new file mode 100644 index 00000000000..3f77c131ded --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Output.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OUTPUT_H__ +#define __COCO_IR_OUTPUT_H__ + +#include "coco/IR/Arg.h" +#include "coco/IR/Entity.h" + +#include +#include + +#include +#include + +namespace coco +{ + +class Output final : public Arg, public Entity +{ +public: + Output(const nncc::core::ADT::tensor::Shape &shape); + +private: + void onTake(Bag *) override; + void onRelease(Bag *) override; +}; + +} // namespace coco + +#endif // __COCO_IR_OUTPUT_H__ diff --git a/compiler/coco/core/include/coco/IR/OutputList.h b/compiler/coco/core/include/coco/IR/OutputList.h new file mode 100644 index 00000000000..0e2abad75bd --- /dev/null +++ b/compiler/coco/core/include/coco/IR/OutputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OUTPUT_LIST_H__ +#define __COCO_IR_OUTPUT_LIST_H__ + +#include "coco/IR/Output.h" + +#include "coco/ADT/PtrList.h" + +namespace coco +{ + +using OutputList = PtrList; + +} // namespace coco + +#endif // __COCO_IR_OUTPUT_LIST_H__ diff --git a/compiler/coco/core/include/coco/IR/OutputManager.h b/compiler/coco/core/include/coco/IR/OutputManager.h new file mode 100644 index 00000000000..b40380388ec --- /dev/null +++ b/compiler/coco/core/include/coco/IR/OutputManager.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_OUTPUT_MANAGER_H__ +#define __COCO_IR_OUTPUT_MANAGER_H__ + +#include "coco/IR/Output.h" +#include "coco/IR/EntityBuilder.h" + +#include "coco/ADT/PtrManager.h" + +namespace coco +{ + +class OutputManager final : public PtrManager, public EntityBuilder +{ +public: + OutputManager(Module *m = nullptr) { module(m); } + +public: + Output *create(const nncc::core::ADT::tensor::Shape &); +}; + +} // namespace coco + +#endif // __COCO_IR_OUTPUT_MANAGER_H__ diff --git a/compiler/coco/core/include/coco/IR/Padding2D.h b/compiler/coco/core/include/coco/IR/Padding2D.h new file mode 100644 index 00000000000..b764656ccad --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Padding2D.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_PADDING_2D_H__ +#define __COCO_IR_PADDING_2D_H__ + +#include + +namespace coco +{ + +class Padding2D +{ +public: + Padding2D() : _top{0}, _bottom{0}, _left{0}, _right{0} + { + // DO NOTHING + } + +public: + Padding2D(uint32_t top, uint32_t bottom, uint32_t left, uint32_t right) + : _top{top}, _bottom{bottom}, _left{left}, _right{right} + { + // DO NOTHING + } + +public: + uint32_t top(void) const { return _top; } + Padding2D &top(uint32_t value); + +public: + uint32_t bottom(void) const { return _bottom; } + Padding2D &bottom(uint32_t value); + +public: + uint32_t left(void) const { return _left; } + Padding2D &left(uint32_t value); + +public: + uint32_t right(void) const { return _right; } + Padding2D &right(uint32_t value); + +private: + uint32_t _top; + uint32_t _bottom; + uint32_t _left; + uint32_t _right; +}; + +} // namespace coco + +#endif // __COCO_IR_PADDING_2D_H__ diff --git a/compiler/coco/core/include/coco/IR/Part.forward.h b/compiler/coco/core/include/coco/IR/Part.forward.h new file mode 100644 index 00000000000..642ea56b5c7 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Part.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_PART_FORWARD_H__ +#define __COCO_IR_PART_FORWARD_H__ + +namespace coco +{ + +class Part; + +} // namespace coco + +#endif // __COCO_IR_PART_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Part.h b/compiler/coco/core/include/coco/IR/Part.h new file mode 100644 index 00000000000..72af217ccd7 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Part.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_PART_H__ +#define __COCO_IR_PART_H__ + +#include "coco/IR/Op.forward.h" + +namespace coco +{ + +/** + * @brief A Part represents the edge between a child Op and its parent Op + */ +class Part final +{ +public: + Part(Op *parent) : _parent{parent} + { + // DO NOTHING + } + +public: + ~Part() { child(nullptr); } + +public: + Op *child(void) const { return _child; } + void child(Op *c); + +public: + Op *parent(void) const { return _parent; } + +private: + Op *_parent = nullptr; + Op *_child = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_PART_H__ diff --git a/compiler/coco/core/include/coco/IR/Read.forward.h b/compiler/coco/core/include/coco/IR/Read.forward.h new file mode 100644 index 00000000000..7fd99e21288 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Read.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_READ_FORWARD_H__ +#define __COCO_IR_READ_FORWARD_H__ + +namespace coco +{ + +class Read; + +} // namespace coco + +#endif // __COCO_IR_READ_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Read.h b/compiler/coco/core/include/coco/IR/Read.h new file mode 100644 index 00000000000..9f62d8bf819 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Read.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_READ_H__ +#define __COCO_IR_READ_H__ + +#include "coco/IR/Bag.h" + +namespace coco +{ + +/** + * @brief A Read represents an edge between a Bag and its Reader + */ +class Read final +{ +public: + Read(Bag::Reader *r) + { + // Initialize link and reader + reader(r); + } + +public: + ~Read(); + +public: + Bag *bag(void) const { return _bag; } + void bag(Bag *bag); + +public: + Bag::Reader *reader(void) const { return _reader; } + void reader(Bag::Reader *r) { _reader = r; } + +private: + Bag *_bag = nullptr; + Bag::Reader *_reader = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_READ_H__ diff --git a/compiler/coco/core/include/coco/IR/ReadSet.h b/compiler/coco/core/include/coco/IR/ReadSet.h new file mode 100644 index 00000000000..c470c4bfd9e --- /dev/null +++ b/compiler/coco/core/include/coco/IR/ReadSet.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_READ_SET_H__ +#define __COCO_IR_READ_SET_H__ + +#include "coco/IR/Read.forward.h" + +#include + +namespace coco +{ + +using ReadSet = std::set; + +} // namespace coco + +#endif // __COCO_IR_READ_SET_H__ diff --git a/compiler/coco/core/include/coco/IR/Step.forward.h b/compiler/coco/core/include/coco/IR/Step.forward.h new file mode 100644 index 00000000000..63506912299 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Step.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_STEP_FORWARD_H__ +#define __COCO_IR_STEP_FORWARD_H__ + +namespace coco +{ + +class Step; + +} // namespace coco + +#endif // __COCO_IR_STEP_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Step.h b/compiler/coco/core/include/coco/IR/Step.h new file mode 100644 index 00000000000..31dad43898b --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Step.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_STEP_H__ +#define __COCO_IR_STEP_H__ + +#include "coco/IR/Op.forward.h" +#include "coco/IR/Instr.forward.h" + +namespace coco +{ + +/** + * @brief A Step denotes the edge between Op and Instr + */ +class Step final +{ +public: + explicit Step(Instr *instr) : _instr{instr} + { + // DO NOTHING + } + +public: + ~Step() { op(nullptr); } + +public: + Op *op(void) const { return _op; } + void op(Op *o); + +public: + Instr *instr(void) const { return _instr; } + +private: + Op *_op = nullptr; + Instr *_instr = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_STEP_H__ diff --git a/compiler/coco/core/include/coco/IR/Stride2D.h b/compiler/coco/core/include/coco/IR/Stride2D.h new file mode 100644 index 00000000000..9e69ffa40a9 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Stride2D.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_STRIDE_2D_H__ +#define __COCO_IR_STRIDE_2D_H__ + +#include + +namespace coco +{ + +class Stride2D +{ +public: + Stride2D() : _vertical{1}, _horizontal{1} + { + // DO NOTHING + } + +public: + Stride2D(uint32_t vertical, uint32_t horizontal) : _vertical{vertical}, _horizontal{horizontal} + { + // DO NOTHING + } + +public: + uint32_t vertical(void) const { return _vertical; } + Stride2D &vertical(uint32_t value); + +public: + uint32_t horizontal(void) const { return _horizontal; } + Stride2D &horizontal(uint32_t value); + +private: + uint32_t _vertical; + uint32_t _horizontal; +}; + +} // namespace coco + +#endif // __COCO_IR_STRIDE_2D_H__ diff --git a/compiler/coco/core/include/coco/IR/Update.forward.h b/compiler/coco/core/include/coco/IR/Update.forward.h new file mode 100644 index 00000000000..059f318c97b --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Update.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_UPDATE_FORWARD_H__ +#define __COCO_IR_UPDATE_FORWARD_H__ + +namespace coco +{ + +class Update; + +} // namespace coco + +#endif // __COCO_IR_UPDATE_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Update.h b/compiler/coco/core/include/coco/IR/Update.h new file mode 100644 index 00000000000..7cf876d74be --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Update.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_UPDATE_H__ +#define __COCO_IR_UPDATE_H__ + +#include "coco/IR/Bag.h" + +namespace coco +{ + +/** + * @brief A Update represents an edge between a Bag and its Updater + */ +class Update final +{ +public: + Update(Bag::Updater *u) { updater(u); } + +public: + ~Update(); + +public: + Bag *bag(void) const { return _bag; } + void bag(Bag *bag); + +public: + Bag::Updater *updater(void) const { return _updater; } + void updater(Bag::Updater *u) { _updater = u; } + +private: + Bag *_bag = nullptr; + Bag::Updater *_updater = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_UPDATE_H__ diff --git a/compiler/coco/core/include/coco/IR/UpdateSet.h b/compiler/coco/core/include/coco/IR/UpdateSet.h new file mode 100644 index 00000000000..1e772adf3e8 --- /dev/null +++ b/compiler/coco/core/include/coco/IR/UpdateSet.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_UPDATE_SET_H__ +#define __COCO_IR_UPDATE_SET_H__ + +#include "coco/IR/Update.forward.h" + +#include + +namespace coco +{ + +using UpdateSet = std::set; + +} // namespace coco + +#endif // __COCO_IR_UPDATE_SET_H__ diff --git a/compiler/coco/core/include/coco/IR/Use.forward.h b/compiler/coco/core/include/coco/IR/Use.forward.h new file mode 100644 index 00000000000..329430bb3ec --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Use.forward.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_USE_FORWARD_H__ +#define __COCO_IR_USE_FORWARD_H__ + +namespace coco +{ + +class Use; + +} // namespace coco + +#endif // __COCO_IR_USE_FORWARD_H__ diff --git a/compiler/coco/core/include/coco/IR/Use.h b/compiler/coco/core/include/coco/IR/Use.h new file mode 100644 index 00000000000..c4c9b98b4ef --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Use.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_USE_H__ +#define __COCO_IR_USE_H__ + +#include "coco/IR/Object.h" + +namespace coco +{ + +class Use final +{ +public: + Use(Object::Consumer *use) : _value{nullptr}, _consumer{use} + { + // DO NOTHING + } + +public: + ~Use() { value(nullptr); } + +public: + Object *value(void) const { return _value; } + +public: + void value(Object *value); + +public: + Object::Consumer *consumer(void) const { return _consumer; } + +private: + Object *_value; + Object::Consumer *_consumer = nullptr; +}; + +} // namespace coco + +#endif // __COCO_IR_USE_H__ diff --git a/compiler/coco/core/include/coco/IR/UseSet.h b/compiler/coco/core/include/coco/IR/UseSet.h new file mode 100644 index 00000000000..a698a733f2e --- /dev/null +++ b/compiler/coco/core/include/coco/IR/UseSet.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_USE_SET_H__ +#define __COCO_IR_USE_SET_H__ + +#include "coco/IR/Use.forward.h" + +#include + +namespace coco +{ + +using UseSet = std::set; + +} // namespace coco + +#endif // __COCO_IR_USE_SET_H__ diff --git a/compiler/coco/core/include/coco/IR/Window2D.h b/compiler/coco/core/include/coco/IR/Window2D.h new file mode 100644 index 00000000000..a434538f3ae --- /dev/null +++ b/compiler/coco/core/include/coco/IR/Window2D.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_WINDOW_2D_H__ +#define __COCO_IR_WINDOW_2D_H__ + +#include + +namespace coco +{ + +class Window2D +{ +public: + Window2D() : _vertical{1}, _horizontal{1} + { + // DO NOTHING + } + +public: + Window2D(uint32_t vertical, uint32_t horizontal) : _vertical{vertical}, _horizontal{horizontal} + { + // DO NOTHING + } + +public: + uint32_t height(void) const { return _vertical; } + void height(uint32_t value) { _vertical = value; } + +public: + uint32_t width(void) const { return _horizontal; } + void width(uint32_t value) { _horizontal = value; } + +private: + // TODO Rename these fields as _height and _width, respectively + uint32_t _vertical; + uint32_t _horizontal; +}; + +} // namespace coco + +#endif // __COCO_IR_WINDOW_2D_H__ diff --git a/compiler/coco/core/src/ADT/DLinkedList.test.cpp b/compiler/coco/core/src/ADT/DLinkedList.test.cpp new file mode 100644 index 00000000000..563a39653f0 --- /dev/null +++ b/compiler/coco/core/src/ADT/DLinkedList.test.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/ADT/DLinkedList.h" + +#include + +#include + +namespace +{ + +class Parent; +class Child; + +using ChildList = coco::DLinkedList::Head; + +class Parent +{ +public: + friend void coco::DLinkedList::joined(Parent *, Child *); + friend void coco::DLinkedList::leaving(Parent *, Child *); + +public: + Parent() : _list{this}, _count{0} + { + // DO NOTHING + } + +public: + ChildList *children(void) { return &_list; } + uint32_t count(void) const { return _count; } + +private: + ChildList _list; + uint32_t _count; +}; + +class Child final : public coco::DLinkedList::Node +{ +public: + ~Child() + { + if (parent()) + { + detach(); + } + } +}; + +} // namespace + +namespace coco +{ + +template <> void DLinkedList::joined(Parent *p, Child *) { p->_count += 1; } +template <> void DLinkedList::leaving(Parent *p, Child *) { p->_count -= 1; } + +template <> ChildList *DLinkedList::head(Parent *p) { return p->children(); } + +} // namespace coco + +namespace +{ +class DLinkedListTest : public ::testing::Test +{ +public: + virtual ~DLinkedListTest() + { + // NOTE Child SHOULD BE freed before parent + for (auto child : _children) + { + delete child; + } + + for (auto parent : _parents) + { + delete parent; + } + } + +protected: + template T *create(void); + + void destroy(Child *); + +private: + std::set<::Parent *> _parents; + std::set<::Child *> _children; +}; + +template <>::Parent *DLinkedListTest::create(void) +{ + auto parent = new ::Parent; + _parents.insert(parent); + return parent; +} + +template <>::Child *DLinkedListTest::create(void) +{ + auto child = new ::Child; + _children.insert(child); + return child; +} + +void DLinkedListTest::destroy(Child *child) +{ + _children.erase(child); + delete child; +} + +} // namespace + +TEST_F(DLinkedListTest, append) +{ + auto parent = create<::Parent>(); + auto child = create<::Child>(); + + parent->children()->append(child); + + ASSERT_EQ(child->parent(), parent); + ASSERT_EQ(child->prev(), nullptr); + ASSERT_EQ(child->next(), nullptr); + + ASSERT_EQ(parent->children()->head(), child); + ASSERT_EQ(parent->children()->tail(), child); + ASSERT_EQ(parent->count(), 1); +} + +TEST_F(DLinkedListTest, insert_two_elements) +{ + auto parent = create<::Parent>(); + + ASSERT_EQ(parent->children()->head(), nullptr); + ASSERT_EQ(parent->children()->tail(), nullptr); + + auto child_1 = create<::Child>(); + + ASSERT_EQ(child_1->parent(), nullptr); + ASSERT_EQ(child_1->prev(), nullptr); + ASSERT_EQ(child_1->next(), nullptr); + + parent->children()->append(child_1); + + ASSERT_EQ(child_1->parent(), parent); + ASSERT_EQ(child_1->prev(), nullptr); + ASSERT_EQ(child_1->next(), nullptr); + + ASSERT_EQ(parent->children()->head(), child_1); + ASSERT_EQ(parent->children()->tail(), child_1); + + auto child_2 = create<::Child>(); + + ASSERT_EQ(child_2->parent(), nullptr); + ASSERT_EQ(child_2->prev(), nullptr); + ASSERT_EQ(child_2->next(), nullptr); + + child_2->insertAfter(child_1); + + ASSERT_EQ(child_2->parent(), parent); + ASSERT_EQ(child_2->prev(), child_1); + ASSERT_EQ(child_2->next(), nullptr); + + ASSERT_EQ(child_1->parent(), parent); + ASSERT_EQ(child_1->prev(), nullptr); + ASSERT_EQ(child_1->next(), child_2); + + ASSERT_EQ(parent->children()->head(), child_1); + ASSERT_EQ(parent->children()->tail(), child_2); +} + +TEST_F(DLinkedListTest, insertBefore) +{ + auto parent = create<::Parent>(); + + auto child_1 = create<::Child>(); + auto child_2 = create<::Child>(); + + parent->children()->append(child_1); + child_2->insertBefore(child_1); + + ASSERT_EQ(child_2->parent(), parent); + ASSERT_EQ(child_2->prev(), nullptr); + ASSERT_EQ(child_2->next(), child_1); + + ASSERT_EQ(child_1->parent(), parent); + ASSERT_EQ(child_1->prev(), child_2); + ASSERT_EQ(child_1->next(), nullptr); + + ASSERT_EQ(parent->children()->head(), child_2); + ASSERT_EQ(parent->children()->tail(), child_1); +} + +TEST_F(DLinkedListTest, prepend_after_append) +{ + auto parent = create<::Parent>(); + + auto child_1 = create<::Child>(); + auto child_2 = create<::Child>(); + + parent->children()->append(child_1); + parent->children()->prepend(child_2); + + ASSERT_EQ(child_2->next(), child_1); + + ASSERT_EQ(child_1->parent(), parent); + ASSERT_EQ(child_1->prev(), child_2); + ASSERT_EQ(child_1->next(), nullptr); + + ASSERT_EQ(parent->children()->head(), child_2); + ASSERT_EQ(parent->children()->tail(), child_1); +} + +TEST_F(DLinkedListTest, detach) +{ + auto parent = create<::Parent>(); + + auto child_1 = create<::Child>(); + auto child_2 = create<::Child>(); + + parent->children()->append(child_1); + parent->children()->append(child_2); + + child_1->detach(); + + ASSERT_EQ(child_1->parent(), nullptr); + ASSERT_EQ(child_1->prev(), nullptr); + ASSERT_EQ(child_1->next(), nullptr); + + ASSERT_EQ(child_2->parent(), parent); + ASSERT_EQ(child_2->prev(), nullptr); + + ASSERT_EQ(parent->children()->head(), child_2); + ASSERT_EQ(parent->children()->tail(), child_2); + + child_2->detach(); + + ASSERT_EQ(child_2->parent(), nullptr); + ASSERT_EQ(child_2->prev(), nullptr); + ASSERT_EQ(child_2->next(), nullptr); + + ASSERT_TRUE(parent->children()->empty()); + ASSERT_EQ(parent->children()->head(), nullptr); + ASSERT_EQ(parent->children()->tail(), nullptr); +} + +TEST_F(DLinkedListTest, node_destructor) +{ + auto parent = create<::Parent>(); + + auto child_1 = create<::Child>(); + auto child_2 = create<::Child>(); + + parent->children()->append(child_1); + parent->children()->append(child_2); + + destroy(child_2); + + ASSERT_EQ(parent->children()->head(), child_1); + ASSERT_EQ(parent->children()->tail(), child_1); + ASSERT_EQ(child_1->next(), nullptr); + ASSERT_EQ(child_1->prev(), nullptr); + + destroy(child_1); + + ASSERT_EQ(parent->children()->head(), nullptr); + ASSERT_EQ(parent->children()->tail(), nullptr); +} diff --git a/compiler/coco/core/src/ADT/PtrList.cpp b/compiler/coco/core/src/ADT/PtrList.cpp new file mode 100644 index 00000000000..ea2beb06bfd --- /dev/null +++ b/compiler/coco/core/src/ADT/PtrList.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/ADT/PtrList.h" + +// NOTE Do NOT delete this file; this file checks the completeness of 'PtrList.h' diff --git a/compiler/coco/core/src/ADT/PtrList.test.cpp b/compiler/coco/core/src/ADT/PtrList.test.cpp new file mode 100644 index 00000000000..dcbad8b9091 --- /dev/null +++ b/compiler/coco/core/src/ADT/PtrList.test.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/ADT/PtrList.h" + +#include + +#include + +namespace +{ +struct Object +{ +}; +} + +TEST(ADT_PTR_LIST, ctor) +{ + coco::PtrList l; + + ASSERT_EQ(l.size(), 0); +} + +TEST(ADT_PTR_LIST, insert) +{ + coco::PtrList l; + + std::unique_ptr ptr{new Object}; + + l.insert(ptr.get()); + + ASSERT_EQ(l.size(), 1); + ASSERT_EQ(l.at(0), ptr.get()); +} diff --git a/compiler/coco/core/src/ADT/PtrManager.test.cpp b/compiler/coco/core/src/ADT/PtrManager.test.cpp new file mode 100644 index 00000000000..bb9056f2953 --- /dev/null +++ b/compiler/coco/core/src/ADT/PtrManager.test.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/ADT/PtrManager.h" + +#include + +#include + +namespace +{ +struct Count +{ + uint32_t allocated; + uint32_t freed; + + Count() : allocated{0}, freed{0} + { + // DO NOTHING + } +}; + +class Object +{ +public: + Object(Count *count, uint32_t value) : _count{count}, _value{value} { _count->allocated += 1; } + +public: + ~Object() { _count->freed += 1; } + +public: + uint32_t value(void) const { return _value; } + +private: + Count *const _count; + +private: + uint32_t _value; +}; + +struct ObjectManager final : public coco::PtrManager +{ + Object *alloc(Count *count, uint32_t value) + { + std::unique_ptr o{new Object{count, value}}; + return take(std::move(o)); + } + + void free(Object *o) { release(o); } +}; +} + +TEST(ADT_PTR_MANAGER, usecase) +{ + Count c; + + ASSERT_EQ(c.allocated, 0); + ASSERT_EQ(c.freed, 0); + + { + ::ObjectManager mgr; + + auto obj_1 = mgr.alloc(&c, 3); + auto obj_2 = mgr.alloc(&c, 4); + + EXPECT_EQ(c.allocated, 2); + ASSERT_EQ(c.freed, 0); + + EXPECT_EQ(mgr.size(), 2); + EXPECT_EQ(mgr.at(0), obj_1); + EXPECT_EQ(mgr.at(1), obj_2); + + // Let's delete obj_1 + mgr.free(obj_1); + + EXPECT_EQ(c.allocated, 2); + ASSERT_EQ(c.freed, 1); + + EXPECT_EQ(mgr.size(), 1); + EXPECT_EQ(mgr.at(0), obj_2); + } + + // PtrManger SHOULD destruct all of the allocated object when it is destructed. + ASSERT_EQ(c.allocated, 2); + ASSERT_EQ(c.freed, 2); +} diff --git a/compiler/coco/core/src/IR.test.cpp b/compiler/coco/core/src/IR.test.cpp new file mode 100644 index 00000000000..3f8c0ad3466 --- /dev/null +++ b/compiler/coco/core/src/IR.test.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR.h" + +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +using nncc::core::ADT::feature::num_elements; + +using nncc::core::ADT::kernel::num_elements; + +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::num_elements; + +// +// 'caffe_conv' test demonstrates how to translate the following Caffe network into coco IR: +// +// layer { +// name: "data" +// type: "Input" +// top: "data" +// input_param: { shape: { dim: 1 dim: 1 dim: 3 dim: 3 } } +// } +// +// layer { +// name: "conv" +// type: "Convolution" +// bottom: "data" +// top: "conv" +// blobs { +// ... +// shape { dim: 1 dim: 1 dim: 3 dim: 3 } +// } +// convolution_param { +// bias_term: false +// num_output: 1 +// kernel_size: 3 +// } +// } +// +TEST(IR, caffe_conv) +{ + // For inter-layer communication + std::map bags; + std::map shapes; + + std::set top_blobs; + + // Create a module and block + auto m = coco::Module::create(); + auto blk = m->entity()->block()->create(); + + // Next, append the block to the module + m->block()->append(blk); + + // Now, the block belongs to the module (and has no sibling) + ASSERT_EQ(blk->parent(), m.get()); + ASSERT_EQ(blk->next(), nullptr); + ASSERT_EQ(blk->prev(), nullptr); + + // The head and tail points to the appended block + ASSERT_EQ(m->block()->head(), blk); + ASSERT_EQ(m->block()->tail(), blk); + + // Let's translate the first 'Input' layer + { + using nncc::core::ADT::tensor::Shape; + + const Shape shape{1, 1, 3, 3}; + + auto bag = m->entity()->bag()->create(num_elements(shape)); + auto input = m->entity()->input()->create(shape); + + input->bag(bag); + input->name("data"); + + // Caffe uses lexical layout for tensors + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const static LexicalLayout l{}; + const auto offset = static_cast(l.offset(shape, e.current())); + + input->at(e.current()) = coco::ElemID{offset}; + } + + m->input()->insert(input); + + bags["data"] = bag; + shapes["data"] = shape; + + top_blobs = {"data"}; + } + + // Next, translate 'Convolution' layer + { + using nncc::core::ADT::feature::CHWLayout; + using nncc::core::ADT::kernel::NCHWLayout; + + const nncc::core::ADT::feature::Shape ifm_shape{1, 3, 3}; + auto ifm_bag = bags["data"]; + auto ifm_obj = m->entity()->object()->create(); + auto ifm_layout = coco::FeatureLayouts::BCHW::create(ifm_shape); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(std::move(ifm_layout)); + + const nncc::core::ADT::kernel::Shape ker_shape{1, 1, 3, 3}; + auto ker_bag = m->entity()->bag()->create(num_elements(ker_shape)); + auto ker_layout = coco::KernelLayouts::Generic::create(ker_shape); + + ker_layout->reorder(); + + auto ker_obj = m->entity()->object()->create(); + + ker_obj->bag(ker_bag); + ker_obj->layout(std::move(ker_layout)); + + const nncc::core::ADT::feature::Shape ofm_shape{1, 1, 1}; + auto ofm_bag = m->entity()->bag()->create(1 * 1 * 1); + auto ofm_obj = m->entity()->object()->create(); + auto ofm_layout = coco::FeatureLayouts::BCHW::create(ifm_shape); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(std::move(ofm_layout)); + + // Create Load operation + auto load = m->entity()->op()->create(); + + load->object(ifm_obj); + + // Create Conv2D operation + // + // NOTE Conv2D op in coco IR does not perform BiasAdd + auto op = m->entity()->op()->create(); + + op->ker(ker_obj); + + // Create UnitF instruction with Conv2D operation + auto ins = m->entity()->instr()->create(); + + ins->out(ofm_obj); + ins->op(op); + + // Append the instruction (to the block) + blk->instr()->append(ins); + + bags["conv"] = ofm_bag; + shapes["conv"] = nncc::core::ADT::tensor::Shape{1, 1, 1, 1}; + + top_blobs = {"conv"}; + } + + // Finalize + for (const auto &top_blob : top_blobs) + { + const auto &shape = shapes[top_blob]; + + auto output = m->entity()->output()->create(shape); + + output->bag(bags[top_blob]); + output->name(top_blob); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const static LexicalLayout l{}; + const auto offset = static_cast(l.offset(shape, e.current())); + + output->at(e.current()) = coco::ElemID{offset}; + } + + m->output()->insert(output); + } + + // Let's validate the constructed IR + { + // There is one input whose name is 'data' + ASSERT_EQ(m->input()->size(), 1); + ASSERT_EQ(m->input()->at(0)->name(), "data"); + + // There is one output whose name is 'conv' + ASSERT_EQ(m->output()->size(), 1); + ASSERT_EQ(m->output()->at(0)->name(), "conv"); + + ASSERT_FALSE(m->block()->empty()); + + // There is one block in the module + auto blk = m->block()->head(); + + ASSERT_EQ(blk->next(), nullptr); + ASSERT_FALSE(blk->instr()->empty()); + + // There is one instruction in the block + auto ins = blk->instr()->head(); + + ASSERT_EQ(ins->next(), nullptr); + + // That instruction is 'Eval' + // TODO Rename 'unit' + auto unit = ins->asEval(); + + ASSERT_NE(unit, nullptr); + +// TODO Rewrite below test +#if 0 + // Input #0 points to IFM + ASSERT_NE(unit->ifm(), nullptr); + ASSERT_EQ(unit->ifm()->bag(), m->input()->at(0)->bag()); +#endif + + // Output #0 points to OFM + ASSERT_NE(unit->out(), nullptr); + ASSERT_EQ(unit->out()->bag(), m->output()->at(0)->bag()); + + // The actual operation is Conv2D + auto conv = unit->op()->asConv2D(); + + ASSERT_NE(conv, nullptr); + + // Let's check Kernel Object + ASSERT_NE(conv->ker(), nullptr); +// TODO Rewrite below test +#if 0 + ASSERT_NE(conv->ker()->bag(), unit->ifm()->bag()); + ASSERT_NE(conv->ker()->bag(), unit->ofm()->bag()); +#endif + +// One may find the correspondence among Input, Output, and Objects through ElemID +// TODO Rewrite below test +#if 0 + { + auto input_0 = m->input()->at(0); + auto ifm = unit->ifm(); + + nncc::core::ADT::tensor::Index input_index{0, 0, 2, 2}; + + // Here we can check that Input(0, 0, 2, 2) corresponds to IFM(0, 2, 2) + ASSERT_EQ(input_0->at(input_index).value(), ifm->at(0, 2, 2).value()); + } +#endif + } +} + +// +// This test demonstrates how to use 'replaceWith' method +// +TEST(IR, bag_replaceWith) +{ + auto m = coco::Module::create(); + + auto bag_1 = m->entity()->bag()->create(1); + auto bag_2 = m->entity()->bag()->create(1); + + auto obj = m->entity()->object()->create(); + obj->bag(bag_1); + + auto shuffle_1 = m->entity()->instr()->create(); + shuffle_1->into(bag_1); + + auto shuffle_2 = m->entity()->instr()->create(); + shuffle_2->from(bag_1); + + ASSERT_EQ(obj->bag(), bag_1); + ASSERT_EQ(shuffle_1->into(), bag_1); + ASSERT_EQ(shuffle_2->from(), bag_1); + + bag_1->replaceAllDepsWith(bag_2); + + ASSERT_EQ(obj->bag(), bag_2); + ASSERT_EQ(shuffle_1->into(), bag_1); + ASSERT_EQ(shuffle_2->from(), bag_1); + + bag_1->replaceWith(bag_2); + + ASSERT_EQ(obj->bag(), bag_2); + ASSERT_EQ(shuffle_1->into(), bag_2); + ASSERT_EQ(shuffle_2->from(), bag_2); +} diff --git a/compiler/coco/core/src/IR/Arg.cpp b/compiler/coco/core/src/IR/Arg.cpp new file mode 100644 index 00000000000..b6f9c47778f --- /dev/null +++ b/compiler/coco/core/src/IR/Arg.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Arg.h" + +#include +#include + +#include + +namespace +{ + +const nncc::core::ADT::tensor::LexicalLayout l; + +} // namespace + +namespace coco +{ + +Arg::Arg(const nncc::core::ADT::tensor::Shape &shape) : _shape{shape}, _bag{nullptr} +{ + _map.resize(nncc::core::ADT::tensor::num_elements(shape)); +} + +void Arg::bag(Bag *bag) +{ + if (_bag != nullptr) + { + onRelease(_bag); + _bag = nullptr; + } + + assert(_bag == nullptr); + + if (bag != nullptr) + { + _bag = bag; + onTake(_bag); + } +} + +ElemID &Arg::at(const nncc::core::ADT::tensor::Index &index) +{ + return _map.at(l.offset(_shape, index)); +} + +const ElemID &Arg::at(const nncc::core::ADT::tensor::Index &index) const +{ + return _map.at(l.offset(_shape, index)); +} + +void Arg::reorder(const nncc::core::ADT::tensor::Layout &l) +{ + using nncc::core::ADT::tensor::IndexEnumerator; + + for (IndexEnumerator e{shape()}; e.valid(); e.advance()) + { + const auto offset = static_cast(l.offset(shape(), e.current())); + + at(e.current()) = coco::ElemID{offset}; + } +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Arg.test.cpp b/compiler/coco/core/src/IR/Arg.test.cpp new file mode 100644 index 00000000000..391e0590134 --- /dev/null +++ b/compiler/coco/core/src/IR/Arg.test.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Arg.h" + +#include +#include + +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; + +namespace +{ +class ArgTest : public ::testing::Test +{ +protected: + coco::Arg *allocate(const Shape &shape) + { + auto arg = new coco::Arg{shape}; + _allocated.emplace_back(arg); + return arg; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(ArgTest, constructor) +{ + const Shape shape{1, 3, 3, 1}; + + auto arg = allocate(shape); + + ASSERT_EQ(arg->shape(), shape); + ASSERT_TRUE(arg->name().empty()); + ASSERT_EQ(arg->bag(), nullptr); +} + +TEST_F(ArgTest, name_update) +{ + const Shape shape{1, 3, 3, 1}; + + auto arg = allocate(shape); + + arg->name("data"); + ASSERT_EQ(arg->name(), "data"); +} + +TEST_F(ArgTest, at) +{ + const Shape shape{1, 3, 3, 1}; + + auto arg = allocate(shape); + + coco::Arg *mutable_ptr = arg; + const coco::Arg *immutable_ptr = arg; + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + mutable_ptr->at(e.current()) = coco::ElemID{16}; + } + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + ASSERT_EQ(immutable_ptr->at(e.current()).value(), 16); + } +} + +TEST_F(ArgTest, reorder) +{ + const Shape shape{2, 2, 2, 2}; + + auto arg = allocate(shape); + + arg->reorder(); + + ASSERT_EQ(arg->at(Index{0, 0, 0, 0}).value(), 0); + ASSERT_EQ(arg->at(Index{0, 0, 0, 1}).value(), 1); +} diff --git a/compiler/coco/core/src/IR/AvgPool2D.test.cpp b/compiler/coco/core/src/IR/AvgPool2D.test.cpp new file mode 100644 index 00000000000..d62bc9b7d2f --- /dev/null +++ b/compiler/coco/core/src/IR/AvgPool2D.test.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsAvgPool2D : public coco::Op::Visitor +{ + bool visit(const coco::AvgPool2D *) override { return true; } +}; + +class AvgPool2DTest : public ::testing::Test +{ +public: + AvgPool2DTest() + { + // DO NOTHING + } + +protected: + coco::AvgPool2D *allocate(void) + { + auto op = new coco::AvgPool2D; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(AvgPool2DTest, initialization) +{ + auto op = allocate(); + + coco::AvgPool2D *mutable_ptr = op; + const coco::AvgPool2D *immutable_ptr = op; + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + // arg() should be nullptr on construction + ASSERT_EQ(immutable_ptr->arg(), nullptr); + + // divisor() SHOULD be unknow on construction + ASSERT_EQ(immutable_ptr->divisor(), coco::AvgPool2D::Divisor::Unknown); + + // window() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->window(), nullptr); + ASSERT_EQ(mutable_ptr->window(), immutable_ptr->window()); + + // pad() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->pad(), nullptr); + ASSERT_EQ(mutable_ptr->pad(), immutable_ptr->pad()); + + // stride() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->stride(), nullptr); + ASSERT_EQ(mutable_ptr->stride(), immutable_ptr->stride()); +} + +TEST_F(AvgPool2DTest, asAvgPool2D) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asAvgPool2D(), op); + ASSERT_EQ(mutable_base->asAvgPool2D(), immutable_base->asAvgPool2D()); +} + +TEST_F(AvgPool2DTest, accept) +{ + // Test 'AvgPool2D' class + auto op = allocate(); + + coco::AvgPool2D *mutable_ptr = op; + const coco::AvgPool2D *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsAvgPool2D{})); + ASSERT_TRUE(immutable_ptr->accept(IsAvgPool2D{})); +} + +TEST_F(AvgPool2DTest, disivor) +{ + auto op = allocate(); + + op->divisor(coco::AvgPool2D::Divisor::Static); + + ASSERT_EQ(op->divisor(), coco::AvgPool2D::Divisor::Static); +} diff --git a/compiler/coco/core/src/IR/Bag.cpp b/compiler/coco/core/src/IR/Bag.cpp new file mode 100644 index 00000000000..7dce4858705 --- /dev/null +++ b/compiler/coco/core/src/IR/Bag.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Bag.h" + +#include "coco/IR/Object.h" +#include "coco/IR/Read.h" +#include "coco/IR/Update.h" + +#include + +namespace coco +{ + +Bag::Bag(uint32_t size) : _size{size} +{ + // DO NOTHING +} + +Bag::~Bag() +{ + // All the references over a bag SHOULD be dropped before its destruction + assert(deps()->size() == 0); + assert(reads()->size() == 0); + assert(updates()->size() == 0); +} + +uint32_t Bag::size(void) const { return _size; } + +bool Bag::isInput(void) const { return _input != nullptr; } +bool Bag::isOutput(void) const { return _output != nullptr; } + +const DepSet *Bag::deps(void) const { return &_deps; } +const ReadSet *Bag::reads(void) const { return &_reads; } +const UpdateSet *Bag::updates(void) const { return &_updates; } + +void Bag::replaceWith(Bag *b) +{ + assert(!isInput() && !isOutput()); + + replaceAllDepsWith(b); + // Replace all the occurence inside Read + while (!(reads()->empty())) + { + auto read = *(reads()->begin()); + assert(read->bag() == this); + read->bag(b); + } + + // Replace all the occurence insider Update + while (!(updates()->empty())) + { + auto update = *(updates()->begin()); + assert(update->bag() == this); + update->bag(b); + } + + assert(deps()->empty()); + assert(reads()->empty()); + assert(updates()->empty()); +} + +void Bag::replaceAllDepsWith(Bag *b) +{ + // Replace all the occurence inside Dep + while (!(deps()->empty())) + { + auto dep = *(deps()->begin()); + assert(dep->bag() == this); + dep->bag(b); + } +} + +ObjectSet dependent_objects(const Bag *b) +{ + ObjectSet res; + + for (const auto &dep : *(b->deps())) + { + if (auto obj = dep->object()) + { + res.insert(obj); + } + } + + return res; +} + +Bag::ReaderSet readers(const Bag *b) +{ + Bag::ReaderSet res; + + for (auto obj : dependent_objects(b)) + { + for (auto consumer : consumers(obj)) + { + // NOTE Object::Consumer inherits Bag::Reader + res.insert(consumer); + } + } + + for (auto read : *b->reads()) + { + auto reader = read->reader(); + assert(reader != nullptr); + res.insert(reader); + } + + return res; +} + +Bag::UpdaterSet updaters(const Bag *b) +{ + Bag::UpdaterSet res; + + for (auto obj : dependent_objects(b)) + { + if (auto p = producer(obj)) + { + res.insert(p); + } + } + + for (auto update : *b->updates()) + { + auto updater = update->updater(); + assert(updater != nullptr); + res.insert(updater); + } + + return res; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Bag.test.cpp b/compiler/coco/core/src/IR/Bag.test.cpp new file mode 100644 index 00000000000..9995e81ef2f --- /dev/null +++ b/compiler/coco/core/src/IR/Bag.test.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Bag.h" + +#include + +TEST(IR_BAG, ctor_should_set_size) +{ + coco::Bag b{3}; + + ASSERT_EQ(b.size(), 3); + + // Bag has no read/updates at the beginning + EXPECT_EQ(b.reads()->size(), 0); + EXPECT_EQ(b.updates()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/BagManager.cpp b/compiler/coco/core/src/IR/BagManager.cpp new file mode 100644 index 00000000000..10fe69d571b --- /dev/null +++ b/compiler/coco/core/src/IR/BagManager.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BagManager.h" + +#include + +namespace coco +{ + +Bag *BagManager::create(uint32_t size) +{ + auto bag = stdex::make_unique(size); + modulize(bag.get()); + return take(std::move(bag)); +} + +void BagManager::destroy(Bag *b) { release(b); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/BagManager.test.cpp b/compiler/coco/core/src/IR/BagManager.test.cpp new file mode 100644 index 00000000000..bf135a95164 --- /dev/null +++ b/compiler/coco/core/src/IR/BagManager.test.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BagManager.h" + +#include + +TEST(IR_BAG_MANAGER, create) +{ + coco::BagManager mgr; + + auto bag = mgr.create(3); + + ASSERT_EQ(bag->size(), 3); +} + +TEST(IR_BAG_MANAGER, destruct) +{ + coco::BagManager mgr; + + auto b = mgr.create(3); + mgr.destroy(b); + + ASSERT_EQ(mgr.size(), 0); +} diff --git a/compiler/coco/core/src/IR/Block.cpp b/compiler/coco/core/src/IR/Block.cpp new file mode 100644 index 00000000000..14c026039fd --- /dev/null +++ b/compiler/coco/core/src/IR/Block.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Block.h" +#include "coco/IR/Module.h" + +#include + +namespace coco +{ + +template <> void DLinkedList::joined(Module *, Block *curr_blk) +{ + assert(!curr_blk->index().valid()); + uint32_t value = 0; + + if (auto prev_blk = curr_blk->prev()) + { + value = prev_blk->index().value() + 1; + } + + for (auto blk = curr_blk; blk; blk = blk->next()) + { + blk->_index.set(value++); + } +} + +template <> void DLinkedList::leaving(Module *, Block *curr_blk) +{ + assert(curr_blk->index().valid()); + uint32_t value = curr_blk->index().value(); + + for (auto blk = curr_blk->next(); blk; blk = blk->next()) + { + blk->_index.set(value++); + } + + curr_blk->_index.reset(); +} + +template <> BlockList *DLinkedList::head(Module *m) { return m->block(); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Block.test.cpp b/compiler/coco/core/src/IR/Block.test.cpp new file mode 100644 index 00000000000..c2acc89f711 --- /dev/null +++ b/compiler/coco/core/src/IR/Block.test.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Block.h" + +#include + +TEST(IR_BLOCK, default_block_has_empty_instr_list) +{ + coco::Block blk; + + ASSERT_TRUE(blk.instr()->empty()); + ASSERT_EQ(blk.instr()->head(), nullptr); + ASSERT_EQ(blk.instr()->tail(), nullptr); +} diff --git a/compiler/coco/core/src/IR/BlockIndex.cpp b/compiler/coco/core/src/IR/BlockIndex.cpp new file mode 100644 index 00000000000..8cb56724cbe --- /dev/null +++ b/compiler/coco/core/src/IR/BlockIndex.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BlockIndex.h" + +#include + +namespace coco +{ + +void BlockIndex::set(uint32_t value) +{ + assert(value != undefined); + _value = value; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/BlockIndex.test.cpp b/compiler/coco/core/src/IR/BlockIndex.test.cpp new file mode 100644 index 00000000000..68afb889e9a --- /dev/null +++ b/compiler/coco/core/src/IR/BlockIndex.test.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BlockIndex.h" + +#include + +namespace +{ + +class BlockIndexTest : public ::testing::Test +{ +}; + +} // namespace + +TEST_F(BlockIndexTest, default_constructor) +{ + coco::BlockIndex blk_ind; + + ASSERT_FALSE(blk_ind.valid()); +} + +TEST_F(BlockIndexTest, explicit_constructor) +{ + coco::BlockIndex blk_ind{3}; + + ASSERT_TRUE(blk_ind.valid()); + ASSERT_EQ(blk_ind.value(), 3); +} + +TEST_F(BlockIndexTest, operator_lt) +{ + // Valid index is always less than undefined one. + ASSERT_TRUE(coco::BlockIndex(3) < coco::BlockIndex()); + ASSERT_TRUE(coco::BlockIndex(3) < coco::BlockIndex(4)); +} diff --git a/compiler/coco/core/src/IR/BlockManager.cpp b/compiler/coco/core/src/IR/BlockManager.cpp new file mode 100644 index 00000000000..5e3b88173df --- /dev/null +++ b/compiler/coco/core/src/IR/BlockManager.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BlockManager.h" + +#include + +#include + +namespace coco +{ + +Block *BlockManager::create(void) +{ + auto blk = stdex::make_unique(); + modulize(blk.get()); + return take(std::move(blk)); +} + +void BlockManager::destroy(Block *blk) +{ + assert(blk->parent() == nullptr); + assert(blk->prev() == nullptr); + assert(blk->next() == nullptr); + release(blk); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/BlockManager.test.cpp b/compiler/coco/core/src/IR/BlockManager.test.cpp new file mode 100644 index 00000000000..94f69b773d9 --- /dev/null +++ b/compiler/coco/core/src/IR/BlockManager.test.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/BlockManager.h" + +#include +#include + +#include + +namespace +{ +class BlockManagerTest : public ::testing::Test +{ +public: + // Create a coco::BlockManager for testing + coco::BlockManager *allocate(void) + { + auto p = new coco::BlockManager; + _allocated.emplace_back(p); + return p; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(BlockManagerTest, create) +{ + auto mgr = allocate(); + auto blk = mgr->create(); + + ASSERT_NE(blk, nullptr); +} + +TEST_F(BlockManagerTest, destroy) +{ + auto mgr = allocate(); + auto blk_1 = mgr->create(); + auto blk_2 = mgr->create(); + + mgr->destroy(blk_1); + + ASSERT_EQ(mgr->size(), 1); + ASSERT_EQ(mgr->at(0), blk_2); +} diff --git a/compiler/coco/core/src/IR/Consumer.mock.h b/compiler/coco/core/src/IR/Consumer.mock.h new file mode 100644 index 00000000000..7d7cc492a3c --- /dev/null +++ b/compiler/coco/core/src/IR/Consumer.mock.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_CONSUMER_MOCK_H__ +#define __COCO_IR_CONSUMER_MOCK_H__ + +#include "coco/IR/Object.h" + +namespace +{ +namespace mock +{ +struct Consumer final : public coco::Object::Consumer +{ + coco::Instr *loc(void) override { return nullptr; } +}; +} // namespace mock +} // namespace + +#endif // __COCO_IR_CONSUMER_MOCK_H__ diff --git a/compiler/coco/core/src/IR/Conv2D.cpp b/compiler/coco/core/src/IR/Conv2D.cpp new file mode 100644 index 00000000000..19395a15869 --- /dev/null +++ b/compiler/coco/core/src/IR/Conv2D.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include + +namespace coco +{ + +Conv2D::Conv2D() : _ker{this}, _arg{this} +{ + // DO NOTHING +} + +uint32_t Conv2D::arity(void) const +{ + // Conv2D has one argument (IFM) + // NOTE This design is subject to change + return 1; +} + +Op *Conv2D::arg(DBGARG(uint32_t, n)) const +{ + assert(n < arity()); + return arg(); +} + +std::set Conv2D::uses(void) const +{ + std::set res; + + if (ker()) + { + res.insert(ker()); + } + + if (auto ifm = arg()) + { + for (auto obj : ifm->uses()) + { + res.insert(obj); + } + } + + return res; +} + +void Conv2D::ker(KernelObject *ker) { _ker.value(ker); } + +KernelObject *Conv2D::ker(void) const +{ + if (auto obj = _ker.value()) + { + assert(obj->asKernel() != nullptr); + return obj->asKernel(); + } + + return nullptr; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Conv2D.test.cpp b/compiler/coco/core/src/IR/Conv2D.test.cpp new file mode 100644 index 00000000000..df0a2470b75 --- /dev/null +++ b/compiler/coco/core/src/IR/Conv2D.test.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" +#include "coco/IR/ObjectManager.h" + +#include +#include + +#include + +#include + +using stdex::make_unique; + +namespace +{ +class Conv2DTest : public ::testing::Test +{ +public: + Conv2DTest() + { + // DO NOTHING + } + +protected: + coco::Conv2D *allocate(void) + { + auto op = new coco::Conv2D; + _allocated.emplace_back(op); + return op; + } + +protected: + coco::ObjectManager obj_mgr; + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(Conv2DTest, ctor) +{ + auto op = allocate(); + + // arg() should be initialized as nullptr on construction + ASSERT_EQ(op->arg(), nullptr); + // ker() should be initialized as nullptr on construction + ASSERT_EQ(op->ker(), nullptr); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + ASSERT_EQ(op->group(), 1); + + ASSERT_NE(op->pad(), nullptr); + ASSERT_EQ(op->pad()->top(), 0); + ASSERT_EQ(op->pad()->bottom(), 0); + ASSERT_EQ(op->pad()->left(), 0); + ASSERT_EQ(op->pad()->right(), 0); + + ASSERT_NE(op->stride(), nullptr); + ASSERT_EQ(op->stride()->vertical(), 1); + ASSERT_EQ(op->stride()->horizontal(), 1); +} + +TEST_F(Conv2DTest, asConv2D) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asConv2D(), op); + ASSERT_EQ(mutable_base->asConv2D(), immutable_base->asConv2D()); +} + +namespace +{ +struct IsConv2D : public coco::Op::Visitor +{ + bool visit(const coco::Conv2D *) override { return true; } +}; +} // namespace + +TEST_F(Conv2DTest, ker_update) +{ + // Prepare a kernel object for testing + auto obj = obj_mgr.create(); + + // Test 'Conv2D' class + auto op = allocate(); + + op->ker(obj); + ASSERT_EQ(op->ker(), obj); + + // Op now uses 'obj' + { + auto uses = op->uses(); + + ASSERT_NE(uses.find(obj), uses.end()); + } + + // ker method should enlist op itself as a consumer of a given kernel object + { + auto consumers = coco::consumers(obj); + + ASSERT_EQ(consumers.size(), 1); + ASSERT_NE(consumers.find(op), consumers.end()); + } +} + +TEST_F(Conv2DTest, accept) +{ + // Test 'Conv2D' class + auto op = allocate(); + + coco::Conv2D *mutable_ptr = op; + const coco::Conv2D *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsConv2D{})); + ASSERT_TRUE(immutable_ptr->accept(IsConv2D{})); +} + +TEST_F(Conv2DTest, destructor) +{ + // Prepare a kernel object for testing + auto obj = obj_mgr.create(); + + // Create 'Conv2D' op + auto op = make_unique(); + + op->ker(obj); + + // Destroy 'Conv2D' op + op.reset(); + + ASSERT_EQ(obj->uses()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/Def.cpp b/compiler/coco/core/src/IR/Def.cpp new file mode 100644 index 00000000000..1546b669371 --- /dev/null +++ b/compiler/coco/core/src/IR/Def.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Def.h" + +#include + +namespace coco +{ + +void Def::value(Object *value) +{ + if (_value) + { + _value->def(nullptr); + _value = nullptr; + } + + assert(_value == nullptr); + + if (value) + { + _value = value; + _value->def(this); + } + + assert(_value == value); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Def.test.cpp b/compiler/coco/core/src/IR/Def.test.cpp new file mode 100644 index 00000000000..98455c09ee6 --- /dev/null +++ b/compiler/coco/core/src/IR/Def.test.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Def.h" +#include "coco/IR/ObjectManager.h" + +#include "coco/IR/FeatureObject.h" + +#include + +#include "Producer.mock.h" + +#include + +using stdex::make_unique; + +namespace +{ +class DefTest : public ::testing::Test +{ +protected: + coco::ObjectManager obj_mgr; +}; +} // namespace + +TEST_F(DefTest, constructor) +{ + auto o = obj_mgr.create(); + + ::mock::Producer producer; + coco::Def def{&producer}; + + ASSERT_EQ(def.value(), nullptr); +} + +TEST_F(DefTest, value) +{ + auto o = obj_mgr.create(); + + ::mock::Producer producer; + coco::Def def{&producer}; + + def.value(o); + + ASSERT_EQ(def.value(), o); + + ASSERT_EQ(o->def(), &def); + + def.value(nullptr); + + ASSERT_EQ(o->def(), nullptr); +} + +TEST_F(DefTest, unlink_on_destruction) +{ + auto o = obj_mgr.create(); + + ::mock::Producer producer; + auto def = make_unique(&producer); + + def->value(o); + ASSERT_EQ(o->def(), def.get()); + + // Let's destruct the allocated slot + def.reset(nullptr); + + // The def of Object SHOULD BE updated + ASSERT_EQ(o->def(), nullptr); +} diff --git a/compiler/coco/core/src/IR/Dep.cpp b/compiler/coco/core/src/IR/Dep.cpp new file mode 100644 index 00000000000..6a5d3cafbe0 --- /dev/null +++ b/compiler/coco/core/src/IR/Dep.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Dep.h" +#include "coco/IR/Object.h" + +#include + +namespace coco +{ + +Dep::~Dep() { bag(nullptr); } + +void Dep::bag(Bag *bag) +{ + if (_bag != nullptr) + { + // Remove bag <-> dep link + assert(_bag->deps()->find(this) != _bag->deps()->end()); + _bag->mutable_deps()->erase(this); + + // Reset _bag + _bag = nullptr; + } + + assert(_bag == nullptr); + + if (bag != nullptr) + { + // Set _bag + _bag = bag; + + // Create bag <-> dep link + _bag->mutable_deps()->insert(this); + } + + assert(_bag == bag); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Dep.test.cpp b/compiler/coco/core/src/IR/Dep.test.cpp new file mode 100644 index 00000000000..e2104a8afae --- /dev/null +++ b/compiler/coco/core/src/IR/Dep.test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Dep.h" + +#include "coco/IR/BagManager.h" + +#include "coco/IR/ObjectManager.h" +#include "coco/IR/FeatureObject.h" + +#include + +using namespace nncc::core::ADT; + +namespace +{ +class DepTest : public ::testing::Test +{ +protected: + coco::BagManager bag_mgr; + coco::ObjectManager obj_mgr; +}; +} // namespace + +TEST_F(DepTest, default_constructor) +{ + coco::Dep dep; + + ASSERT_EQ(dep.bag(), nullptr); + ASSERT_EQ(dep.object(), nullptr); +} + +TEST_F(DepTest, bag_update) +{ + auto bag = bag_mgr.create(3); + + coco::Dep dep; + + // NOTE b->object() is not updated here + dep.bag(bag); + + ASSERT_EQ(dep.bag(), bag); +} + +TEST_F(DepTest, bag_update_with_link_and_object) +{ + auto bag = bag_mgr.create(3); + auto obj = obj_mgr.create(); + + coco::Dep dep; + + dep.object(obj); + + dep.bag(bag); + + auto deps = coco::dependent_objects(bag); + + ASSERT_EQ(deps.size(), 1); + ASSERT_NE(deps.count(obj), 0); +} diff --git a/compiler/coco/core/src/IR/ElemID.cpp b/compiler/coco/core/src/IR/ElemID.cpp new file mode 100644 index 00000000000..145bb986a43 --- /dev/null +++ b/compiler/coco/core/src/IR/ElemID.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/ElemID.h" + +namespace coco +{ + +bool operator==(const ElemID &lhs, const ElemID &rhs) { return lhs.value() == rhs.value(); } +bool operator<(const ElemID &lhs, const ElemID &rhs) { return lhs.value() < rhs.value(); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/ElemID.test.cpp b/compiler/coco/core/src/IR/ElemID.test.cpp new file mode 100644 index 00000000000..dff2fa27c3d --- /dev/null +++ b/compiler/coco/core/src/IR/ElemID.test.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/ElemID.h" + +#include + +#include + +TEST(IR_ELEM_ID, constructor) +{ + coco::ElemID id{128}; + + ASSERT_EQ(id.value(), 128); +} + +TEST(IR_ELEM_ID, copy) +{ + coco::ElemID src{16}; + coco::ElemID dst{32}; + + dst = src; + + ASSERT_EQ(dst.value(), 16); +} + +TEST(IR_ELEM_ID, std_vector_compatible) +{ + // ElemID SHOULD be compatible with standard container (including std::vector) + std::vector vec; + + vec.resize(16); + vec.clear(); + vec.emplace_back(coco::ElemID{128}); + + ASSERT_EQ(vec.at(0).value(), 128); +} + +TEST(IR_ELEM_ID, operator_eq) +{ + ASSERT_TRUE(coco::ElemID{16} == coco::ElemID{16}); + ASSERT_FALSE(coco::ElemID{16} == coco::ElemID{17}); +} + +TEST(IR_ELEM_ID, operator_lt) +{ + ASSERT_FALSE(coco::ElemID{16} < coco::ElemID{16}); + ASSERT_TRUE(coco::ElemID{16} < coco::ElemID{17}); +} diff --git a/compiler/coco/core/src/IR/EntityManager.cpp b/compiler/coco/core/src/IR/EntityManager.cpp new file mode 100644 index 00000000000..f6f2cb38219 --- /dev/null +++ b/compiler/coco/core/src/IR/EntityManager.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/EntityManager.h" + +// NOTE Do NOT delete this file; this file enforces compiler to check whether 'EntityManager.h' is +// complete. diff --git a/compiler/coco/core/src/IR/Eval.cpp b/compiler/coco/core/src/IR/Eval.cpp new file mode 100644 index 00000000000..dcf579049d5 --- /dev/null +++ b/compiler/coco/core/src/IR/Eval.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Instrs.h" +#include "coco/IR/Op.h" + +namespace coco +{ + +Eval::Eval() : _out{this}, _step{this} +{ + // DO NOTHING +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Eval.test.cpp b/compiler/coco/core/src/IR/Eval.test.cpp new file mode 100644 index 00000000000..6469f676377 --- /dev/null +++ b/compiler/coco/core/src/IR/Eval.test.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Instrs.h" +#include "coco/IR/ObjectManager.h" +#include "coco/IR/OpManager.h" + +#include + +namespace +{ +class EvalTest : public ::testing::Test +{ +public: + virtual ~EvalTest() = default; + +protected: + coco::Eval *allocate(void) + { + auto ins = new coco::Eval{}; + _allocated.emplace_back(ins); + return ins; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(EvalTest, constructor) +{ + auto ins = allocate(); + + ASSERT_EQ(ins->out(), nullptr); + ASSERT_EQ(ins->op(), nullptr); +} + +TEST_F(EvalTest, asEval) +{ + auto ins = allocate(); + + coco::Instr *mutable_ptr = ins; + const coco::Instr *immutable_ptr = ins; + + ASSERT_NE(mutable_ptr->asEval(), nullptr); + ASSERT_EQ(mutable_ptr->asEval(), immutable_ptr->asEval()); +} diff --git a/compiler/coco/core/src/IR/FeatureLayouts.cpp b/compiler/coco/core/src/IR/FeatureLayouts.cpp new file mode 100644 index 00000000000..98423e01f18 --- /dev/null +++ b/compiler/coco/core/src/IR/FeatureLayouts.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/FeatureLayouts.h" + +#include +#include + +#include + +using namespace nncc::core::ADT::feature; + +// +// BCHW Layout +// +namespace coco +{ +namespace FeatureLayouts +{ + +const FeatureLayout::ID *BCHW::uid(void) +{ + struct LayoutID final : public FeatureLayout::ID + { + }; + static LayoutID id; + return &id; +} + +ElemID BCHW::at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const +{ + static CHWLayout l; + + uint32_t offset = 0; + offset += b * num_elements(_shape); + offset += l.offset(_shape, ch, row, col); + return ElemID{offset}; +} + +std::unique_ptr BCHW::create(const nncc::core::ADT::feature::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new BCHW{FeatureShape{shape}}}; +} + +} // namespace FeatureLayouts +} // namespace coco + +// +// BHWC Layout +// +namespace coco +{ +namespace FeatureLayouts +{ + +const FeatureLayout::ID *BHWC::uid(void) +{ + struct LayoutID final : public FeatureLayout::ID + { + }; + static LayoutID id; + return &id; +} + +ElemID BHWC::at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const +{ + static HWCLayout l; + + uint32_t offset = 0; + offset += b * num_elements(_shape); + offset += l.offset(_shape, ch, row, col); + + return ElemID{offset}; +} + +std::unique_ptr BHWC::create(const nncc::core::ADT::feature::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new BHWC{FeatureShape{shape}}}; +} + +std::unique_ptr BHWC::create(const FeatureShape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new BHWC{shape}}; +} + +} // namespace FeatureLayouts +} // namespace coco + +// +// BC: Channel-major Channel-wise Layout +// +namespace coco +{ +namespace FeatureLayouts +{ + +const FeatureLayout::ID *BC::uid(void) +{ + struct LayoutID final : public FeatureLayout::ID + { + }; + static LayoutID id; + return &id; +} + +// NOTE BC layout ignores row/col as its name suggests +ElemID BC::at(uint32_t b, uint32_t ch, uint32_t /*row*/, uint32_t /*col*/) const +{ + assert(b < shape().batch()); + + uint32_t offset = 0; + + offset += b * _shape.depth(); + offset += ch; + + return ElemID{offset}; +} + +std::unique_ptr BC::create(const nncc::core::ADT::feature::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new BC{FeatureShape{shape}}}; +} + +} // namespace FeatureLayouts +} // namespace coco + +// +// Generic Layout +// +namespace coco +{ +namespace FeatureLayouts +{ + +Generic::Generic(const FeatureShape &shape) : _shape{shape} +{ + _content.resize(_shape.batch() * num_elements(_shape)); +} + +const FeatureLayout::ID *Generic::uid(void) +{ + struct LayoutID final : public FeatureLayout::ID + { + }; + static LayoutID id; + return &id; +} + +uint32_t Generic::offset(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const +{ + static nncc::core::ADT::feature::CHWLayout l{}; + + uint32_t res = 0; + + res += b * num_elements(_shape); + res += l.offset(shape(), ch, row, col); + + return res; +} + +ElemID &Generic::at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) +{ + return _content.at(offset(b, ch, row, col)); +} + +ElemID Generic::at(uint32_t b, uint32_t ch, uint32_t row, uint32_t col) const +{ + return _content.at(offset(b, ch, row, col)); +} + +void Generic::reorder(const nncc::core::ADT::feature::Layout &l) +{ + assert(shape().batch() == 1); + + for (uint32_t ch = 0; ch < shape().depth(); ++ch) + { + for (uint32_t row = 0; row < shape().height(); ++row) + { + for (uint32_t col = 0; col < shape().width(); ++col) + { + at(0, ch, row, col) = ElemID{l.offset(shape(), ch, row, col)}; + } + } + } +} + +std::unique_ptr Generic::create(const nncc::core::ADT::feature::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new Generic{shape}}; +} + +} // namespace FeatureLayouts +} // namespace coco diff --git a/compiler/coco/core/src/IR/FeatureLayouts.test.cpp b/compiler/coco/core/src/IR/FeatureLayouts.test.cpp new file mode 100644 index 00000000000..9f9772dd821 --- /dev/null +++ b/compiler/coco/core/src/IR/FeatureLayouts.test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/FeatureLayouts.h" + +#include + +using namespace nncc::core::ADT; + +TEST(FeatureLayoutsTest, BC) +{ + // NOTE The current implementation uses a hard-coded "batch" value + const uint32_t B = 1; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 5; + + auto l = coco::FeatureLayouts::BC::create(feature::Shape{C, H, W}); + + ASSERT_EQ(l->batch(), B); + ASSERT_EQ(l->depth(), C); + ASSERT_EQ(l->height(), H); + ASSERT_EQ(l->width(), W); + + // Check whether BC layout is actually channel-wise + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + ASSERT_EQ(l->at(b, ch, 0, 0), l->at(b, ch, row, col)); + } + } + } + } + + // Check whether BC layout is actually channel-major + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 1; ch < C; ++ch) + { + ASSERT_EQ(l->at(b, ch - 1, 0, 0).value() + 1, l->at(b, ch, 0, 0).value()); + } + } + + for (uint32_t b = 1; b < B; ++b) + { + ASSERT_EQ(l->at(b - 1, C - 1, 0, 0).value() + 1, l->at(b, 0, 0, 0).value()); + } +} diff --git a/compiler/coco/core/src/IR/FeatureObject.cpp b/compiler/coco/core/src/IR/FeatureObject.cpp new file mode 100644 index 00000000000..46de988743b --- /dev/null +++ b/compiler/coco/core/src/IR/FeatureObject.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/FeatureObject.h" + +#include + +namespace coco +{ + +FeatureObject::~FeatureObject() +{ + // DO NOTHING +} + +const FeatureShape &FeatureObject::shape(void) const { return _layout->shape(); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/FeatureObject.test.cpp b/compiler/coco/core/src/IR/FeatureObject.test.cpp new file mode 100644 index 00000000000..23188f8669d --- /dev/null +++ b/compiler/coco/core/src/IR/FeatureObject.test.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/FeatureObject.h" +#include "coco/IR/FeatureLayouts.h" + +#include +#include + +#include + +using namespace nncc::core::ADT; + +namespace +{ +class FeatureObjectTest : public ::testing::Test +{ +protected: + coco::FeatureObject *allocate() + { + auto o = new coco::FeatureObject{}; + _allocated.emplace_back(o); + return o; + } + + // TODO Deprecate this method + coco::FeatureObject *allocate(const coco::FeatureShape &shape) + { + auto o = new coco::FeatureObject{}; + o->layout(coco::FeatureLayouts::Generic::create(shape)); + _allocated.emplace_back(o); + return o; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(FeatureObjectTest, ctor) +{ + const coco::FeatureShape shape{1, 3, 3}; + + auto o = allocate(shape); + + ASSERT_EQ(o->shape(), shape); + ASSERT_EQ(o->kind(), coco::Object::Kind::Feature); +} + +// TODO Reimplement this test as a test for GenericFeatureLayout +#if 0 +TEST_F(FeatureObjectTest, at) +{ + const uint32_t C = 1; + const uint32_t H = 3; + const uint32_t W = 3; + + const coco::FeatureShape shape{C, H, W}; + + auto o = allocate(shape); + + coco::FeatureObject *mutable_ptr = o; + const coco::FeatureObject *immutable_ptr = o; + + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + mutable_ptr->at(ch, row, col) = coco::ElemID{16}; + } + } + } + + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + ASSERT_EQ(immutable_ptr->at(ch, row, col).value(), 16); + } + } + } +} +#endif + +TEST_F(FeatureObjectTest, asFeature) +{ + const coco::FeatureShape shape{1, 3, 3}; + + auto o = allocate(shape); + + coco::Object *mutable_object = o; + const coco::Object *immutable_object = o; + + ASSERT_NE(mutable_object->asFeature(), nullptr); + ASSERT_EQ(mutable_object->asFeature(), immutable_object->asFeature()); +} + +TEST_F(FeatureObjectTest, casting_helpers) +{ + auto obj = allocate(); + + ASSERT_TRUE(coco::isa(obj)); + ASSERT_EQ(coco::cast(obj), obj); + ASSERT_EQ(coco::safe_cast(obj), obj); +} diff --git a/compiler/coco/core/src/IR/FeatureShape.test.cpp b/compiler/coco/core/src/IR/FeatureShape.test.cpp new file mode 100644 index 00000000000..ceeab02b7d6 --- /dev/null +++ b/compiler/coco/core/src/IR/FeatureShape.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/FeatureShape.h" + +#include + +TEST(FeatureShapeTest, constructor_with_4_arguments) +{ + const coco::FeatureShape shape{1, 2, 3, 4}; + + ASSERT_EQ(shape.batch(), 1); + ASSERT_EQ(shape.depth(), 2); + ASSERT_EQ(shape.height(), 3); + ASSERT_EQ(shape.width(), 4); +} diff --git a/compiler/coco/core/src/IR/Input.cpp b/compiler/coco/core/src/IR/Input.cpp new file mode 100644 index 00000000000..4385ac26c50 --- /dev/null +++ b/compiler/coco/core/src/IR/Input.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Input.h" + +#include + +namespace coco +{ + +Input::Input(const nncc::core::ADT::tensor::Shape &shape) : Arg{shape} +{ + // DO NOT?HING +} + +void Input::onTake(Bag *bag) +{ + assert(bag->input() == nullptr); + bag->input(this); +} + +void Input::onRelease(Bag *bag) +{ + assert(bag->input() == this); + bag->input(nullptr); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Input.test.cpp b/compiler/coco/core/src/IR/Input.test.cpp new file mode 100644 index 00000000000..7cc1731cc37 --- /dev/null +++ b/compiler/coco/core/src/IR/Input.test.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Input.h" +#include "coco/IR/BagManager.h" + +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +TEST(IR_INPUT, ctor_should_set_shape) +{ + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Input input{shape}; + + ASSERT_EQ(input.shape(), shape); + ASSERT_TRUE(input.name().empty()); +} + +TEST(IR_INPUT, bag_update) +{ + // Create a bag + coco::BagManager bag_mgr; + + auto bag = bag_mgr.create(9); + + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Input input{shape}; + + input.bag(bag); + ASSERT_EQ(input.bag(), bag); + + // bag(...) method SHOULD update 'bag' type + ASSERT_TRUE(bag->isInput()); +} + +TEST(IR_INPUT, name_update) +{ + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Input input{shape}; + + input.name("data"); + ASSERT_EQ(input.name(), "data"); +} + +TEST(IR_INPUT, at) +{ + const Shape shape{1, 3, 3, 1}; + coco::Input input{shape}; + + coco::Input *mutable_ptr = &input; + const coco::Input *immutable_ptr = &input; + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + mutable_ptr->at(e.current()) = coco::ElemID{16}; + } + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + ASSERT_EQ(immutable_ptr->at(e.current()).value(), 16); + } +} diff --git a/compiler/coco/core/src/IR/InputManager.cpp b/compiler/coco/core/src/IR/InputManager.cpp new file mode 100644 index 00000000000..6d5b9470bdc --- /dev/null +++ b/compiler/coco/core/src/IR/InputManager.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InputManager.h" + +#include + +namespace coco +{ + +Input *InputManager::create(const nncc::core::ADT::tensor::Shape &shape) +{ + auto input = stdex::make_unique(shape); + modulize(input.get()); + return take(std::move(input)); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/InputManager.test.cpp b/compiler/coco/core/src/IR/InputManager.test.cpp new file mode 100644 index 00000000000..be43113b436 --- /dev/null +++ b/compiler/coco/core/src/IR/InputManager.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InputManager.h" + +#include + +TEST(IR_INPUT_MANAGER, make) +{ + coco::InputManager mgr; + + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + auto input = mgr.create(shape); + + ASSERT_EQ(input->shape(), shape); +} diff --git a/compiler/coco/core/src/IR/Instr.cpp b/compiler/coco/core/src/IR/Instr.cpp new file mode 100644 index 00000000000..9f000ba1c7b --- /dev/null +++ b/compiler/coco/core/src/IR/Instr.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Instr.h" +#include "coco/IR/Block.h" + +#include + +namespace coco +{ + +template <> void DLinkedList::joined(Block *, Instr *curr_ins) +{ + assert(!curr_ins->index().valid()); + uint32_t value = 0; + + if (auto prev_ins = curr_ins->prev()) + { + value = prev_ins->index().value() + 1; + } + + for (auto ins = curr_ins; ins; ins = ins->next()) + { + ins->_index.set(value++); + } +} + +template <> void DLinkedList::leaving(Block *, Instr *curr_ins) +{ + assert(curr_ins->index().valid()); + uint32_t value = curr_ins->index().value(); + + for (auto ins = curr_ins->next(); ins; ins = ins->next()) + { + ins->_index.set(value++); + } + + curr_ins->_index.reset(); +} + +template <> InstrList *DLinkedList::head(Block *b) { return b->instr(); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/InstrIndex.cpp b/compiler/coco/core/src/IR/InstrIndex.cpp new file mode 100644 index 00000000000..c447cfc420d --- /dev/null +++ b/compiler/coco/core/src/IR/InstrIndex.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InstrIndex.h" + +#include + +namespace coco +{ + +void InstrIndex::set(uint32_t value) +{ + assert(value != undefined); + _value = value; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/InstrIndex.test.cpp b/compiler/coco/core/src/IR/InstrIndex.test.cpp new file mode 100644 index 00000000000..40f5d49defb --- /dev/null +++ b/compiler/coco/core/src/IR/InstrIndex.test.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InstrIndex.h" + +#include + +namespace +{ + +class InstrIndexTest : public ::testing::Test +{ +}; + +} // namespace + +TEST_F(InstrIndexTest, default_constructor) +{ + coco::InstrIndex ins_ind; + + ASSERT_FALSE(ins_ind.valid()); +} + +TEST_F(InstrIndexTest, explicit_constructor) +{ + coco::InstrIndex ins_ind{3}; + + ASSERT_TRUE(ins_ind.valid()); + ASSERT_EQ(ins_ind.value(), 3); +} + +TEST_F(InstrIndexTest, operator_lt) +{ + // Valid index is always less than undefined one. + ASSERT_TRUE(coco::InstrIndex(3) < coco::InstrIndex()); + ASSERT_TRUE(coco::InstrIndex(3) < coco::InstrIndex(4)); +} diff --git a/compiler/coco/core/src/IR/InstrManager.cpp b/compiler/coco/core/src/IR/InstrManager.cpp new file mode 100644 index 00000000000..32f1cbf28d7 --- /dev/null +++ b/compiler/coco/core/src/IR/InstrManager.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InstrManager.h" + +#include "coco/IR/Op.h" + +#include + +namespace coco +{ + +void InstrManager::destroy(Instr *ins) +{ + // ins SHOULD BE detached from any block before destroy call + assert(ins->parent() == nullptr); + release(ins); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/InstrManager.test.cpp b/compiler/coco/core/src/IR/InstrManager.test.cpp new file mode 100644 index 00000000000..23d9f8e8656 --- /dev/null +++ b/compiler/coco/core/src/IR/InstrManager.test.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/InstrManager.h" +#include "coco/IR/Op.h" + +#include + +namespace +{ + +// Dummy custom instruction for testing +struct CustomInstr final : public coco::Instr +{ +}; + +class InstrManagerTest : public ::testing::Test +{ +public: + virtual ~InstrManagerTest() = default; + +protected: + coco::InstrManager mgr; +}; +} // namespace + +TEST_F(InstrManagerTest, create_Shuffle) +{ + auto ins = mgr.create(); + ASSERT_NE(ins, nullptr); + mgr.destroy(ins); +} + +TEST_F(InstrManagerTest, create_Custom) +{ + auto ins = mgr.create(); + ASSERT_NE(ins, nullptr); + mgr.destroy(ins); +} diff --git a/compiler/coco/core/src/IR/KernelLayouts.cpp b/compiler/coco/core/src/IR/KernelLayouts.cpp new file mode 100644 index 00000000000..6e9a1575a56 --- /dev/null +++ b/compiler/coco/core/src/IR/KernelLayouts.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/KernelLayouts.h" + +#include +#include + +#include + +using namespace nncc::core::ADT::kernel; + +using nncc::core::ADT::kernel::num_elements; +using nncc::core::ADT::kernel::Shape; + +// +// NCHW Layout +// +namespace coco +{ +namespace KernelLayouts +{ + +const KernelLayout::ID *NCHW::uid(void) +{ + struct LayoutID final : public KernelLayout::ID + { + }; + static LayoutID id; + return &id; +} + +ElemID NCHW::at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const +{ + static NCHWLayout l; + return ElemID{l.offset(_shape, n, ch, row, col)}; +} + +std::unique_ptr NCHW::create(const nncc::core::ADT::kernel::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new NCHW{shape}}; +} + +} // namespace KernelLayouts +} // namespace coco + +// +// NHWC Layout +// +namespace coco +{ +namespace KernelLayouts +{ + +const KernelLayout::ID *NHWC::uid(void) +{ + struct LayoutID final : public KernelLayout::ID + { + }; + static LayoutID id; + return &id; +} + +ElemID NHWC::at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const +{ + static NHWCLayout l; + return ElemID{l.offset(_shape, n, ch, row, col)}; +} + +std::unique_ptr NHWC::create(const nncc::core::ADT::kernel::Shape &shape) +{ + // NOTE It is impossible to use make_unique here as the constructor is private + return std::unique_ptr{new NHWC{shape}}; +} + +} // namespace KernelLayouts +} // namespace coco + +// +// Generic Layout +// +namespace +{ + +nncc::core::ADT::kernel::NCHWLayout l; + +} // namespace + +namespace coco +{ +namespace KernelLayouts +{ + +Generic::Generic(const nncc::core::ADT::kernel::Shape &shape) : _shape{shape} +{ + _content.resize(num_elements(_shape)); +} + +const KernelLayout::ID *Generic::uid(void) +{ + struct LayoutID final : public KernelLayout::ID + { + }; + static LayoutID id; + return &id; +} + +ElemID &Generic::at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) +{ + return _content.at(l.offset(_shape, n, ch, row, col)); +} + +ElemID Generic::at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const +{ + return _content.at(l.offset(_shape, n, ch, row, col)); +} + +void Generic::reorder(const nncc::core::ADT::kernel::Layout &l) +{ + for (uint32_t n = 0; n < shape().count(); ++n) + { + for (uint32_t ch = 0; ch < shape().depth(); ++ch) + { + for (uint32_t row = 0; row < shape().height(); ++row) + { + for (uint32_t col = 0; col < shape().width(); ++col) + { + at(n, ch, row, col) = ElemID{l.offset(shape(), n, ch, row, col)}; + } + } + } + } +} + +std::unique_ptr Generic::create(const Shape &shape) +{ + return std::unique_ptr{new Generic{shape}}; +} + +} // namespace KernelLayouts +} // namespace coco diff --git a/compiler/coco/core/src/IR/KernelLayouts.test.cpp b/compiler/coco/core/src/IR/KernelLayouts.test.cpp new file mode 100644 index 00000000000..df13cb051c7 --- /dev/null +++ b/compiler/coco/core/src/IR/KernelLayouts.test.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/KernelLayouts.h" + +#include +#include + +#include + +using namespace nncc::core::ADT; + +TEST(KernelLayoutsTest, NCHW_increment) +{ + const uint32_t N = 2; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 4; + + auto l = coco::KernelLayouts::NCHW::create(kernel::Shape{N, C, H, W}); + + // check NCHW order + ASSERT_EQ(l->at(0, 0, 0, 1).value(), l->at(0, 0, 0, 0).value() + 1); + ASSERT_EQ(l->at(0, 0, 1, 0).value(), l->at(0, 0, 0, 0).value() + W); + ASSERT_EQ(l->at(0, 1, 0, 0).value(), l->at(0, 0, 0, 0).value() + H * W); + ASSERT_EQ(l->at(1, 0, 0, 0).value(), l->at(0, 0, 0, 0).value() + C * H * W); +} + +TEST(KernelLayoutsTest, NHWC_increment) +{ + const uint32_t N = 2; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 4; + + auto l = coco::KernelLayouts::NHWC::create(kernel::Shape{N, C, H, W}); + + // check NHWC order + ASSERT_EQ(l->at(0, 1, 0, 0).value(), l->at(0, 0, 0, 0).value() + 1); + ASSERT_EQ(l->at(0, 0, 0, 1).value(), l->at(0, 0, 0, 0).value() + C); + ASSERT_EQ(l->at(0, 0, 1, 0).value(), l->at(0, 0, 0, 0).value() + W * C); + ASSERT_EQ(l->at(1, 0, 0, 0).value(), l->at(0, 0, 0, 0).value() + H * W * C); +} + +TEST(KernelLayoutsTest, Generic_increment) +{ + const uint32_t N = 2; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 4; + + auto nchw = coco::KernelLayouts::Generic::create(kernel::Shape{N, C, H, W}); + auto nhwc = coco::KernelLayouts::Generic::create(kernel::Shape{N, C, H, W}); + + // reorder + nchw->reorder(kernel::NCHWLayout()); + nhwc->reorder(kernel::NHWCLayout()); + + // check NCHW order + ASSERT_EQ(nchw->at(0, 0, 0, 1).value(), nchw->at(0, 0, 0, 0).value() + 1); + ASSERT_EQ(nchw->at(0, 0, 1, 0).value(), nchw->at(0, 0, 0, 0).value() + W); + ASSERT_EQ(nchw->at(0, 1, 0, 0).value(), nchw->at(0, 0, 0, 0).value() + H * W); + ASSERT_EQ(nchw->at(1, 0, 0, 0).value(), nchw->at(0, 0, 0, 0).value() + C * H * W); + + // check NHWC order + ASSERT_EQ(nhwc->at(0, 1, 0, 0).value(), nhwc->at(0, 0, 0, 0).value() + 1); + ASSERT_EQ(nhwc->at(0, 0, 0, 1).value(), nhwc->at(0, 0, 0, 0).value() + C); + ASSERT_EQ(nhwc->at(0, 0, 1, 0).value(), nhwc->at(0, 0, 0, 0).value() + W * C); + ASSERT_EQ(nhwc->at(1, 0, 0, 0).value(), nhwc->at(0, 0, 0, 0).value() + H * W * C); +} + +TEST(KernelLayoutsTest, Generic_at) +{ + const uint32_t N = 2; + const uint32_t C = 3; + const uint32_t H = 4; + const uint32_t W = 4; + + auto l = coco::KernelLayouts::Generic::create(kernel::Shape{N, C, H, W}); + + ASSERT_NE(l.get(), nullptr); + + coco::KernelLayouts::Generic *mutable_ptr = l.get(); + const coco::KernelLayouts::Generic *immutable_ptr = l.get(); + + for (uint32_t n = 0; n < N; ++n) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + mutable_ptr->at(n, ch, row, col) = coco::ElemID{16}; + } + } + } + } + + for (uint32_t n = 0; n < N; ++n) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + ASSERT_EQ(immutable_ptr->at(n, ch, row, col).value(), 16); + } + } + } + } +} diff --git a/compiler/coco/core/src/IR/KernelObject.cpp b/compiler/coco/core/src/IR/KernelObject.cpp new file mode 100644 index 00000000000..79c298b43e6 --- /dev/null +++ b/compiler/coco/core/src/IR/KernelObject.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/KernelObject.h" +#include "coco/IR/KernelLayouts.h" + +#include + +namespace coco +{ + +KernelObject::KernelObject(const nncc::core::ADT::kernel::Shape &shape) +{ + _layout = KernelLayouts::Generic::create(shape); +} + +KernelObject::~KernelObject() +{ + // DO NOTHING +} + +const nncc::core::ADT::kernel::Shape &KernelObject::shape(void) const { return _layout->shape(); } + +ElemID KernelObject::at(uint32_t n, uint32_t ch, uint32_t row, uint32_t col) const +{ + return _layout->at(n, ch, row, col); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/KernelObject.test.cpp b/compiler/coco/core/src/IR/KernelObject.test.cpp new file mode 100644 index 00000000000..f227764ca65 --- /dev/null +++ b/compiler/coco/core/src/IR/KernelObject.test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/KernelObject.h" + +#include +#include + +#include + +using namespace nncc::core::ADT; + +namespace +{ +class KernelObjectTest : public ::testing::Test +{ +protected: + coco::KernelObject *allocate() + { + auto o = new coco::KernelObject{}; + _allocated.emplace_back(o); + return o; + } + + coco::KernelObject *allocate(const kernel::Shape &shape) + { + auto o = new coco::KernelObject{shape}; + _allocated.emplace_back(o); + return o; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(KernelObjectTest, constructor) +{ + const nncc::core::ADT::kernel::Shape shape{1, 1, 3, 3}; + auto o = allocate(shape); + + ASSERT_EQ(o->shape(), shape); + ASSERT_EQ(o->kind(), coco::Object::Kind::Kernel); +} + +TEST_F(KernelObjectTest, asKernel) +{ + const nncc::core::ADT::kernel::Shape shape{1, 1, 3, 3}; + auto o = allocate(shape); + + coco::Object *mutable_object = o; + const coco::Object *immutable_object = o; + + ASSERT_NE(mutable_object->asKernel(), nullptr); + ASSERT_EQ(mutable_object->asKernel(), immutable_object->asKernel()); +} + +TEST_F(KernelObjectTest, casting_helpers) +{ + auto obj = allocate(); + + ASSERT_TRUE(coco::isa(obj)); + ASSERT_EQ(coco::cast(obj), obj); + ASSERT_EQ(coco::safe_cast(obj), obj); +} diff --git a/compiler/coco/core/src/IR/Load.cpp b/compiler/coco/core/src/IR/Load.cpp new file mode 100644 index 00000000000..4985e925419 --- /dev/null +++ b/compiler/coco/core/src/IR/Load.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include + +namespace coco +{ + +Load::Load() : _obj{this} +{ + // DO NOTHING +} + +uint32_t Load::arity(void) const +{ + // Load has no child Op + return 0; +} + +Op *Load::arg(uint32_t) const +{ + assert(!"Load has no argument"); + return nullptr; +} + +std::set Load::uses(void) const +{ + std::set res; + + if (auto obj = object()) + { + res.insert(obj); + } + + return res; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/MaxPool2D.test.cpp b/compiler/coco/core/src/IR/MaxPool2D.test.cpp new file mode 100644 index 00000000000..864edddb3c2 --- /dev/null +++ b/compiler/coco/core/src/IR/MaxPool2D.test.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsMaxPool2D : public coco::Op::Visitor +{ + bool visit(const coco::MaxPool2D *) override { return true; } +}; + +class MaxPool2DTest : public ::testing::Test +{ +public: + MaxPool2DTest() + { + // DO NOTHING + } + +protected: + coco::MaxPool2D *allocate(void) + { + auto op = new coco::MaxPool2D; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(MaxPool2DTest, initialization) +{ + auto op = allocate(); + + coco::MaxPool2D *mutable_ptr = op; + const coco::MaxPool2D *immutable_ptr = op; + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + // arg() should be nullptr on construction + ASSERT_EQ(immutable_ptr->arg(), nullptr); + + // window() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->window(), nullptr); + ASSERT_EQ(mutable_ptr->window(), immutable_ptr->window()); + + // stride() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->stride(), nullptr); + ASSERT_EQ(mutable_ptr->stride(), immutable_ptr->stride()); + + // pad() SHOULD return a valid pointer + ASSERT_NE(mutable_ptr->pad(), nullptr); + ASSERT_EQ(mutable_ptr->pad(), immutable_ptr->pad()); +} + +TEST_F(MaxPool2DTest, asMaxPool2D) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asMaxPool2D(), op); + ASSERT_EQ(mutable_base->asMaxPool2D(), immutable_base->asMaxPool2D()); +} + +TEST_F(MaxPool2DTest, accept) +{ + // Test 'MaxPool2D' class + auto op = allocate(); + + coco::MaxPool2D *mutable_ptr = op; + const coco::MaxPool2D *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsMaxPool2D{})); + ASSERT_TRUE(immutable_ptr->accept(IsMaxPool2D{})); +} diff --git a/compiler/coco/core/src/IR/Module.cpp b/compiler/coco/core/src/IR/Module.cpp new file mode 100644 index 00000000000..0b65ceedc28 --- /dev/null +++ b/compiler/coco/core/src/IR/Module.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Module.h" + +#include + +using stdex::make_unique; + +namespace +{ + +struct EntityManagerImpl final : public coco::EntityManager +{ +public: + std::unique_ptr _bag; + +public: + coco::BagManager *bag(void) override { return _bag.get(); } + const coco::BagManager *bag(void) const override { return _bag.get(); } + +public: + std::unique_ptr _object; + +public: + coco::ObjectManager *object(void) override { return _object.get(); } + const coco::ObjectManager *object(void) const override { return _object.get(); } + +public: + std::unique_ptr _op; + +public: + coco::OpManager *op(void) override { return _op.get(); } + const coco::OpManager *op(void) const override { return _op.get(); } + +public: + coco::InstrManager *instr(void) override { return _instr.get(); } + const coco::InstrManager *instr(void) const override { return _instr.get(); } + +public: + coco::BlockManager *block(void) override { return _block.get(); } + const coco::BlockManager *block(void) const override { return _block.get(); } + +public: + std::unique_ptr _input; + +public: + coco::InputManager *input(void) override { return _input.get(); } + const coco::InputManager *input(void) const override { return _input.get(); } + +public: + std::unique_ptr _output; + +public: + coco::OutputManager *output(void) override { return _output.get(); } + const coco::OutputManager *output(void) const override { return _output.get(); } + +public: + // WARN Do NOT change the order of these fields: _block -> _instr + // + // Note that each instruction may have a reference to a block, and + // the destructor of Instr accesses this 'block' reference. + // + // Thus, Instr entities SHOULD BE destructed before Block entities are destructed. + std::unique_ptr _block; + std::unique_ptr _instr; +}; + +} // namespace + +namespace +{ + +class ModuleImpl final : public coco::Module +{ +public: + coco::EntityManager *entity(void) override { return _entity.get(); } + const coco::EntityManager *entity(void) const override { return _entity.get(); } + +public: + std::unique_ptr _block; + +public: + coco::BlockList *block(void) override { return _block.get(); } + const coco::BlockList *block(void) const override { return _block.get(); } + +public: + std::unique_ptr _input; + +public: + coco::InputList *input(void) override { return _input.get(); } + const coco::InputList *input(void) const override { return _input.get(); } + +public: + std::unique_ptr _output; + +public: + coco::OutputList *output(void) override { return _output.get(); } + const coco::OutputList *output(void) const override { return _output.get(); } + +public: + // WARN _entity SHOULD BE declared after _block in order to allow each Block(s) to detach itself. + // + // If not, Block is destructed after its corresponding BlockList is destructed, which results + // in invalid memory access during the update on BlockList (inside Block's destructor). + std::unique_ptr _entity; +}; + +} // namespace + +namespace coco +{ + +std::unique_ptr Module::create(void) +{ + auto m = make_unique<::ModuleImpl>(); + + auto mgr = make_unique<::EntityManagerImpl>(); + { + mgr->_bag = make_unique(m.get()); + mgr->_object = make_unique(m.get()); + mgr->_op = make_unique(m.get()); + mgr->_instr = make_unique(m.get()); + mgr->_block = make_unique(m.get()); + mgr->_input = make_unique(m.get()); + mgr->_output = make_unique(m.get()); + } + m->_entity = std::move(mgr); + + m->_block = make_unique(m.get()); + m->_input = make_unique(); + m->_output = make_unique(); + + return std::move(m); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Module.test.cpp b/compiler/coco/core/src/IR/Module.test.cpp new file mode 100644 index 00000000000..b55ceacb84f --- /dev/null +++ b/compiler/coco/core/src/IR/Module.test.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Module.h" + +#include + +TEST(IR_MODULE, create) +{ + auto m = coco::Module::create(); + + ASSERT_NE(m.get(), nullptr); + + coco::Module *mutable_m = m.get(); + const coco::Module *immutable_m = m.get(); + + ASSERT_NE(mutable_m->entity(), nullptr); + ASSERT_NE(immutable_m->entity(), nullptr); + + ASSERT_NE(mutable_m->entity()->bag(), nullptr); + ASSERT_EQ(immutable_m->entity()->bag(), mutable_m->entity()->bag()); + + ASSERT_NE(mutable_m->entity()->object(), nullptr); + ASSERT_EQ(immutable_m->entity()->object(), mutable_m->entity()->object()); + + ASSERT_NE(mutable_m->entity()->op(), nullptr); + ASSERT_EQ(immutable_m->entity()->op(), mutable_m->entity()->op()); + + ASSERT_NE(mutable_m->entity()->instr(), nullptr); + ASSERT_EQ(immutable_m->entity()->instr(), mutable_m->entity()->instr()); + + ASSERT_NE(mutable_m->entity()->block(), nullptr); + ASSERT_EQ(immutable_m->entity()->block(), mutable_m->entity()->block()); + + ASSERT_NE(mutable_m->entity()->input(), nullptr); + ASSERT_EQ(immutable_m->entity()->input(), mutable_m->entity()->input()); + + ASSERT_NE(mutable_m->entity()->output(), nullptr); + ASSERT_EQ(immutable_m->entity()->output(), mutable_m->entity()->output()); + + ASSERT_NE(mutable_m->block(), nullptr); + ASSERT_EQ(immutable_m->block(), mutable_m->block()); + + ASSERT_NE(mutable_m->input(), nullptr); + ASSERT_EQ(immutable_m->input(), mutable_m->input()); + + ASSERT_NE(mutable_m->output(), nullptr); + ASSERT_EQ(immutable_m->output(), mutable_m->output()); +} + +TEST(IR_MODULE, append_two_blocks) +{ + auto m = coco::Module::create(); + + auto blk_1 = m->entity()->block()->create(); + m->block()->append(blk_1); + + auto blk_2 = m->entity()->block()->create(); + m->block()->append(blk_2); + + ASSERT_EQ(m->block()->head(), blk_1); + ASSERT_EQ(m->block()->tail(), blk_2); + + ASSERT_EQ(blk_1->prev(), nullptr); + ASSERT_EQ(blk_1->next(), blk_2); + + ASSERT_EQ(blk_2->prev(), blk_1); + ASSERT_EQ(blk_2->next(), nullptr); + + ASSERT_EQ(blk_1->index().value(), 0); + ASSERT_EQ(blk_2->index().value(), 1); +} + +TEST(IR_MODULE, append_two_instrs) +{ + auto m = coco::Module::create(); + + auto blk = m->entity()->block()->create(); + auto ins_1 = m->entity()->instr()->create(); + auto ins_2 = m->entity()->instr()->create(); + + blk->instr()->append(ins_1); + blk->instr()->append(ins_2); + + ASSERT_EQ(blk->instr()->head(), ins_1); + ASSERT_EQ(blk->instr()->tail(), ins_2); + + ASSERT_EQ(ins_1->parent(), blk); + ASSERT_EQ(ins_1->prev(), nullptr); + ASSERT_EQ(ins_1->next(), ins_2); + + ASSERT_EQ(ins_2->parent(), blk); + ASSERT_EQ(ins_2->prev(), ins_1); + ASSERT_EQ(ins_2->next(), nullptr); + + ASSERT_EQ(ins_1->index().value(), 0); + ASSERT_EQ(ins_2->index().value(), 1); +} + +TEST(IR_MODULE, iterate_constant_block) +{ + auto m = coco::Module::create(); + auto blk = m->entity()->block()->create(); + auto ins_1 = m->entity()->instr()->create(); + auto ins_2 = m->entity()->instr()->create(); + + blk->instr()->append(ins_1); + blk->instr()->append(ins_2); + + const coco::Block *immutable_blk = blk; + + ASSERT_EQ(immutable_blk->instr()->head(), ins_1); + ASSERT_EQ(immutable_blk->instr()->head()->next(), ins_2); +} + +TEST(IR_MODULE, input_as_output) +{ + // Some NN frameworks allows users to use a network input as its output. + // + // For example, let us consider the following Caffe network + // + // name: "example" + // layer { + // name: "l" + // type: "Input" + // top: "data" + // input_param { shape: { dim: 1 dim: 1 dim: 3 dim: 3 } } + // } + // + // "data" blob is the input of this network, and it is also the output of this network. + const nncc::core::ADT::tensor::Shape shape{1, 1, 3, 3}; + + auto m = coco::Module::create(); + auto bag = m->entity()->bag()->create(9); + + auto input = m->entity()->input()->create(shape); + auto output = m->entity()->output()->create(shape); + + input->name("data"); + input->bag(bag); + + output->name("data"); + output->bag(bag); + + ASSERT_TRUE(bag->isInput()); + ASSERT_TRUE(bag->isOutput()); + + output->bag(nullptr); + + ASSERT_TRUE(bag->isInput()); + ASSERT_FALSE(bag->isOutput()); +} + +/** + * This test ensures that IR entities allocated via EntityManager have a correct module link + */ +TEST(IR_Module, create_entites) +{ + using namespace coco; + using namespace nncc::core::ADT; + + auto m = Module::create(); + auto entity = m->entity(); + + ASSERT_EQ(entity->bag()->create(1)->module(), m.get()); + ASSERT_EQ(entity->object()->create()->module(), m.get()); + ASSERT_EQ(entity->object()->create()->module(), m.get()); +#define OP(Name) ASSERT_EQ(entity->op()->create()->module(), m.get()); +#include "coco/IR/Op.lst" +#undef OP +#define INSTR(Name) \ + { \ + auto ins = entity->instr()->create(); \ + ASSERT_EQ(ins->module(), m.get()); \ + ASSERT_TRUE(coco::isa(ins)); \ + ASSERT_NE(coco::safe_cast(ins), nullptr); \ + } +#include "coco/IR/Instr.lst" +#undef INSTR + ASSERT_EQ(entity->block()->create()->module(), m.get()); + ASSERT_EQ(entity->input()->create(tensor::Shape{1})->module(), m.get()); + ASSERT_EQ(entity->output()->create(tensor::Shape{1})->module(), m.get()); +} diff --git a/compiler/coco/core/src/IR/Object.cpp b/compiler/coco/core/src/IR/Object.cpp new file mode 100644 index 00000000000..6a51a61a334 --- /dev/null +++ b/compiler/coco/core/src/IR/Object.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Object.h" +#include "coco/IR/Def.h" +#include "coco/IR/Use.h" + +#include +#include + +namespace coco +{ + +Object::Object() +{ + // Register self to Dep + _dep.object(this); +} + +Def *Object::def(void) const { return _def; } + +void Object::def(Def *d) +{ + // This assert enforces users to explicitly reset def before update. + // + // Let's consider an object o with def d0. + // + // The following code is allowed: + // o->def(nullptr); + // o->def(d1); + // + // However, the following code is not allowed: + // o->def(d1); + // + assert((_def == nullptr) || (d == nullptr)); + _def = d; +} + +const UseSet *Object::uses(void) const { return &_uses; } +UseSet *Object::mutable_uses(void) { return &_uses; } + +Object::Producer *producer(const Object *obj) +{ + if (auto d = obj->def()) + { + return d->producer(); + } + + return nullptr; +} + +Object::ConsumerSet consumers(const Object *obj) +{ + Object::ConsumerSet res; + + for (const auto &use : *(obj->uses())) + { + if (auto consumer = use->consumer()) + { + res.insert(consumer); + } + } + + return res; +} + +/** + * Casting Helpers + * + * TODO Use Macro to reduce code duplication + */ +template <> bool isa(const Object *o) { return o->asFeature() != nullptr; } +template <> bool isa(const Object *o) { return o->asKernel() != nullptr; } + +template <> FeatureObject *cast(Object *o) +{ + assert(o != nullptr); + auto res = o->asFeature(); + assert(res != nullptr); + return res; +} + +template <> KernelObject *cast(Object *o) +{ + assert(o != nullptr); + auto res = o->asKernel(); + assert(res != nullptr); + return res; +} + +template <> FeatureObject *safe_cast(Object *o) +{ + // NOTE o may be nullptr + return (o == nullptr) ? nullptr : o->asFeature(); +} + +template <> KernelObject *safe_cast(Object *o) +{ + // NOTE o may be nullptr + return (o == nullptr) ? nullptr : o->asKernel(); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Object.test.cpp b/compiler/coco/core/src/IR/Object.test.cpp new file mode 100644 index 00000000000..2a2e4db2335 --- /dev/null +++ b/compiler/coco/core/src/IR/Object.test.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Object.h" +#include "coco/IR/BagManager.h" + +#include + +#include + +namespace +{ +class ObjectTest : public ::testing::Test +{ +protected: + coco::BagManager bag_mgr; +}; +} // namespace + +namespace +{ +namespace mock +{ +struct Object : public coco::Object +{ +public: + virtual ~Object() = default; +}; +} // namespace mock +} // namespace + +TEST_F(ObjectTest, ctor) +{ + ::mock::Object obj; + + // Newly created object should not have a backing bag + ASSERT_EQ(obj.bag(), nullptr); + + // Newly created object should not have def and uses + ASSERT_EQ(obj.def(), nullptr); + ASSERT_TRUE(obj.uses()->empty()); +} + +TEST_F(ObjectTest, bag_update) +{ + // Prepare bag + auto bag = bag_mgr.create(1); + + // Test 'Object' class through a mock-up object + ::mock::Object obj; + + obj.bag(bag); + + // 'bag(Bag *)' should affect the return of 'bag(void)' + ASSERT_EQ(obj.bag(), bag); + + // User SHOULD be able to access dependent objects through 'bag' + { + auto deps = coco::dependent_objects(bag); + ASSERT_EQ(deps.size(), 1); + ASSERT_EQ(deps.count(&obj), 1); + } + + // Unlink Object-Bag relation + obj.bag(nullptr); + + ASSERT_EQ(obj.bag(), nullptr); + + { + auto deps = coco::dependent_objects(bag); + ASSERT_EQ(deps.size(), 0); + } +} + +TEST_F(ObjectTest, destructor) +{ + auto bag = bag_mgr.create(1); + + // Destruct Object after proper initialization + { + ::mock::Object obj; + + obj.bag(bag); + } + + // Object SHOULD be unlinked from Bag on destruction + { + auto deps = coco::dependent_objects(bag); + ASSERT_EQ(deps.size(), 0); + } +} + +TEST_F(ObjectTest, safe_cast) +{ + ASSERT_EQ(coco::safe_cast(nullptr), nullptr); + ASSERT_EQ(coco::safe_cast(nullptr), nullptr); +} diff --git a/compiler/coco/core/src/IR/ObjectManager.cpp b/compiler/coco/core/src/IR/ObjectManager.cpp new file mode 100644 index 00000000000..1b7215a041b --- /dev/null +++ b/compiler/coco/core/src/IR/ObjectManager.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/ObjectManager.h" + +#include "coco/IR/FeatureObject.h" +#include "coco/IR/KernelObject.h" + +#include + +#include + +using stdex::make_unique; + +namespace coco +{ + +template <> FeatureObject *ObjectManager::create(void) +{ + auto feature = make_unique(); + modulize(feature.get()); + return take(std::move(feature)); +} + +template <> KernelObject *ObjectManager::create(void) +{ + auto kernel = make_unique(); + modulize(kernel.get()); + return take(std::move(kernel)); +} + +void ObjectManager::destroy(Object *o) +{ + assert(o->def() == nullptr); + assert(o->uses()->size() == 0); + release(o); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/ObjectManager.test.cpp b/compiler/coco/core/src/IR/ObjectManager.test.cpp new file mode 100644 index 00000000000..781775f2599 --- /dev/null +++ b/compiler/coco/core/src/IR/ObjectManager.test.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/ObjectManager.h" +#include "coco/IR/BagManager.h" + +#include "coco/IR/FeatureObject.h" +#include "coco/IR/KernelObject.h" + +#include + +TEST(IR_OBJECT_MANAGER, create_feature_with_template) +{ + coco::ObjectManager mgr; + + auto feature = mgr.create(); + + ASSERT_EQ(feature->layout(), nullptr); +} + +TEST(IR_OBJECT_MANAGER, create_kernel_with_template) +{ + coco::ObjectManager mgr; + + auto kernel = mgr.create(); + + ASSERT_EQ(kernel->layout(), nullptr); +} + +TEST(IR_OBJECT_MANAGER, destroy) +{ + coco::BagManager bag_mgr; + coco::ObjectManager obj_mgr; + + auto bag = bag_mgr.create(3); + auto feature = obj_mgr.create(); + + feature->bag(bag); + + obj_mgr.destroy(feature); + + // Object SHOULD BE unlinked from its dependent bag on destruction + ASSERT_EQ(bag->deps()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/Op.cpp b/compiler/coco/core/src/IR/Op.cpp new file mode 100644 index 00000000000..d3808a9d624 --- /dev/null +++ b/compiler/coco/core/src/IR/Op.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Op.h" +#include "coco/IR/Step.h" +#include "coco/IR/Part.h" + +#include + +namespace coco +{ +Op::~Op() +{ + // NOTE Op SHOULD NOT be referred by an instruction to be destructed + assert(_step == nullptr); +} + +Instr *Op::parent(void) const +{ + // Get the parent instruction specified by _step for root nodes + if (_step) + { + // Op SHOULD BE a root node + assert(_part == nullptr); + assert(_step->instr() != nullptr); + return _step->instr(); + } + + // Get the parent instruction of its parent Op for non-root nodes + if (_part) + { + assert(_part->parent() != nullptr); + return _part->parent()->parent(); + } + + return nullptr; +} + +Op *Op::up(void) const +{ + if (_part) + { + assert(_part->parent() != nullptr); + return _part->parent(); + } + return nullptr; +} + +// +// UnaryOP trait +// +UnaryOp::UnaryOp() : _arg{this} +{ + // DO NOTHING +} + +uint32_t UnaryOp::arity(void) const +{ + // There is only one argument + return 1; +} + +Op *UnaryOp::arg(DBGARG(uint32_t, n)) const +{ + assert(n < 1); + return arg(); +} + +std::set UnaryOp::uses(void) const +{ + std::set res; + + if (auto ifm = arg()) + { + for (auto obj : ifm->uses()) + { + res.insert(obj); + } + } + + return res; +} + +// +// BinaryOp trait +// +BinaryOp::BinaryOp() : _left{this}, _right{this} +{ + // DO NOTHING +} + +uint32_t BinaryOp::arity(void) const +{ + // There are two arguments + return 2; +} + +Op *BinaryOp::arg(uint32_t n) const +{ + assert(n < arity()); + + return (n == 0) ? left() : right(); +} + +std::set BinaryOp::uses(void) const +{ + std::set res; + + if (auto l = left()) + { + for (auto obj : l->uses()) + { + res.insert(obj); + } + } + + if (auto r = right()) + { + for (auto obj : r->uses()) + { + res.insert(obj); + } + } + + return res; +} + +// +// Additional Helpers +// +Op *root(Op *cur) +{ + while (cur->up()) + { + cur = cur->up(); + } + return cur; +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/OpManager.cpp b/compiler/coco/core/src/IR/OpManager.cpp new file mode 100644 index 00000000000..c87b704feff --- /dev/null +++ b/compiler/coco/core/src/IR/OpManager.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/OpManager.h" + +#include + +#include +#include +#include + +using stdex::make_unique; + +namespace coco +{ + +OpManager::~OpManager() +{ + std::set roots; + + for (uint32_t n = 0; n < size(); ++n) + { + auto op = at(n); + + if (op->up() != nullptr) + { + continue; + } + + roots.insert(op); + } + + for (const auto &op : roots) + { + destroy_all(op); + } +} + +// +// Each Op class SHOULD be default constructible +// +#define OP(Name) \ + template <> Name *OpManager::create(void) \ + { \ + auto op = make_unique(); \ + modulize(op.get()); \ + return take(std::move(op)); \ + } +#include "coco/IR/Op.lst" +#undef OP + +void OpManager::destroy(Op *op) +{ + assert(op->parent() == nullptr); + release(op); +} + +void OpManager::destroy_all(Op *op) +{ + assert(op->parent() == nullptr); + assert(op->up() == nullptr); + + std::queue q; + + q.emplace(op); + + while (q.size() > 0) + { + auto cur = q.front(); + q.pop(); + + // Insert child op nodes + for (uint32_t n = 0; n < cur->arity(); ++n) + { + if (auto child = cur->arg(n)) + { + q.emplace(child); + } + } + + // Destroy the current op node + destroy(cur); + } +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/OpManager.test.cpp b/compiler/coco/core/src/IR/OpManager.test.cpp new file mode 100644 index 00000000000..9d463b3e4db --- /dev/null +++ b/compiler/coco/core/src/IR/OpManager.test.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/OpManager.h" + +#include + +namespace +{ + +class OpManagerTest : public ::testing::Test +{ +protected: + coco::OpManager mgr; +}; + +} // namespace + +TEST(IR_OP_MANAGER, create_Conv2D) +{ + coco::OpManager mgr; + + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST(IR_OP_MANAGER, create_AvgPool2D) +{ + coco::OpManager mgr; + + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, ReLU) +{ + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, ReLU6) +{ + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, Sqrt) +{ + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, Sub) +{ + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, Div) +{ + auto obj = mgr.create(); + + ASSERT_NE(obj, nullptr); +} + +TEST_F(OpManagerTest, PadF) +{ + auto op = mgr.create(); + ASSERT_NE(op, nullptr); + mgr.destroy(op); +} + +TEST_F(OpManagerTest, destroy) +{ + auto op = mgr.create(); + mgr.destroy(op); + ASSERT_EQ(mgr.size(), 0); +} + +TEST_F(OpManagerTest, destroy_all) +{ + // Create a Op tree + auto load_op = mgr.create(); + auto conv_op = mgr.create(); + + conv_op->arg(load_op); + + mgr.destroy_all(conv_op); + + ASSERT_EQ(mgr.size(), 0); +} + +TEST_F(OpManagerTest, destroy_all_partial_tree) +{ + // Create a (partial) Op tree + auto conv_op = mgr.create(); + + mgr.destroy_all(conv_op); + + ASSERT_EQ(mgr.size(), 0); +} diff --git a/compiler/coco/core/src/IR/Ops.cpp b/compiler/coco/core/src/IR/Ops.cpp new file mode 100644 index 00000000000..1c1ef5d2817 --- /dev/null +++ b/compiler/coco/core/src/IR/Ops.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +namespace coco +{ + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Ops.test.cpp b/compiler/coco/core/src/IR/Ops.test.cpp new file mode 100644 index 00000000000..ae979b2bfcf --- /dev/null +++ b/compiler/coco/core/src/IR/Ops.test.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" +#include "coco/IR/ObjectManager.h" +#include "coco/IR/OpManager.h" + +#include +#include + +#include + +#include + +using stdex::make_unique; + +/** + * Section: Add Op + */ +namespace +{ + +class AddTest : public ::testing::Test +{ +public: + AddTest() + { + // DO NOTHING + } + +protected: + coco::Add *allocate(void) + { + auto op = new coco::Add; + _allocated.emplace_back(op); + return op; + } + +protected: + coco::ObjectManager obj_mgr; + +private: + std::vector> _allocated; +}; + +} // namespace + +TEST_F(AddTest, constructor) +{ + auto op = allocate(); + + ASSERT_EQ(op->left(), nullptr); + ASSERT_EQ(op->right(), nullptr); +} + +/** + * Section: Mul Op + */ +TEST(MulTest, constructor) +{ + auto op = make_unique(); + + ASSERT_EQ(op->left(), nullptr); + ASSERT_EQ(op->right(), nullptr); +} + +/** + * Section: Div Op + */ +TEST(DivTest, constructor) +{ + auto op = make_unique(); + + ASSERT_EQ(op->left(), nullptr); + ASSERT_EQ(op->right(), nullptr); +} + +/** + * Section: Op Helpers + */ +namespace +{ + +class OpHelperTest : public ::testing::Test +{ +public: + OpHelperTest() + { + // DO NOTHING + } + +protected: + template Op *allocate(void) { return op_mgr.create(); } + +protected: + coco::ObjectManager obj_mgr; + +private: + coco::OpManager op_mgr; +}; + +} // namespace + +TEST_F(OpHelperTest, root) +{ + auto load = allocate(); + + ASSERT_EQ(root(load), load); + + auto avgpool = allocate(); + + avgpool->arg(load); + + ASSERT_EQ(root(load), avgpool); + ASSERT_EQ(root(avgpool), avgpool); +} diff --git a/compiler/coco/core/src/IR/Output.cpp b/compiler/coco/core/src/IR/Output.cpp new file mode 100644 index 00000000000..7b6d1870b90 --- /dev/null +++ b/compiler/coco/core/src/IR/Output.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Output.h" + +#include + +namespace coco +{ + +Output::Output(const nncc::core::ADT::tensor::Shape &shape) : Arg{shape} +{ + // DO NOTHING +} + +void Output::onTake(Bag *bag) +{ + assert(bag->output() == nullptr); + bag->output(this); +} + +void Output::onRelease(Bag *bag) +{ + assert(bag->output() == this); + bag->output(nullptr); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Output.test.cpp b/compiler/coco/core/src/IR/Output.test.cpp new file mode 100644 index 00000000000..715a83875c0 --- /dev/null +++ b/compiler/coco/core/src/IR/Output.test.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Output.h" +#include "coco/IR/BagManager.h" + +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +TEST(IR_OUTPUT, ctor_should_set_shape) +{ + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Output output{shape}; + + ASSERT_EQ(output.shape(), shape); +} + +TEST(IR_OUTPUT, bag_update) +{ + // Create a bag for test + coco::BagManager bag_mgr; + + auto bag = bag_mgr.create(9); + + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Output output{shape}; + + output.bag(bag); + ASSERT_EQ(output.bag(), bag); + + // bag(...) method SHOULD update 'bag' type + ASSERT_TRUE(bag->isOutput()); + + output.bag(nullptr); + + // bag(nullptr) SHOULD revert 'bag' type + ASSERT_FALSE(bag->isOutput()); +} + +TEST(IR_OUTPUT, name_update) +{ + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + coco::Output output{shape}; + + output.name("softmax"); + ASSERT_EQ(output.name(), "softmax"); +} + +TEST(IR_OUTPUT, at) +{ + const Shape shape{1, 3, 3, 1}; + coco::Output input{shape}; + + coco::Output *mutable_ptr = &input; + const coco::Output *immutable_ptr = &input; + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + mutable_ptr->at(e.current()) = coco::ElemID{16}; + } + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + ASSERT_EQ(immutable_ptr->at(e.current()).value(), 16); + } +} diff --git a/compiler/coco/core/src/IR/OutputManager.cpp b/compiler/coco/core/src/IR/OutputManager.cpp new file mode 100644 index 00000000000..86b9580acb3 --- /dev/null +++ b/compiler/coco/core/src/IR/OutputManager.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/OutputManager.h" + +#include + +namespace coco +{ + +Output *OutputManager::create(const nncc::core::ADT::tensor::Shape &shape) +{ + auto output = stdex::make_unique(shape); + modulize(output.get()); + return take(std::move(output)); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/OutputManager.test.cpp b/compiler/coco/core/src/IR/OutputManager.test.cpp new file mode 100644 index 00000000000..80b38b42ca6 --- /dev/null +++ b/compiler/coco/core/src/IR/OutputManager.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/OutputManager.h" + +#include + +TEST(IR_OUTPUT_MANAGER, make) +{ + coco::OutputManager mgr; + + const nncc::core::ADT::tensor::Shape shape{1, 3, 3, 1}; + auto output = mgr.create(shape); + + ASSERT_EQ(output->shape(), shape); +} diff --git a/compiler/coco/core/src/IR/PadF.test.cpp b/compiler/coco/core/src/IR/PadF.test.cpp new file mode 100644 index 00000000000..b443d86fb2b --- /dev/null +++ b/compiler/coco/core/src/IR/PadF.test.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsPadF : public coco::Op::Visitor +{ + bool visit(const coco::PadF *) override { return true; } +}; + +class PadFTest : public ::testing::Test +{ +public: + PadFTest() + { + // DO NOTHING + } + +protected: + coco::PadF *allocate(void) + { + auto op = new coco::PadF; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(PadFTest, initialization) +{ + auto op = allocate(); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + // arg() should be nullptr on construction + ASSERT_EQ(op->arg(), nullptr); + + // pad() should be a valid + ASSERT_NE(op->pad(), nullptr); +} + +TEST_F(PadFTest, asPadF) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asPadF(), op); + ASSERT_EQ(mutable_base->asPadF(), immutable_base->asPadF()); +} + +TEST_F(PadFTest, accept) +{ + // Test 'PadF' class + auto op = allocate(); + + coco::PadF *mutable_ptr = op; + const coco::PadF *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsPadF{})); + ASSERT_TRUE(immutable_ptr->accept(IsPadF{})); +} diff --git a/compiler/coco/core/src/IR/Padding2D.cpp b/compiler/coco/core/src/IR/Padding2D.cpp new file mode 100644 index 00000000000..8cdc42638d6 --- /dev/null +++ b/compiler/coco/core/src/IR/Padding2D.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Padding2D.h" + +namespace coco +{ + +Padding2D &Padding2D::top(uint32_t value) +{ + _top = value; + return (*this); +} + +Padding2D &Padding2D::bottom(uint32_t value) +{ + _bottom = value; + return (*this); +} + +Padding2D &Padding2D::left(uint32_t value) +{ + _left = value; + return (*this); +} + +Padding2D &Padding2D::right(uint32_t value) +{ + _right = value; + return (*this); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Padding2D.test.cpp b/compiler/coco/core/src/IR/Padding2D.test.cpp new file mode 100644 index 00000000000..292ce7d17d6 --- /dev/null +++ b/compiler/coco/core/src/IR/Padding2D.test.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Padding2D.h" + +#include + +TEST(IR_PADDING, default_constructor) +{ + coco::Padding2D pad; + + ASSERT_EQ(pad.top(), 0); + ASSERT_EQ(pad.bottom(), 0); + ASSERT_EQ(pad.left(), 0); + ASSERT_EQ(pad.right(), 0); +} + +TEST(IR_PADDING, explicit_constructor_4) +{ + coco::Padding2D pad{1, 2, 3, 4}; + + ASSERT_EQ(pad.top(), 1); + ASSERT_EQ(pad.bottom(), 2); + ASSERT_EQ(pad.left(), 3); + ASSERT_EQ(pad.right(), 4); +} + +TEST(IR_PADDING, update) +{ + coco::Padding2D pad; + + pad.top(1).bottom(2).left(3).right(4); + + ASSERT_EQ(pad.top(), 1); + ASSERT_EQ(pad.bottom(), 2); + ASSERT_EQ(pad.left(), 3); + ASSERT_EQ(pad.right(), 4); +} diff --git a/compiler/coco/core/src/IR/Part.cpp b/compiler/coco/core/src/IR/Part.cpp new file mode 100644 index 00000000000..bf68c1feb05 --- /dev/null +++ b/compiler/coco/core/src/IR/Part.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Part.h" +#include "coco/IR/Op.h" + +#include + +namespace coco +{ + +void Part::child(Op *c) +{ + if (_child != nullptr) + { + assert(_child->_part == this); + _child->_part = nullptr; + _child = nullptr; + } + + assert(_child == nullptr); + + if (c != nullptr) + { + assert(c->_part == nullptr); + assert(c->_step == nullptr); + _child = c; + _child->_part = this; + } +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Part.test.cpp b/compiler/coco/core/src/IR/Part.test.cpp new file mode 100644 index 00000000000..87e0e151609 --- /dev/null +++ b/compiler/coco/core/src/IR/Part.test.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Part.h" +#include "coco/IR/Op.h" + +#include + +#include + +using stdex::make_unique; + +namespace +{ +namespace mock +{ + +// TODO Inherit UnaryOp instead of Op +struct Op final : public coco::Op +{ +public: + Op() : _arg{this} + { + // DO NOTHING + } + +public: + uint32_t arity(void) const final { return 1; } + coco::Op *arg(uint32_t n) const final { return arg(); } + + std::set uses() const override { throw std::runtime_error{"Not supported"}; } + +public: + ::coco::Op *arg(void) const { return _arg.child(); } + void arg(::coco::Op *child) { _arg.child(child); } + +private: + coco::Part _arg; +}; + +} // namespace mock +} // namespace + +TEST(PartTest, destructor) +{ + auto parent = make_unique<::mock::Op>(); + auto child = make_unique<::mock::Op>(); + + parent->arg(child.get()); + ASSERT_EQ(parent->arg(), child.get()); + ASSERT_EQ(child->up(), parent.get()); + + parent.reset(); + + // NOTE parent SHOULD unlink itself from child on destruction + ASSERT_EQ(child->up(), nullptr); +} diff --git a/compiler/coco/core/src/IR/Producer.mock.h b/compiler/coco/core/src/IR/Producer.mock.h new file mode 100644 index 00000000000..ffc343ee827 --- /dev/null +++ b/compiler/coco/core/src/IR/Producer.mock.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_PRODUCER_MOCK_H__ +#define __COCO_IR_PRODUCER_MOCK_H__ + +#include "coco/IR/Object.h" + +namespace +{ +namespace mock +{ +struct Producer final : public coco::Object::Producer +{ + coco::Instr *loc(void) override { return nullptr; } +}; +} // namespace mock +} // namespace + +#endif // __COCO_IR_PRODUCER_MOCK_H__ diff --git a/compiler/coco/core/src/IR/ReLU.test.cpp b/compiler/coco/core/src/IR/ReLU.test.cpp new file mode 100644 index 00000000000..22ef1730e88 --- /dev/null +++ b/compiler/coco/core/src/IR/ReLU.test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsReLU : public coco::Op::Visitor +{ + bool visit(const coco::ReLU *) override { return true; } +}; + +class ReLUTest : public ::testing::Test +{ +public: + ReLUTest() + { + // DO NOTHING + } + +protected: + coco::ReLU *allocate(void) + { + auto op = new coco::ReLU; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(ReLUTest, initialization) +{ + auto op = allocate(); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + ASSERT_EQ(op->arg(), nullptr); +} + +TEST_F(ReLUTest, asReLU) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asReLU(), op); + ASSERT_EQ(mutable_base->asReLU(), immutable_base->asReLU()); +} + +TEST_F(ReLUTest, accept) +{ + // Test 'ReLU' class + auto op = allocate(); + + coco::ReLU *mutable_ptr = op; + const coco::ReLU *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsReLU{})); + ASSERT_TRUE(immutable_ptr->accept(IsReLU{})); +} diff --git a/compiler/coco/core/src/IR/ReLU6.test.cpp b/compiler/coco/core/src/IR/ReLU6.test.cpp new file mode 100644 index 00000000000..dd148254faa --- /dev/null +++ b/compiler/coco/core/src/IR/ReLU6.test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsReLU6 : public coco::Op::Visitor +{ + bool visit(const coco::ReLU6 *) override { return true; } +}; + +class ReLU6Test : public ::testing::Test +{ +public: + ReLU6Test() + { + // DO NOTHING + } + +protected: + coco::ReLU6 *allocate(void) + { + auto op = new coco::ReLU6; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(ReLU6Test, initialization) +{ + auto op = allocate(); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + ASSERT_EQ(op->arg(), nullptr); +} + +TEST_F(ReLU6Test, asReLU6) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asReLU6(), op); + ASSERT_EQ(mutable_base->asReLU6(), immutable_base->asReLU6()); +} + +TEST_F(ReLU6Test, accept) +{ + // Test 'ReLU6' class + auto op = allocate(); + + coco::ReLU6 *mutable_ptr = op; + const coco::ReLU6 *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsReLU6{})); + ASSERT_TRUE(immutable_ptr->accept(IsReLU6{})); +} diff --git a/compiler/coco/core/src/IR/Read.cpp b/compiler/coco/core/src/IR/Read.cpp new file mode 100644 index 00000000000..ea01cce1d6b --- /dev/null +++ b/compiler/coco/core/src/IR/Read.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Read.h" + +#include + +namespace coco +{ + +Read::~Read() +{ + // Unlink self from Bag if there is a linked bag + bag(nullptr); +} + +void Read::bag(Bag *bag) +{ + if (_bag) + { + _bag->mutable_reads()->erase(this); + _bag = nullptr; + } + + assert(_bag == nullptr); + + if (bag) + { + _bag = bag; + _bag->mutable_reads()->insert(this); + } + + assert(_bag == bag); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Read.test.cpp b/compiler/coco/core/src/IR/Read.test.cpp new file mode 100644 index 00000000000..7c36820a6fc --- /dev/null +++ b/compiler/coco/core/src/IR/Read.test.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Read.h" +#include "coco/IR/BagManager.h" + +#include "Reader.mock.h" + +#include + +namespace +{ +class ReadTest : public ::testing::Test +{ +protected: + coco::BagManager bag_mgr; +}; +} // namespace + +TEST_F(ReadTest, constructor) +{ + // TODO Rename 'read' as 'reader' + ::mock::Reader read; + + // TODO Rename 'slot' + coco::Read slot{&read}; + + ASSERT_EQ(slot.bag(), nullptr); +} + +TEST_F(ReadTest, value) +{ + // TODO Rename 'read' as 'reader' + ::mock::Reader read; + + // TODO Rename 'slot' + coco::Read slot{&read}; + + auto bag = bag_mgr.create(16); + + slot.bag(bag); + + ASSERT_EQ(slot.bag(), bag); + + ASSERT_EQ(bag->reads()->size(), 1); + ASSERT_NE(bag->reads()->find(&slot), bag->reads()->end()); + + slot.bag(nullptr); + + ASSERT_EQ(slot.bag(), nullptr); + + ASSERT_EQ(bag->reads()->size(), 0); +} + +TEST_F(ReadTest, unlink_on_destruction) +{ + // TODO Rename 'read' as 'reader' + ::mock::Reader reader; + + auto bag = bag_mgr.create(1); + + { + coco::Read read{&reader}; + read.bag(bag); + } + + ASSERT_EQ(bag->reads()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/Reader.mock.h b/compiler/coco/core/src/IR/Reader.mock.h new file mode 100644 index 00000000000..0965abfeb51 --- /dev/null +++ b/compiler/coco/core/src/IR/Reader.mock.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_READER_MOCK_H__ +#define __COCO_IR_READER_MOCK_H__ + +#include "coco/IR/Bag.h" + +namespace +{ +namespace mock +{ +struct Reader final : public coco::Bag::Reader +{ + coco::Instr *loc(void) override { return nullptr; } +}; +} // namespace mock +} // namespace + +#endif // __COCO_IR_READER_MOCK_H__ diff --git a/compiler/coco/core/src/IR/Shuffle.cpp b/compiler/coco/core/src/IR/Shuffle.cpp new file mode 100644 index 00000000000..f8007dd1bfe --- /dev/null +++ b/compiler/coco/core/src/IR/Shuffle.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Instrs.h" + +namespace coco +{ + +uint32_t Shuffle::size(void) const { return _content.size(); } + +std::set Shuffle::range(void) const +{ + std::set res; + + for (auto it = _content.begin(); it != _content.end(); ++it) + { + res.insert(it->first); + } + + return res; +} + +void Shuffle::insert(const ElemID &from, const ElemID &into) { _content[into] = from; } + +void Shuffle::from(Bag *b) { _from.bag(b); } +void Shuffle::into(Bag *b) { _into.bag(b); } + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Shuffle.test.cpp b/compiler/coco/core/src/IR/Shuffle.test.cpp new file mode 100644 index 00000000000..f564c08c3fe --- /dev/null +++ b/compiler/coco/core/src/IR/Shuffle.test.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Instrs.h" +#include "coco/IR/ObjectManager.h" +#include "coco/IR/OpManager.h" + +#include + +namespace +{ +class ShuffleTest : public ::testing::Test +{ +public: + virtual ~ShuffleTest() = default; + +protected: + coco::Shuffle *allocate(void) + { + auto ins = new coco::Shuffle; + _allocated.emplace_back(ins); + return ins; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(ShuffleTest, constructor) +{ + auto ins = allocate(); + + ASSERT_EQ(ins->from(), nullptr); + ASSERT_EQ(ins->into(), nullptr); +} + +TEST_F(ShuffleTest, asShuffle) +{ + auto ins = allocate(); + + coco::Instr *mutable_ptr = ins; + const coco::Instr *immutable_ptr = ins; + + ASSERT_NE(mutable_ptr->asShuffle(), nullptr); + ASSERT_EQ(mutable_ptr->asShuffle(), immutable_ptr->asShuffle()); +} + +TEST_F(ShuffleTest, size) +{ + auto shuffle = allocate(); + + shuffle->insert(coco::ElemID{3}, coco::ElemID{2}); + shuffle->insert(coco::ElemID{3}, coco::ElemID{5}); + + ASSERT_EQ(shuffle->size(), 2); + ASSERT_EQ(shuffle->range().size(), shuffle->size()); +} + +TEST_F(ShuffleTest, range) +{ + auto shuffle = allocate(); + + shuffle->insert(coco::ElemID{3}, coco::ElemID{2}); + shuffle->insert(coco::ElemID{3}, coco::ElemID{5}); + + auto range = shuffle->range(); + + EXPECT_EQ(range.size(), 2); + EXPECT_NE(range.count(coco::ElemID{2}), 0); + EXPECT_NE(range.count(coco::ElemID{5}), 0); +} + +TEST_F(ShuffleTest, defined) +{ + auto shuffle = allocate(); + + shuffle->insert(coco::ElemID{3}, coco::ElemID{2}); + + EXPECT_TRUE(shuffle->defined(coco::ElemID{2})); + EXPECT_FALSE(shuffle->defined(coco::ElemID{3})); +} diff --git a/compiler/coco/core/src/IR/Sqrt.test.cpp b/compiler/coco/core/src/IR/Sqrt.test.cpp new file mode 100644 index 00000000000..cf9b232ea36 --- /dev/null +++ b/compiler/coco/core/src/IR/Sqrt.test.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsSqrt : public coco::Op::Visitor +{ + bool visit(const coco::Sqrt *) override { return true; } +}; + +class SqrtTest : public ::testing::Test +{ +public: + SqrtTest() + { + // DO NOTHING + } + +protected: + coco::Sqrt *allocate(void) + { + auto op = new coco::Sqrt; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(SqrtTest, initialization) +{ + auto op = allocate(); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); + + ASSERT_EQ(op->arg(), nullptr); +} + +TEST_F(SqrtTest, asSqrt) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asSqrt(), op); + ASSERT_EQ(mutable_base->asSqrt(), immutable_base->asSqrt()); +} + +TEST_F(SqrtTest, accept) +{ + // Test 'Sqrt' class + auto op = allocate(); + + coco::Sqrt *mutable_ptr = op; + const coco::Sqrt *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsSqrt{})); + ASSERT_TRUE(immutable_ptr->accept(IsSqrt{})); +} diff --git a/compiler/coco/core/src/IR/Step.cpp b/compiler/coco/core/src/IR/Step.cpp new file mode 100644 index 00000000000..04400d46b64 --- /dev/null +++ b/compiler/coco/core/src/IR/Step.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Step.h" +#include "coco/IR/Op.h" + +#include + +namespace coco +{ + +void Step::op(Op *o) +{ + if (_op != nullptr) + { + // Unlink step from _op + assert(_op->_step == this); + _op->_step = nullptr; + + // Reset _op + _op = nullptr; + } + + assert(_op == nullptr); + + if (o) + { + // Update _op + _op = o; + + // Link step to _op + assert(_op->_step == nullptr); + _op->_step = this; + } + + assert(_op == o); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Stride2D.cpp b/compiler/coco/core/src/IR/Stride2D.cpp new file mode 100644 index 00000000000..a034876ef95 --- /dev/null +++ b/compiler/coco/core/src/IR/Stride2D.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Stride2D.h" + +namespace coco +{ + +Stride2D &Stride2D::vertical(uint32_t value) +{ + _vertical = value; + return (*this); +} + +Stride2D &Stride2D::horizontal(uint32_t value) +{ + _horizontal = value; + return (*this); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Stride2D.test.cpp b/compiler/coco/core/src/IR/Stride2D.test.cpp new file mode 100644 index 00000000000..43d159ee077 --- /dev/null +++ b/compiler/coco/core/src/IR/Stride2D.test.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Stride2D.h" + +#include + +TEST(IR_STRIDE_2D, default_constructor) +{ + coco::Stride2D stride; + + ASSERT_EQ(stride.vertical(), 1); + ASSERT_EQ(stride.horizontal(), 1); +} + +TEST(IR_STRIDE_2D, explicit_constructor_4) +{ + coco::Stride2D stride{2, 3}; + + ASSERT_EQ(stride.vertical(), 2); + ASSERT_EQ(stride.horizontal(), 3); +} + +TEST(IR_STRIDE_2D, update) +{ + coco::Stride2D stride; + + stride.vertical(2).horizontal(3); + + ASSERT_EQ(stride.vertical(), 2); + ASSERT_EQ(stride.horizontal(), 3); +} diff --git a/compiler/coco/core/src/IR/Sub.test.cpp b/compiler/coco/core/src/IR/Sub.test.cpp new file mode 100644 index 00000000000..6c8b9ba54b9 --- /dev/null +++ b/compiler/coco/core/src/IR/Sub.test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Ops.h" + +#include +#include + +#include + +namespace +{ +struct IsSub : public coco::Op::Visitor +{ + bool visit(const coco::Sub *) override { return true; } +}; + +class SubTest : public ::testing::Test +{ +public: + SubTest() + { + // DO NOTHING + } + +protected: + coco::Sub *allocate(void) + { + auto op = new coco::Sub; + _allocated.emplace_back(op); + return op; + } + +private: + std::vector> _allocated; +}; +} // namespace + +TEST_F(SubTest, initialization) +{ + auto op = allocate(); + + // arguments should be empty on construction + ASSERT_EQ(op->left(), nullptr); + ASSERT_EQ(op->right(), nullptr); + + // uses() should be empty on construction + ASSERT_EQ(op->uses().size(), 0); + // parent() should be nullptr on construction + ASSERT_EQ(op->parent(), nullptr); +} + +TEST_F(SubTest, asSub) +{ + auto op = allocate(); + + coco::Op *mutable_base = op; + const coco::Op *immutable_base = op; + + ASSERT_EQ(mutable_base->asSub(), op); + ASSERT_EQ(mutable_base->asSub(), immutable_base->asSub()); +} + +TEST_F(SubTest, accept) +{ + // Test 'Sub' class + auto op = allocate(); + + coco::Sub *mutable_ptr = op; + const coco::Sub *immutable_ptr = op; + + ASSERT_TRUE(mutable_ptr->accept(IsSub{})); + ASSERT_TRUE(immutable_ptr->accept(IsSub{})); +} diff --git a/compiler/coco/core/src/IR/Update.cpp b/compiler/coco/core/src/IR/Update.cpp new file mode 100644 index 00000000000..8e81c85cf5e --- /dev/null +++ b/compiler/coco/core/src/IR/Update.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Update.h" + +#include + +namespace coco +{ + +Update::~Update() +{ + // Unlink self from a linked bag if it exists + bag(nullptr); +} + +void Update::bag(Bag *bag) +{ + if (_bag) + { + _bag->mutable_updates()->erase(this); + _bag = nullptr; + } + + assert(_bag == nullptr); + + if (bag) + { + _bag = bag; + _bag->mutable_updates()->insert(this); + } + + assert(_bag == bag); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Update.test.cpp b/compiler/coco/core/src/IR/Update.test.cpp new file mode 100644 index 00000000000..0bd35599820 --- /dev/null +++ b/compiler/coco/core/src/IR/Update.test.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Update.h" +#include "coco/IR/BagManager.h" + +#include "Updater.mock.h" + +#include + +namespace +{ +class UpdateTest : public ::testing::Test +{ +protected: + coco::BagManager bag_mgr; +}; +} // namespace + +TEST_F(UpdateTest, constructor) +{ + // TODO Rename 'update' + ::mock::Updater update; + + // TODO Rename 'slot' + coco::Update slot{&update}; + + ASSERT_EQ(slot.bag(), nullptr); +} + +TEST_F(UpdateTest, value) +{ + // TODO Rename 'update' + ::mock::Updater update; + + // TODO Rename 'slot' + coco::Update slot{&update}; + + auto bag = bag_mgr.create(16); + + slot.bag(bag); + + ASSERT_EQ(slot.bag(), bag); + + ASSERT_EQ(bag->updates()->size(), 1); + ASSERT_NE(bag->updates()->find(&slot), bag->updates()->end()); + + slot.bag(nullptr); + + ASSERT_EQ(slot.bag(), nullptr); + + ASSERT_EQ(bag->updates()->size(), 0); +} + +TEST_F(UpdateTest, unlink_on_destruction) +{ + ::mock::Updater updater; + + auto bag = bag_mgr.create(1); + + { + coco::Update update{&updater}; + update.bag(bag); + ASSERT_EQ(bag->updates()->size(), 1); + } + + ASSERT_EQ(bag->updates()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/Updater.mock.h b/compiler/coco/core/src/IR/Updater.mock.h new file mode 100644 index 00000000000..6441cdd02e9 --- /dev/null +++ b/compiler/coco/core/src/IR/Updater.mock.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_UPDATER_MOCK_H__ +#define __COCO_IR_UPDATER_MOCK_H__ + +#include "coco/IR/Bag.h" + +namespace +{ +namespace mock +{ +struct Updater final : public coco::Bag::Updater +{ + coco::Instr *loc(void) override { return nullptr; } +}; +} // namespace mock +} // namespace + +#endif // __COCO_IR_UPDATER_MOCK_H__ diff --git a/compiler/coco/core/src/IR/Use.cpp b/compiler/coco/core/src/IR/Use.cpp new file mode 100644 index 00000000000..cd9b68105b7 --- /dev/null +++ b/compiler/coco/core/src/IR/Use.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Use.h" + +#include + +namespace coco +{ + +void Use::value(Object *value) +{ + if (_value) + { + _value->mutable_uses()->erase(this); + _value = nullptr; + } + + assert(_value == nullptr); + + if (value) + { + _value = value; + _value->mutable_uses()->insert(this); + } + + assert(_value == value); +} + +} // namespace coco diff --git a/compiler/coco/core/src/IR/Use.test.cpp b/compiler/coco/core/src/IR/Use.test.cpp new file mode 100644 index 00000000000..3191e985261 --- /dev/null +++ b/compiler/coco/core/src/IR/Use.test.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Use.h" +#include "coco/IR/ObjectManager.h" + +#include "coco/IR/FeatureObject.h" + +#include "Consumer.mock.h" + +#include + +#include + +using stdex::make_unique; + +namespace +{ +class UseTest : public ::testing::Test +{ +protected: + coco::ObjectManager obj_mgr; +}; +} // namespace + +TEST_F(UseTest, constructor) +{ + auto o = obj_mgr.create(); + + // TODO Rename 'use' + ::mock::Consumer use; + + coco::Use slot{&use}; + + ASSERT_EQ(slot.value(), nullptr); +} + +TEST_F(UseTest, value) +{ + auto o = obj_mgr.create(); + + // TODO Rename 'use' + ::mock::Consumer use; + + coco::Use slot{&use}; + + slot.value(o); + + ASSERT_EQ(slot.value(), o); + + ASSERT_EQ(o->uses()->size(), 1); + ASSERT_NE(o->uses()->find(&slot), o->uses()->end()); + + slot.value(nullptr); + + ASSERT_EQ(slot.value(), nullptr); + + ASSERT_EQ(o->uses()->size(), 0); +} + +TEST_F(UseTest, destructor) +{ + ::mock::Consumer consumer; + + auto o = obj_mgr.create(); + auto use = make_unique(&consumer); + + use->value(o); + use.reset(); + + // ~Use SHOULD unlink itself from linked Object (if exists) + ASSERT_EQ(o->uses()->size(), 0); +} diff --git a/compiler/coco/core/src/IR/Window2D.test.cpp b/compiler/coco/core/src/IR/Window2D.test.cpp new file mode 100644 index 00000000000..c0e9192378d --- /dev/null +++ b/compiler/coco/core/src/IR/Window2D.test.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Window2D.h" + +#include + +TEST(IR_WINDOW_2D, default_constructor) +{ + coco::Window2D window; + + ASSERT_EQ(window.height(), 1); + ASSERT_EQ(window.width(), 1); +} + +TEST(IR_WINDOW_2D, explicit_constructor_4) +{ + coco::Window2D window{2, 3}; + + ASSERT_EQ(window.height(), 2); + ASSERT_EQ(window.width(), 3); +} + +TEST(IR_WINDOW_2D, update) +{ + coco::Window2D window; + + window.height(2); + window.width(3); + + ASSERT_EQ(window.height(), 2); + ASSERT_EQ(window.width(), 3); +} diff --git a/compiler/coco/generic/CMakeLists.txt b/compiler/coco/generic/CMakeLists.txt new file mode 100644 index 00000000000..02fbf67f530 --- /dev/null +++ b/compiler/coco/generic/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(coco_generic SHARED ${SOURCES}) +target_include_directories(coco_generic PUBLIC include) +target_link_libraries(coco_generic PUBLIC coco_core) +target_link_libraries(coco_generic PRIVATE stdex) +target_link_libraries(coco_generic PRIVATE nncc_common) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is required for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(coco_generic_test ${TESTS}) +target_link_libraries(coco_generic_test coco_generic) +# stdex is a PRIVATE dependency of coco_generic, and thus is not linked to coco_generic_test +# even though coco_generic_test is linked to coco_generic +target_link_libraries(coco_generic_test stdex) diff --git a/compiler/coco/generic/include/coco/ADT/Span.h b/compiler/coco/generic/include/coco/ADT/Span.h new file mode 100644 index 00000000000..240e6afec28 --- /dev/null +++ b/compiler/coco/generic/include/coco/ADT/Span.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_ADT_SPAN_H__ +#define __COCO_ADT_SPAN_H__ + +#include +#include + +namespace coco +{ + +/** + * @brief A Span is a non-owing reference to a memory chunk + * + * @note A Span DOES NOT OWN a memory chunk. + */ +template class Span +{ +public: + Span(T *data, uint32_t size) : _data{data}, _size{size} + { + // DO NOTHING + } + +public: + T *data(void) { return _data; } + const T *data(void) const { return _data; } + +public: + uint32_t size(void) const { return _size; } + +public: + T &operator[](uint32_t n) + { + assert(n < _size); + return *(_data + n); + } + +public: + const T &operator[](uint32_t n) const + { + assert(n < _size); + return *(_data + n); + } + +private: + T *_data; + uint32_t _size; +}; + +} // namespace coco + +#endif // __COCO_ADT_SPAN_H__ diff --git a/compiler/coco/generic/include/coco/IR/Data.h b/compiler/coco/generic/include/coco/IR/Data.h new file mode 100644 index 00000000000..0cbee85e9db --- /dev/null +++ b/compiler/coco/generic/include/coco/IR/Data.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_DATA_H__ +#define __COCO_IR_DATA_H__ + +#include "coco/IR/PlainWeightContext.h" + +#include + +namespace coco +{ + +/** + * @brief Core coco entity for constant weights + */ +struct Data +{ + virtual ~Data() = default; + + /** + * @brief Return true if a given bag has an allocated weight data + */ + virtual bool allocated(const coco::Bag *) const = 0; + + /** + * @brief Release a memory chunk allocated for weight data of a given bag + * + * WARN Do NOT invoke release for a bag "b" for which allocated(b) does NOT hold + */ + virtual void release(const coco::Bag *) = 0; + + virtual PlainWeightContext *f32(void) = 0; + virtual const PlainWeightContext *f32(void) const = 0; + + static std::unique_ptr create(void); +}; + +} // namespace coco + +#endif // __COCO_IR_DATA_H__ diff --git a/compiler/coco/generic/include/coco/IR/PlainWeightContext.h b/compiler/coco/generic/include/coco/IR/PlainWeightContext.h new file mode 100644 index 00000000000..5100e9d90c9 --- /dev/null +++ b/compiler/coco/generic/include/coco/IR/PlainWeightContext.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __COCO_IR_PLAIN_WEIGHT_CONTEXT_H__ +#define __COCO_IR_PLAIN_WEIGHT_CONTEXT_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/KernelObject.h" + +#include "coco/ADT/Span.h" + +#include +#include + +#include + +namespace coco +{ + +/** + * @brief Non-quantized (plain) Weight Data Accessor + */ +template struct PlainWeightContext +{ + virtual ~PlainWeightContext() = default; + + /** + * @brief Allocate a weight space for a given blob + * + * @require the following code SHOULD work for any bag "b": + * PlainWeightContext ctx; + * + * auto span = ctx.allocate(b); + * assert(span.data() != nullptr); + * assert(span.size() == bag->size()); + */ + virtual Span allocate(const Bag *) = 0; + + /** + * @brief Return a pointer to the underlying storage + * + * @note weight returns a null-span S for an invalid bag + * i.e S.data() == nullptr and S.size() == 0 + */ + virtual Span weight(const Bag *) = 0; + + virtual std::unique_ptr> access(const KernelObject *) = 0; + virtual std::unique_ptr> read(const KernelObject *) const = 0; +}; + +} // namespace coco + +#endif // __COCO_IR_PLAIN_WEIGHT_CONTEXT_H__ diff --git a/compiler/coco/generic/src/ADT/Span.test.cpp b/compiler/coco/generic/src/ADT/Span.test.cpp new file mode 100644 index 00000000000..c313233a287 --- /dev/null +++ b/compiler/coco/generic/src/ADT/Span.test.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/ADT/Span.h" + +#include + +TEST(SpanTest, constructor) +{ + const uint32_t arr_size = 16; + int arr_data[arr_size]; + + coco::Span span{arr_data, arr_size}; + + coco::Span &ref = span; + const coco::Span &cref = span; + + ASSERT_EQ(ref.data(), arr_data); + ASSERT_EQ(cref.data(), arr_data); + ASSERT_EQ(ref.size(), arr_size); +} + +TEST(SpanTest, array_subscript_operator) +{ + // Create a stack-allocated chunk + const uint32_t arr_size = 16; + int arr_data[arr_size]; + + for (uint32_t n = 0; n < arr_size; ++n) + { + arr_data[n] = n; + } + + // Create a Span + coco::Span span{arr_data, arr_size}; + + coco::Span &ref = span; + const coco::Span &cref = span; + + ASSERT_EQ(ref[3], 3); + ASSERT_EQ(cref[3], 3); + + arr_data[3] = 16; + + ASSERT_EQ(ref[3], 16); + ASSERT_EQ(cref[3], 16); +} diff --git a/compiler/coco/generic/src/IR/Data.cpp b/compiler/coco/generic/src/IR/Data.cpp new file mode 100644 index 00000000000..b7194725397 --- /dev/null +++ b/compiler/coco/generic/src/IR/Data.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Data.h" + +#include +#include + +#include + +#include + +using namespace nncc::core::ADT; + +using stdex::make_unique; + +namespace +{ +class BlobContext +{ +public: + void allocate(const coco::Bag *b, uint32_t elemsize) + { + auto buffer = make_unique>(); + buffer->resize(b->size() * elemsize); + + _data[b] = std::move(buffer); + } + + void release(const coco::Bag *b) { _data.erase(b); } + +public: + uint8_t *at(const coco::Bag *b) + { + auto it = _data.find(b); + + if (it != _data.end()) + { + return it->second->data(); + } + + return nullptr; + } + +public: + uint32_t size(const coco::Bag *b) const + { + auto it = _data.find(b); + + if (it != _data.end()) + { + return it->second->size(); + } + + return 0; + } + +private: + std::map>> _data; +}; +} + +namespace +{ + +template class KernelOverlay : public kernel::Reader, public kernel::Accessor +{ +public: + KernelOverlay(T *base, const coco::KernelObject *object) : _base{base}, _object{object} + { + // DO NOTHING + } + +public: + T at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) const override + { + assert(_object->layout() != nullptr); + auto offset = _object->layout()->at(nth, ch, row, col); + return *(_base + offset.value()); + } + +public: + T &at(uint32_t nth, uint32_t ch, uint32_t row, uint32_t col) override + { + assert(_object->layout() != nullptr); + auto offset = _object->layout()->at(nth, ch, row, col); + return *(_base + offset.value()); + } + +private: + T *_base; + const coco::KernelObject *_object; +}; + +} // namespace + +namespace +{ +template class PlainWeightContextImpl final : public coco::PlainWeightContext +{ +public: + PlainWeightContextImpl(BlobContext *blob) : _blob{blob} + { + // DO NOTHING + } + +public: + PlainWeightContextImpl(const PlainWeightContextImpl &) = delete; + PlainWeightContextImpl(PlainWeightContextImpl &&) = delete; + +public: + coco::Span allocate(const coco::Bag *bag) override + { + assert(bag != nullptr); + _blob->allocate(bag, sizeof(T)); + return weight(bag); + } + + coco::Span weight(const coco::Bag *b) override + { + // TODO Check type later + if (auto data = _blob->at(b)) + { + uint32_t byte_size = _blob->size(b); + assert(byte_size % sizeof(T) == 0); + uint32_t elem_size = static_cast(byte_size / sizeof(T)); + + return coco::Span{reinterpret_cast(data), elem_size}; + } + + return coco::Span{nullptr, 0}; + } + +public: + std::unique_ptr> access(const coco::KernelObject *o) override + { + auto b = o->bag(); + assert(b != nullptr); + + if (auto base = reinterpret_cast(_blob->at(b))) + { + return make_unique>(base, o); + } + + return nullptr; + } + +public: + std::unique_ptr> read(const coco::KernelObject *o) const override + { + auto b = o->bag(); + assert(b != nullptr); + + if (auto base = reinterpret_cast(_blob->at(b))) + { + return make_unique>(base, o); + } + + return nullptr; + } + +private: + BlobContext *const _blob; +}; +} // namespace + +namespace +{ +struct DataImpl final : public coco::Data +{ + std::unique_ptr _blob; + std::unique_ptr> _fp32; + + bool allocated(const coco::Bag *b) const override { return _blob->at(b) != nullptr; } + + void release(const coco::Bag *b) override + { + assert(allocated(b)); + _blob->release(b); + } + + coco::PlainWeightContext *f32(void) override { return _fp32.get(); } + const coco::PlainWeightContext *f32(void) const override { return _fp32.get(); } +}; +} // namespace + +namespace coco +{ + +std::unique_ptr Data::create(void) +{ + auto blob = make_unique(); + auto fp32 = make_unique>(blob.get()); + + auto data = make_unique(); + + data->_blob = std::move(blob); + data->_fp32 = std::move(fp32); + + // GCC 4.9 tries to copy data (while GCC 6.X doesn't) + return std::move(data); +} + +} // namespace coco diff --git a/compiler/coco/generic/src/IR/Data.test.cpp b/compiler/coco/generic/src/IR/Data.test.cpp new file mode 100644 index 00000000000..1029dfe9f69 --- /dev/null +++ b/compiler/coco/generic/src/IR/Data.test.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "coco/IR/Data.h" +#include "coco/IR/Module.h" +#include "coco/IR/KernelLayouts.h" + +#include + +#include + +TEST(IR_DATA, construct) +{ + auto data = coco::Data::create(); + + coco::Data *mutable_ptr = data.get(); + const coco::Data *immutable_ptr = data.get(); + + ASSERT_NE(mutable_ptr->f32(), nullptr); + ASSERT_EQ(mutable_ptr->f32(), immutable_ptr->f32()); +} + +TEST(IR_DATA, allocate_and_link_bag) +{ + auto m = coco::Module::create(); + auto d = coco::Data::create(); + + // Create a bag + auto bag = m->entity()->bag()->create(9); + + // weight(...) SHOULD return a null-span for an invalid bag + { + auto span = d->f32()->weight(bag); + + ASSERT_EQ(span.data(), nullptr); + ASSERT_EQ(span.size(), 0); + } + + // Allocate a weight space + { + auto allocated_span = d->f32()->allocate(bag); + + ASSERT_NE(allocated_span.data(), nullptr); + ASSERT_EQ(allocated_span.size(), bag->size()); + + auto retrieved_span = d->f32()->weight(bag); + + ASSERT_EQ(allocated_span.data(), retrieved_span.data()); + ASSERT_EQ(allocated_span.size(), retrieved_span.size()); + } +} diff --git a/compiler/coco/requires.cmake b/compiler/coco/requires.cmake new file mode 100644 index 00000000000..654db88c31a --- /dev/null +++ b/compiler/coco/requires.cmake @@ -0,0 +1 @@ +require("angkor") diff --git a/compiler/cwrap/CMakeLists.txt b/compiler/cwrap/CMakeLists.txt new file mode 100644 index 00000000000..e1ae4d0b506 --- /dev/null +++ b/compiler/cwrap/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(cwrap STATIC ${SOURCES}) +set_target_properties(cwrap PROPERTIES POSITION_INDEPENDENT_CODE ON) +set_target_properties(cwrap PROPERTIES LINKER_LANGUAGE CXX) +target_include_directories(cwrap PUBLIC include) +# Let's apply nncc common compile options +# NOTE This will enable strict compilation (warnings as error). +# Please refer to top-level CMakeLists.txt for details +target_link_libraries(cwrap PRIVATE nncc_common) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(cwrap_test ${TESTS}) +target_link_libraries(cwrap_test cwrap) diff --git a/compiler/cwrap/README.md b/compiler/cwrap/README.md new file mode 100644 index 00000000000..5440ca3f9ea --- /dev/null +++ b/compiler/cwrap/README.md @@ -0,0 +1,23 @@ +# cwrap + +_cwrap_ is a collection of C++ wrappers for POSIX C API. + +## How to use + +Currently it supports only file descriptor. + +## Example +- File Descriptor + +```cpp +cwrap::Fildes fildes{open(path.c_str(), O_RDONLY)}; + +if (fildes.get() < 0) +{ + std::ostringstream ostr; + ostr << "Error: " << path << " not found" << std::endl; + throw std::runtime_error{ostr.str()}; +} + +google::protobuf::io::FileInputStream fis(fildes.get()); +``` diff --git a/compiler/cwrap/include/cwrap/Fildes.h b/compiler/cwrap/include/cwrap/Fildes.h new file mode 100644 index 00000000000..f1061cc57e4 --- /dev/null +++ b/compiler/cwrap/include/cwrap/Fildes.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CWRAP_FILDES_H__ +#define __CWRAP_FILDES_H__ + +namespace cwrap +{ + +/** + * @brief POSIX File Descriptor + * + * @note Fildes owns underlying file descriptor + */ +class Fildes final +{ +public: + Fildes(); + explicit Fildes(int value); + + // NOTE Copy is not allowed + Fildes(const Fildes &) = delete; + Fildes(Fildes &&); + + ~Fildes(); + +public: + Fildes &operator=(Fildes &&); + +public: + int get(void) const; + void set(int value); + + int release(void); + +private: + int _value; +}; + +bool valid(const Fildes &); + +} // namespace cwrap + +#endif // __CWRAP_FILDES_H__ diff --git a/compiler/cwrap/src/Fildes.cpp b/compiler/cwrap/src/Fildes.cpp new file mode 100644 index 00000000000..5ccb83f0537 --- /dev/null +++ b/compiler/cwrap/src/Fildes.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "cwrap/Fildes.h" + +#include +#include + +namespace +{ + +/** + * @note making inline to this function will prevent unused function error + * as error_value() is used only inside assert() + */ +inline bool error_value(int fd) { return fd == -1; } + +inline bool valid_value(int fd) { return fd >= 0; } + +} // namespace + +namespace cwrap +{ + +Fildes::Fildes() : _value{-1} +{ + // DO NOTHING +} + +Fildes::Fildes(int value) : _value{value} +{ + // DO NOTHING + assert(error_value(value) || valid_value(value)); +} + +Fildes::Fildes(Fildes &&fildes) +{ + set(fildes.release()); + assert(error_value(fildes.get())); +} + +Fildes::~Fildes() +{ + assert(error_value(_value) || valid_value(_value)); + + if (valid_value(_value)) + { + close(_value); + _value = -1; + } + + assert(error_value(_value)); +} + +Fildes &Fildes::operator=(Fildes &&fildes) +{ + set(fildes.release()); + return (*this); +} + +int Fildes::get(void) const { return _value; } + +void Fildes::set(int value) +{ + assert(error_value(_value) || valid_value(_value)); + + if (valid_value(_value)) + { + close(_value); + _value = -1; + } + assert(error_value(_value)); + + _value = value; + assert(_value == value); +} + +int Fildes::release(void) +{ + int res = get(); + _value = -1; + return res; +} + +bool valid(const Fildes &fildes) { return valid_value(fildes.get()); } + +} // namespace cwrap diff --git a/compiler/cwrap/src/Fildes.test.cpp b/compiler/cwrap/src/Fildes.test.cpp new file mode 100644 index 00000000000..08e1e2a5ed3 --- /dev/null +++ b/compiler/cwrap/src/Fildes.test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "cwrap/Fildes.h" + +#include +#include +#include + +#include +#include + +#include + +#define DECLARE_TEMPLATE(NAME) char NAME[] = "FILDES-TEST-XXXXXX" + +namespace +{ + +int make_temp(char *name_template) +{ + int fd = mkstemp(name_template); + + if (fd == -1) + { + throw std::runtime_error{"mkstemp failed"}; + } + + return fd; +} + +} // namespace make_temp + +TEST(FildesTest, default_constructor) +{ + cwrap::Fildes fildes; + + ASSERT_FALSE(cwrap::valid(fildes)); +} + +TEST(FildesTest, value_constructor) +{ + DECLARE_TEMPLATE(name_template); + + cwrap::Fildes fildes{make_temp(name_template)}; + + ASSERT_TRUE(cwrap::valid(fildes)); +} + +TEST(FildesTest, move_constructor) +{ + DECLARE_TEMPLATE(src_template); + DECLARE_TEMPLATE(dst_template); + + int src_fd = make_temp(src_template); + int dst_fd = make_temp(dst_template); + + cwrap::Fildes src{src_fd}; + cwrap::Fildes dst{dst_fd}; + + dst = std::move(src); + + ASSERT_FALSE(cwrap::valid(src)); + ASSERT_TRUE(cwrap::valid(dst)); + + ASSERT_EQ(dst.get(), src_fd); + + // "src_fd" SHOULD be valid, and "dst_fd" SHOULD be closed + ASSERT_NE(fcntl(src_fd, F_GETFD), -1); + ASSERT_EQ(fcntl(dst_fd, F_GETFD), -1); +} + +TEST(FildesTest, destructor) +{ + DECLARE_TEMPLATE(name_template); + + int fd = make_temp(name_template); + + ASSERT_NE(fcntl(fd, F_GETFD), -1); + { + cwrap::Fildes fildes{fd}; + } + ASSERT_EQ(fcntl(fd, F_GETFD), -1); +} diff --git a/compiler/dredd-rule-lib/CMakeLists.txt b/compiler/dredd-rule-lib/CMakeLists.txt new file mode 100644 index 00000000000..b39d8627265 --- /dev/null +++ b/compiler/dredd-rule-lib/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# copy rule-lib.sh (a library of shell script functions) +# +set(SOURCE_RULE_LIB "${CMAKE_CURRENT_SOURCE_DIR}/rule-lib.sh") +set(TARGET_RULE_LIB "${CMAKE_CURRENT_BINARY_DIR}/rule-lib.sh") + +add_custom_command( + OUTPUT ${TARGET_RULE_LIB} + COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_RULE_LIB}" "${TARGET_RULE_LIB}" + DEPENDS ${SOURCE_RULE_LIB} + COMMENT "Generate rule lib" +) + +# Generate dependencies +add_custom_target(dredd_rule_lib ALL DEPENDS ${TARGET_RULE_LIB}) + +# How to get the path of rule-lib.sh in other CMakeLists.txt +# +# get_target_property(DREDD_RULE_LIB_DIR +# dredd_rule_lib BINARY_DIR) +# set(RULE_LIB_PATH "${DREDD_RULE_LIB_DIR}/rule-lib.sh") diff --git a/compiler/dredd-rule-lib/README.md b/compiler/dredd-rule-lib/README.md new file mode 100644 index 00000000000..348b0aefba9 --- /dev/null +++ b/compiler/dredd-rule-lib/README.md @@ -0,0 +1,112 @@ +# dredd-rule-lib + +*dredd-rule-lib* is a library that defines functions to run *dredd* tests, which checks non-functional aspect of compiled files. + +## Terms + +Assume that we want to check the size of generated tflite file to be less than 1024 Bytes. +In such case, we'd like to use the following terms: + +- "metric" : *file size* +- "rule" : *file size < 1024* +- "metric function": `file_size` that returns size of a compiled tflite file + +Models (input of test) exist in *model repo*, where + +- "model repo" : directory where models exist. For *tf2tflite-dredd-pbtxt-test*, model repo is + `res/TensorFlowTests`. + +## Metrics supported + +The following metric functions are provided: +- `all_op_count` : the count of operations inside a compiled tflite file +- `file_size` : the size of compiled tflite file +- In addition, `op_count`, `conv2d_weight_not_constant`, etc. +- Please , refer to [`rule-lib.sh`](rule-lib.sh) for metric functions + +## Related projects - *dredd* tests + +Four *dredd* test projects use *dredd-rule-lib*: + +- *tf2tflite-dredd-pbtxt-test* + - Models in `pbtxt`, text file, are compiled into `tflite` file. + - Then `rule` file that each model has is checked against the `tflite` file. +- *tf2tflite-dredd-pb-test* + - Models in `pb`, binary file, are compiled into `tflite` file. + - Then `rule` file that each model has is checked against the `tflite` file. +- *tf2circle-dredd-pbtxt-test* + - Models in `pbtxt`, text file, are compiled into `circle` file. + - Then `rule` file that each model has is checked against the `circle` file. +- *tf2circle-dredd-pb-test* + - Models in `pb`, binary file, are compiled into `circle` file. + - Then `rule` file that each model has is checked against the `circle` file. + +## Rule file + +To be a target of *dredd*-tests, a `.rule` file **must** exist in a model directory. +Please refer to `res/TensorFlowTests/NET_0025/tflite_1.0_rel_requirement.rule` for an example. + +### Naming convention of rule file + +Note that the file name `tflite_1.0_rel_requirement.rule` is our convention containing the +information below: +- Generated file type (`tflite`) +- SDK version (`1.0_rel`) +- Purpose (`requirement`) + +## How do all these work? + +For *tf2tflite-dredd-pbtxt-test*, (*tf2circle-dredd-pbtxt-test* works similarly) + +``` +model repo tf2tflite-dredd-pbtxt-test +----------------------------------------------------------------------------------------------- + NET_0025 + ├── test.pbtxt ----------------------> converted to NET_0025.pb, and then NET_0025.tflite + | /|\ + ├── test.info ---------------------------+ + | (input/output info of model) + | + └── tflite_1.0_rel_requirement.rule --> running rule file against tflite --> pass or fail + /|\ + dredd-rule-lib | (using) + ---------------------- | + rule-lib.sh | + - defining rule function --+ +``` + +For *tf2tflite-dredd-pb-test*, (*tf2circle-dredd-pb-test* works similarly) + +``` +model repo tf2tflite-dredd-pb-test +----------------------------------------------------------------------------------------------- + Inception_v3 + ├── model.pb ------------------------> converted to Inception_v3.tflite + | /|\ + ├── model.info --------------------------+ + | (input/output info of model) + | + └── tflite_1.0_rel_requirement.rule --> running rule file against tflite --> pass or fail + /|\ + dredd-rule-lib | (using) + ---------------------- | + rule-lib.sh | + - defining rule function --+ +``` + +## Model repo and How to add a model as a target of a *dredd*-test. + +For *tf2tflite-dredd-pbtxt-test* and *tf2circle-dredd-pbtxt-test*, +model repo is `res/TensorFlowTests`. + +To add a model into these tests, the model directory name should be added into one of the following files: +- `test.lst` : This file resides in git +- `test.local.lst` : This file is ignored by git. Use this for personal purpose. + +For *tf2tflite-dredd-pb-test* and *tf2circle-dredd-pb-test*, +model repo is `tf2tflite-dredd-pb-test/contrib` and .`tf2circle-dredd-pb-test/contrib` respectively. + +Use these tests for binary models in large size. + +To add a model into these tests, the model directory name should be added into the following file: +- `contrib.lst` : This file is ignored by git. diff --git a/compiler/dredd-rule-lib/rule-lib.sh b/compiler/dredd-rule-lib/rule-lib.sh new file mode 100755 index 00000000000..8ebe3d7aff0 --- /dev/null +++ b/compiler/dredd-rule-lib/rule-lib.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# the following env vars should be defined to call dredd function (except RULE): +# COMPILED_FILE +# INSPECT_PROG_PATH +# VERIFY_PROG_PATH +# ERROR_LOG + +# exit if unknown var is used +set -u + +# --------------- +# HELPER FUNCTION + +init_error_log() +{ + # create ${ERROR_LOG} that redirect stderr for pipe + exec 2>"${ERROR_LOG}" +} + +argc_check() +{ + ACTUAL_ARGC=$1 + EXPECTED_ARGC=$2 + + if [ "$#" -ne 2 ];then + echo "argc_check : param count must be 2" > ${ERROR_LOG} + echo "error" # return value of sub-shell + exit 1 + fi + + if [ ${ACTUAL_ARGC} -ne ${EXPECTED_ARGC} ];then + echo "arg count mismatch: actual = ${ACTUAL_ARGC} vs expected = ${EXPECTED_ARGC}" > ${ERROR_LOG} + echo "error" # return value of sub-shell + exit 1 + fi +} + +file_path_check() +{ + argc_check $# 1 + + if [ ! -f $1 ]; then + echo "$1 does not exist" > ${ERROR_LOG} + echo "error" # return value of sub-shell + exit 1 + fi +} + +check_success_exit_code() +{ + ACTUAL_EXIT_CODE=$1 + EXPECTED_SUCCESS_CODE=$2 + + if [ ${ACTUAL_EXIT_CODE} -ne ${EXPECTED_SUCCESS_CODE} ];then + echo "error" + exit 1 + fi +} + +check_error_exit_code() +{ + ACTUAL_EXIT_CODE=$1 + EXPECTED_ERROR_CODE=$2 + + if [ ${ACTUAL_EXIT_CODE} -eq ${EXPECTED_ERROR_CODE} ];then + echo "error" + exit 1 + fi +} + +# END of HELPER FUNCTION +# ---------------------- + +# +# Define rule +# +# - Params: rule name (metric), actual value, condition, expected value +# - condition is '=', '!=', '<', '>', '<=', '>='. Refer to "man expr" +# - Return +# - 0 : success +# - 1 : fail (condition check fail) +# + +RULE() +{ + argc_check $# 4 + + RULE_NAME=$1 + ACTUAL=$2 + COND=$3 + EXPECTED=$4 + + # not to exit when expr result with 0 + set +e + + expr ${ACTUAL} ${COND} ${EXPECTED} > /dev/null + RESULT=$? + + # roll-back + set -e + + # Note: return value of 'expr' + # - 0 : result is true + # - 1 : result is false + # - 2 : error + + if [ ${RESULT} -eq 0 ];then + echo -e "** [${RULE_NAME}] \t success \t ([actual: ${ACTUAL}] ${COND} [expected: ${EXPECTED}])" + elif [ ${RESULT} -eq 1 ];then + echo -e "** [${RULE_NAME}] \t ** fail \t ([actual: ${ACTUAL}] ${COND} [expected: ${EXPECTED}])" + else + echo -e "\t** Error in [expr ${ACTUAL} ${COND} ${EXPECTED}]" + fi + + return ${RESULT} +} + +# +# Define each function to get quality value +# + +# Note: These function is called by a sub-shell. +# So return value should be passed through "echo return_value" +# tip: for debugging, surround the code with "set -x" and "set +x" + +file_size() +{ + file_path_check ${COMPILED_FILE} + + set -o pipefail + + ACTUAL=`init_error_log ; cat ${COMPILED_FILE} | wc -c` + + check_success_exit_code $? 0 + + echo ${ACTUAL} +} + +all_op_count() +{ + file_path_check ${COMPILED_FILE} + file_path_check ${INSPECT_PROG_PATH} + + set -o pipefail + + ACTUAL=`init_error_log ; ${INSPECT_PROG_PATH} --operators ${COMPILED_FILE} | wc -l` + + check_success_exit_code $? 0 + + echo ${ACTUAL} +} + +op_count() +{ + argc_check $# 1 + file_path_check ${COMPILED_FILE} + file_path_check ${INSPECT_PROG_PATH} + + set -o pipefail + + RESULT=`init_error_log ; ${INSPECT_PROG_PATH} --operators ${COMPILED_FILE}` + check_success_exit_code $? 0 + + # note : grep's exit code is 2 in case of error. + ACTUAL=`init_error_log ; echo "${RESULT}" | grep -wc "$1"` + check_error_exit_code $? 2 + + echo ${ACTUAL} +} + +conv2d_weight_not_constant() +{ + file_path_check ${COMPILED_FILE} + file_path_check ${INSPECT_PROG_PATH} + + set -o pipefail + + ACTUAL=`init_error_log ; \ + ${INSPECT_PROG_PATH} --conv2d_weight ${COMPILED_FILE} | \ + awk -F, '{ if ($2 != "CONST") print $0}' | wc -l` + + check_success_exit_code $? 0 + + echo ${ACTUAL} +} + +verify_file_format() +{ + file_path_check ${COMPILED_FILE} + file_path_check ${VERIFY_PROG_PATH} + + set -o pipefail + + ACTUAL=`init_error_log ; ${VERIFY_PROG_PATH} ${COMPILED_FILE} | grep -c "PASS"` + + # note grep can exit with 1 ("PASS" not found) and this is treated as an error + check_success_exit_code $? 0 + + echo ${ACTUAL} +} + +# TODO define more qullity test function diff --git a/compiler/enco-intf/CMakeLists.txt b/compiler/enco-intf/CMakeLists.txt new file mode 100644 index 00000000000..6014512c877 --- /dev/null +++ b/compiler/enco-intf/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(frontend) +add_subdirectory(cmdline) diff --git a/compiler/enco-intf/cmdline/CMakeLists.txt b/compiler/enco-intf/cmdline/CMakeLists.txt new file mode 100644 index 00000000000..91221ca1a10 --- /dev/null +++ b/compiler/enco-intf/cmdline/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(enco_intf_cmdline INTERFACE) +target_include_directories(enco_intf_cmdline INTERFACE include) diff --git a/compiler/enco-intf/cmdline/include/cmdline/View.h b/compiler/enco-intf/cmdline/include/cmdline/View.h new file mode 100644 index 00000000000..dd8d1d7eb58 --- /dev/null +++ b/compiler/enco-intf/cmdline/include/cmdline/View.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CMDLINE_VIEW_H__ +#define __CMDLINE_VIEW_H__ + +#include + +namespace cmdline +{ + +struct View +{ + virtual ~View() = default; + + virtual uint32_t size(void) const = 0; + virtual const char *at(uint32_t n) const = 0; +}; + +} // namespace cmdline + +#endif // __CMDLINE_VIEW_H__ diff --git a/compiler/enco-intf/frontend/CMakeLists.txt b/compiler/enco-intf/frontend/CMakeLists.txt new file mode 100644 index 00000000000..164dbd2b530 --- /dev/null +++ b/compiler/enco-intf/frontend/CMakeLists.txt @@ -0,0 +1,4 @@ +add_library(enco_intf_frontend INTERFACE) +target_include_directories(enco_intf_frontend INTERFACE include) +target_link_libraries(enco_intf_frontend INTERFACE coco_core) +target_link_libraries(enco_intf_frontend INTERFACE coco_generic) diff --git a/compiler/enco-intf/frontend/include/enco/Bundle.h b/compiler/enco-intf/frontend/include/enco/Bundle.h new file mode 100644 index 00000000000..7c3dca88f93 --- /dev/null +++ b/compiler/enco-intf/frontend/include/enco/Bundle.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_BUNDLE_H__ +#define __ENCO_BUNDLE_H__ + +#include "coco/IR/Module.h" +#include "coco/IR/Data.h" + +#include + +namespace enco +{ + +class Bundle +{ +public: + Bundle() = default; + +public: + coco::Module *module(void) const { return _m.get(); } + void module(std::unique_ptr &&m) { _m = std::move(m); } + +public: + coco::Data *data(void) const { return _d.get(); } + void data(std::unique_ptr &&d) { _d = std::move(d); } + +private: + std::unique_ptr _m; + std::unique_ptr _d; +}; + +} // namespace enco + +#endif // __ENCO_BUNDLE_H__ diff --git a/compiler/enco-intf/frontend/include/enco/Frontend.h b/compiler/enco-intf/frontend/include/enco/Frontend.h new file mode 100644 index 00000000000..d3a48183acb --- /dev/null +++ b/compiler/enco-intf/frontend/include/enco/Frontend.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_FRONTEND_H__ +#define __ENCO_FRONTEND_H__ + +#include "Bundle.h" + +namespace enco +{ + +struct Frontend +{ + virtual ~Frontend() = default; + + virtual Bundle load(void) const = 0; +}; + +} // namespace enco + +#endif // __FRONTEND_H__ diff --git a/compiler/enco/CMakeLists.txt b/compiler/enco/CMakeLists.txt new file mode 100644 index 00000000000..17300e25e33 --- /dev/null +++ b/compiler/enco/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(core) +add_subdirectory(frontend) +add_subdirectory(cli) +add_subdirectory(test) diff --git a/compiler/enco/README.md b/compiler/enco/README.md new file mode 100644 index 00000000000..d995a1e554e --- /dev/null +++ b/compiler/enco/README.md @@ -0,0 +1,25 @@ +# enco + +_enco_ is a tool which translates a NN model into a C++ source code that implements the following functions: +``` +struct Network; + +Network *Network_construct(); +void Network_destruct(Network *net); + +unsigned Network_input_count(const Network *); +const char *Network_input_name(const Network *, unsigned n); +unsigned Network_input_rank(const Network *, unsigned n); +unsigned Network_input_dim(const Network *, unsigned n, unsigned axis); +void Network_input_bind(Network *net, unsigned n, const void *ptr, unsigned len); + +unsigned Network_output_count(const Network *net); +const char *Network_output_name(const Network *, unsigned n); +unsigned Network_output_rank(const Network *, unsigned n); +unsigned Network_output_dim(const Network *, unsigned n, unsigned axis); +void Network_output_bind(Network *net, unsigned n, void *ptr, unsigned len); + +void Network_invoke(Network *net); +``` + +Generated C++ code internally uses Android NN API for acceleration. diff --git a/compiler/enco/cli/CMakeLists.txt b/compiler/enco/cli/CMakeLists.txt new file mode 100644 index 00000000000..5a43ab655ff --- /dev/null +++ b/compiler/enco/cli/CMakeLists.txt @@ -0,0 +1,11 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(enco-cli ${SOURCES}) +target_include_directories(enco-cli PRIVATE src) +target_link_libraries(enco-cli enco_intf_cmdline) +target_link_libraries(enco-cli enco_intf_frontend) +target_link_libraries(enco-cli enco_core) +target_link_libraries(enco-cli stdex) +target_link_libraries(enco-cli dl) +# Let's use project-wide compile options +target_link_libraries(enco-cli nncc_common) diff --git a/compiler/enco/cli/src/Driver.cpp b/compiler/enco/cli/src/Driver.cpp new file mode 100644 index 00000000000..185bb13b980 --- /dev/null +++ b/compiler/enco/cli/src/Driver.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include + +#include + +#include +#include + +#include + +namespace cmdline +{ + +// TODO Extract this helper class +class Vector : public cmdline::View +{ +public: + uint32_t size(void) const { return _args.size(); } + +public: + const char *at(uint32_t nth) const { return _args.at(nth).c_str(); } + +public: + Vector &append(const std::string &arg) + { + _args.emplace_back(arg); + return (*this); + } + +private: + std::vector _args; +}; + +} // namespace cmdline + +namespace +{ + +class Zone +{ +public: + Zone() = default; + +public: + const cmdline::View *args(void) const { return &_args; } + +public: + void append(const std::string &arg) { _args.append(arg); } + +private: + cmdline::Vector _args; +}; + +} // namespace + +#include + +namespace +{ + +class FrontendFactory +{ +public: + FrontendFactory(const std::string &path) + { + _handle = dlopen(path.c_str(), RTLD_LAZY); + assert(_handle != nullptr); + } + +public: + // Copy is not allowed to avoid double close + FrontendFactory(const FrontendFactory &) = delete; + FrontendFactory(FrontendFactory &&) = delete; + +public: + ~FrontendFactory() { dlclose(_handle); } + +private: + using Entry = std::unique_ptr (*)(const cmdline::View &); + +private: + Entry entry(void) const + { + auto entry = reinterpret_cast(dlsym(_handle, "make_frontend")); + assert(entry != nullptr); + return entry; + } + +public: + std::unique_ptr make(const cmdline::View *args) const + { + auto fn = entry(); + return fn(*args); + } + +private: + void *_handle; +}; + +} // namespace + +namespace +{ + +class FrontendZone : public Zone +{ +public: + FrontendZone(const std::string &path) : _factory{path} + { + // DO NOTHING + } + +public: + const FrontendFactory *factory(void) const { return &_factory; } + +private: + FrontendFactory _factory; +}; + +} // namespace + +#include + +#include + +#include +#include + +static int entry(int argc, char **argv) +{ + // Usage: + // [Command] --frontend [Frontend .so path] --frontend-arg ... + std::unique_ptr frontend_zone; + cmdline::Vector backend_args; + + // Simple argument parser (based on map) + std::map> argparse; + + argparse["--frontend"] = [&](const std::string &path) { + frontend_zone = stdex::make_unique(path); + }; + + argparse["--frontend-arg"] = [&](const std::string &arg) { frontend_zone->append(arg); }; + argparse["--backend-arg"] = [&](const std::string &arg) { backend_args.append(arg); }; + + if (argc < 2) + { + std::cerr << "Usage:" << std::endl; + std::cerr << "[Command] --frontend [.so path]" << std::endl; + std::cerr << " --frontend-arg [argument] ..." << std::endl; + std::cerr << " --backend-arg [argument] ..." << std::endl; + return 255; + } + + for (int n = 1; n < argc; n += 2) + { + const std::string tag{argv[n]}; + const std::string arg{argv[n + 1]}; + + auto it = argparse.find(tag); + + if (it == argparse.end()) + { + std::cerr << "Option '" << tag << "' is not supported" << std::endl; + return 255; + } + + it->second(arg); + } + + assert(frontend_zone != nullptr); + + auto frontend = frontend_zone->factory()->make(frontend_zone->args()); + + auto bundle = frontend->load(); + + auto backend = make_backend(backend_args); + + backend->compile(bundle.module(), bundle.data()); + + return 0; +} + +#ifdef NDEBUG +int main(int argc, char **argv) +{ + try + { + return entry(argc, argv); + } + catch (const std::exception &e) + { + std::cerr << "ERROR: " << e.what() << std::endl; + } + + return 255; +} +#else // NDEBUG +int main(int argc, char **argv) +{ + // NOTE main does not catch internal exceptions for debug build to make it easy to + // check the stacktrace with a debugger + return entry(argc, argv); +} +#endif // !NDEBUG diff --git a/compiler/enco/core/CMakeLists.txt b/compiler/enco/core/CMakeLists.txt new file mode 100644 index 00000000000..f437e687ab6 --- /dev/null +++ b/compiler/enco/core/CMakeLists.txt @@ -0,0 +1,35 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +### +### enco_core is built as a shared library to support "interactive debugging". +### +### interactive debugging helpers are stripped during linking when enco_core is +### built as a static library +### +add_library(enco_core SHARED ${SOURCES}) +target_include_directories(enco_core PRIVATE src) +target_include_directories(enco_core PUBLIC include) +target_link_libraries(enco_core PUBLIC enco_intf_cmdline) +target_link_libraries(enco_core PUBLIC coco_core) +target_link_libraries(enco_core PUBLIC coco_generic) +# These libraries are linked for internal use, and thus does not appear in public headers. +target_link_libraries(enco_core PRIVATE pp) +target_link_libraries(enco_core PRIVATE morph) +target_link_libraries(enco_core PRIVATE stdex) +# Let's use nncc project-wide build options +target_link_libraries(enco_core PRIVATE nncc_common) + +nnas_find_package(GTest QUIET) + +if(NOT GTest_FOUND) + return() +endif(NOT GTest_FOUND) + +add_executable(enco_core_test ${TESTS}) +target_include_directories(enco_core_test PRIVATE src) +target_link_libraries(enco_core_test gtest_main) +target_link_libraries(enco_core_test enco_core) +target_link_libraries(enco_core_test morph) +add_test(enco_core_test enco_core_test) diff --git a/compiler/enco/core/include/enco/Backend.h b/compiler/enco/core/include/enco/Backend.h new file mode 100644 index 00000000000..5da903ed20b --- /dev/null +++ b/compiler/enco/core/include/enco/Backend.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_BACKEND_H__ +#define __ENCO_BACKEND_H__ + +#include "cmdline/View.h" + +#include "coco/IR/Module.h" +#include "coco/IR/Data.h" + +#include + +namespace enco +{ + +struct Backend +{ + virtual ~Backend() = default; + + virtual void compile(coco::Module *m, coco::Data *d) = 0; +}; + +} // namespace enco + +std::unique_ptr make_backend(const cmdline::View &); + +#endif // __ENCO_BACKEND_H__ diff --git a/compiler/enco/core/src/ANN/Binder.h b/compiler/enco/core/src/ANN/Binder.h new file mode 100644 index 00000000000..71b95676bfe --- /dev/null +++ b/compiler/enco/core/src/ANN/Binder.h @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_BINDER_H__ +#define __ANN_BINDER_H__ + +#include "ANN/IR/Module.h" + +#include + +#include + +#include + +/** + * @brief A bridge between ann::Module and coco::Block + */ +class ANNBinder +{ +public: + ANNBinder(coco::Block *block, std::unique_ptr &&module) + : _block{block}, _module{std::move(module)} + { + // DO NOTHING + } + +public: + const coco::Block *block(void) const { return _block; } + coco::Block *block(void) { return _block; } + +public: + const ann::Module *module(void) const { return _module.get(); } + +public: + /** + * @brief Return the set of bags that the current ANN subnet accesses + */ + std::set bags(void) const + { + std::set res; + + for (auto it = _operands.begin(); it != _operands.end(); ++it) + { + res.insert(it->first); + } + + return res; + } + +public: + template ann::OperandID addOperand(void) + { + return _module->operand()->create(ann::dtype()); + }; + + template ann::OperandID addOperand(const nncc::core::ADT::tensor::Shape &shape) + { + return _module->operand()->create(ann::dtype(), shape); + } + +public: + template ann::OperandID addOperand(const coco::FeatureObject *obj) + { + auto bag = obj->bag(); + assert(bag != nullptr); + + auto it = _operands.find(bag); + + if (it != _operands.end()) + { + return it->second; + } + + auto operand = addOperand(morph::nnapi::as_tensor_shape(obj->shape())); + _operands[obj->bag()] = operand; + return operand; + }; + + template ann::OperandID addOperand(const coco::KernelObject *obj) + { + auto bag = obj->bag(); + assert(bag != nullptr); + + auto it = _operands.find(bag); + + if (it != _operands.end()) + { + return it->second; + } + + auto operand = addOperand(morph::nnapi::as_tensor_shape(obj->shape())); + _operands[obj->bag()] = operand; + return operand; + }; + +public: + /// @brief Set scalar weight + template void setOperand(const ann::OperandID &id, const T &value) + { + static_assert(std::is_arithmetic::value, "T should be arithmetic"); + auto weight = _module->weight()->create(); + weight->fill(value); + _module->operand()->at(id)->weight(weight); + } + + /// @brief Set non-scalar weight + template void setOperand(const ann::OperandID &id, It beg, It end) + { + auto weight = _module->weight()->create(); + weight->fill(beg, end); + _module->operand()->at(id)->weight(weight); + } + +public: + void addOperation(ann::Operation::Code code, std::initializer_list inputs, + std::initializer_list outputs) + { + _module->operation()->create(code, inputs, outputs); + } + +public: + /** + * @brief Identify a sequence of coco::Bag * as subnet's inputs + * + * NOTE 1. This method takes input iterator over coco::Bag * values + * NOTE 2. All the identifyInputs class except the last one will be ignored if there are + * multiple identifyInputs calls + */ + template void identifyInputs(It beg, It end) + { + _inputs.clear(); + _module->input()->clear(); + + for (auto it = beg; it != end; ++it) + { + auto const bag = *it; + _inputs.emplace_back(*it); + _module->input()->emplace_back(_operands.at(bag)); + } + } + + template void identifyInputs(T &&values) + { + identifyInputs(std::begin(values), std::end(values)); + } + +public: + /** + * @brief Identify a sequence of coco::Bag * as subnet's outputs + * + * NOTE 1. This method takes input iterator over coco::Bag * values + * NOTE 2. All the identifyOutputs class except the last one will be ignored if there are + * multiple identifyOutputs calls + */ + template void identifyOutputs(It beg, It end) + { + _outputs.clear(); + _module->output()->clear(); + + for (auto it = beg; it != end; ++it) + { + auto const bag = *it; + _outputs.emplace_back(bag); + _module->output()->emplace_back(_operands.at(bag)); + } + } + + template void identifyOutputs(T &&values) + { + identifyOutputs(std::begin(values), std::end(values)); + } + +public: + coco::Bag *input(uint32_t n) const { return _inputs.at(n); } + coco::Bag *output(uint32_t n) const { return _outputs.at(n); } + +public: + /** + * @brief Return true if a given bag has an associated operand in ANN IR + */ + bool associated(coco::Bag *b) const { return _operands.find(b) != _operands.end(); } + + /** + * @brief Return operand ID associated with a given bag + * @note The behavior of operand(b) is defined only when associated(b) holds. + */ + ann::OperandID operand(coco::Bag *b) const + { + assert(associated(b)); + return _operands.at(b); + } + +private: + coco::Block *const _block; + std::unique_ptr _module; + +private: + std::vector _inputs; + std::vector _outputs; + +private: + /// @brief Operand ID assigned for each coco::Bag + std::map _operands; +}; + +#endif // __ANN_BINDER_H__ diff --git a/compiler/enco/core/src/ANN/Context.cpp b/compiler/enco/core/src/ANN/Context.cpp new file mode 100644 index 00000000000..d4d1882faf2 --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ANN/Context.h" + +#include + +ANNBinder *ANNContext::create(coco::Block *blk) +{ + auto mod = stdex::make_unique(); + auto obj = stdex::make_unique(blk, std::move(mod)); + auto ptr = obj.get(); + + _binders.emplace_back(std::move(obj)); + _map[blk] = ptr; + + return ptr; +} diff --git a/compiler/enco/core/src/ANN/Context.h b/compiler/enco/core/src/ANN/Context.h new file mode 100644 index 00000000000..915651eb5ea --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_CONTEXT_H__ +#define __ANN_CONTEXT_H__ + +#include "ANN/Binder.h" + +#include +#include + +#include + +struct ANNContext +{ +public: + ANNBinder *create(coco::Block *blk); + +public: + uint32_t count(void) const { return _binders.size(); } + +public: + ANNBinder *nth(uint32_t n) { return _binders.at(n).get(); } + const ANNBinder *nth(uint32_t n) const { return _binders.at(n).get(); } + +public: + ANNBinder *find(const coco::Block *blk) const + { + auto it = _map.find(blk); + + if (it == _map.end()) + { + return nullptr; + } + + return it->second; + } + +private: + std::vector> _binders; + std::map _map; +}; + +#endif // __ANN_CONTEXT_H__ diff --git a/compiler/enco/core/src/ANN/Context.test.cpp b/compiler/enco/core/src/ANN/Context.test.cpp new file mode 100644 index 00000000000..7fd26f30c0e --- /dev/null +++ b/compiler/enco/core/src/ANN/Context.test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Context.h" + +#include + +#include + +namespace +{ +class ANNContextTest : public ::testing::Test +{ +public: + ANNContextTest() { m = coco::Module::create(); } + +public: + virtual ~ANNContextTest() = default; + +protected: + std::unique_ptr m; +}; +} + +TEST_F(ANNContextTest, constructor) +{ + ANNContext ann_ctx; + + ASSERT_EQ(ann_ctx.count(), 0); +} + +TEST_F(ANNContextTest, create) +{ + ANNContext ann_ctx; + + auto blk = m->entity()->block()->create(); + auto binder = ann_ctx.create(blk); + + ASSERT_NE(binder, nullptr); +} + +TEST_F(ANNContextTest, find) +{ + ANNContext ann_ctx; + + // CASE: Corresponding binder does not exist + { + auto blk = m->entity()->block()->create(); + ASSERT_EQ(ann_ctx.find(blk), nullptr); + } + + // CASE: Corresponding binder does exist + { + auto blk = m->entity()->block()->create(); + auto binder_created = ann_ctx.create(blk); + auto binder_found = ann_ctx.find(blk); + + ASSERT_EQ(binder_created, binder_found); + } +} diff --git a/compiler/enco/core/src/ANN/IR/DType.cpp b/compiler/enco/core/src/ANN/IR/DType.cpp new file mode 100644 index 00000000000..7d4585a4967 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DType.h" + +namespace ann +{ + +template <> DType dtype(void) { return DType::S32; } +template <> DType dtype(void) { return DType::F32; } + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/DType.h b/compiler/enco/core/src/ANN/IR/DType.h new file mode 100644 index 00000000000..b7583b09a71 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_DTYPE_H__ +#define __ANN_IR_DTYPE_H__ + +#include + +namespace ann +{ + +enum class DType +{ + UNK, + S32, + F32 +}; + +template DType dtype(void); + +} // namespace ann + +#endif // __ANN_IR_DTYPE_H__ diff --git a/compiler/enco/core/src/ANN/IR/DType.test.cpp b/compiler/enco/core/src/ANN/IR/DType.test.cpp new file mode 100644 index 00000000000..8184ece9b01 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/DType.test.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DType.h" + +#include + +TEST(ANN_IR_DTYPE, dtype) +{ + ASSERT_EQ(ann::dtype(), ann::DType::S32); + ASSERT_EQ(ann::dtype(), ann::DType::F32); +} diff --git a/compiler/enco/core/src/ANN/IR/InputList.h b/compiler/enco/core/src/ANN/IR/InputList.h new file mode 100644 index 00000000000..51f0fd95ae5 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/InputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_INPUT_LIST_H__ +#define __ANN_IR_INPUT_LIST_H__ + +#include "ANN/IR/OperandID.h" + +#include + +namespace ann +{ + +using InputList = std::vector; + +} // namespace ann + +#endif // __ANN_IR_INPUT_LIST_H__ diff --git a/compiler/enco/core/src/ANN/IR/Module.h b/compiler/enco/core/src/ANN/IR/Module.h new file mode 100644 index 00000000000..b443b42355f --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Module.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_MODULE_H__ +#define __ANN_IR_MODULE_H__ + +#include "ANN/IR/WeightInventory.h" +#include "ANN/IR/OperandInventory.h" +#include "ANN/IR/OperationInventory.h" +#include "ANN/IR/InputList.h" +#include "ANN/IR/OutputList.h" + +namespace ann +{ + +class Module +{ +public: + Module() = default; + +public: + WeightInventory *weight(void) { return &_weight; } + const WeightInventory *weight(void) const { return &_weight; } + + OperandInventory *operand(void) { return &_operand; } + const OperandInventory *operand(void) const { return &_operand; } + + OperationInventory *operation(void) { return &_operation; } + const OperationInventory *operation(void) const { return &_operation; } + + InputList *input(void) { return &_input; } + const InputList *input(void) const { return &_input; } + + OutputList *output(void) { return &_output; } + const OutputList *output(void) const { return &_output; } + +private: + WeightInventory _weight; + OperandInventory _operand; + OperationInventory _operation; + InputList _input; + OutputList _output; +}; + +} // namespace ann + +#endif // __ANN_IR_MODULE_H__ diff --git a/compiler/enco/core/src/ANN/IR/Module.test.cpp b/compiler/enco/core/src/ANN/IR/Module.test.cpp new file mode 100644 index 00000000000..4b946c875f9 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Module.test.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Module.h" + +#include + +TEST(ANN_IR_MODULE, constructor) +{ + ann::Module m; + + ann::Module *mutable_ptr = &m; + const ann::Module *immutable_ptr = &m; + + ASSERT_NE(mutable_ptr->weight(), nullptr); + ASSERT_EQ(mutable_ptr->weight(), immutable_ptr->weight()); + + ASSERT_NE(mutable_ptr->operand(), nullptr); + ASSERT_EQ(mutable_ptr->operand(), immutable_ptr->operand()); + + ASSERT_NE(mutable_ptr->operation(), nullptr); + ASSERT_EQ(mutable_ptr->operation(), immutable_ptr->operation()); +} diff --git a/compiler/enco/core/src/ANN/IR/Operand.h b/compiler/enco/core/src/ANN/IR/Operand.h new file mode 100644 index 00000000000..3b15ed739a8 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operand.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OPERAND_H__ +#define __ANN_IR_OPERAND_H__ + +#include "ANN/IR/DType.h" +#include "ANN/IR/Weight.h" + +#include + +namespace ann +{ + +class Operand +{ +public: + virtual ~Operand() = default; + +public: + DType dtype(void) const { return _dtype; } + void dtype(const DType &dtype) { _dtype = dtype; } + + const Weight *weight(void) const { return _weight; } + void weight(const Weight *weight) { _weight = weight; } + +private: + DType _dtype = DType::UNK; + const Weight *_weight = nullptr; +}; + +} // namespace ann + +namespace ann +{ + +/** + * @brief Plain (non-qunatized) Scalar Operand + */ +struct ScalarOperand final : public Operand +{ +}; + +} // namespace ann + +namespace ann +{ + +/** + * @brief Plain (non-qunatized) Tensor Operand + */ +struct TensorOperand final : public Operand +{ +public: + TensorOperand(const nncc::core::ADT::tensor::Shape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + const nncc::core::ADT::tensor::Shape &shape(void) const { return _shape; } + +private: + nncc::core::ADT::tensor::Shape _shape; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_H__ diff --git a/compiler/enco/core/src/ANN/IR/Operand.test.cpp b/compiler/enco/core/src/ANN/IR/Operand.test.cpp new file mode 100644 index 00000000000..98ac4ebd0eb --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operand.test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Operand.h" + +#include + +TEST(ANN_IR_SCALAR_OPERAND, constructor) +{ + const ann::ScalarOperand operand; + + ASSERT_EQ(operand.dtype(), ann::DType::UNK); + ASSERT_EQ(operand.weight(), nullptr); +} + +TEST(ANN_IR_TENSOR_OPERAND, constructor) +{ + const nncc::core::ADT::tensor::Shape shape{1, 2}; + const ann::TensorOperand operand{shape}; + + ASSERT_EQ(operand.dtype(), ann::DType::UNK); + ASSERT_EQ(operand.weight(), nullptr); + ASSERT_EQ(operand.shape(), shape); +} diff --git a/compiler/enco/core/src/ANN/IR/OperandID.h b/compiler/enco/core/src/ANN/IR/OperandID.h new file mode 100644 index 00000000000..f1617aacb96 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandID.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OPERAND_ID_H__ +#define __ANN_IR_OPERAND_ID_H__ + +#include + +namespace ann +{ + +class OperandID +{ +public: + OperandID() : _value{0} + { + // DO NOTHING + } + +public: + explicit OperandID(uint32_t value) : _value{value} + { + // DO NOTHING + } + +public: + uint32_t value(void) const { return _value; } + +private: + uint32_t _value; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_ID_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperandID.test.cpp b/compiler/enco/core/src/ANN/IR/OperandID.test.cpp new file mode 100644 index 00000000000..04c23b9c84a --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandID.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "OperandID.h" + +#include + +TEST(ANN_IR_OPERAND_ID, default_constructor) +{ + ann::OperandID id; + + ASSERT_EQ(id.value(), 0); +} + +TEST(ANN_IR_OPERAND_ID, explicit_constructor) +{ + ann::OperandID id{4}; + + ASSERT_EQ(id.value(), 4); +} diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.cpp b/compiler/enco/core/src/ANN/IR/OperandInventory.cpp new file mode 100644 index 00000000000..c7ad3881146 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ANN/IR/OperandInventory.h" + +#include + +using stdex::make_unique; + +namespace ann +{ + +OperandID OperandInventory::create(const DType &dtype) +{ + uint32_t id = _operands.size(); + + auto operand = make_unique(); + operand->dtype(dtype); + + _operands.emplace_back(std::move(operand)); + + return OperandID{id}; +} + +OperandID OperandInventory::create(const DType &dtype, const nncc::core::ADT::tensor::Shape &shape) +{ + uint32_t id = _operands.size(); + + auto operand = make_unique(shape); + operand->dtype(dtype); + + _operands.emplace_back(std::move(operand)); + + return OperandID{id}; +} + +Operand *OperandInventory::at(const OperandID &id) { return _operands.at(id.value()).get(); } + +const Operand *OperandInventory::at(const OperandID &id) const +{ + return _operands.at(id.value()).get(); +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.h b/compiler/enco/core/src/ANN/IR/OperandInventory.h new file mode 100644 index 00000000000..23eb081193d --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OPERAND_INVENTORY_H__ +#define __ANN_IR_OPERAND_INVENTORY_H__ + +#include "ANN/IR/OperandID.h" +#include "ANN/IR/Operand.h" + +#include + +#include +#include + +namespace ann +{ + +class OperandInventory +{ +public: + OperandID create(const DType &); + OperandID create(const DType &, const nncc::core::ADT::tensor::Shape &); + +public: + template void each(Callable &&cb) const + { + for (uint32_t n = 0; n < _operands.size(); ++n) + { + cb(OperandID{n}, _operands.at(n).get()); + } + } + +public: + Operand *at(const OperandID &id); + const Operand *at(const OperandID &id) const; + +private: + std::vector> _operands; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERAND_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp b/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp new file mode 100644 index 00000000000..e576752bc68 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperandInventory.test.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "OperandInventory.h" + +#include + +TEST(ANN_IR_OPERAND_INVENTORY, constructor) +{ + ann::OperandInventory inven; + + uint32_t count = 0; + + inven.each([&](const ann::OperandID &, const ann::Operand *) { ++count; }); + + ASSERT_EQ(count, 0); +} diff --git a/compiler/enco/core/src/ANN/IR/Operation.def b/compiler/enco/core/src/ANN/IR/Operation.def new file mode 100644 index 00000000000..68fd394cf20 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.def @@ -0,0 +1,17 @@ +#ifndef ANN_OPERATION +#error Define ANN_OPERATION first +#endif // ANN_OPERATION + +// ANN_OPERATION(TAG, ENUM_VALUE) +ANN_OPERATION(ADD, ANEURALNETWORKS_ADD) +ANN_OPERATION(MUL, ANEURALNETWORKS_MUL) +ANN_OPERATION(CONV_2D, ANEURALNETWORKS_CONV_2D) +ANN_OPERATION(DEPTHWISE_CONV_2D, ANEURALNETWORKS_DEPTHWISE_CONV_2D) +ANN_OPERATION(MAX_POOL_2D, ANEURALNETWORKS_MAX_POOL_2D) +ANN_OPERATION(AVG_POOL_2D, ANEURALNETWORKS_AVERAGE_POOL_2D) +ANN_OPERATION(RELU, ANEURALNETWORKS_RELU) +ANN_OPERATION(RELU6, ANEURALNETWORKS_RELU6) +ANN_OPERATION(PAD, ANEURALNETWORKS_PAD) +ANN_OPERATION(CONCAT, ANEURALNETWORKS_CONCATENATION) +ANN_OPERATION(SUB, ANEURALNETWORKS_SUB) +ANN_OPERATION(DIV, ANEURALNETWORKS_DIV) diff --git a/compiler/enco/core/src/ANN/IR/Operation.h b/compiler/enco/core/src/ANN/IR/Operation.h new file mode 100644 index 00000000000..cacc2b794f1 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OPERATION_H__ +#define __ANN_IR_OPERATION_H__ + +#include "ANN/IR/OperandID.h" + +#include +#include + +namespace ann +{ + +class Operation +{ +public: + enum class Code + { +#define ANN_OPERATION(TAG, VALUE) TAG, +#include "Operation.def" +#undef ANN_OPERATION + }; + +public: + Operation(const Code &code, std::initializer_list inputs, + std::initializer_list outputs) + : _code{code}, _inputs{inputs}, _outputs{outputs} + { + // DO NOTHING + } + +public: + const Code &code(void) const { return _code; } + const std::vector &inputs(void) const { return _inputs; } + const std::vector &outputs(void) const { return _outputs; } + +private: + Code _code; + std::vector _inputs; + std::vector _outputs; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERATION_H__ diff --git a/compiler/enco/core/src/ANN/IR/Operation.test.cpp b/compiler/enco/core/src/ANN/IR/Operation.test.cpp new file mode 100644 index 00000000000..d1b716733f1 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Operation.test.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Operation.h" + +#include + +TEST(ANN_IR_OPERATION, constructor) +{ + ann::Operation op{ann::Operation::Code::CONV_2D, {}, {}}; + + ASSERT_EQ(op.code(), ann::Operation::Code::CONV_2D); + ASSERT_EQ(op.inputs().size(), 0); + ASSERT_EQ(op.outputs().size(), 0); +} diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.cpp b/compiler/enco/core/src/ANN/IR/OperationInventory.cpp new file mode 100644 index 00000000000..37d48c1709a --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "OperationInventory.h" + +#include + +using stdex::make_unique; + +namespace ann +{ + +void OperationInventory::create(Operation::Code code, std::initializer_list inputs, + std::initializer_list outputs) +{ + _operations.emplace_back(make_unique(code, inputs, outputs)); +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.h b/compiler/enco/core/src/ANN/IR/OperationInventory.h new file mode 100644 index 00000000000..11c6be98a1f --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OPERATION_INVENTORY_H__ +#define __ANN_IR_OPERATION_INVENTORY_H__ + +#include "ANN/IR/Operation.h" +#include "ANN/IR/OperandID.h" + +#include + +#include + +namespace ann +{ + +class OperationInventory +{ +public: + void create(Operation::Code code, std::initializer_list inputs, + std::initializer_list outputs); + +public: + uint32_t count(void) const { return _operations.size(); } + +public: + const Operation *at(uint32_t n) const { return _operations.at(n).get(); } + +private: + std::vector> _operations; +}; + +} // namespace ann + +#endif // __ANN_IR_OPERATION_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp b/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp new file mode 100644 index 00000000000..0e91a4f535e --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OperationInventory.test.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "OperationInventory.h" + +#include + +TEST(ANN_IR_OPERATION_INVENTORY, constructor) +{ + ann::OperationInventory inven; + + ASSERT_EQ(inven.count(), 0); +} + +TEST(ANN_IR_OPERATION_INVENTORY, create) +{ + ann::OperationInventory inven; + + inven.create(ann::Operation::Code::CONV_2D, {ann::OperandID{0}}, {ann::OperandID{3}}); + + ASSERT_EQ(inven.count(), 1); + ASSERT_NE(inven.at(0), nullptr); + + ASSERT_EQ(inven.at(0)->code(), ann::Operation::Code::CONV_2D); + ASSERT_EQ(inven.at(0)->inputs().size(), 1); + ASSERT_EQ(inven.at(0)->outputs().size(), 1); +} diff --git a/compiler/enco/core/src/ANN/IR/OutputList.h b/compiler/enco/core/src/ANN/IR/OutputList.h new file mode 100644 index 00000000000..2dd89113803 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/OutputList.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_OUTPUT_LIST_H__ +#define __ANN_IR_OUTPUT_LIST_H__ + +#include "ANN/IR/OperandID.h" + +#include + +namespace ann +{ + +using OutputList = std::vector; + +} // namespace ann + +#endif // __ANN_IR_OUTPUT_LIST_H__ diff --git a/compiler/enco/core/src/ANN/IR/Weight.h b/compiler/enco/core/src/ANN/IR/Weight.h new file mode 100644 index 00000000000..062aa6d19a2 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Weight.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ANN_IR_WEIGHT_H__ +#define __ANN_IR_WEIGHT_H__ + +#include + +#include +#include + +namespace ann +{ + +class Weight +{ +public: + const uint8_t *base(void) const { return _buffer.data(); } + uint32_t size(void) const { return _buffer.size(); } + +public: + template void fill(const T &value) + { + static_assert(std::is_arithmetic::value, "T should be arithmetic"); + _buffer.clear(); + + auto arr = reinterpret_cast(&value); + + for (uint32_t b = 0; b < sizeof(T); ++b) + { + _buffer.emplace_back(arr[b]); + } + } + + template void fill(It beg, It end) + { + _buffer.clear(); + + for (auto it = beg; it != end; ++it) + { + const auto value = *it; + auto arr = reinterpret_cast(&value); + + for (uint32_t b = 0; b < sizeof(value); ++b) + { + _buffer.emplace_back(arr[b]); + } + } + } + +private: + std::vector _buffer; +}; + +} // namespace ann + +#endif // __ANN_IR_WEIGHT_H__ diff --git a/compiler/enco/core/src/ANN/IR/Weight.test.cpp b/compiler/enco/core/src/ANN/IR/Weight.test.cpp new file mode 100644 index 00000000000..53532114c93 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/Weight.test.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Weight.h" + +#include + +TEST(ANN_IR_WEIGHT, constructor) +{ + ann::Weight weight; + + ASSERT_EQ(weight.base(), nullptr); + ASSERT_EQ(weight.size(), 0); +} + +TEST(ANN_IR_WEIGHT, fill_scalar_int) +{ + ann::Weight weight; + + weight.fill(3); + + ASSERT_NE(weight.base(), nullptr); + ASSERT_EQ(*reinterpret_cast(weight.base()), 3); +} + +TEST(ANN_IR_WEIGHT, fill_vector_float) +{ + std::vector values{1.0f, 2.0f}; + + ann::Weight weight; + + weight.fill(values.begin(), values.end()); + + ASSERT_NE(weight.base(), nullptr); + + auto arr = reinterpret_cast(weight.base()); + + ASSERT_FLOAT_EQ(arr[0], 1.0f); + ASSERT_FLOAT_EQ(arr[1], 2.0f); +} diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.cpp b/compiler/enco/core/src/ANN/IR/WeightInventory.cpp new file mode 100644 index 00000000000..d8809ac08c0 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "WeightInventory.h" + +#include + +using stdex::make_unique; + +namespace ann +{ + +Weight *WeightInventory::create(void) +{ + auto hnd = make_unique(); + auto ptr = hnd.get(); + _weights.push_back(std::move(hnd)); + return ptr; +} + +} // namespace ann diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.h b/compiler/enco/core/src/ANN/IR/WeightInventory.h new file mode 100644 index 00000000000..fd166837f9b --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __WEIGHT_INVENTORY_H__ +#define __WEIGHT_INVENTORY_H__ + +#include "ANN/IR/Weight.h" + +#include + +namespace ann +{ + +class WeightInventory +{ +public: + Weight *create(void); + +private: + std::vector> _weights; +}; + +} // namespace ann + +#endif // __WEIGHT_INVENTORY_H__ diff --git a/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp b/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp new file mode 100644 index 00000000000..143bdfddf61 --- /dev/null +++ b/compiler/enco/core/src/ANN/IR/WeightInventory.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "WeightInventory.h" + +#include + +TEST(ANN_IR_WEIGHT_INVENTORY, create) +{ + ann::WeightInventory inven; + + auto weight = inven.create(); + + ASSERT_EQ(weight->base(), nullptr); + ASSERT_EQ(weight->size(), 0); +} diff --git a/compiler/enco/core/src/AsmCode.cpp b/compiler/enco/core/src/AsmCode.cpp new file mode 100644 index 00000000000..70d6f30b3a8 --- /dev/null +++ b/compiler/enco/core/src/AsmCode.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "AsmCode.h" + +namespace enco +{ + +void AsmCode::dump(std::ostream &os) const +{ + os << ".section .rodata" << std::endl; + os << ".global " << _varname << std::endl; + // Please refer to https://www.sourceware.org/binutils/docs/as/Type.html#Type for details + os << ".type " << _varname << ", STT_OBJECT" << std::endl; + os << ".align " << 4 << std::endl; + os << _varname << ":" << std::endl; + os << ".incbin " << '"' << _filename << '"' << std::endl; +} + +} // namespace enco diff --git a/compiler/enco/core/src/AsmCode.h b/compiler/enco/core/src/AsmCode.h new file mode 100644 index 00000000000..c43892888a6 --- /dev/null +++ b/compiler/enco/core/src/AsmCode.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_ASM_CODE_H__ +#define __ENCO_ASM_CODE_H__ + +#include +#include + +namespace enco +{ + +class AsmCode +{ +public: + AsmCode(const std::string &filename, const std::string &varname) + : _filename{filename}, _varname{varname} + { + // DO NOTHING + } + +public: + void dump(std::ostream &) const; + +private: + std::string _filename; + std::string _varname; +}; + +} // namespace enco + +static inline std::ostream &operator<<(std::ostream &os, const enco::AsmCode &code) +{ + code.dump(os); + return os; +} + +#endif // __ENCO_ASM_CODE_H__ diff --git a/compiler/enco/core/src/Backend.cpp b/compiler/enco/core/src/Backend.cpp new file mode 100644 index 00000000000..d4bec7447cb --- /dev/null +++ b/compiler/enco/core/src/Backend.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "enco/Backend.h" + +#include "IRValidator.h" + +#include "Session.h" +#include "Pipeline.h" + +#include "Code.h" +#include "AsmCode.h" +#include "CppCode.h" + +#include "Transforms/Duplicate.h" +#include "Transforms/FeatureUnification.h" +#include "Transforms/AvgPoolLowering.h" +#include "Transforms/IntrinsicSelection.h" +#include "Transforms/DataLayoutConversion.h" +#include "Transforms/IndirectCopyElimination.h" +#include "Transforms/IdenticalObjectReduction.h" +#include "Transforms/DuplicatedObjectReduction.h" +#include "Transforms/DeadObjectElimination.h" +#include "Transforms/ConstantFolding.h" +#include "Transforms/CopyLowering.h" +#include "Transforms/ConcatLowering.h" +#include "Transforms/FreeInstrElimination.h" +#include "Transforms/FreeOpElimination.h" +#include "Transforms/DeadBagElimination.h" +#include "Transforms/Optimizations.h" +#include "Transforms/Split.h" +#include "Transforms/GlobalDataGeneration.h" + +#include + +#include +#include +#include + +using stdex::make_unique; +using namespace enco; + +namespace +{ + +// has_inout_bag(m) returns true if there is a pair of coco::Input and coco::Output that share +// the same bag as their backing storage +inline bool has_inout_bag(const coco::Module *m) +{ + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (bag->isInput() && bag->isOutput()) + { + return true; + } + } + return false; +} + +class BackendImpl final : public enco::Backend +{ +public: + BackendImpl(const std::string &prefix) : _prefix{prefix} + { + // DO NOTHING + } + +public: + void compile(coco::Module *m, coco::Data *d) override; + +private: + std::string _prefix; +}; + +void BackendImpl::compile(coco::Module *m, coco::Data *d) +{ + auto sess = make_session(m, d); + + // validate if IR from frontend is correct + assert(validate(code(sess))); + + enco::Pipeline pipeline; + + // Configure pipeline + + // As explained below, the current implementation does not work if there is a pair of input/output + // that share the same bag as their underlying bag. + // + // BagDuplicationPass creates a copy of such bags in order to eliminate such a pair. + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + // Insert data ordering if necessary + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + // Eliminate dead object + // + // NOTE Dead Object Elimination (DOE) is performed before Copy lowering + // in order to reduce compilation overhead. + pipeline.append(make_unique()); + // Lower Copy as Shuffle + pipeline.append(make_unique()); + // Lower ConcatF as Shuffle if it is not delegated to NNAPI yet + pipeline.append(make_unique()); + pipeline.append(make_unique()); + pipeline.append(make_unique()); + // NOTE Free Op Elimination should be applied after Free Instr Elimination + // - Free Instr Elimination may generate additional free Op(s) + pipeline.append(make_unique()); + pipeline.append(make_unique()); + // Split instructions into a set of phases (each block serves as a phase) + pipeline.append(make_unique()); + + // Apply transforms in the pipeline + for (uint32_t n = 0; n < pipeline.size(); ++n) + { + const auto &pass = pipeline.at(n); + + pass.run(sess); + } + + // The current implementation will assign memory region for each bag as follows: + // Bind input bag to the region provided by Network_input_bind + // Bind output bag to the region provided by Network_output_bind + // Bind intermediate bag to the region allocated during execution + // + // Note that this scheme does not work if there is a pair of input/output + // that share the same bag as their underlying bag + assert(!has_inout_bag(code(sess)->module())); + + const std::string data_var = "data"; + const std::string data_filename = _prefix + ".bin"; + + // Generate 'bin' file + { + std::ofstream ofs{data_filename, std::ios::binary}; + generate_global_data(ofs, code(sess)); + } + + // Generate 'embed.S' file + { + std::ofstream ofs{_prefix + ".embed.S"}; + ofs << AsmCode{data_filename, data_var}; + } + + // TODO Run various transforms over enco::Code + + std::ofstream ofs{_prefix + ".cpp"}; + ofs << CppCode{data_var, code(sess)} << std::endl; +} + +} // namespace enco + +#include + +std::unique_ptr make_backend(const cmdline::View &cmdline) +{ + return make_unique<::BackendImpl>(cmdline.at(0)); +} diff --git a/compiler/enco/core/src/Code.h b/compiler/enco/core/src/Code.h new file mode 100644 index 00000000000..91756d5f8c9 --- /dev/null +++ b/compiler/enco/core/src/Code.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CODE_H__ +#define __ENCO_CODE_H__ + +#include "ANN/Context.h" + +#include +#include + +namespace enco +{ + +struct Code +{ +public: + Code(coco::Module *module, coco::Data *data) : _module{module}, _data{data} + { + // DO NOTHING + } + +public: + coco::Module *module(void) const { return _module; } + coco::Data *data(void) const { return _data; } + +private: + coco::Module *const _module; + coco::Data *const _data; +}; + +} // namespace enco + +#endif // __ENCO_CODE_H__ diff --git a/compiler/enco/core/src/Code.test.cpp b/compiler/enco/core/src/Code.test.cpp new file mode 100644 index 00000000000..8e96e4751c8 --- /dev/null +++ b/compiler/enco/core/src/Code.test.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Code.h" + +#include + +TEST(CODE, constructor) +{ + auto m = coco::Module::create(); + auto d = coco::Data::create(); + + enco::Code code{m.get(), d.get()}; + + ASSERT_EQ(code.module(), m.get()); + ASSERT_EQ(code.data(), d.get()); +} diff --git a/compiler/enco/core/src/CodeIndex.h b/compiler/enco/core/src/CodeIndex.h new file mode 100644 index 00000000000..7f2da646378 --- /dev/null +++ b/compiler/enco/core/src/CodeIndex.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CODE_INDEX_H__ +#define __CODE_INDEX_H__ + +#include +#include + +/** + * @brief A CodeIndex denotes the index of instruction inside the whole module + */ +class CodeIndex +{ +public: + CodeIndex() = default; + +public: + CodeIndex(const coco::BlockIndex &blk_ind, const coco::InstrIndex &ins_ind) + : _blk_ind{blk_ind}, _ins_ind{ins_ind} + { + } + +public: + const coco::BlockIndex &block(void) const { return _blk_ind; } + const coco::InstrIndex &instr(void) const { return _ins_ind; } + +private: + coco::BlockIndex _blk_ind; + coco::InstrIndex _ins_ind; +}; + +static inline coco::BlockIndex block_index(const coco::Block *blk) +{ + if (blk == nullptr) + { + return coco::BlockIndex{}; + } + + return blk->index(); +} + +static inline CodeIndex code_index(const coco::Instr *ins) +{ + return CodeIndex{block_index(ins->parent()), ins->index()}; +} + +static inline bool operator<(const CodeIndex &lhs, const CodeIndex &rhs) +{ + if (lhs.block() < rhs.block()) + { + return true; + } + + if (lhs.block().value() > rhs.block().value()) + { + return false; + } + + return lhs.instr() < rhs.instr(); +} + +#endif // __CODE_INDEX_H__ diff --git a/compiler/enco/core/src/CppCode.cpp b/compiler/enco/core/src/CppCode.cpp new file mode 100644 index 00000000000..aa5ef315674 --- /dev/null +++ b/compiler/enco/core/src/CppCode.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CppCode.h" + +#include "Transforms/GlobalDataGeneration.h" +#include "Transforms/Split.h" + +#include "CppGen/MemoryContext.h" + +#include "CppGen/Host.h" +#include "CppGen/Subnet.h" + +#include "Dims.h" + +#include +#include + +#include +#include +#include +#include + +namespace +{ + +struct SubnetInfo +{ + std::string struct_name; + /// @brief The field name (in this subnet struct) of ANeuralNetworksCompilation value + std::string compilation_field; + + /// @brief The field name (in Network struct) for this subnet + std::string field_name; +}; + +struct NetworkStruct +{ + pp::LinearDocument def; +}; + +struct InvokeFunction +{ + pp::LinearDocument head; + pp::LinearDocument body; + pp::LinearDocument tail{pp::LinearDocument::Direction::Reverse}; + +public: + /** @brief Create a (fresh) local variable */ + std::string local(void) { return pp::fmt("v_", ++_var_count); } + +private: + uint32_t _var_count = 0; +}; + +/** + * @brief Enumerate a set of Bag accessed by a given instruction + * + * Supported instruction: + * "Shuffle" + */ +class AccessedBagAccumulator : public coco::Instr::Visitor +{ +public: + AccessedBagAccumulator(std::set *out) : _out{out} + { + // Validate "out" + assert(_out != nullptr); + } + +public: + void visit(const coco::Shuffle *shuffle) override + { + assert(shuffle->from() != nullptr); + assert(shuffle->into() != nullptr); + + _out->insert(shuffle->from()); + _out->insert(shuffle->into()); + } + +private: + std::set *_out; +}; + +/** + * @brief Return a set of bags that SHOULD have a host allocation + */ +std::set hosted(const enco::Code *code) +{ + std::set res; + + auto m = code->module(); + auto ann_ctx = enco::SubnetManager::context(m); + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + if (auto ann_binder = ann_ctx->find(blk)) + { + // Case: The current block is ANN-compatible + + // Each ANN input SHOULD have a corresponding host allocation + for (uint32_t n = 0; n < ann_binder->module()->input()->size(); ++n) + { + res.insert(ann_binder->input(n)); + } + + // Each ANN output SHOULD have a corresponding host allocation + for (uint32_t n = 0; n < ann_binder->module()->output()->size(); ++n) + { + res.insert(ann_binder->output(n)); + } + } + else + { + // Every bag that ANN-incompatible block accesses SHOULD have a corresponding host allocation + AccessedBagAccumulator acc{&res}; + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + ins->accept(acc); + } + } + } + + return res; +} +} // namespace + +namespace enco +{ + +void CppCode::dump(std::ostream &os) const +{ + auto m = _code->module(); + auto d = _code->data(); + auto ann_ctx = enco::SubnetManager::context(m); + + NetworkStruct network; + InvokeFunction invoke; + pp::LinearDocument internal; + + auto data_exp = [this](const GlobalOffset &off) { return pp::fmt(_varname, " + ", off); }; + + // Record the subnet information + std::map subnet_ctx; + + /** + * Create a struct for each android NN network of the following form: + * + * struct [Name] + * { + * ... + * + * [Name]() // constructor + * { + * ... + * } + * + * ~[Name]() // destructor + * { + * ... + * } + * }; + * + */ + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + SubnetStructBuilder builder; + + auto subnet_binder = ann_ctx->nth(n); + auto subnet_struct_name = pp::fmt("Subnet_", subnet_ctx.size()); + auto subnet_field_name = pp::fmt("_subnet_", subnet_ctx.size()); + + // Create global data variable + auto emit_weight = [&](const ann::OperandID &, const ann::Operand *info) { + if (info->weight()) + { + auto size = info->weight()->size(); + auto off = enco::GlobalData::data_offset(info); + auto base_exp = pp::fmt("reinterpret_cast(", data_exp(off), ")"); + auto size_exp = pp::fmt(size); + + builder.expr(info, base_exp, size_exp); + } + }; + subnet_binder->module()->operand()->each(emit_weight); + + auto subnet_struct_content = builder.build(subnet_binder); + + // Emit C++ declaration + internal.append("struct ", subnet_struct_name); + internal.append("{"); + internal.indent(); + + internal.append(subnet_struct_content->def()); + + internal.append(subnet_struct_name, "()"); + internal.append("{"); + internal.indent(); + internal.append(subnet_struct_content->ctor()); + internal.unindent(); + internal.append("}"); + + internal.append("~", subnet_struct_name, "()"); + internal.append("{"); + internal.indent(); + internal.append(subnet_struct_content->dtor()); + internal.unindent(); + internal.append("}"); + + internal.unindent(); + internal.append("};"); + + // Declare subnet field + network.def.append(subnet_struct_name, " ", subnet_field_name, ";"); + + // Update subnet context + SubnetInfo subnet_info; + + subnet_info.struct_name = subnet_struct_name; + subnet_info.compilation_field = subnet_struct_content->compilation(); + subnet_info.field_name = subnet_field_name; + + assert(subnet_ctx.find(subnet_binder) == subnet_ctx.end()); + subnet_ctx[subnet_binder] = subnet_info; + } + + MemoryContext mem; + + // Set dedicated memory region for network inputs + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + mem.base(m->input()->at(n)->bag(), pp::fmt("net->inputs[", n, "].ptr")); + mem.size(m->input()->at(n)->bag(), pp::fmt("net->inputs[", n, "].len")); + } + + // Set dedicated memory region for network outputs + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + mem.base(m->output()->at(n)->bag(), pp::fmt("net->outputs[", n, "].ptr")); + mem.size(m->output()->at(n)->bag(), pp::fmt("net->outputs[", n, "].len")); + } + + // Set dedicated memory region for constant weight values + // TODO Support non-constant bags with initial values + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (!d->allocated(bag)) + { + // Skip if no weight exists + continue; + } + + // TODO Support non-float(fp32) weight + auto offset = enco::GlobalData::data_offset(bag); + + auto base_expr = data_exp(offset); + auto size_expr = pp::fmt(bag->size() * sizeof(float)); + + mem.base(bag, base_expr); + mem.size(bag, size_expr); + } + + // Set dedicated memory reigion for intermediate buffer(s) + for (const auto &bag : hosted(_code)) + { + // Skip if a bag is already allocated + if (mem.member(bag)) + { + continue; + } + + auto name = invoke.local(); + + invoke.head.append("auto ", name, " = new uint8_t[", bag->size() * sizeof(float), "];"); + invoke.tail.append("delete[] ", name, ";"); + + mem.base(bag, name); + mem.size(bag, pp::fmt(bag->size() * sizeof(float))); + } + + // Create Code Block Builder + SubnetBlockCompiler subnet_compiler{mem}; + + for (auto it = subnet_ctx.begin(); it != subnet_ctx.end(); ++it) + { + // Specify how to access ANeuralNetworksCompilation + const auto &info = it->second; + subnet_compiler.bind(it->first, pp::fmt("net->", info.field_name, ".", info.compilation_field)); + } + + HostBlockCompiler host_compiler{mem}; + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + invoke.body.append("{"); + invoke.body.indent(); + + if (auto binder = ann_ctx->find(blk)) + { + // Generate code that invokes Android NN sub-network + auto lines = subnet_compiler.compile(binder); + invoke.body.append(*lines); + } + else + { + // Generate code on-the-fly for Android NN-incompatible blocks + auto lines = host_compiler.compile(blk); + invoke.body.append(*lines); + } + + invoke.body.unindent(); + invoke.body.append("}"); + } + + // + // Generate full C++ source code with code snippet + // + const std::string name{"Network"}; + + pp::LinearDocument includes; + { + // Include Android NN API header + includes.append("#include "); + includes.append(); + + includes.append("#include "); + includes.append("#include "); + includes.append("#include "); + } + + pp::LinearDocument net_def; + { + net_def.append("struct ", name, " {"); + net_def.indent(); + net_def.append("struct Shape { uint32_t rank; const uint32_t *dims; };"); + net_def.append("struct Input {"); + net_def.indent(); + net_def.append("const char *name;"); + net_def.append("const uint8_t *ptr;"); + net_def.append("unsigned len;"); + net_def.append("Shape shape;"); + net_def.unindent(); + net_def.append("};"); + net_def.append("struct Output {"); + net_def.indent(); + net_def.append("const char *name;"); + net_def.append("uint8_t *ptr;"); + net_def.append("unsigned len;"); + net_def.append("Shape shape;"); + net_def.unindent(); + net_def.append("};"); + net_def.append(); + net_def.append(name, "();"); + net_def.append("~", name, "();"); + + net_def.append(); + net_def.append(network.def); + net_def.append(); + + net_def.append("std::arrayinput()->size(), "> inputs;"); + net_def.append("std::arrayoutput()->size(), "> outputs;"); + + net_def.unindent(); + net_def.append("};"); + } + + pp::LinearDocument net_ctor; + { + net_ctor.append("Network::Network() {"); + net_ctor.indent(); + + // Initialize input metadata + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + auto dims = as_dims(input->shape()); + + auto name_off = enco::GlobalData::name_offset(input); + auto name_exp = pp::fmt("reinterpret_cast(", data_exp(name_off), ")"); + auto dims_off = enco::GlobalData::dims_offset(input); + auto dims_exp = pp::fmt("reinterpret_cast(", data_exp(dims_off), ")"); + + net_ctor.append("inputs.at(", n, ").name = ", name_exp, ";"); + net_ctor.append("inputs.at(", n, ").shape.rank = ", dims.size(), ";"); + net_ctor.append("inputs.at(", n, ").shape.dims = ", dims_exp, ";"); + } + + // Initialize output metadata + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + auto dims = as_dims(output->shape()); + + auto name_off = enco::GlobalData::name_offset(output); + auto name_exp = pp::fmt("reinterpret_cast(", data_exp(name_off), ")"); + auto dims_off = enco::GlobalData::dims_offset(output); + auto dims_exp = pp::fmt("reinterpret_cast(", data_exp(dims_off), ")"); + + net_ctor.append("outputs.at(", n, ").name = ", name_exp, ";"); + net_ctor.append("outputs.at(", n, ").shape.rank = ", dims.size(), ";"); + net_ctor.append("outputs.at(", n, ").shape.dims = ", dims_exp, ";"); + } + + // TODO Implement this + net_ctor.unindent(); + net_ctor.append("}"); + } + + pp::LinearDocument net_dtor; + { + net_dtor.append("Network::~Network() {"); + net_dtor.indent(); + // TODO Implement this + net_dtor.unindent(); + net_dtor.append("}"); + } + + pp::LinearDocument source; + + source.append(includes); + source.append(); + source.append("extern uint8_t ", _varname, "[];"); + source.append(); + + source.append("namespace"); + source.append("{"); + source.append(internal); + source.append("} // namespace"); + source.append(); + source.append(net_def); + source.append(); + source.append(net_ctor); + source.append(); + source.append(net_dtor); + + source.append(); + source.append(name, " *", name, "_construct() { return new ", name, "{}; }"); + source.append("void ", name, "_destruct(", name, " *net) { delete net; }"); + + source.append(); + + // Emit Network_input_count function + source.append("unsigned ", name, "_input_count(const ", name, " *net) {"); + source.indent(); + source.append("return net->inputs.size();"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_input_name function + source.append("const char *", name, "_input_name(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->inputs.at(n).name;"); + source.unindent(); + source.append("}"); + + // Emit Network_input_rank function + source.append("unsigned ", name, "_input_rank(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->inputs.at(n).shape.rank;"); + source.unindent(); + source.append("}"); + + // Emit Network_input_dim function + source.append("unsigned ", name, "_input_dim(const ", name, " *net, unsigned n, unsigned axe)"); + source.append("{"); + source.indent(); + source.append("return net->inputs.at(n).shape.dims[axe];"); + source.unindent(); + source.append("}"); + + // Emit Network_input_bind function + source.append("void ", name, "_input_bind(", name, + " *net, unsigned n, const void *ptr, unsigned len) {"); + source.indent(); + source.append("net->inputs.at(n).ptr = reinterpret_cast(ptr);"); + source.append("net->inputs.at(n).len = len;"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_output_count function + source.append("unsigned ", name, "_output_count(const ", name, " *net) {"); + source.indent(); + source.append("return net->outputs.size();"); + source.unindent(); + source.append("}"); + + source.append(); + + // Emit Network_output_name function + source.append("const char *", name, "_output_name(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->outputs.at(n).name;"); + source.unindent(); + source.append("}"); + + // Emit Network_output_rank function + source.append("unsigned ", name, "_output_rank(const ", name, " *net, unsigned n) {"); + source.indent(); + source.append("return net->outputs.at(n).shape.rank;"); + source.unindent(); + source.append("}"); + + // Emit Network_output_dim function + source.append("unsigned ", name, "_output_dim(const ", name, " *net, unsigned n, unsigned axe)"); + source.append("{"); + source.indent(); + source.append("return net->outputs.at(n).shape.dims[axe];"); + source.unindent(); + source.append("}"); + + // Emit Network_output_bind function + source.append("void ", name, "_output_bind(", name, + " *net, unsigned n, void *ptr, unsigned len) {"); + source.indent(); + source.append("net->outputs.at(n).ptr = reinterpret_cast(ptr);"); + source.append("net->outputs.at(n).len = len;"); + source.unindent(); + source.append("}"); + + source.append(); + + source.append("void ", name, "_invoke(", name, " *net) {"); + source.indent(); + source.append(invoke.head); + source.append(invoke.body); + source.append(invoke.tail); + source.unindent(); + source.append("}"); + + os << source; +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppCode.h b/compiler/enco/core/src/CppCode.h new file mode 100644 index 00000000000..c52ea1d5de0 --- /dev/null +++ b/compiler/enco/core/src/CppCode.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CPP_CODE_H__ +#define __ENCO_CPP_CODE_H__ + +#include "Code.h" + +#include + +namespace enco +{ + +class CppCode +{ +public: + CppCode(const std::string &varname, const Code *code) : _varname{varname}, _code{code} + { + // DO NOTHING + } + +public: + void dump(std::ostream &) const; + +private: + const std::string _varname; + const Code *_code; +}; + +} // namespace enco + +static inline std::ostream &operator<<(std::ostream &os, const enco::CppCode &code) +{ + code.dump(os); + return os; +} + +#endif // __ENCO_CPP_CODE_H__ diff --git a/compiler/enco/core/src/CppGen/Host.cpp b/compiler/enco/core/src/CppGen/Host.cpp new file mode 100644 index 00000000000..37e0583d74a --- /dev/null +++ b/compiler/enco/core/src/CppGen/Host.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Host.h" + +#include + +#include + +#include +#include + +namespace +{ + +/** + * @brief Data transfer between flat arrays + * + * Transfer(from, into) denotes the following C code: + * dst[into] = src[from]; + */ +class Transfer +{ +public: + Transfer(uint32_t from, uint32_t into) : _from{from}, _into{into} + { + // DO NOTHING + } + +public: + uint32_t from(void) const { return _from; } + uint32_t into(void) const { return _into; } + +private: + uint32_t _from; + uint32_t _into; +}; + +using TransferSequence = std::vector; + +/** + * @brief Convert Shuffle instruction as a sequence of data transfer + */ +TransferSequence as_transfer_sequence(const coco::Shuffle *shuffle) +{ + TransferSequence seq; + + for (const auto &dst : shuffle->range()) + { + const auto src = shuffle->at(dst); + seq.emplace_back(src.value(), dst.value()); + } + + return seq; +} + +/** + * Given a sequence of N data transfers, + * find_loop tries to compute count, src_step, dst_step that satisfies + * the following properties: + * + * First, N should be a multiple of count. + * Below we refer to that multiplier as 'window' (= N / count) + * + * Second, + * for all n in [0, count), + * for all k in [0, window), + * from[n * window + k] == from[k] + src_step, and + * into[n * window + k] == into[k] + dst_step + */ +bool find_loop(TransferSequence::const_iterator beg, TransferSequence::const_iterator end, + uint32_t *p_count, uint32_t *p_src_step, uint32_t *p_dst_step) +{ + assert(p_count != nullptr); + assert(p_src_step != nullptr); + assert(p_dst_step != nullptr); + + const uint32_t size = end - beg; + + for (uint32_t window = 1; window <= size; ++window) + { + if (size % window != 0) + { + continue; + } + + auto src_step_at = [&beg, window](uint32_t n) { + return (beg + n)->from() - (beg + n - window)->from(); + }; + + auto dst_step_at = [&beg, window](uint32_t n) { + return (beg + n)->into() - (beg + n - window)->into(); + }; + + const uint32_t count = size / window; + const uint32_t src_step = src_step_at(window); + const uint32_t dst_step = dst_step_at(window); + + bool consistent = true; + + for (uint32_t n = window + 1; n < size; ++n) + { + if ((src_step_at(n) != src_step) || (dst_step_at(n) != dst_step)) + { + consistent = false; + break; + } + } + + if (consistent) + { + *p_count = count; + *p_src_step = src_step; + *p_dst_step = dst_step; + return true; + } + } + + return false; +} + +/** + * @brief Single transfer loop (a triple of count, source step, detination step) + */ +class TransferLoop +{ +public: + class Step + { + public: + Step(uint32_t src, uint32_t dst) : _src{src}, _dst{dst} + { + // DO NOTHING + } + + public: + uint32_t src(void) const { return _src; } + uint32_t dst(void) const { return _dst; } + + private: + uint32_t _src; + uint32_t _dst; + }; + +public: + TransferLoop(uint32_t count, uint32_t src_step, uint32_t dst_step) + : _count{count}, _step{src_step, dst_step} + { + // DO NOTHING + } + +public: + uint32_t count(void) const { return _count; } + const Step &step(void) const { return _step; } + +private: + uint32_t _count; + Step _step; +}; + +/** + * @brief Nested transfer loops + */ +using TransferNest = std::vector; + +/** + * @brief Construct nested transfer loop-nest that correponds to a given Shuffle instruction + */ +TransferNest as_nest(const TransferSequence &seq) +{ + TransferNest nest; + + auto beg = seq.begin(); + auto end = seq.end(); + + uint32_t window = end - beg; + uint32_t count = 0; + uint32_t src_step = 0; + uint32_t dst_step = 0; + + while ((window > 1) && find_loop(beg, end, &count, &src_step, &dst_step)) + { + assert(window % count == 0); + + window /= count; + end = beg + window; + + nest.emplace_back(count, src_step, dst_step); + } + + return nest; +}; + +uint32_t loop_count(const TransferNest &nest) +{ + uint32_t count = 1; + + for (const auto &loop : nest) + { + count *= loop.count(); + } + + return count; +}; + +class InstrPrinter : public coco::Instr::Visitor +{ +public: + InstrPrinter(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +private: + pp::LinearDocument visit(const coco::Shuffle *shuffle) override + { + auto from = shuffle->from(); + auto into = shuffle->into(); + + // + // Analyze 'Shuffle' pattern, and convert it as nested loops + // + auto tseq = as_transfer_sequence(shuffle); + auto nest = as_nest(tseq); + assert(tseq.size() % loop_count(nest) == 0); + uint32_t window = tseq.size() / loop_count(nest); + + // + // Generate loop body + // + pp::EnclosedDocument loop_body; + + auto var_at = [](uint32_t lv) { return pp::fmt("_", lv); }; + + for (uint32_t lv = 0; lv < nest.size(); ++lv) + { + auto var = var_at(lv); + + loop_body.front().append("for (uint32_t ", var, " = 0; ", var, " < ", nest.at(lv).count(), + "; ++", var, ") {"); + loop_body.front().indent(); + + loop_body.back().append("}"); + loop_body.back().indent(); + } + + std::string src_index = "0"; + std::string dst_index = "0"; + + for (uint32_t lv = 0; lv < nest.size(); ++lv) + { + src_index += pp::fmt(" + ", nest.at(lv).step().src(), " * ", var_at(lv)); + dst_index += pp::fmt(" + ", nest.at(lv).step().dst(), " * ", var_at(lv)); + } + + for (uint32_t n = 0; n < window; ++n) + { + const auto src_base = pp::fmt("reinterpret_cast(", _mem.base(from), ")"); + const auto dst_base = pp::fmt("reinterpret_cast(", _mem.base(into), ")"); + + loop_body.front().append(dst_base, "[", dst_index, " + ", tseq.at(n).into(), "] = ", src_base, + "[", src_index, " + ", tseq.at(n).from(), "];"); + } + + pp::LinearDocument res; + res.append(loop_body); + return res; + } + +private: + const enco::MemoryContext &_mem; +}; + +} // namespace + +namespace enco +{ + +std::unique_ptr HostBlockCompiler::compile(const coco::Block *blk) const +{ + InstrPrinter prn{_mem}; + + auto res = stdex::make_unique(); + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + res->append(ins->accept(prn)); + } + + return std::move(res); +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/Host.h b/compiler/enco/core/src/CppGen/Host.h new file mode 100644 index 00000000000..0adb7fe1fbc --- /dev/null +++ b/compiler/enco/core/src/CppGen/Host.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CPP_GEN_HOST_H__ +#define __ENCO_CPP_GEN_HOST_H__ + +#include "CppGen/MemoryContext.h" + +#include +#include + +namespace enco +{ + +/*** + * @brief Generate C++ code that does not depend on Anroid NN API + */ +class HostBlockCompiler +{ +public: + HostBlockCompiler(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +public: + std::unique_ptr compile(const coco::Block *blk) const; + +private: + const enco::MemoryContext &_mem; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_HOST_H__ diff --git a/compiler/enco/core/src/CppGen/MemoryContext.cpp b/compiler/enco/core/src/CppGen/MemoryContext.cpp new file mode 100644 index 00000000000..e522968a83c --- /dev/null +++ b/compiler/enco/core/src/CppGen/MemoryContext.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MemoryContext.h" + +#include + +namespace enco +{ + +bool MemoryContext::member(const coco::Bag *bag) const +{ + // NOTE _base and _size SHOULD BE consistent + if (_base.find(bag) != _base.end()) + { + assert(_size.find(bag) != _size.end()); + return true; + } + + assert(_size.find(bag) == _size.end()); + return false; +} + +void MemoryContext::base(const coco::Bag *bag, const std::string &exp) { _base[bag] = exp; } +void MemoryContext::size(const coco::Bag *bag, const std::string &exp) { _size[bag] = exp; } + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/MemoryContext.h b/compiler/enco/core/src/CppGen/MemoryContext.h new file mode 100644 index 00000000000..99c20f3e800 --- /dev/null +++ b/compiler/enco/core/src/CppGen/MemoryContext.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ +#define __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ + +#include + +#include +#include + +namespace enco +{ + +/** + * @brief Record C/C++ expression that denotes the base and size of memory region + * dedicated to each bag + */ +class MemoryContext +{ +public: + /** + * @brief Check whether a base/size expression for a given bag + */ + bool member(const coco::Bag *bag) const; + +public: + void base(const coco::Bag *bag, const std::string &exp); + void size(const coco::Bag *bag, const std::string &exp); + +public: + const std::string &base(const coco::Bag *bag) const { return _base.at(bag); } + const std::string &size(const coco::Bag *bag) const { return _size.at(bag); } + +private: + std::map _base; + std::map _size; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_MEMORY_CONTEXT_H__ diff --git a/compiler/enco/core/src/CppGen/Subnet.cpp b/compiler/enco/core/src/CppGen/Subnet.cpp new file mode 100644 index 00000000000..9a636c6ae84 --- /dev/null +++ b/compiler/enco/core/src/CppGen/Subnet.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CppGen/Subnet.h" + +#include "Dims.h" +#include "String.h" + +#include + +#include + +#include + +using stdex::make_unique; +using enco::concat; + +#define S(content) #content + +namespace ann +{ +static std::ostream &operator<<(std::ostream &os, const ann::OperandID &id) +{ + os << id.value(); + return os; +} +} // namespace ann + +namespace +{ + +class SubnetStructImpl final : public enco::SubnetStruct +{ +public: + SubnetStructImpl() : _dtor{pp::LinearDocument::Direction::Reverse} + { + // DO NOTHING + } + +public: + std::string model(void) const override { return "_model"; } + std::string compilation(void) const override { return "_compilation"; } + +public: + const pp::MultiLineText &def(void) const override { return _def; } + pp::LinearDocument *def(void) { return &_def; } + +public: + const pp::MultiLineText &ctor(void) const override { return _ctor; } + pp::LinearDocument *ctor(void) { return &_ctor; } + +public: + const pp::MultiLineText &dtor(void) const override { return _dtor; } + pp::LinearDocument *dtor(void) { return &_dtor; } + +private: + pp::LinearDocument _def; + pp::LinearDocument _ctor; + pp::LinearDocument _dtor; +}; + +struct CodeFragment +{ + virtual ~CodeFragment() = default; + + virtual void dump(pp::LinearDocument *) const = 0; +}; + +pp::LinearDocument *operator<<(pp::LinearDocument *doc, const CodeFragment &fragment) +{ + fragment.dump(doc); + return doc; +} + +const char *scalar_operand_code(const ann::DType &dtype) +{ + switch (dtype) + { + case ann::DType::S32: + return "ANEURALNETWORKS_INT32"; + default: + break; + }; + + throw std::invalid_argument("dtype"); +} + +const char *tensor_operand_code(const ann::DType &dtype) +{ + switch (dtype) + { + case ann::DType::S32: + return "ANEURALNETWORKS_TENSOR_INT32"; + case ann::DType::F32: + return "ANEURALNETWORKS_TENSOR_FLOAT32"; + default: + break; + }; + + throw std::invalid_argument("dtype"); +} + +class ScalarOperandDecl final : public CodeFragment +{ +public: + ScalarOperandDecl(const std::string &model, const ann::DType &dtype) + : _model{model}, _dtype{dtype} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("{"); + doc->indent(); + doc->append("ANeuralNetworksOperandType t;"); + doc->append(); + doc->append("t.type = ", scalar_operand_code(_dtype), ";"); + doc->append("t.dimensionCount = 0;"); + doc->append("t.dimensions = nullptr;"); + doc->append("t.scale = 1.0f;"); + doc->append("t.zeroPoint = 0;"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperand(", _model, ", &t);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + ann::DType _dtype; +}; + +class TensorOperandDecl final : public CodeFragment +{ +public: + TensorOperandDecl(const std::string &model, const ann::DType &dtype, + const nncc::core::ADT::tensor::Shape &shape) + : _model{model}, _dtype{dtype}, _shape{shape} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + const auto rank = _shape.rank(); + const auto dims = as_dims(_shape); + + assert(rank == dims.size()); + + doc->append("{"); + doc->indent(); + doc->append("uint32_t d[", rank, "] = { ", concat(", ", dims.begin(), dims.end()), " };"); + doc->append(); + doc->append("ANeuralNetworksOperandType t;"); + doc->append(); + doc->append("t.type = ", tensor_operand_code(_dtype), ";"); + doc->append("t.dimensionCount = ", rank, ";"); + doc->append("t.dimensions = d;"); + doc->append("t.scale = 1.0f;"); + doc->append("t.zeroPoint = 0;"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperand(", _model, ", &t);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + ann::DType _dtype; + nncc::core::ADT::tensor::Shape _shape; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_setOperandValue + */ +class WeightDecl final : public CodeFragment +{ +public: + WeightDecl(const std::string &model, const ann::OperandID &id, const std::string &base, + const std::string &size) + : _model{model}, _id{id}, _base{base}, _size{size} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("ANeuralNetworksModel_setOperandValue(", _model, ", ", _id.value(), ", ", _base, + ", ", _size, ");"); + } + +private: + std::string _model; + ann::OperandID _id; + std::string _base; + std::string _size; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_addOperation + */ +class OperationDecl final : public CodeFragment +{ +public: + OperationDecl(const std::string &model, const ann::Operation *op) : _model{model}, _op{op} + { + // DO NOTHING + } + +private: + static std::string opcode(const ann::Operation::Code &code) + { + switch (code) + { +#define ANN_OPERATION(TAG, ENUM) \ + case ann::Operation::Code::TAG: \ + return #ENUM; +#include "ANN/IR/Operation.def" +#undef ANN_OPERATION + default: + throw std::invalid_argument{"code"}; + }; + } + +public: + void dump(pp::LinearDocument *doc) const override + { + const auto in_count = _op->inputs().size(); + auto in_beg = _op->inputs().begin(); + auto in_end = _op->inputs().end(); + + const auto out_count = _op->outputs().size(); + auto out_beg = _op->outputs().begin(); + auto out_end = _op->outputs().end(); + + auto op = opcode(_op->code()); + + doc->append("{"); + doc->indent(); + doc->append("uint32_t inputs[", in_count, "] = { ", concat(", ", in_beg, in_end), " };"); + doc->append("uint32_t outputs[", out_count, "] = { ", concat(", ", out_beg, out_end), " };"); + doc->append(); + doc->append("ANeuralNetworksModel_addOperation(", _model, ", ", op, ", ", in_count, + ", inputs, ", out_count, ", outputs);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _model; + const ann::Operation *_op; +}; + +/** + * @brief Code fragment that calls ANeuralNetworksModel_identifyInputsAndOutputs + */ +class ArgumentDecl final : public CodeFragment +{ +public: + ArgumentDecl(const std::string &mname, const ANNBinder *binder) : _mname{mname}, _binder{binder} + { + // DO NOTHING + } + +public: + void dump(pp::LinearDocument *doc) const override + { + doc->append("{"); + doc->indent(); + + auto module = _binder->module(); + const uint32_t input_count = module->input()->size(); + + doc->append("uint32_t inputs[", input_count, "];"); + for (uint32_t n = 0; n < input_count; ++n) + { + doc->append("inputs[", n, "] = ", module->input()->at(n), ";"); + } + + const uint32_t output_count = module->output()->size(); + + doc->append("uint32_t outputs[", output_count, "];"); + for (uint32_t n = 0; n < output_count; ++n) + { + doc->append("outputs[", n, "] = ", module->output()->at(n), ";"); + } + + doc->append("ANeuralNetworksModel_identifyInputsAndOutputs(", _mname, ", ", input_count, + ", inputs, ", output_count, ", outputs);"); + doc->unindent(); + doc->append("}"); + } + +private: + std::string _mname; + const ANNBinder *_binder; +}; + +} // namespace + +namespace enco +{ + +std::unique_ptr SubnetStructBuilder::build(const ANNBinder *binder) const +{ + auto res = make_unique(); + + auto mname = res->model(); + auto cname = res->compilation(); + + res->def()->append("ANeuralNetworksModel *", mname, ";"); + res->def()->append("ANeuralNetworksCompilation *", cname, ";"); + + res->ctor()->append("ANeuralNetworksModel_create(&", mname, ");"); + res->dtor()->append("ANeuralNetworksModel_free(", mname, ");"); + + binder->module()->operand()->each([&](const ann::OperandID &id, const ann::Operand *info) { + // TODO Remove dynamic cast + if (auto scalar = dynamic_cast(info)) + { + res->ctor() << ScalarOperandDecl{mname, scalar->dtype()}; + } + else if (auto tensor = dynamic_cast(info)) + { + res->ctor() << TensorOperandDecl{mname, tensor->dtype(), tensor->shape()}; + } + else + { + throw std::runtime_error{"Unsupported"}; + } + + if (_weighted.find(info) != _weighted.end()) + { + const auto &base_exp = _base_exprs.at(info); + const auto &size_exp = _size_exprs.at(info); + + res->ctor() << WeightDecl{mname, id, base_exp, size_exp}; + } + }); + + for (unsigned n = 0; n < binder->module()->operation()->count(); ++n) + { + auto op = binder->module()->operation()->at(n); + res->ctor() << OperationDecl{mname, op}; + } + + // Emit ANeuralNetworksModel_identifyInputsAndOutputs call + res->ctor() << ArgumentDecl{mname, binder}; + + // Emit ANeuralNetworksModel_finish call + res->ctor()->append("ANeuralNetworksModel_finish(", mname, ");"); + + // Create compilation + res->ctor()->append("ANeuralNetworksCompilation_create(", mname, ", &", cname, ");"); + res->dtor()->append("ANeuralNetworksCompilation_free(", cname, ");"); + + // Finalize compilation + res->ctor()->append("ANeuralNetworksCompilation_finish(", cname, ");"); + + return std::move(res); +} + +std::unique_ptr SubnetBlockCompiler::compile(const ANNBinder *binder) const +{ + auto res = make_unique(); + + const auto compilation = _compilation_ctx.at(binder); + + res->append("ANeuralNetworksExecution *execution;"); + res->append("ANeuralNetworksEvent *event;"); + res->append(); + res->append("ANeuralNetworksExecution_create(", compilation, ", &execution);"); + + // Emit ANeuralNetworksExecution_setInput call(s) + for (uint32_t n = 0; n < binder->module()->input()->size(); ++n) + { + auto bag = binder->input(n); + auto base = _mem.base(bag); + auto size = _mem.size(bag); + + res->append("ANeuralNetworksExecution_setInput(execution, ", n, ", nullptr, ", base, ", ", size, + ");"); + } + + // Emit ANeuralNetworksExecution_setOutput call(s) + for (uint32_t n = 0; n < binder->module()->output()->size(); ++n) + { + auto bag = binder->output(n); + auto base = _mem.base(bag); + auto size = _mem.size(bag); + + res->append("ANeuralNetworksExecution_setOutput(execution, ", n, ", nullptr, ", base, ", ", + size, ");"); + } + + res->append("ANeuralNetworksExecution_startCompute(execution, &event);"); + res->append("ANeuralNetworksEvent_wait(event);"); + res->append("ANeuralNetworksEvent_free(event);"); + + res->append("ANeuralNetworksExecution_free(execution);"); + + return std::move(res); +} + +} // namespace enco diff --git a/compiler/enco/core/src/CppGen/Subnet.h b/compiler/enco/core/src/CppGen/Subnet.h new file mode 100644 index 00000000000..4a573887624 --- /dev/null +++ b/compiler/enco/core/src/CppGen/Subnet.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CPP_GEN_SUBNET_H__ +#define __ENCO_CPP_GEN_SUBNET_H__ + +#include "ANN/Binder.h" +#include "CppGen/MemoryContext.h" + +#include +#include +#include + +namespace enco +{ + +/** + * @brief A C++ struct that provides Android NN model & compilation + */ +struct SubnetStruct +{ + virtual ~SubnetStruct() = default; + + /// @brief Return the field name of ANeuralNetworksModel value + virtual std::string model(void) const = 0; + /// @brief Return the field name of ANeuralNetworksCompilatoin value + virtual std::string compilation(void) const = 0; + + virtual const pp::MultiLineText &def(void) const = 0; + virtual const pp::MultiLineText &ctor(void) const = 0; + virtual const pp::MultiLineText &dtor(void) const = 0; +}; + +class SubnetStructBuilder +{ +public: + std::unique_ptr build(const ANNBinder *binder) const; + +public: + void expr(const ann::Operand *oper, const std::string &base, const std::string &size) + { + _weighted.insert(oper); + _base_exprs[oper] = base; + _size_exprs[oper] = size; + } + +private: + std::set _weighted; + std::map _base_exprs; + std::map _size_exprs; +}; + +/** + * @brief Generate C++ code that invokes Android NN subnet + */ +class SubnetBlockCompiler +{ +public: + SubnetBlockCompiler(const enco::MemoryContext &mem) : _mem(mem) + { + // DO NOTHING + } + +public: + /// @brief Specify how to access ANeuralNetworksCompilation value (C expression) + void bind(const ANNBinder *binder, const std::string &exp) { _compilation_ctx[binder] = exp; } + +public: + std::unique_ptr compile(const ANNBinder *binder) const; + +private: + const enco::MemoryContext &_mem; + std::map _compilation_ctx; +}; + +} // namespace enco + +#endif // __ENCO_CPP_GEN_SUBNET_H__ diff --git a/compiler/enco/core/src/Dims.h b/compiler/enco/core/src/Dims.h new file mode 100644 index 00000000000..e0a4fd44d23 --- /dev/null +++ b/compiler/enco/core/src/Dims.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DIMS_H__ +#define __DIMS_H__ + +#include + +static inline std::vector as_dims(const nncc::core::ADT::tensor::Shape &shape) +{ + std::vector res; + + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + res.emplace_back(shape.dim(axis)); + } + + return res; +} + +#endif // __DIMS_H__ diff --git a/compiler/enco/core/src/IRUtils.cpp b/compiler/enco/core/src/IRUtils.cpp new file mode 100644 index 00000000000..59f6b0dbeb4 --- /dev/null +++ b/compiler/enco/core/src/IRUtils.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IRUtils.h" + +#include + +namespace enco +{ + +/** + * @brief Substitute all the USE occurrences of an object with another object + * @param from Object to be replaced + * @param into Object to be used instead + * NOTE This maybe used when something like -- 'from' will be removed so we need + * to replace object Consumers that use 'from' to 'into' + * EXAMPLE + * { + * subst(child, bigone); + * m->entity()->object()->destroy(child); + * } + * This code will change all the Consumers that use 'child' to 'bigone' and + * destroy the 'child' object. + */ +void subst(coco::Object *from, coco::Object *into) +{ + assert(from != into); + + while (!from->uses()->empty()) + { + auto use = *(from->uses()->begin()); + + use->value(into); + } +} + +std::vector instr_sequence(coco::Module *m) +{ + std::vector res; + + for (auto B = m->block()->head(); B; B = B->next()) + { + for (auto I = B->instr()->head(); I; I = I->next()) + { + res.emplace_back(I); + } + } + + return res; +} + +} // namespace enco diff --git a/compiler/enco/core/src/IRUtils.h b/compiler/enco/core/src/IRUtils.h new file mode 100644 index 00000000000..da0754303bb --- /dev/null +++ b/compiler/enco/core/src/IRUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_IR_UTILS_H__ +#define __ENCO_IR_UTILS_H__ + +#include + +#include + +namespace enco +{ + +/** + * @brief Replace all the "USE" of 'from' with 'into' + * + * NOTE subst(from, into) WILL NOT update 'DEF' + */ +void subst(coco::Object *from, coco::Object *into); + +/** + * @brief Return instructions in execution order + */ +std::vector instr_sequence(coco::Module *m); + +} // namespace enco + +#endif // __ENCO_IR_UTILS_H__ diff --git a/compiler/enco/core/src/IRValidator.cpp b/compiler/enco/core/src/IRValidator.cpp new file mode 100644 index 00000000000..1337b88e4a4 --- /dev/null +++ b/compiler/enco/core/src/IRValidator.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IRValidator.h" + +#include + +namespace enco +{ + +coco::FeatureShape output_shape(coco::Conv2D *conv2D) +{ + auto load = conv2D->arg()->asLoad(); + assert(load); + + auto ifm = load->object()->asFeature(); + assert(ifm); + + auto ker = conv2D->ker(); + auto stride = conv2D->stride(); + auto pad = conv2D->pad(); + + auto striding_width = ifm->shape().width() + pad->left() + pad->right() - ker->shape().width(); + auto striding_height = ifm->shape().height() + pad->top() + pad->bottom() - ker->shape().height(); + + // Normally the formula is round(striding_width)/stride->horizontal. + // in coco IR, striding_width should be a multiple of stride->horizontal(), so round(...) was + // removed. So does striding_height. + assert(striding_width % stride->horizontal() == 0); + assert(striding_height % stride->vertical() == 0); + + auto ofm_width = striding_width / stride->horizontal() + 1; + auto ofm_height = striding_height / stride->vertical() + 1; + + return coco::FeatureShape(ifm->shape().batch(), ker->shape().count(), ofm_height, ofm_width); +} + +bool validate_output_shape(Code *code) +{ + auto module = code->module(); + + // for each eval ( conv2d ( ... ) ), check the output shape of conv2D matches output of eval + for (auto blk = module->block()->head(); blk; blk = blk->next()) + { + for (auto instr = blk->instr()->head(); instr; instr = instr->next()) + { + auto eval = instr->asEval(); + if (eval == nullptr) + continue; + + auto op = eval->op(); + if (!op->asConv2D()) + continue; + + auto conv2D = op->asConv2D(); + auto expected_shape = output_shape(conv2D); + + auto eval_out = eval->out()->asFeature(); + assert(eval_out); + + auto actual_shape = eval_out->shape(); + + if (actual_shape != expected_shape) + return false; + } + } + return true; +} + +bool validate(Code *code) { return validate_output_shape(code); } + +} // namespace enco diff --git a/compiler/enco/core/src/IRValidator.h b/compiler/enco/core/src/IRValidator.h new file mode 100644 index 00000000000..f4adb0a5ee1 --- /dev/null +++ b/compiler/enco/core/src/IRValidator.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_IR_VALIDATOR_H__ +#define __ENCO_IR_VALIDATOR_H__ + +#include "Code.h" + +namespace enco +{ + +bool validate(Code *code); + +} // namespace enco + +#endif // __ENCO_IR_VALIDATOR_H__ diff --git a/compiler/enco/core/src/IRValidator.test.cpp b/compiler/enco/core/src/IRValidator.test.cpp new file mode 100644 index 00000000000..14cda6173da --- /dev/null +++ b/compiler/enco/core/src/IRValidator.test.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IRValidator.h" + +#include "Code.h" + +#include + +#include + +namespace +{ + +using IntList4 = std::array; +using IntList2 = std::array; + +} // namespace + +// The layout of ifm, ker, ofm is NHWC, pad == {top, bottom, left, right}, and stride == {vertical, +// horizontal}. +std::unique_ptr get_conv2D(IntList4 ifm, IntList4 ker, IntList4 ofm, IntList4 pad, + IntList2 stride) +{ + auto module = coco::Module::create(); + auto block = module->entity()->block()->create(); + auto eval = module->entity()->instr()->create(); + auto load = module->entity()->op()->create(); + auto conv2D = module->entity()->op()->create(); + + auto ifm_obj = module->entity()->object()->create(); + coco::FeatureShape ifm_shape(ifm[0], ifm[3], ifm[1], ifm[2]); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(ifm_shape)); + + auto ofm_obj = module->entity()->object()->create(); + coco::FeatureShape ofm_shape(ofm[0], ofm[3], ofm[1], ofm[2]); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + auto ker_obj = module->entity()->object()->create(); + nncc::core::ADT::kernel::Shape ker_shape(ker[0], ker[3], ker[1], ker[2]); + ker_obj->layout(coco::KernelLayouts::NHWC::create(ker_shape)); + + // linking entities + module->block()->append(block); + block->instr()->append(eval); + eval->op(conv2D); + eval->out(ofm_obj); + load->object(ifm_obj); + conv2D->ker(ker_obj); + conv2D->arg(load); + + // param setting + conv2D->pad()->top(pad[0]).bottom(pad[1]).left(pad[2]).right(pad[3]); + conv2D->stride()->vertical(stride[0]).horizontal(stride[1]); + + return std::move(module); +} + +TEST(IRValidatorTest, conv2D_simple) +{ + auto ifm_nhwc = IntList4{1, 3, 3, 2}; + auto ker_nhwc = IntList4{1, 1, 1, 2}; + auto ofm_nhwc = IntList4{1, 3, 3, 1}; + + auto pad_tblr = IntList4{0, 0, 0, 0}; + auto stride_vh = IntList2{1, 1}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_TRUE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_stride_2) +{ + auto ifm_nhwc = IntList4{1, 4, 4, 3}; + auto ker_nhwc = IntList4{2, 2, 2, 3}; + auto ofm_nhwc = IntList4{1, 3, 3, 2}; + + auto pad_tblr = IntList4{1, 1, 1, 1}; + auto stride_vh = IntList2{2, 2}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_TRUE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_output_batch_check) +{ + auto ifm_nhwc = IntList4{1, 2, 2, 2}; + auto ker_nhwc = IntList4{3, 1, 1, 2}; // expected output depth is 3 + auto ofm_nhwc = IntList4{1, 2, 2, 1}; // but 1 + + auto pad_tblr = IntList4{0, 0, 0, 0}; + auto stride_vh = IntList2{1, 1}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_FALSE(enco::validate(&code)); +} + +TEST(IRValidatorTest, conv2D_wrong_HW) +{ + auto ifm_nhwc = IntList4{1, 2, 2, 1}; + auto ker_nhwc = IntList4{1, 2, 2, 1}; + auto ofm_nhwc = IntList4{1, 1, 1, 1}; // HW should be 2, 2 + + auto pad_tblr = IntList4{1, 1, 1, 1}; + auto stride_vh = IntList2{2, 2}; + + auto module = get_conv2D(ifm_nhwc, ker_nhwc, ofm_nhwc, pad_tblr, stride_vh); + enco::Code code{module.get(), nullptr}; + + ASSERT_FALSE(enco::validate(&code)); +} diff --git a/compiler/enco/core/src/Pass.h b/compiler/enco/core/src/Pass.h new file mode 100644 index 00000000000..d78cfaad3d2 --- /dev/null +++ b/compiler/enco/core/src/Pass.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_PASS_H__ +#define __ENCO_PASS_H__ + +#include "Session.h" + +#include + +namespace enco +{ + +class Pass +{ +public: + class Name + { + public: + Name(const std::string &content) : _content{content} + { + // DO NOTHING + } + + Name(const Name &) = default; + Name(Name &&) = default; + + ~Name() = default; + + public: + const std::string &content(void) const { return _content; } + + private: + std::string _content; + }; + +public: + Pass(const Name &name) : _name{name} + { + // DO NOTHING + } + + Pass(const Pass &) = delete; + Pass(Pass &&) = delete; + + virtual ~Pass() = default; + +public: + const Name &name(void) const { return _name; } + +public: + virtual void run(const SessionID &) const = 0; + +private: + Name _name; +}; + +static inline Pass::Name pass_name(const std::string &name) { return Pass::Name{name}; } + +} // namespace enco + +#define PASS_CTOR(NAME) \ + NAME() : enco::Pass { enco::pass_name(#NAME) } + +#endif // __ENCO_PASS_H__ diff --git a/compiler/enco/core/src/Pass.test.cpp b/compiler/enco/core/src/Pass.test.cpp new file mode 100644 index 00000000000..112bd7478e7 --- /dev/null +++ b/compiler/enco/core/src/Pass.test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Pass.h" + +#include + +namespace +{ + +struct ExamplePass final : public enco::Pass +{ + PASS_CTOR(ExamplePass) + { + // DO NOTHING + } + + void run(const enco::SessionID &) const override { return; } +}; + +} // namespace + +TEST(PASS, ctor) +{ + ExamplePass pass; + + ASSERT_EQ(pass.name().content(), "ExamplePass"); +} diff --git a/compiler/enco/core/src/Pipeline.h b/compiler/enco/core/src/Pipeline.h new file mode 100644 index 00000000000..8ab43c16a6a --- /dev/null +++ b/compiler/enco/core/src/Pipeline.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_PIPELINE_H__ +#define __ENCO_PIPELINE_H__ + +#include "Pass.h" + +#include +#include +#include + +namespace enco +{ + +class Pipeline +{ +public: + uint32_t size(void) const { return _passes.size(); } + +public: + const Pass &at(uint32_t n) const { return *(_passes.at(n)); } + +public: + void append(std::unique_ptr &&pass) { _passes.emplace_back(std::move(pass)); } + +private: + std::vector> _passes; +}; + +} // namespace enco + +#endif // __ENCO_PIPELINE_H__ diff --git a/compiler/enco/core/src/Pipeline.test.cpp b/compiler/enco/core/src/Pipeline.test.cpp new file mode 100644 index 00000000000..1cd730e98d4 --- /dev/null +++ b/compiler/enco/core/src/Pipeline.test.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Pipeline.h" + +#include + +TEST(PIPELINE, default_ctor) +{ + enco::Pipeline pipeline; + + ASSERT_EQ(pipeline.size(), 0); +} diff --git a/compiler/enco/core/src/Session.cpp b/compiler/enco/core/src/Session.cpp new file mode 100644 index 00000000000..034f2389242 --- /dev/null +++ b/compiler/enco/core/src/Session.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Session.h" + +#include + +#include +#include + +using stdex::make_unique; + +namespace +{ + +std::map> sess_to_code; +std::map module_to_sess; +std::map data_to_sess; + +} // namespace + +namespace enco +{ + +SessionID make_session(coco::Module *m, coco::Data *d) +{ + static uint32_t sess = 0; + SessionID curr{sess++}; + + sess_to_code[curr] = make_unique(m, d); + module_to_sess[m] = curr; + data_to_sess[d] = curr; + + return curr; +} + +SessionID session(const coco::Module *m) { return module_to_sess.at(m); } +SessionID session(const coco::Data *d) { return data_to_sess.at(d); } + +coco::Module *module(const SessionID &sess) { return sess_to_code.at(sess)->module(); } +coco::Data *data(const SessionID &sess) { return sess_to_code.at(sess)->data(); } + +Code *code(const SessionID &sess) { return sess_to_code.at(sess).get(); } + +} // namespace enco diff --git a/compiler/enco/core/src/Session.h b/compiler/enco/core/src/Session.h new file mode 100644 index 00000000000..b6d502f3b6f --- /dev/null +++ b/compiler/enco/core/src/Session.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_SESSION_H__ +#define __ENCO_SESSION_H__ + +#include "Code.h" + +namespace enco +{ + +// TODO Rewrite this definition +using SessionID = uint32_t; + +SessionID make_session(coco::Module *m, coco::Data *d); + +SessionID session(const coco::Module *m); +SessionID session(const coco::Data *d); + +coco::Module *module(const SessionID &); +coco::Data *data(const SessionID &); + +static inline coco::Module *module(const coco::Data *d) { return module(session(d)); } +static inline coco::Data *data(const coco::Module *m) { return data(session(m)); } + +// WARN This API is introduced just for backward compatibility +// Do NOT use this anymore as it will be removed +Code *code(const SessionID &); + +} // namespace enco + +#endif // __ENCO_SESSION_H__ diff --git a/compiler/enco/core/src/String.h b/compiler/enco/core/src/String.h new file mode 100644 index 00000000000..0f04f1ffe6e --- /dev/null +++ b/compiler/enco/core/src/String.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_STRING_H__ +#define __ENCO_STRING_H__ + +// +// String-manipulating routines +// +#include +#include + +#include + +namespace enco +{ + +template void concat(std::ostream &os, const std::string &sep, It beg, It end) +{ + uint32_t count = 0; + + for (auto it = beg; it != end; ++it, ++count) + { + if (count == 0) + { + os << *it; + } + else + { + os << sep << *it; + } + } +} + +template std::string concat(const std::string &sep, It beg, It end) +{ + std::stringstream ss; + concat(ss, sep, beg, end); + return ss.str(); +} + +} // namespace enco + +#endif // __ENCO_STRING_H__ diff --git a/compiler/enco/core/src/Support/Debugging.cpp b/compiler/enco/core/src/Support/Debugging.cpp new file mode 100644 index 00000000000..bd65a27d855 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Debugging.h" + +#include +#include + +#include + +#include + +#define DEBUGGING_API_P(NAME, TYPE, VAR) \ + static void _##NAME(const TYPE *); \ + void NAME(long p) { NAME(reinterpret_cast(p)); } \ + void NAME(const TYPE *p) \ + { \ + if (p == nullptr) \ + { \ + std::cout << "(nullptr)" << std::endl; \ + } \ + else \ + { \ + _##NAME(p); \ + } \ + } \ + void _##NAME(const TYPE *VAR) + +namespace +{ + +class SectionBuilder +{ +public: + SectionBuilder(const std::string &tag) : _tag{tag} + { + // DO NOTHING + } + +public: + template pp::LinearDocument build(Callback cb) const + { + pp::LinearDocument res; + + res.append(_tag, " {"); + res.indent(); + + cb(res); + + res.unindent(); + res.append("}"); + + return res; + } + +private: + std::string _tag; +}; + +template +pp::LinearDocument operator<<(const SectionBuilder &builder, Callback cb) +{ + return builder.build(std::forward(cb)); +} + +SectionBuilder section(const std::string &tag) { return SectionBuilder{tag}; } +} + +/** + * SECTION: Bag + */ +namespace +{ + +pp::LinearDocument describe(const coco::Bag *bag) +{ + pp::LinearDocument doc; + + doc.append("addr: ", bag); + doc.append("size: ", bag->size()); + // TODO Print Read + // TODO Print Update + // TODO Print Dep + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_bags, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + assert(bag != nullptr); + + auto set = [bag](pp::LinearDocument &doc) { doc.append(describe(bag)); }; + auto desc = section("bag").build(set); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Object + */ +namespace +{ +std::string op_kind(const coco::Op *op); + +/** + * @brief Return the def(producer) type of object + */ +std::string def_kind(const coco::Def *def) +{ + if (def) + { + if (auto instr = dynamic_cast(def->producer())) + { + std::stringstream ss; + + if (auto eval = instr->asEval()) + { + ss << op_kind(eval->op()) << "(" << instr << ")"; + return ss.str(); + } + else if (instr->asCopy()) + { + ss << "Copy(" << instr << ")"; + return ss.str(); + } + else if (instr->asShuffle()) + { + ss << "Shuffle(" << instr << ")"; + return ss.str(); + } + } + else + { + return "(unknown)"; + } + } + + return "(none)"; +} + +pp::LinearDocument describe(const coco::Object *obj) +{ + pp::LinearDocument doc; + + doc.append("addr: ", obj); + doc.append("bag: ", obj->bag()); + doc.append("producer: ", def_kind(obj->def())); + // TODO Show Uses + // TODO Show FeatureObject/KernelObect info + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_objects, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + auto obj = m->entity()->object()->at(n); + assert(obj != nullptr); + + auto set = [obj](pp::LinearDocument &doc) { doc.append(describe(obj)); }; + auto desc = section("object").build(set); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Op + */ +namespace +{ + +struct OpTree +{ +public: + OpTree(const coco::Op *op) : _op{op} + { + // DO NOTHING + } + +public: + const coco::Op *root(void) const { return _op; } + +private: + const coco::Op *_op; +}; + +std::string op_kind(const coco::Op *op) +{ + struct OpKind : public coco::Op::Visitor + { + std::string visit(const coco::Load *) override { return "Load"; } + std::string visit(const coco::Conv2D *) override { return "Conv2D"; } + std::string visit(const coco::MaxPool2D *) override { return "MaxPool2D"; } + std::string visit(const coco::AvgPool2D *) override { return "AvgPool2D"; } + std::string visit(const coco::PadF *) override { return "PadF"; } + std::string visit(const coco::ReLU *) override { return "ReLU"; } + std::string visit(const coco::Add *) override { return "Add"; } + std::string visit(const coco::Mul *) override { return "Mul"; } + std::string visit(const coco::ConcatF *) override { return "ConcatF"; } + std::string visit(const coco::Sub *) override { return "Sub"; } + std::string visit(const coco::Sqrt *) override { return "Sqrt"; } + std::string visit(const coco::Div *) override { return "Div"; } + }; + + OpKind v; + + return op->accept(v); +} + +pp::LinearDocument describe(const coco::Padding2D *pad) +{ + pp::LinearDocument doc; + + doc.append("top: ", pad->top()); + doc.append("bottom: ", pad->bottom()); + doc.append("left: ", pad->left()); + doc.append("right: ", pad->right()); + + return doc; +} + +pp::LinearDocument describe(const coco::Stride2D *stride) +{ + pp::LinearDocument doc; + + doc.append("vertical: ", stride->vertical()); + doc.append("horizontal ", stride->horizontal()); + + return doc; +} + +pp::LinearDocument describe(const coco::Conv2D *conv) +{ + pp::LinearDocument doc; + + doc.append("arg: ", conv->arg()); + doc.append("ker: ", conv->ker()); + doc.append("group: ", conv->group()); + + if (auto pad = conv->pad()) + { + auto set = [pad](pp::LinearDocument &doc) { doc.append(describe(pad)); }; + auto desc = section("pad").build(set); + doc.append(desc); + } + + if (auto stride = conv->stride()) + { + auto set = [stride](pp::LinearDocument &doc) { doc.append(describe(stride)); }; + auto desc = section("stride").build(set); + doc.append(desc); + } + + return doc; +} + +pp::LinearDocument describe(const coco::Op *op) +{ + pp::LinearDocument doc; + + doc.append("addr: ", op); + doc.append("kind: ", op_kind(op)); + doc.append("parent(instr): ", op->parent()); + doc.append("up(op): ", op->up()); + + if (auto conv = op->asConv2D()) + { + auto set = [conv](pp::LinearDocument &doc) { doc.append(describe(conv)); }; + auto desc = section("conv2d").build(set); + doc.append(desc); + } + else if (auto load = op->asLoad()) + { + auto set = [load](pp::LinearDocument &doc) { doc.append(describe(load->object())); }; + auto desc = section("load").build(set); + doc.append(desc); + } + + return doc; +} + +pp::LinearDocument describe(const OpTree &t, bool verbose = false) +{ + pp::LinearDocument doc; + + struct Frame + { + public: + Frame(const coco::Op *op) : _op{op}, _indicator{0} + { + // op SHOULD BE valid + assert(_op != nullptr); + } + + public: + /** + * @brief Return a pointer to coco::Op of interest + */ + const coco::Op *op(void) const { return _op; } + + /** + * @brief Return the indicator + * + * Let's assume that the arity of a coco::Op of interest is N + * INDICATOR 0 -> Print the op itself + * INDICATOR 1 -> Print the first argument + * ... + * INDICATOR N -> Print the N-th argument + * INDICATOR N + 1 -> Done + */ + uint32_t indicator(void) const { return _indicator; } + + public: + void advance(void) { _indicator += 1; } + + private: + const coco::Op *_op; + uint32_t _indicator; + }; + + std::stack stack; + + stack.emplace(t.root()); + + while (stack.size() > 0) + { + auto op = stack.top().op(); + uint32_t indicator = stack.top().indicator(); + + if (indicator == 0) + { + doc.append(op_kind(op), " (", op, ")"); + + doc.indent(); + stack.top().advance(); + + // TODO Need to update it to better design for verbose flag + if (verbose) + { + auto set = [op](pp::LinearDocument &doc) { doc.append(describe(op)); }; + auto desc = section("op").build(set); + doc.append(desc); + } + } + else if (indicator < op->arity() + 1) + { + stack.top().advance(); + stack.emplace(op->arg(indicator - 1)); + } + else + { + assert(indicator == op->arity() + 1); + doc.unindent(); + stack.pop(); + } + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_op, coco::Op, op) +{ + { + std::cout << describe(op) << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_op_tree, coco::Op, op) +{ + { + std::cout << describe(OpTree(op)) << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_all_ops, coco::Module, m) +{ + SectionBuilder section_builder{"op"}; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + auto op = m->entity()->op()->at(n); + assert(op != nullptr); + + auto desc = section("op").build([op](pp::LinearDocument &doc) { doc.append(describe(op)); }); + + std::cout << desc << std::endl; + } +} + +/** + * SECTION: Instr + */ +namespace +{ + +std::string kind(const coco::Instr *ins) +{ + struct InstrKind : public coco::Instr::Visitor + { + std::string visit(const coco::Eval *) override { return "Eval"; } + std::string visit(const coco::Copy *) override { return "Copy"; } + std::string visit(const coco::Shuffle *) override { return "Shuffle"; } + }; + + InstrKind v; + + return ins->accept(v); +} + +pp::LinearDocument describe(const coco::Instr *ins, bool verbose = false) +{ + pp::LinearDocument doc; + + doc.append("addr: ", ins); + doc.append("kind: ", kind(ins)); + doc.append("parent: ", ins->parent()); + + // TODO Need to update it to better design for verbose flag + if (verbose) + { + if (auto eval = ins->asEval()) + { + auto optset = [eval, verbose](pp::LinearDocument &doc) { + doc.append(describe(OpTree(eval->op()), verbose)); + }; + auto optdesc = section("op").build(optset); + doc.append(optdesc); + + auto outset = [eval](pp::LinearDocument &doc) { doc.append(describe(eval->out())); }; + auto outdesc = section("out").build(outset); + doc.append(outdesc); + } + else if (auto copy = ins->asCopy()) + { + auto from = [copy](pp::LinearDocument &doc) { doc.append(describe(copy->from())); }; + auto into = [copy](pp::LinearDocument &doc) { doc.append(describe(copy->into())); }; + + auto fdesc = section("from").build(from); + doc.append(fdesc); + + auto idesc = section("into").build(into); + doc.append(idesc); + } + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_all_instrs, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_all_instrs_v, coco::Module, m) +{ + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins, true)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; + } +} + +DEBUGGING_API_P(enco_dump_instr, coco::Instr, ins) +{ + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins, true)); }; + auto desc = section("instr").build(setter); + + std::cout << desc << std::endl; +} + +/** + * SECTION: Block + */ +namespace +{ + +pp::LinearDocument describe(const coco::Block *blk) +{ + pp::LinearDocument doc; + + for (auto ins = blk->instr()->head(); ins; ins = ins->next()) + { + auto setter = [ins](pp::LinearDocument &doc) { doc.append(describe(ins)); }; + auto desc = section("instr").build(setter); + doc.append(desc); + } + + return doc; +} + +} // namespace + +DEBUGGING_API_P(enco_dump_block, coco::Block, blk) { std::cout << describe(blk) << std::endl; } diff --git a/compiler/enco/core/src/Support/Debugging.h b/compiler/enco/core/src/Support/Debugging.h new file mode 100644 index 00000000000..c28356e7603 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @file Debugging.h + * @brief This file includes various interactive debugging helpers + */ + +#ifndef __ENCO_SUPPORT_DEBUGGING_H__ +#define __ENCO_SUPPORT_DEBUGGING_H__ + +#include + +static_assert(sizeof(long) == sizeof(void *), "sizeof(long) == sizeof(pointer)"); + +/** + * Debugging API with a single pointer argument + */ +#define DEBUGGING_API_P(NAME, TYPE) \ + void NAME(const TYPE *); \ + void NAME(long); + +/** + * Print the details of all the allocated coco::Bag in coco::Module + * + * (gdb) call enco_dump_all_bags(bag->module()) + * (gdb) call enco_dump_all_bags(0x...) + */ +DEBUGGING_API_P(enco_dump_all_bags, coco::Module); + +/** + * Print the details of all the allocated coco::Object in coco::Module + * + * (gdb) call enco_dump_all_objects(obj->module()) + * (gdb) call enco_dump_all_objects(0x...) + */ +DEBUGGING_API_P(enco_dump_all_objects, coco::Module); + +/** + * Print the details of coco::Op + * + * (gdb) call enco_dump_op(op) + * (gdb) call enco_dump_op(0x....) + */ +DEBUGGING_API_P(enco_dump_op, coco::Op); + +/** + * Print the (simplified) tree layout of coco::Op + * + * (gdb) call enco_dump_op_tree(op) + * (gdb) call enco_dump_op_tree(0x....) + */ +DEBUGGING_API_P(enco_dump_op_tree, coco::Op); + +/** + * Print the details of all the allocated coco::Op in coco::Module + * + * (gdb) call enco_dump_all_ops(op->module()) + * (gdb) call enco_dump_all_ops(0x....) + */ +DEBUGGING_API_P(enco_dump_all_ops, coco::Module); + +/** + * Print the details of all the allocated coco::Instr in coco::Module + * + * (gdb) call enco_dump_all_instrs(instr->module()) + * (gdb) call enco_dump_all_instrs(0x...) + */ +DEBUGGING_API_P(enco_dump_all_instrs, coco::Module); + +/** + * Print the more details of all the allocated coco::Instr in coco::Module + * + * (gdb) call enco_dump_all_instrs_v(instr->module()) + * (gdb) call enco_dump_all_instrs_v(0x...) + */ +DEBUGGING_API_P(enco_dump_all_instrs_v, coco::Module); + +/** + * Print the details of a given coco::Instr + * + * (gdb) call enco_dump_instr(instr) + * (gdb) call enco_dump_instr(0x...) + */ +DEBUGGING_API_P(enco_dump_instr, coco::Instr); + +/** + * Print the details of all the instruction in a given block + * + * (gdb) call enco_dump_block(b) + * (gdb) call enco_dump_block(0x...) + */ +DEBUGGING_API_P(enco_dump_block, coco::Block); + +#undef DEBUGGING_API_P + +#endif // __ENCO_SUPPORT_DEBUGGING_H__ diff --git a/compiler/enco/core/src/Support/Debugging.test.cpp b/compiler/enco/core/src/Support/Debugging.test.cpp new file mode 100644 index 00000000000..49a2ad16218 --- /dev/null +++ b/compiler/enco/core/src/Support/Debugging.test.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Debugging.h" + +#include + +// This test aims to check whether debugging API is actually defined +TEST(DebuggingTest, defined) +{ + enco_dump_op(nullptr); + enco_dump_all_ops(nullptr); +} diff --git a/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp b/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp new file mode 100644 index 00000000000..17502fb1ff8 --- /dev/null +++ b/compiler/enco/core/src/Transforms/AvgPoolLowering.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "AvgPoolLowering.h" +#include "IRUtils.h" + +#include + +#include +#include + +#include +#include + +using namespace nncc::core::ADT; +using nncc::core::ADT::feature::num_elements; + +namespace +{ + +bool empty(coco::Padding2D *pad) +{ + return (pad->top() == 0) && (pad->bottom() == 0) && (pad->left() == 0) && (pad->right() == 0); +} + +/** + * @brief Return a set of AvgPool2D operations (in Eval instruction) that SHOULD be lowered + */ +std::set candidates(coco::Module *m) +{ + std::set res; + + for (auto I : enco::instr_sequence(m)) + { + if (auto eval = I->asEval()) + { + if (auto avgpool = eval->op()->asAvgPool2D()) + { + /* Originally it was preferred to use `auto load = avgpool->arg()->asLoad()' for + * consitent style with other if statements. + * Someone may think compiler will be happy because `load` in `if` statement can + * be considered as a use, however, it turend out that it is not the case. + */ + if (avgpool->arg()->asLoad()) + { + if (avgpool->divisor() == coco::AvgPool2D::Divisor::Static) + { + res.insert(avgpool); + } + } + } + } + } + + return res; +} + +} // namespace + +namespace +{ +namespace ShapeTransform +{ + +class Pad +{ +public: + Pad(const coco::Padding2D *pad) : _pad{pad} + { + // DO NOTHING + } + +public: + /// @brief Return the expected OFM shape for a given IFM shape + feature::Shape forward(const feature::Shape &ifm_shape) const + { + const uint32_t OFM_C = ifm_shape.depth(); + const uint32_t OFM_H = ifm_shape.height() + _pad->top() + _pad->bottom(); + const uint32_t OFM_W = ifm_shape.width() + _pad->left() + _pad->right(); + + return feature::Shape{OFM_C, OFM_H, OFM_W}; + } + +private: + const coco::Padding2D *_pad; +}; + +} // namespace ShapeTransform + +ShapeTransform::Pad shape_xform(const coco::Padding2D *pad) { return ShapeTransform::Pad{pad}; } + +} // namespace + +namespace +{ + +class PadInstrBuilder final +{ +public: + PadInstrBuilder(const coco::Padding2D *pad) : _pad{pad} + { + // DO NOTHING + } + +public: + coco::Instr *build(coco::FeatureObject *ifm_obj, coco::FeatureObject *ofm_obj) const + { + assert(ifm_obj->module() == ofm_obj->module()); + auto m = ifm_obj->module(); + assert(m != nullptr); + + auto load_op = m->entity()->op()->create(); + + load_op->object(ifm_obj); + + auto pad_op = m->entity()->op()->create(); + + pad_op->arg(load_op); + + pad_op->pad()->top(_pad->top()); + pad_op->pad()->bottom(_pad->bottom()); + pad_op->pad()->left(_pad->left()); + pad_op->pad()->right(_pad->right()); + + auto pad_instr = m->entity()->instr()->create(); + + pad_instr->out(ofm_obj); + pad_instr->op(pad_op); + + return pad_instr; + } + +private: + const coco::Padding2D *_pad; +}; + +PadInstrBuilder pad_instr_builder(const coco::Padding2D *pad) { return PadInstrBuilder{pad}; } + +} // namespace + +namespace +{ + +class AvgPoolRewritePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void AvgPoolRewritePass::runOnModule(coco::Module *m) const +{ + // Lower AvgPool2D op that resides in Eval instruction + for (auto avgpool : candidates(m)) + { + auto ins = avgpool->parent(); + auto load = avgpool->arg()->asLoad(); + + assert(ins != nullptr); + assert(load != nullptr); + assert(avgpool->divisor() == coco::AvgPool2D::Divisor::Static); + + if (empty(avgpool->pad())) + { + // NOTE If there is no padding, Static and PaddingExcluded schemes are equivalent + avgpool->divisor(coco::AvgPool2D::Divisor::PaddingExcluded); + } + else + { + // Before: Static AvgPool2D with Padding + // After: PadF; PaddingExcluded AvgPool2D without Padding + + // Create PadF + auto ifm_obj = load->object()->asFeature(); + assert(ifm_obj != nullptr); + + auto pad_shape = shape_xform(avgpool->pad()).forward(ifm_obj->shape()); + auto pad_bag = m->entity()->bag()->create(num_elements(pad_shape)); + auto pad_obj = m->entity()->object()->create(); + + pad_obj->bag(pad_bag); + pad_obj->layout(coco::FeatureLayouts::BHWC::create(pad_shape)); + + auto pad_instr = pad_instr_builder(avgpool->pad()).build(ifm_obj, pad_obj); + + // Insert PadF before AvgPool2D + pad_instr->insertBefore(ins); + + // Rewrite AvgPool2D as PaddingExcluded AvgPool2D without Padding + load->object(pad_obj); + + avgpool->divisor(coco::AvgPool2D::Divisor::PaddingExcluded); + avgpool->pad()->top(0); + avgpool->pad()->bottom(0); + avgpool->pad()->left(0); + avgpool->pad()->right(0); + } + } +} + +void AvgPoolRewritePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void lower_avgpool(enco::Code *code) +{ + AvgPoolRewritePass pass; + pass.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/AvgPoolLowering.h b/compiler/enco/core/src/Transforms/AvgPoolLowering.h new file mode 100644 index 00000000000..71a5253df4f --- /dev/null +++ b/compiler/enco/core/src/Transforms/AvgPoolLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __REWRITE_H__ +#define __REWRITE_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Rewrite NN API-incompatible average pooling + */ +void lower_avgpool(enco::Code *); + +struct AvgPoolLoweringPass final : public Pass +{ + PASS_CTOR(AvgPoolLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_avgpool(code(sess)); } +}; + +} // namespace enco + +#endif // __REWRITE_H__ diff --git a/compiler/enco/core/src/Transforms/ConcatLowering.cpp b/compiler/enco/core/src/Transforms/ConcatLowering.cpp new file mode 100644 index 00000000000..bf613c98364 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConcatLowering.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CopyLowering.h" +#include "IRUtils.h" + +#include + +#include +#include + +using namespace nncc::core::ADT; + +namespace +{ + +inline uint32_t as_tensor_axis(const coco::ConcatF::Axis &axis) +{ + switch (axis) + { + case coco::ConcatF::Axis::Batch: + return 0; + case coco::ConcatF::Axis::Depth: + return 1; + case coco::ConcatF::Axis::Height: + return 2; + case coco::ConcatF::Axis::Width: + return 3; + default: + break; + }; + + throw std::invalid_argument{"axis is unknown value"}; +} + +tensor::Shape as_tensor_shape(const coco::FeatureLayout *l) +{ + assert(l != nullptr); + + tensor::Shape res; + + res.resize(4); + + res.dim(as_tensor_axis(coco::ConcatF::Axis::Batch)) = l->batch(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Depth)) = l->depth(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Height)) = l->height(); + res.dim(as_tensor_axis(coco::ConcatF::Axis::Width)) = l->width(); + + return res; +} + +coco::ElemID as_element_index(const coco::FeatureLayout *l, const tensor::Index &idx) +{ + assert(l != nullptr); + assert(idx.rank() == 4); + + const auto b = idx.at(as_tensor_axis(coco::ConcatF::Axis::Batch)); + const auto ch = idx.at(as_tensor_axis(coco::ConcatF::Axis::Depth)); + const auto row = idx.at(as_tensor_axis(coco::ConcatF::Axis::Height)); + const auto col = idx.at(as_tensor_axis(coco::ConcatF::Axis::Width)); + + return l->at(b, ch, row, col); +} + +std::set candidates(coco::Module *m) +{ + std::set res; + + for (auto ins : enco::instr_sequence(m)) + { + if (auto eval = ins->asEval()) + { + if (eval->op()->asConcatF()) + { + res.insert(eval); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void lower_concat(enco::Code *code) +{ + auto m = code->module(); + + for (auto eval : candidates(m)) + { + auto concat_f = eval->op()->asConcatF(); + assert(concat_f != nullptr); + + auto left_feature = concat_f->left()->asLoad()->object()->asFeature(); + assert(left_feature != nullptr); + auto left_shape = as_tensor_shape(left_feature->layout()); + + auto right_feature = concat_f->right()->asLoad()->object()->asFeature(); + assert(right_feature != nullptr); + auto right_shape = as_tensor_shape(right_feature->layout()); + + auto out_feature = eval->out()->asFeature(); + assert(out_feature != nullptr); + auto out_shape = as_tensor_shape(out_feature->layout()); + + auto concat_axe = as_tensor_axis(concat_f->axis()); + + // Lower: Left -> Output + { + auto src_feature = left_feature; + auto src_shape = left_shape; + + auto ins = m->entity()->instr()->create(); + + assert(src_feature->bag() != nullptr); + assert(out_feature->bag() != nullptr); + + ins->from(src_feature->bag()); + ins->into(out_feature->bag()); + + for (tensor::IndexEnumerator e{src_shape}; e.valid(); e.advance()) + { + tensor::Index src_index = e.current(); + tensor::Index out_index = e.current(); + + auto from = as_element_index(src_feature->layout(), src_index); + auto into = as_element_index(out_feature->layout(), out_index); + + ins->insert(from, into); + } + + ins->insertAfter(eval); + } + + // Lower: Right -> Output + { + auto src_feature = right_feature; + auto src_shape = right_shape; + + auto ins = m->entity()->instr()->create(); + + assert(src_feature->bag() != nullptr); + assert(out_feature->bag() != nullptr); + + ins->from(src_feature->bag()); + ins->into(out_feature->bag()); + + for (tensor::IndexEnumerator e{src_shape}; e.valid(); e.advance()) + { + tensor::Index src_index = e.current(); + tensor::Index out_index = e.current(); + + out_index.at(concat_axe) = out_index.at(concat_axe) + left_shape.dim(concat_axe); + + auto from = as_element_index(src_feature->layout(), src_index); + auto into = as_element_index(out_feature->layout(), out_index); + + ins->insert(from, into); + } + + ins->insertAfter(eval); + } + + // Unlink "Eval" and "ConcatF" op tree + eval->op(nullptr); + + // Delete "Concat" op tree + m->entity()->op()->destroy(concat_f->left()); + m->entity()->op()->destroy(concat_f->right()); + m->entity()->op()->destroy(concat_f); + + // Deatch "Eval" instruction from the block + eval->detach(); + + // Delete "Eval" instruction + m->entity()->instr()->destroy(eval); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/ConcatLowering.h b/compiler/enco/core/src/Transforms/ConcatLowering.h new file mode 100644 index 00000000000..5d20e627b6e --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConcatLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_CONCAT_LOWERING_H__ +#define __ENCO_CONCAT_LOWERING_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Lower eval(Concat(...)) as a sequence of shuffle instructions + */ +void lower_concat(enco::Code *code); + +struct ConcatLoweringPass final : public Pass +{ + PASS_CTOR(ConcatLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_concat(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_CONCAT_LOWERING_H__ diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.cpp b/compiler/enco/core/src/Transforms/ConstantFolding.cpp new file mode 100644 index 00000000000..cd6f2235106 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.cpp @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConstantFolding.h" +#include "Session.h" + +#include +#include +#include + +namespace +{ + +/** + * @brief is_constant_bag(b) returns true if the bag "b" has corresponding weight + */ +bool is_constant_bag(coco::Bag *b) +{ + auto m = b->module(); + auto d = enco::data(m); + return d->allocated(b); +} + +class ConstantBagEnumerator +{ +public: + ConstantBagEnumerator(enco::Code *code) : _code{code} + { + // DO NOTHING + } + +public: + template void enumerate(Callable cb) const + { + auto m = _code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto b = m->entity()->bag()->at(n); + + if (is_constant_bag(b)) + { + cb(b); + } + } + } + +private: + enco::Code *_code; +}; + +template void operator<<(const ConstantBagEnumerator &e, Callable &&cb) +{ + e.enumerate(std::forward(cb)); +} + +ConstantBagEnumerator constant_bag_enumerator(enco::Code *code) +{ + return ConstantBagEnumerator{code}; +} + +} // namespace + +namespace +{ + +/** + * @brief Take the first element from the queue + * @note The queue SHOULD have at least one element. + */ +template T take(std::queue &q) +{ + assert(q.size() > 0); + auto res = q.front(); + q.pop(); + return res; +} + +} // namespace + +namespace +{ + +void fold_constant(std::queue &q, coco::Copy *copy) +{ + auto m = copy->module(); + auto d = enco::data(m); + + auto src_obj = copy->from(); + auto src_bag = src_obj->bag(); + + auto dst_obj = copy->into(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + // NOTE d->allocated(bag) returns true if bag has corresponding initial + // values (e.g. convolution kernel) + assert(d->allocated(src_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto src_span = d->f32()->weight(src_bag); + + assert(src_span.data() != nullptr); + + auto src_feature = src_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (src_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(src_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + + auto dst_span = d->f32()->weight(dst_bag); + + assert(src_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(src_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(src_feature->layout()->height() == dst_feature->layout()->height()); + assert(src_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = src_feature->layout()->batch(); + uint32_t const C = src_feature->layout()->depth(); + uint32_t const H = src_feature->layout()->height(); + uint32_t const W = src_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto src_ind = src_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + dst_span[dst_ind.value()] = src_span[src_ind.value()]; + } + } + } + } + + // Let's detach copy + copy->from(nullptr); + copy->into(nullptr); + copy->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +template +void fold_constant_op(std::queue &q, coco::UnaryOp *op, Callable evaluate) +{ + auto m = op->module(); + auto d = enco::data(m); + + auto ins = op->parent(); + auto eval = ins->asEval(); + + // UnaryOp has only one arg + auto src_obj = *(op->uses().begin()); + auto src_bag = src_obj->bag(); + + auto dst_obj = eval->out(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + assert(d->allocated(src_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto src_span = d->f32()->weight(src_bag); + assert(src_span.data() != nullptr); + + auto src_feature = src_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (src_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(src_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + auto dst_span = d->f32()->weight(dst_bag); + + assert(src_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(src_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(src_feature->layout()->height() == dst_feature->layout()->height()); + assert(src_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = src_feature->layout()->batch(); + uint32_t const C = src_feature->layout()->depth(); + uint32_t const H = src_feature->layout()->height(); + uint32_t const W = src_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto src_ind = src_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + evaluate(&dst_span[dst_ind.value()], src_span[src_ind.value()]); + } + } + } + } + + // Let's detach eval + eval->out(nullptr); + eval->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +template +void fold_constant_op(std::queue &q, coco::BinaryOp *op, Callable evaluate) +{ + auto m = op->module(); + auto d = enco::data(m); + + auto ins = op->parent(); + auto eval = ins->asEval(); + + // Already folded by the other bag + if (!eval->out()) + { + return; + } + + auto lhs_load = op->left()->asLoad(); + auto lhs_obj = lhs_load->object(); + auto lhs_bag = lhs_obj->bag(); + + auto rhs_load = op->right()->asLoad(); + auto rhs_obj = rhs_load->object(); + auto rhs_bag = rhs_obj->bag(); + + auto dst_obj = eval->out(); + auto dst_bag = dst_obj->bag(); + + // Output calculation should not be folded + // TODO Reduce code duplication of this kind + if (dst_bag->isOutput()) + { + return; + } + + // The other bag is non-constant + if (!d->allocated(lhs_bag) || !d->allocated(rhs_bag)) + { + return; + } + + assert(d->allocated(lhs_bag)); + assert(d->allocated(rhs_bag)); + assert(!d->allocated(dst_bag)); + + // TODO Support other data type + auto lhs_span = d->f32()->weight(lhs_bag); + auto rhs_span = d->f32()->weight(rhs_bag); + assert(lhs_span.data() != nullptr); + assert(rhs_span.data() != nullptr); + + auto lhs_feature = lhs_obj->asFeature(); + auto rhs_feature = rhs_obj->asFeature(); + auto dst_feature = dst_obj->asFeature(); + + // TODO Support other object type + if (lhs_feature == nullptr || rhs_feature == nullptr || dst_feature == nullptr) + { + return; + } + + assert(lhs_feature != nullptr); + assert(rhs_feature != nullptr); + assert(dst_feature != nullptr); + + // Allocate weight for destination + d->f32()->allocate(dst_bag); + auto dst_span = d->f32()->weight(dst_bag); + + assert(lhs_feature->layout()->batch() == rhs_feature->layout()->batch()); + assert(lhs_feature->layout()->depth() == rhs_feature->layout()->depth()); + assert(lhs_feature->layout()->height() == rhs_feature->layout()->height()); + assert(lhs_feature->layout()->width() == rhs_feature->layout()->width()); + + assert(lhs_feature->layout()->batch() == dst_feature->layout()->batch()); + assert(lhs_feature->layout()->depth() == dst_feature->layout()->depth()); + assert(lhs_feature->layout()->height() == dst_feature->layout()->height()); + assert(lhs_feature->layout()->width() == dst_feature->layout()->width()); + + uint32_t const B = lhs_feature->layout()->batch(); + uint32_t const C = lhs_feature->layout()->depth(); + uint32_t const H = lhs_feature->layout()->height(); + uint32_t const W = lhs_feature->layout()->width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + auto lhs_ind = lhs_feature->layout()->at(b, ch, row, col); + auto rhs_ind = rhs_feature->layout()->at(b, ch, row, col); + auto dst_ind = dst_feature->layout()->at(b, ch, row, col); + + evaluate(&dst_span[dst_ind.value()], lhs_span[lhs_ind.value()], + rhs_span[rhs_ind.value()]); + } + } + } + } + + // Let's detach eval + eval->out(nullptr); + eval->detach(); + + // Let's visit destination bag! + q.push(dst_bag); +} + +void fold_constant(std::queue &q, coco::Eval *eval) +{ + // TODO Support other data types + if (auto op = eval->op()->asSqrt()) + { + fold_constant_op(q, op, [](float *dst, float value) { *dst = std::sqrt(value); }); + } + else if (auto op = eval->op()->asAdd()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs + rhs; }); + } + else if (auto op = eval->op()->asSub()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs - rhs; }); + } + else if (auto op = eval->op()->asMul()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs * rhs; }); + } + else if (auto op = eval->op()->asDiv()) + { + fold_constant_op(q, op, [](float *dst, float lhs, float rhs) { *dst = lhs / rhs; }); + } + else + { + // Not supported opteration, do nothing + // TODO Support other operations + } +} + +void fold_constant(std::queue &q, coco::Instr *ins) +{ + if (auto copy = coco::safe_cast(ins)) + { + fold_constant(q, copy); + return; + } + if (auto eval = coco::safe_cast(ins)) + { + fold_constant(q, eval); + return; + } + + // TODO Add more cases for constant folding +} + +} // namespace + +namespace enco +{ + +void fold_constants(enco::Code *code) +{ + std::queue q; + + // Collect the initial set of "constant" bag + constant_bag_enumerator(code) << [&q](coco::Bag *bag) { q.push(bag); }; + + while (!q.empty()) + { + auto candidate_bag = take(q); + + // Scan the readers of each candidate bag + for (auto reader : coco::readers(candidate_bag)) + { + // TODO Decide how to handle the reader with unknown instruction + if (auto ins = reader->loc()) + { + fold_constant(q, ins); + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.h b/compiler/enco/core/src/Transforms/ConstantFolding.h new file mode 100644 index 00000000000..6faa9c87698 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONSTANT_FOLDING_H__ +#define __CONSTANT_FOLDING_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Evaluate "constant" expressions at compile time + */ +void fold_constants(enco::Code *); + +struct ConstantFoldingPass final : public Pass +{ + PASS_CTOR(ConstantFoldingPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { fold_constants(code(sess)); } +}; + +} // namespace enco + +#endif // __CONSTANT_FOLDING_H__ diff --git a/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp b/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp new file mode 100644 index 00000000000..5ac71ac14c8 --- /dev/null +++ b/compiler/enco/core/src/Transforms/ConstantFolding.test.cpp @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConstantFolding.h" +#include "Session.h" + +#include +#include + +namespace +{ + +class BinaryNetwork +{ +public: + BinaryNetwork(coco::Module *module, coco::Data *data) : _module{module}, _data{data} + { + // DO NOTHING + } + + template void build(void); + + void fold(void) + { + // Execute constant folding + enco::make_session(_module, _data); + enco::Code code{_module, _data}; + enco::fold_constants(&code); + } + +public: + coco::Bag *out; + coco::Bag *lhs; + coco::Bag *rhs; + + coco::Eval *eval; + +private: + coco::Module *_module; + coco::Data *_data; +}; + +template void BinaryNetwork::build(void) +{ + // Create lhs bag and object + auto lhs_bag = _module->entity()->bag()->create(12); + auto lhs_obj = _module->entity()->object()->template create(); + coco::FeatureShape lhs_shape(1, 2, 2, 3); + lhs_obj->bag(lhs_bag); + lhs_obj->layout(coco::FeatureLayouts::BHWC::create(lhs_shape)); + + // Create rhs bag and object + auto rhs_bag = _module->entity()->bag()->create(12); + auto rhs_obj = _module->entity()->object()->template create(); + coco::FeatureShape rhs_shape(1, 2, 2, 3); + rhs_obj->bag(rhs_bag); + rhs_obj->layout(coco::FeatureLayouts::BHWC::create(rhs_shape)); + + // Create output bag and object + auto output_bag = _module->entity()->bag()->create(12); + auto output_obj = _module->entity()->object()->template create(); + coco::FeatureShape ofm_shape(1, 2, 2, 3); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + // Create instruction and operations + auto block = _module->entity()->block()->create(); + auto eval = _module->entity()->instr()->template create(); + auto load_lhs = _module->entity()->op()->template create(); + auto load_rhs = _module->entity()->op()->template create(); + auto add_op = _module->entity()->op()->template create(); + + _module->block()->append(block); + block->instr()->append(eval); + + load_lhs->object(lhs_obj); + load_rhs->object(rhs_obj); + add_op->left(load_lhs); + add_op->right(load_rhs); + + eval->op(add_op); + eval->out(output_obj); + + // Create a handle + this->lhs = lhs_bag; + this->rhs = rhs_bag; + this->out = output_bag; + + this->eval = eval; +} + +} // namespace + +TEST(ConstantFoldingTest, sqrt) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + // Create input bag and object + auto input_bag = module->entity()->bag()->create(12); + auto input_obj = module->entity()->object()->create(); + coco::FeatureShape ifm_shape(1, 2, 2, 3); + input_obj->bag(input_bag); + input_obj->layout(coco::FeatureLayouts::BHWC::create(ifm_shape)); + + // Create output bag and object + auto output_bag = module->entity()->bag()->create(12); + auto output_obj = module->entity()->object()->create(); + coco::FeatureShape ofm_shape(1, 2, 2, 3); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_shape)); + + // Insert values into input bag + data->f32()->allocate(input_bag); + auto input = data->f32()->weight(input_bag); + for (uint32_t idx = 0; idx < input.size(); ++idx) + { + input[idx] = (float)idx; + } + + // Create instruction and operations + auto block = module->entity()->block()->create(); + auto eval = module->entity()->instr()->create(); + auto load = module->entity()->op()->create(); + auto sqrt_op = module->entity()->op()->create(); + + module->block()->append(block); + block->instr()->append(eval); + + load->object(input_obj); + sqrt_op->arg(load); + + eval->op(sqrt_op); + eval->out(output_obj); + + // Execute constant folding + enco::make_session(module.get(), data.get()); + enco::Code code{module.get(), data.get()}; + enco::fold_constants(&code); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], std::sqrt(input[idx])); + } +} + +TEST(ConstantFoldingTest, element_wise_add) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] + rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_sub) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] - rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_mul) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] * rhs[idx]); + } +} + +TEST(ConstantFoldingTest, element_wise_div) +{ + auto module = coco::Module::create(); + auto data = coco::Data::create(); + + BinaryNetwork net{module.get(), data.get()}; + + // Build a network + net.build(); + + // Create alises + auto lhs_bag = net.lhs; + auto rhs_bag = net.rhs; + auto output_bag = net.out; + auto eval = net.eval; + + // Insert values into lhs and rhs bag + data->f32()->allocate(lhs_bag); + data->f32()->allocate(rhs_bag); + auto lhs = data->f32()->weight(lhs_bag); + auto rhs = data->f32()->weight(rhs_bag); + for (uint32_t idx = 0; idx < lhs.size(); ++idx) + { + lhs[idx] = (float)idx; + rhs[idx] = 1.5; + } + + // Execute constant folding + net.fold(); + + // Validate the result + ASSERT_EQ(data->allocated(output_bag), true); + ASSERT_EQ(eval->out(), nullptr); + + auto output = data->f32()->weight(output_bag); + for (uint32_t idx = 0; idx < output.size(); ++idx) + { + ASSERT_FLOAT_EQ(output[idx], lhs[idx] / rhs[idx]); + } +} diff --git a/compiler/enco/core/src/Transforms/CopyLowering.cpp b/compiler/enco/core/src/Transforms/CopyLowering.cpp new file mode 100644 index 00000000000..ceb3bbd5c4f --- /dev/null +++ b/compiler/enco/core/src/Transforms/CopyLowering.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CopyLowering.h" + +#include +#include + +// +// Lower Copy as Shuffle +// +namespace enco +{ + +void lower_copy(enco::Code *code) +{ + auto m = code->module(); + + std::set lowered_copies; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + + assert(ins != nullptr); + + if (ins->parent() == nullptr) + { + // Skip if instruction does not belong to a list + continue; + } + + auto copy = ins->asCopy(); + + if (copy == nullptr) + { + // Skip if instruction is not a copy + continue; + } + + // TODO Support non-Feature objects + auto ifm = copy->from()->asFeature(); + auto ofm = copy->into()->asFeature(); + + if ((ifm == nullptr) || (ofm == nullptr)) + { + continue; + } + + assert(ifm->layout()->batch() == ofm->layout()->batch()); + assert(ifm->layout()->shape() == ofm->layout()->shape()); + + auto shuffle = m->entity()->instr()->create(); + + shuffle->from(ifm->bag()); + shuffle->into(ofm->bag()); + + const uint32_t B = ifm->layout()->batch(); + const uint32_t C = ifm->layout()->shape().depth(); + const uint32_t H = ifm->layout()->shape().height(); + const uint32_t W = ifm->layout()->shape().width(); + + for (uint32_t b = 0; b < B; ++b) + { + for (uint32_t ch = 0; ch < C; ++ch) + { + for (uint32_t row = 0; row < H; ++row) + { + for (uint32_t col = 0; col < W; ++col) + { + const auto from = ifm->layout()->at(b, ch, row, col); + const auto into = ofm->layout()->at(b, ch, row, col); + + shuffle->insert(from, into); + } + } + } + } + + shuffle->insertBefore(copy); + lowered_copies.insert(copy); + } + + // Destroy lowered copy + for (const auto © : lowered_copies) + { + copy->detach(); + m->entity()->instr()->destroy(copy); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/CopyLowering.h b/compiler/enco/core/src/Transforms/CopyLowering.h new file mode 100644 index 00000000000..51f0f83e22b --- /dev/null +++ b/compiler/enco/core/src/Transforms/CopyLowering.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_LOWER_H__ +#define __ENCO_LOWER_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Lower copy(...) instruction into shuffle(...) + */ +void lower_copy(enco::Code *code); + +struct CopyLoweringPass final : public Pass +{ + PASS_CTOR(CopyLoweringPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { lower_copy(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_LOWER_H__ diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp b/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp new file mode 100644 index 00000000000..9d65d1c0ba0 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.cpp @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DataLayoutConversion.h" +#include "Session.h" +#include "IRUtils.h" + +#include "coex/IR.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace +{ + +coco::Copy *make_copy(coco::FeatureObject *from, coco::FeatureObject *into) +{ + auto m = from->module(); + assert(m != nullptr); + assert(from->module() == into->module()); + + auto copy = m->entity()->instr()->create(); + + copy->from(from); + copy->into(into); + + return copy; +} + +coco::FeatureObject *clone_feature(const coco::FeatureObject *oldobj) +{ + auto module = oldobj->module(); + auto newobj = module->entity()->object()->create(); + newobj->layout(coco::FeatureLayouts::BHWC::create(oldobj->shape())); + + if (oldobj->bag() != nullptr) + { + using nncc::core::ADT::feature::num_elements; + + // NOTE The size of bag should be at least "BxHxWxC" as "newobj" uses BHWC layout + const uint32_t batch = newobj->layout()->batch(); + const uint32_t count = num_elements(newobj->layout()->shape()); + const uint32_t bag_size = batch * count; + + // Clone bag only when there is a backing bag for a given feature object + auto newbag = module->entity()->bag()->create(bag_size); + newobj->bag(newbag); + } + + return newobj; +} + +/** + * @brief Insert Copy before Load if necessary + * + * @require "load" should be bounded + */ +void insert_copy_before_load(coco::Load *load) +{ + assert(load->parent() != nullptr); + assert(load->parent()->parent() != nullptr); + + if (auto obj = load->object()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + load->object(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(load->parent()); + } + } + } +} + +/** + * @brief Insert Copy after Eval if necessary + */ +void insert_copy_after_eval(coco::Eval *eval) +{ + if (auto out = eval->out()) + { + if (auto ofm = out->asFeature()) + { + if (ofm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ofm; + auto newobj = clone_feature(oldobj); + + eval->out(newobj); + + auto copy = make_copy(newobj, oldobj); + copy->insertAfter(eval); + } + } + } +} + +/** + * @brief Insert copy (for data layout change) before/after ANNDepthConcatF if necessary + */ +void convert_data_layout(ANNDepthConcatF *concat) +{ + if (auto out = concat->out()) + { + if (auto ofm = out->asFeature()) + { + if (ofm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ofm; + auto newobj = clone_feature(oldobj); + + concat->out(newobj); + + auto copy = make_copy(newobj, oldobj); + copy->insertAfter(concat); + } + } + } + + if (auto obj = concat->fst()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + concat->fst(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(concat); + } + } + } + + if (auto obj = concat->snd()) + { + if (auto ifm = obj->asFeature()) + { + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + auto oldobj = ifm; + auto newobj = clone_feature(oldobj); + + concat->snd(newobj); + + auto copy = make_copy(oldobj, newobj); + copy->insertBefore(concat); + } + } + } +} + +/** + * @brief Update convolution kernel data layout + */ +void change_conv2d_kernel_layout(coco::Conv2D *conv) +{ + auto m = conv->module(); + assert(m != nullptr); + auto d = enco::data(enco::session(m)); + assert(d != nullptr); + + auto old_obj = conv->ker(); + assert(old_obj != nullptr); + auto old_bag = old_obj->bag(); + assert(old_bag != nullptr); + + if (old_obj->layout()->id() == coco::KernelLayouts::NHWC::uid()) + { + // Skip if kernel already uses NHWC layout + return; + } + + const auto &ker_shape = old_obj->shape(); + + assert(d->allocated(old_bag)); + + auto new_bag = m->entity()->bag()->create(old_bag->size()); + auto new_obj = m->entity()->object()->create(); + + new_obj->bag(new_bag); + new_obj->layout(coco::KernelLayouts::NHWC::create(ker_shape)); + + d->f32()->allocate(new_bag); + + auto src = d->f32()->read(old_obj); + auto dst = d->f32()->access(new_obj); + + const auto ker_N = ker_shape.count(); + const auto ker_C = ker_shape.depth(); + const auto ker_H = ker_shape.height(); + const auto ker_W = ker_shape.width(); + + for (uint32_t n = 0; n < ker_N; ++n) + { + for (uint32_t ch = 0; ch < ker_C; ++ch) + { + for (uint32_t row = 0; row < ker_H; ++row) + { + for (uint32_t col = 0; col < ker_W; ++col) + { + dst->at(n, ch, row, col) = src->at(n, ch, row, col); + } + } + } + } + + conv->ker(new_obj); + d->release(old_bag); +} + +} // namespace + +namespace +{ + +/** + * @brief Return the set of all of bounded Load Op(s) in a given module + * + * @note 'bounded' means it will be exectuted + */ +std::set loads(coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + auto op = m->entity()->op()->at(n); + + // Skip if this op is dangling + if (op->parent() == nullptr) + { + continue; + } + + // Skip if eval instruction of this op is dangling + if (op->parent()->parent() == nullptr) + { + continue; + } + + if (auto load = m->entity()->op()->at(n)->asLoad()) + { + res.insert(load); + } + } + + return res; +} + +/** + * @brief Return the set of every (allocated) Eval instruction in a given module + */ +std::set evals(coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto eval = m->entity()->instr()->at(n)->asEval()) + { + res.insert(eval); + } + } + + return res; +} + +/** + * @brief Return the set of allocated Conv2D op in a given module + */ +std::set convs(coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + if (auto op = m->entity()->op()->at(n)->asConv2D()) + { + res.insert(op); + } + } + + return res; +} + +/** + * @brief Return the set of "bounded" ANNDepthConcatF instructions + */ +std::set depth_concats(coco::Module *m) +{ + std::set res; + + for (auto ins : enco::instr_sequence(m)) + { + if (auto depth_concat_f = coco::safe_cast(ins)) + { + res.insert(depth_concat_f); + } + } + + return res; +} + +class NormalizePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void NormalizePass::runOnModule(coco::Module *m) const +{ + // Insert Copy before all Load Op (if necessary) + for (auto load : loads(m)) + { + insert_copy_before_load(load); + } + + // Insert Copy after all Eval Instr (if necessary) + for (auto eval : evals(m)) + { + insert_copy_after_eval(eval); + } + + // Change Kernel Layout of Conv2D opertion (if necessary) + for (auto conv : convs(m)) + { + change_conv2d_kernel_layout(conv); + } + + // Insert Copy (for Layout Conversion) before/after ANNDepthConcatF instructions (if necessary) + for (auto depth_concat : depth_concats(m)) + { + convert_data_layout(depth_concat); + } +} + +void NormalizePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void convert_data_layout(enco::Code *code) +{ + NormalizePass pass; + pass.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.h b/compiler/enco/core/src/Transforms/DataLayoutConversion.h new file mode 100644 index 00000000000..ac4052c8bf8 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ +#define __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Insert data reordering if necessary + */ +void convert_data_layout(enco::Code *code); + +struct DataLayoutConversionPass final : public enco::Pass +{ + PASS_CTOR(DataLayoutConversionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { convert_data_layout(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DATA_LAYOUT_CONVERSION_H__ diff --git a/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp b/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp new file mode 100644 index 00000000000..812e38a7893 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DataLayoutConversion.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DataLayoutConversion.h" + +#include + +TEST(DataLayoutConversionTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Load op + m->entity()->instr()->create(); + + enco::Code code{m.get(), nullptr}; + ASSERT_EQ(m->entity()->instr()->size(), 1); + + // "conver_data_layout" SHOULD NOT crash even if there is a "free" Load op + enco::convert_data_layout(&code); +} diff --git a/compiler/enco/core/src/Transforms/DeadBagElimination.cpp b/compiler/enco/core/src/Transforms/DeadBagElimination.cpp new file mode 100644 index 00000000000..b3c598a55ba --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadBagElimination.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DeadBagElimination.h" + +#include + +namespace +{ + +/// @brief Return true if a given bag is marked as either input or output +bool is_public(const coco::Bag *b) { return b->isInput() || b->isOutput(); } + +/// @brief Return the set of "dead" bags in a given module +std::set dead_bags(const coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (coco::readers(bag).empty() && !is_public(bag)) + { + res.insert(bag); + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_dead_bag(enco::Code *code) +{ + auto m = code->module(); + + // Destroy a dead bag and its updaters + for (auto bag : dead_bags(m)) + { + for (auto updater : coco::updaters(bag)) + { + auto ins = updater->loc(); + + assert(ins != nullptr); + + ins->detach(); + m->entity()->instr()->destroy(ins); + } + + bag->replaceWith(nullptr); + m->entity()->bag()->destroy(bag); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DeadBagElimination.h b/compiler/enco/core/src/Transforms/DeadBagElimination.h new file mode 100644 index 00000000000..87e03e8ac99 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadBagElimination.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ +#define __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate dead bags + * + * A bag is referred to as dead if it is neither input nor output, and has no read. If a bag is + * dead, it is unnecessary to updates its values as these values are never used. + * + * "eliminate_dead_bag" removes all the dead bags and its updaters from IR. + */ +void eliminate_dead_bag(enco::Code *code); + +struct DeadBagEliminationPass final : public Pass +{ + PASS_CTOR(DeadBagEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_dead_bag(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DEAD_BAG_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp b/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp new file mode 100644 index 00000000000..df8cc628a67 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadObjectElimination.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DeadObjectElimination.h" + +#include + +namespace +{ + +std::set dead_objects(const coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + auto obj = m->entity()->object()->at(n); + + if (auto bag = obj->bag()) + { + if (coco::readers(bag).empty() && !(bag->isOutput())) + { + res.insert(obj); + } + } + else + { + // NOTE Just in case if there are Objects not related to Bags + if (obj->uses()->size() == 0) + { + res.insert(obj); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_dead_object(enco::Code *code) +{ + auto m = code->module(); + + // Destroy a dead object and its producer + for (auto obj : dead_objects(m)) + { + if (auto producer = coco::producer(obj)) + { + auto ins = producer->loc(); + assert(ins != nullptr); + + ins->detach(); + m->entity()->instr()->destroy(ins); + } + + m->entity()->object()->destroy(obj); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DeadObjectElimination.h b/compiler/enco/core/src/Transforms/DeadObjectElimination.h new file mode 100644 index 00000000000..4923e56fd18 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DeadObjectElimination.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ +#define __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate dead objects in IR + * + * An object whose backing bag is unused is referred to as a dead object. + * + * Dead Object Elimination (DOE) eliminates such dead objects along with their producer. + */ +void eliminate_dead_object(enco::Code *code); + +struct DeadObjectEliminationPass final : public Pass +{ + PASS_CTOR(DeadObjectEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_dead_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DEAD_OBJECT_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/Duplicate.cpp b/compiler/enco/core/src/Transforms/Duplicate.cpp new file mode 100644 index 00000000000..91f64a0ade9 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Duplicate.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Duplicate.h" + +#include +#include + +#include + +namespace +{ + +coco::Block *find_or_create_first_block(coco::Module *m) +{ + if (m->block()->empty()) + { + auto blk = m->entity()->block()->create(); + m->block()->append(blk); + return blk; + } + + return m->block()->head(); +} + +} // namespace + +namespace +{ + +class DuplicatePass +{ +private: + void runOnModule(coco::Module *m) const; + +public: + void runOnCode(enco::Code *) const; +}; + +void DuplicatePass::runOnModule(coco::Module *m) const +{ + // Let's find candidates + std::set candidates; + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (bag->isInput() && bag->isOutput()) + { + candidates.insert(bag); + } + } + + // Return if there is no candidate + if (candidates.empty()) + { + return; + } + + std::map input_map; + std::map output_map; + + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + assert(input->bag() != nullptr); + input_map[input->bag()] = input; + } + + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + assert(output->bag() != nullptr); + output_map[output->bag()] = output; + } + + // For each in/out bag, + // 1. Create a new bag of the same size + // 2. Copy the content from the original bag + // 3. Mark the newly created bag as an output + for (const auto &candidate : candidates) + { + assert(coco::updaters(candidate).empty()); + assert(input_map.find(candidate) != input_map.end()); + assert(output_map.find(candidate) != output_map.end()); + + auto src = candidate; + auto dst = m->entity()->bag()->create(src->size()); + + // Create a copy instruction + auto shuffle = m->entity()->instr()->create(); + + shuffle->from(src); + shuffle->into(dst); + + for (uint32_t n = 0; n < src->size(); ++n) + { + shuffle->insert(coco::ElemID{n} /* FROM */, coco::ElemID{n} /* INTO */); + } + + find_or_create_first_block(m)->instr()->prepend(shuffle); + + // Let's use the new bag as an output + output_map.at(src)->bag(dst); + } +} + +void DuplicatePass::runOnCode(enco::Code *code) const { runOnModule(code->module()); } + +} // namespace + +namespace enco +{ + +void duplicate_inout_bag(enco::Code *code) +{ + DuplicatePass duplicate; + duplicate.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Duplicate.h b/compiler/enco/core/src/Transforms/Duplicate.h new file mode 100644 index 00000000000..93baa45893c --- /dev/null +++ b/compiler/enco/core/src/Transforms/Duplicate.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DUPLICATE_H__ +#define __DUPLICATE_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate in/out bags by duplication + */ +void duplicate_inout_bag(enco::Code *code); + +struct BagDuplicationPass final : public Pass +{ + PASS_CTOR(BagDuplicationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { duplicate_inout_bag(code(sess)); } +}; + +} // namespace enco + +#endif // __DUPLICATE_H__ diff --git a/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp new file mode 100644 index 00000000000..fa84c005cf2 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DuplicatedObjectReduction.h" + +#include "CodeIndex.h" +#include "IRUtils.h" + +#include + +namespace +{ + +/** + * @brief Collect feature objects in coco IR + */ +std::set features(const coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->object()->size(); ++n) + { + if (auto feature = m->entity()->object()->at(n)->asFeature()) + { + res.insert(feature); + } + } + + return res; +} + +std::set candidates(const coco::FeatureObject *src) +{ + std::set res; + + for (auto consumer : coco::consumers(src)) + { + if (auto copy = consumer->loc()->asCopy()) + { + auto dst = copy->into()->asFeature(); + assert(dst != nullptr); + + if (dst->layout()->id() == coco::FeatureLayouts::BHWC::uid()) + { + res.insert(dst); + } + } + } + + return res; +} + +CodeIndex code_index(coco::Object::Producer *p) +{ + if (auto ins = p->loc()) + { + return ::code_index(ins); + } + + return CodeIndex{}; +} + +} // namespace + +namespace enco +{ + +void reduce_duplicated_object(enco::Code *code) +{ + auto m = code->module(); + + for (const auto &src : features(m)) + { + auto copied = candidates(src); + + if (copied.size() <= 1) + { + continue; + } + + // Find the dominator + coco::FeatureObject *dominator = nullptr; + + for (auto candidate : copied) + { + if (dominator == nullptr) + { + dominator = candidate; + } + else if (code_index(coco::producer(candidate)) < code_index(coco::producer(dominator))) + { + dominator = candidate; + } + } + + // Replace all the occurunce of dominated objects with its dominator + copied.erase(dominator); + + for (auto dominatee : copied) + { + subst(dominatee, dominator); + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h new file mode 100644 index 00000000000..3aa20058e53 --- /dev/null +++ b/compiler/enco/core/src/Transforms/DuplicatedObjectReduction.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ +#define __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Reduce duplicated feature objects as its dominating feature object + * + * >>> BEFORE <<< + * %obj_0 = Feature(layout: ???) at ... + * %obj_1 = Feature(layout: BHWC) at ... + * %obj_2 = Feature(layout: BHWC) at ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + * ... + * Use(%obj_1) + * Use(%obj_2) + * ... + * + * >>> AFTER <<< + * %obj_0 = Feature(layout: ???) at ... + * %obj_1 = Feature(layout: BHWC) at ... + * %obj_2 = Feature(layout: BHWC) at ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + * ... + * Use(%obj_1) + * Use(%obj_1) <-- CHANGED + * ... + * + * NOTE Given a set of feature objects, a feature object referred to as a dominating + * feature object if its producer proceeds the producer of every feature object + * in the given set + */ +void reduce_duplicated_object(enco::Code *code); + +struct DuplicatedObjectReductionPass final : public Pass +{ + PASS_CTOR(DuplicatedObjectReductionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { reduce_duplicated_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_DUPLICATED_OBJECT_REDUCTION_H__ diff --git a/compiler/enco/core/src/Transforms/FeatureUnification.cpp b/compiler/enco/core/src/Transforms/FeatureUnification.cpp new file mode 100644 index 00000000000..1a7a0a8a48b --- /dev/null +++ b/compiler/enco/core/src/Transforms/FeatureUnification.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FeatureUnification.h" +#include "IRUtils.h" + +#include + +#include +#include + +#include + +using stdex::make_unique; + +namespace +{ + +bool is_static_layout(const coco::FeatureLayout::ID *id) +{ + if (id == coco::FeatureLayouts::BHWC::uid()) + { + return true; + } + + if (id == coco::FeatureLayouts::BCHW::uid()) + { + return true; + } + + return false; +} + +bool is_static_layout(const coco::FeatureLayout *l) { return is_static_layout(l->id()); } +bool is_static_layout(const coco::FeatureObject *f) { return is_static_layout(f->layout()); } + +/** + * @brief Return ture if a given 'feature' is the candidate of unification + */ +bool candidate(const coco::FeatureObject *f) { return is_static_layout(f); } + +/** + * @brief Return true if two features are compatible + * + * Two features are referred to as compatible if these feature are interchangeable. + * + * NOTE The current implementation of "compatible" is sound, but incomplete. + * + * Soundness: + * For all feature objects "lhs" and "rhs" that "compatible(lhs, rhs)" returns true, + * "lhs" and "rhs" are interchangeable. + * + * Completeness: + * For all interchangeable feature objects "lhs" and "rhs", "compatible(lhs, rhs)" returns true. + */ +bool compatible(const coco::FeatureObject *lhs, const coco::FeatureObject *rhs) +{ + assert(candidate(lhs) && candidate(rhs)); + + if (lhs->layout()->id() != rhs->layout()->id()) + { + return false; + } + + if (lhs->layout()->batch() != rhs->layout()->batch()) + { + return false; + } + + if (!(lhs->layout()->shape() == rhs->layout()->shape())) + { + return false; + } + + return true; +} + +/** + * @brief A FeatureGroup denotes a group of FeatureObject(s) + * + * Each FeatureGroup includes at most 1 DEF FeatureObject (a FeatureObject that has a producer), + * and may include multiple USE FeatureObject(s) (a FeatureObject that has no producer). + * + * NOTE FeatureUnification pass internally uses this FeatureGroup to store a group of compatible + * FeatureObject(s) + */ +class FeatureGroup +{ +public: + explicit FeatureGroup(coco::FeatureObject *feature) { insert(feature); } + +public: + uint32_t size(void) const { return _uses.size() + (_def ? 1 : 0); } + +public: + void insert(coco::FeatureObject *feature) + { + if (feature->def() != nullptr) + { + assert(_def == nullptr); + _def = feature; + } + else + { + _uses.insert(feature); + } + } + +public: + coco::FeatureObject *parent(void) const + { + if (_def) + { + return _def; + } + + assert(_uses.size() > 0); + return *(_uses.begin()); + } + +public: + std::set children(void) const + { + auto res = _uses; + res.erase(parent()); + return res; + } + +private: + coco::FeatureObject *_def = nullptr; + std::set _uses; +}; + +} // namespace + +namespace enco +{ + +void unify_feature(enco::Code *code) +{ + auto m = code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + std::vector> groups; + + auto assign_group = [&](coco::FeatureObject *feature) { + // Find a compatible FeatureGroup + FeatureGroup *group = nullptr; + + for (const auto &g : groups) + { + FeatureGroup *candidate = g.get(); + + if (!compatible(candidate->parent(), feature)) + { + continue; + } + + group = candidate; + break; + } + + if (group == nullptr) + { + // Insert FeatureObject into a new FeatureGroup + groups.emplace_back(make_unique(feature)); + } + else + { + // Insert FeatureObject into the compatible FeatureGroup + group->insert(feature); + } + }; + + auto bag = m->entity()->bag()->at(n); + + for (auto o : coco::dependent_objects(bag)) + { + if (auto feature = o->asFeature()) + { + if (candidate(feature)) + { + assign_group(feature); + } + } + } + + for (const auto &g : groups) + { + auto group = g.get(); + for (const auto child : group->children()) + { + subst(child, group->parent()); + assert(child->def() == nullptr); + assert(child->uses()->size() == 0); + m->entity()->object()->destroy(child); + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FeatureUnification.h b/compiler/enco/core/src/Transforms/FeatureUnification.h new file mode 100644 index 00000000000..5ab0f9d7a6d --- /dev/null +++ b/compiler/enco/core/src/Transforms/FeatureUnification.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ +#define __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Remove duplicated feature objects inside each bag + * + * >>> BEFORE <<< + * %b = Bag(...) + * + * %feature_0 = Feature(...) at %b + * %feature_1 = Feature(...) at %b + * + * ... + * Use(%feature_0) + * ... + * Use(%feature_1) + * ... + * + * >>> AFTER <<< + * %b = Bag(...) + * + * %feature_0 = Feature(...) at %b + * ~~%feature_1 = Feature(...) at %b~~ <- REMOVED + * + * ... + * Use(%feature_0) + * ... + * Use(%feature_0) + * ... + * + * Note that all the occurrences of "%feature_1" are replaced with "%feature_0" + */ +void unify_feature(enco::Code *code); + +struct FeatureUnificationPass final : public Pass +{ + PASS_CTOR(FeatureUnificationPass) + { + // DO NOTHING + } + void run(const SessionID &sess) const override { unify_feature(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FEATURE_UNIFICATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp b/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp new file mode 100644 index 00000000000..a62324b28f9 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FreeInstrElimination.h" + +#include +#include + +namespace +{ + +/** + * @brief Return the set of "free" instructions in a given module + */ +std::set free_instrs(const coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto ins = m->entity()->instr()->at(n)) + { + if (ins->parent() == nullptr) + { + res.insert(ins); + } + } + } + + return res; +} + +void destroy(coco::Instr *ins) +{ + auto m = ins->module(); + m->entity()->instr()->destroy(ins); +} + +} // namespace + +namespace enco +{ + +void eliminate_free_instr(coco::Module *m) +{ + for (auto ins : free_instrs(m)) + { + destroy(ins); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.h b/compiler/enco/core/src/Transforms/FreeInstrElimination.h new file mode 100644 index 00000000000..1d311cd352b --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ +#define __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate free instructions + * + * An instruction is referred to as "free" if it is not bound to any "block" + */ +void eliminate_free_instr(coco::Module *mod); + +/** + * @brief Eliminate free instructions + */ +static inline void eliminate_free_instr(enco::Code *code) +{ + // This function is just a wrapper of the above "void eliminate_free_instr(coco::Module *mod)" + eliminate_free_instr(code->module()); +} + +struct FreeInstrEliminationPass final : public Pass +{ + PASS_CTOR(FreeInstrEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_free_instr(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FREE_INSTR_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp b/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp new file mode 100644 index 00000000000..c15f32e7def --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeInstrElimination.test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FreeInstrElimination.h" + +#include + +TEST(FreeInstrEliminationTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Eval instruction + m->entity()->instr()->create(); + + ASSERT_EQ(m->entity()->instr()->size(), 1); + + // Apply "Free Instruction Elimination" + enco::eliminate_free_instr(m.get()); + + ASSERT_EQ(m->entity()->instr()->size(), 0); +} diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.cpp b/compiler/enco/core/src/Transforms/FreeOpElimination.cpp new file mode 100644 index 00000000000..25f2f44d0c4 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FreeOpElimination.h" + +#include +#include + +namespace +{ + +/** + * @brief Return the set of Free Op Elimination candidates + */ +std::set candidates(const coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->op()->size(); ++n) + { + if (auto op = m->entity()->op()->at(n)) + { + if ((op->parent() == nullptr) && (op->up() == nullptr)) + { + res.insert(op); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_free_op(coco::Module *m) +{ + for (auto op : candidates(m)) + { + m->entity()->op()->destroy_all(op); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.h b/compiler/enco/core/src/Transforms/FreeOpElimination.h new file mode 100644 index 00000000000..3aeacada519 --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ +#define __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Eliminate free op + * + * An op is referred to as "free" if it is not bound to any "instruction" + */ +void eliminate_free_op(coco::Module *mod); + +/** + * @brief Eliminate free op + */ +static inline void eliminate_free_op(enco::Code *code) +{ + // This function is just a wrapper of the above "void eliminate_free_op(coco::Module *mod)" + eliminate_free_op(code->module()); +} + +struct FreeOpEliminationPass final : public Pass +{ + PASS_CTOR(FreeOpEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_free_op(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_FREE_OP_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp b/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp new file mode 100644 index 00000000000..41600526bfc --- /dev/null +++ b/compiler/enco/core/src/Transforms/FreeOpElimination.test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FreeOpElimination.h" + +#include + +TEST(FreeOpEliminationTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Load op + m->entity()->op()->create(); + + ASSERT_EQ(m->entity()->op()->size(), 1); + + // Apply "Free Op Elimination" + enco::eliminate_free_op(m.get()); + + ASSERT_EQ(m->entity()->op()->size(), 0); +} diff --git a/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp b/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp new file mode 100644 index 00000000000..152477a5100 --- /dev/null +++ b/compiler/enco/core/src/Transforms/GlobalDataGeneration.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "GlobalDataGeneration.h" +#include "Split.h" +#include "Dims.h" + +#include + +#include + +using stdex::make_unique; + +namespace +{ + +/** + * @brief Manage global variable declarations + */ +class Global +{ +public: + Global(std::ostream &os) : _os(os) + { + // DO NOTHING + } + +public: + /// @brief Create a global constant string (const char *) literal, and return variable name + enco::GlobalOffset constant(const std::string &value); + + /// @brief Create a global constant array variable of type T + template enco::GlobalOffset constant(const std::vector &values); + + /// @brief Create a global constant array variable of byte (uint8_t) type + enco::GlobalOffset constant(const uint8_t *base, uint32_t size); + +private: + uint32_t _offset = 0; + std::ostream &_os; +}; + +enco::GlobalOffset Global::constant(const std::string &s) +{ + auto const base = reinterpret_cast(s.c_str()); + auto const size = s.size() + 1 /* NUL */; + return constant(base, size); +} + +template <> enco::GlobalOffset Global::constant(const std::vector &values) +{ + auto const base = reinterpret_cast(values.data()); + auto const size = sizeof(uint32_t) * values.size(); + return constant(base, size); +} + +enco::GlobalOffset Global::constant(const uint8_t *base, uint32_t size) +{ + auto pos = _os.tellp(); + assert(pos != -1); + + _os.write(reinterpret_cast(base), size); + + return static_cast(pos); +} + +} // namespace + +namespace +{ + +std::map data_offset_ctx; +std::map bag_data_offset_ctx; + +std::map name_offset_ctx; +std::map dims_offset_ctx; + +} // namespace + +namespace enco +{ + +GlobalOffset GlobalData::data_offset(const ann::Operand *o) { return data_offset_ctx.at(o); } + +GlobalOffset GlobalData::data_offset(const coco::Bag *bag) +{ + assert(bag_data_offset_ctx.find(bag) != bag_data_offset_ctx.end()); + return bag_data_offset_ctx.at(bag); +} + +GlobalOffset GlobalData::name_offset(const coco::Input *in) { return name_offset_ctx.at(in); } +GlobalOffset GlobalData::dims_offset(const coco::Input *in) { return dims_offset_ctx.at(in); } + +GlobalOffset GlobalData::name_offset(const coco::Output *out) { return name_offset_ctx.at(out); } +GlobalOffset GlobalData::dims_offset(const coco::Output *out) { return dims_offset_ctx.at(out); } + +void generate_global_data(std::ostream &os, enco::Code *code) +{ + auto m = code->module(); + auto d = code->data(); + + auto ann_ctx = enco::SubnetManager::context(m); + + auto global = make_unique(os); + + // + // Emit Bag's weight + // + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + if (!d->allocated(bag)) + { + // Skip if the weight value does not exist for a given bag + continue; + } + + // NOTE The current implementation assumes that all the values are of float(fp32) type + // TODO Support non-float values + auto span = d->f32()->weight(bag); + + assert(span.data() != nullptr); + assert(span.size() > 0); + + auto const base = reinterpret_cast(span.data()); + uint32_t const size = span.size() * sizeof(float); + + assert(bag_data_offset_ctx.find(bag) == bag_data_offset_ctx.end()); + bag_data_offset_ctx[bag] = global->constant(base, size); + } + + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + auto binder = ann_ctx->nth(n); + + auto emit = [&](const ann::OperandID & /*id*/, const ann::Operand *info) { + if (info->weight()) + { + auto base = info->weight()->base(); + auto size = info->weight()->size(); + + data_offset_ctx[info] = global->constant(base, size); + } + }; + binder->module()->operand()->each(emit); + } + + for (uint32_t n = 0; n < m->input()->size(); ++n) + { + auto input = m->input()->at(n); + auto dims = as_dims(input->shape()); + + name_offset_ctx[input] = global->constant(input->name()); + dims_offset_ctx[input] = global->constant(dims); + } + + for (uint32_t n = 0; n < m->output()->size(); ++n) + { + auto output = m->output()->at(n); + auto dims = as_dims(output->shape()); + + name_offset_ctx[output] = global->constant(output->name()); + dims_offset_ctx[output] = global->constant(dims); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/GlobalDataGeneration.h b/compiler/enco/core/src/Transforms/GlobalDataGeneration.h new file mode 100644 index 00000000000..43343140133 --- /dev/null +++ b/compiler/enco/core/src/Transforms/GlobalDataGeneration.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ +#define __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ + +#include "Code.h" + +#include + +namespace enco +{ + +using GlobalOffset = uint32_t; + +struct GlobalData +{ + static GlobalOffset data_offset(const ann::Operand *); + /** + * @brief Return the weight offset of a given bag + * + * @note The behavior of "data_offset" is undefined if a bag has no weight. + */ + static GlobalOffset data_offset(const coco::Bag *); + + static GlobalOffset name_offset(const coco::Input *); + static GlobalOffset dims_offset(const coco::Input *); + static GlobalOffset name_offset(const coco::Output *); + static GlobalOffset dims_offset(const coco::Output *); +}; + +/** + * @brief Generate 'Global' weight array. + * + * NOTE Succeeding passes can access offsets via "GlobalData" + */ +void generate_global_data(std::ostream &, enco::Code *); + +} // namespace enco + +#endif // __ENCO_TRANSFORM_GLOBAL_DATA_GENERATION_H__ diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp new file mode 100644 index 00000000000..cb996d2acd7 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IdenticalObjectReduction.h" +#include "IRUtils.h" + +#include + +namespace enco +{ + +void reduce_identical_object(enco::Code *code) +{ + auto m = code->module(); + + std::set detached; + + // Preceding optimizations may generate "free" instructions. + // - i.e. an instruction not linked to a block + // + // Let's iterate over only a sequence of "bounded" instructions. + for (auto ins : instr_sequence(m)) + { + assert(ins != nullptr); + assert(ins->parent() != nullptr); + + auto copy = ins->asCopy(); + + if (copy == nullptr) + { + // Skip if instruction is not a copy + continue; + } + + // TODO Support non-Feature Objects + auto ifm = copy->from()->asFeature(); + auto ofm = copy->into()->asFeature(); + + assert(ofm->bag() != nullptr); + + if (ifm->layout()->id() != ofm->layout()->id()) + { + continue; + } + + if (ifm->layout()->id() != coco::FeatureLayouts::BHWC::uid()) + { + continue; + } + + // Skip if this copy produces network output + if (ofm->bag()->output()) + { + // TODO Optimize this case + // + // Note that the code under optimization is of the following form: + // + // %ifm <- Instr(...) + // %ofm <- Copy(%ifm) + // + // Let's assume that "Copy" is the only reader of %ifm (to be precise, its bag). + // + // Then, it is possible to rewrite the above fragment as follows: + // + // %ofm <- Instr(...) + // + continue; + } + + if (ofm->bag()->reads()->size() > 0) + { + // Let us consider the following code: + // + // Bag: + // %bag_0 = Bag(...) + // %bag_1 = Bag(...) + // %bag_2 = Bag(...) + // + // Object: + // %obj_0 = FeatureObject(bag: %bag_0) + // %obj_1 = FeatureObject(bag: %bag_1) + // + // Instr: + // copy an object from %obj_0 into %obj_1 + // shuffle values from %bag_1 into %bag_2 + // eval Conv2D with %obj_1 + // + // Identical Object Reduction (IOR) tries to eliminate the first copy via + // substitution (substitute all the occurrence of %obj_1 as use with %obj_0). + // + // Here is the code transformed by IOR: + // + // Bag: + // %bag_0 = Bag(...) + // %bag_1 = Bag(...) + // %bag_2 = Bag(...) + // + // Object: + // %obj_0 = FeatureObject(bag: %bag_0) + // %obj_1 = FeatureObject(bag: %bag_1) + // + // Instr: + // shuffle values from %bag_1 into %bag_2 + // eval Conv2D with %obj_0 + // + // Note that there is no updater of %bag_1 after IOR, and thus the behavior + // of the first shuffle instruction has changed. + // + // This examples shows that it is impossible to simply substitute %obj_1 + // with %obj_0 in the presence of readers over its backing bag. + continue; + } + + subst(copy->into(), copy->from()); + + copy->detach(); + detached.insert(copy); + } + + for (auto copy : detached) + { + m->entity()->instr()->destroy(copy); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h new file mode 100644 index 00000000000..b5bb25d7c5a --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ +#define __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Reduce identically copied objects as its original object + * + * >>> BEFORE <<< + * %bag_0 = Bag(size: N) + * %bag_1 = Bag(size: N) + * + * %obj_0 = Feature(layout: BHWC) at %bag_0 + * %obj_1 = Feature(layout: BHWC) at %bag_1 + * + * copy(from: %obj_0, into: %obj_1) + * ... + * Use(%obj_0) + * Use(%obj_1) + * ... + * + * >>> AFTER <<< + * %bag_0 = Bag(size: N) + * %bag_1 = Bag(size: N) + * + * %obj_0 = Feature(layout: BHWC) at %bag_0 + * %obj_1 = Feature(layout: BHWC) at %bag_1 + * + * copy(from: %obj_0, into: %obj_1) + * ... + * Use(%obj_0) + * Use(%obj_0) <- %obj_1 is replaced + * ... + */ +void reduce_identical_object(enco::Code *code); + +struct IdenticalObjectReductionPass final : public Pass +{ + PASS_CTOR(IdenticalObjectReductionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { reduce_identical_object(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_IDENTICAL_OBJECT_REDUCTION_H__ diff --git a/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp new file mode 100644 index 00000000000..772bea08e80 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IdenticalObjectReduction.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IdenticalObjectReduction.h" + +#include + +TEST(IdenticalObjectReductionTest, case_000) +{ + auto m = coco::Module::create(); + + // Create a "free" Eval instruction + m->entity()->instr()->create(); + + enco::Code code{m.get(), nullptr}; + + // NOTE This code SHOULD NOT crash + enco::reduce_identical_object(&code); +} diff --git a/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp b/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp new file mode 100644 index 00000000000..b36620f61dc --- /dev/null +++ b/compiler/enco/core/src/Transforms/IndirectCopyElimination.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IndirectCopyElimination.h" + +#include + +namespace +{ + +coco::Copy *as_copy(coco::Instr *ins) { return ins ? ins->asCopy() : nullptr; } + +/** + * @brief Return a set of copy instructions that are accessible from top-level module + */ +std::set linked_copy_instrs(coco::Module *m) +{ + std::set res; + + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + auto ins = m->entity()->instr()->at(n); + assert(ins != nullptr); + + if (ins->parent() && ins->parent()->parent()) + { + if (auto copy = ins->asCopy()) + { + res.insert(copy); + } + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void eliminate_indirect_copy(enco::Code *code) +{ + auto m = code->module(); + + for (auto child : linked_copy_instrs(m)) + { + auto from = child->from(); + assert(from != nullptr); + + // Find the irreducible origin + while (true) + { + if (auto producer = coco::producer(from)) + { + if (auto parent = as_copy(producer->loc())) + { + assert(parent->from() != nullptr); + from = parent->from(); + continue; + } + } + + break; + } + + child->from(from); + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IndirectCopyElimination.h b/compiler/enco/core/src/Transforms/IndirectCopyElimination.h new file mode 100644 index 00000000000..acfdf569b6f --- /dev/null +++ b/compiler/enco/core/src/Transforms/IndirectCopyElimination.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ +#define __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Convert all the indirect copies as a direct copy + * + * >>> BEFORE <<< + * %obj_0 = ... + * %obj_1 = ... + * %obj_2 = ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_1, into: %obj_2) + * + * >>> AFTER <<< + * %obj_0 = ... + * %obj_1 = ... + * %obj_2 = ... + * + * copy(from: %obj_0, into: %obj_1) + * copy(from: %obj_0, into: %obj_2) + * + */ +void eliminate_indirect_copy(enco::Code *code); + +struct IndirectCopyEliminationPass final : public enco::Pass +{ + PASS_CTOR(IndirectCopyEliminationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { eliminate_indirect_copy(code(sess)); } +}; + +} // namespace enco + +#endif // __ENCO_TRANSFORM_INDIRECT_COPY_ELIMINATION_H__ diff --git a/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp b/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp new file mode 100644 index 00000000000..7bf1c492643 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IntrinsicSelection.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IntrinsicSelection.h" + +#include "coex/IR.h" + +namespace +{ + +/** + * @brief Return a backend-speicific coco (extend) instruction + * + * @note rewrite(ins) returns nullptr if selection fails + */ +coco::Instr *rewrite(coco::Instr *curr) +{ + auto m = curr->module(); + assert(m != nullptr); + + if (auto eval = coco::safe_cast(curr)) + { + if (auto concat_f = eval->op()->asConcatF()) + { + auto fst_load = concat_f->left()->asLoad(); + auto snd_load = concat_f->right()->asLoad(); + + if (fst_load && snd_load && (concat_f->axis() == coco::ConcatF::Axis::Depth)) + { + // Here is the pattern of interest + // + // %ofm = eval(ConcatF(Depth, Load(%left), Load(%right))) + // + auto fst_feature = fst_load->object()->asFeature(); + auto snd_feature = snd_load->object()->asFeature(); + assert((fst_feature != nullptr) && (snd_feature != nullptr)); + + auto out_feature = eval->out()->asFeature(); + assert(out_feature != nullptr); + + eval->out(nullptr); + + auto depth_concat = m->entity()->instr()->create(); + + depth_concat->out(out_feature); + depth_concat->fst(fst_feature); + depth_concat->snd(snd_feature); + + return depth_concat; + } + + return nullptr; + } + } + + return nullptr; +} + +} // namespace + +namespace enco +{ + +void select_intrinsic(enco::Code *code) +{ + auto m = code->module(); + + for (auto blk = m->block()->head(); blk; blk = blk->next()) + { + auto ins = blk->instr()->head(); + + while (ins) + { + if (auto rewritten_ins = rewrite(ins)) + { + rewritten_ins->insertBefore(ins); + ins->detach(); + + ins = rewritten_ins; + } + + ins = ins->next(); + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/IntrinsicSelection.h b/compiler/enco/core/src/Transforms/IntrinsicSelection.h new file mode 100644 index 00000000000..67d38eaebd2 --- /dev/null +++ b/compiler/enco/core/src/Transforms/IntrinsicSelection.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __INTRINSIC_SELECTION_H__ +#define __INTRINSIC_SELECTION_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Select Intricsic (API) to be used + * + * This pass is analogue of "Instruction Selection" pass. This "Intrisic Selection" pass + * will replace a general coco IR instruction into a backend-specific coco (extended) IR + * instruction. + */ +void select_intrinsic(enco::Code *); + +struct IntrinsicSelectionPass final : public Pass +{ + PASS_CTOR(IntrinsicSelectionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { select_intrinsic(code(sess)); } +}; + +} // namespace enco + +#endif // __INTRINSIC_SELECTION_H__ diff --git a/compiler/enco/core/src/Transforms/Optimizations.cpp b/compiler/enco/core/src/Transforms/Optimizations.cpp new file mode 100644 index 00000000000..7f0974dd010 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Optimizations.cpp @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Optimizations.h" +#include "CodeIndex.h" + +#include + +namespace enco +{ + +void generate_bypass_shuffle(enco::Code *code) +{ + auto m = code->module(); + + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + // NOTE The current implementation assumes that all the updates occurs before the first read + // TODO Remove this assumption + for (auto u : coco::updaters(bag)) + { + if ((u->loc() == nullptr) || (u->loc()->asShuffle() == nullptr)) + { + // Skip if updater is not a Shuffle instruction + continue; + } + + for (auto r : coco::readers(bag)) + { + if ((r->loc() == nullptr) || (r->loc()->asShuffle() == nullptr)) + { + // Skip if reader is not a Shuffle instruction + continue; + } + + auto shuffle_1 = u->loc()->asShuffle(); + auto shuffle_2 = r->loc()->asShuffle(); + + // Construct a shuffle instruction + auto shuffle_3 = m->entity()->instr()->create(); + + shuffle_3->from(shuffle_1->from()); + shuffle_3->into(shuffle_2->into()); + + // Attempt to construct a valid bypass shuffle instruction + bool valid = true; + + for (const auto &C : shuffle_2->range()) + { + auto B = shuffle_2->at(C); + + if (!shuffle_1->defined(B)) + { + valid = false; + break; + } + + auto A = shuffle_1->at(B); + + shuffle_3->insert(A, C); + } + + if (valid) + { + // Insert shuffle_3 before shuffle_2 if shuffle_3 is a valid bypass of shuffle_2 + shuffle_3->insertBefore(shuffle_2); + + // NOTE shuffle_2 SHOULD BE detached and destroyed after shuffle_3 is inserted + shuffle_2->detach(); + m->entity()->instr()->destroy(shuffle_2); + } + else + { + // Destroy shuffle_3 (bypass shuffle) if it is invalid + m->entity()->instr()->destroy(shuffle_3); + } + } + } + } +} + +} // namespace enco + +// +// Hoist Object +// +namespace +{ + +bool hoistable(const coco::Shuffle *shuffle) +{ + auto range = shuffle->range(); + + if (range.size() != shuffle->into()->size()) + { + return false; + } + + for (const auto &dst : range) + { + if (shuffle->at(dst).value() != dst.value()) + { + return false; + } + } + + return true; +} + +bool complete(const coco::Shuffle *s) { return s->range().size() == s->into()->size(); } + +bool compatible(const coco::Shuffle *s1, const coco::Shuffle *s2) +{ + if (s1->from() != s2->from()) + { + return false; + } + + if (s1->into()->size() != s2->into()->size()) + { + return false; + } + + auto range_1 = s1->range(); + auto range_2 = s2->range(); + + if (range_1.size() != range_2.size()) + { + return false; + } + + bool res = true; + + for (const auto &dst : range_2) + { + if (!s1->defined(dst)) + { + res = false; + break; + } + + auto src_1 = s1->at(dst); + auto src_2 = s2->at(dst); + + if (src_1.value() != src_2.value()) + { + res = false; + break; + } + } + + return res; +} + +} // namespace + +namespace enco +{ + +void hoist_object(enco::Code *code) +{ + auto m = code->module(); + + // + // Case 1 + // + for (uint32_t n = 0; n < m->entity()->instr()->size(); ++n) + { + if (auto shuffle = m->entity()->instr()->at(n)->asShuffle()) + { + if (shuffle->parent() == nullptr) + { + continue; + } + + if (hoistable(shuffle)) + { + auto from = shuffle->from(); + auto into = shuffle->into(); + + into->replaceAllDepsWith(from); + } + } + } + + // + // Case 2 + // + for (uint32_t n = 0; n < m->entity()->bag()->size(); ++n) + { + auto bag = m->entity()->bag()->at(n); + + std::map collected; + + for (auto reader : coco::readers(bag)) + { + if (auto ins = reader->loc()) + { + if (auto shuffle = ins->asShuffle()) + { + collected[code_index(shuffle)] = shuffle; + } + } + } + + std::vector sorted; + + for (auto it = collected.begin(); it != collected.end(); ++it) + { + sorted.emplace_back(it->second); + } + + for (uint32_t curr = 0; curr < sorted.size(); ++curr) + { + auto const curr_ins = sorted.at(curr); + auto const curr_bag = curr_ins->into(); + + if (!complete(curr_ins)) + { + continue; + } + + for (uint32_t next = curr + 1; next < sorted.size(); ++next) + { + auto const next_ins = sorted.at(next); + auto const next_bag = next_ins->into(); + + if (!complete(next_ins)) + { + continue; + } + + if (compatible(curr_ins, next_ins)) + { + next_bag->replaceAllDepsWith(curr_bag); + } + } + } + } +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Optimizations.h b/compiler/enco/core/src/Transforms/Optimizations.h new file mode 100644 index 00000000000..7cfc2305c0b --- /dev/null +++ b/compiler/enco/core/src/Transforms/Optimizations.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_OPTIMIZATIONS_H__ +#define __ENCO_OPTIMIZATIONS_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +/** + * @brief Add a bypass Shuffle if two continued Shuffles map same from-into + * + * %bag_1 = Bag(size: N) + * %bag_2 = Bag(size: N) + * %bag_3 = Bag(size: N) + * + * >>> BEFORE <<< + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) + * Shuffle(from: %bag_2, into: %bag_3, [0 -> 0]) + * + * Let's refer to the former shuffle as Shuffle 1 and the latter one as Shuffle 2. + * We can replace Shuffle 2 with new Shuffle 3 as follows when Shuffle 1 and + * Shuffle 2 map to the same position. + * + * >>> AFTER <<< + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- Shuffle 1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- Shuffle 3 + * + * Note that Shuffle 1 can be eliminated when %bag_2 is not used + */ +void generate_bypass_shuffle(enco::Code *code); + +struct BypassGenerationPass final : public Pass +{ + PASS_CTOR(BypassGenerationPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { generate_bypass_shuffle(code(sess)); } +}; + +/** + * @brief Update the base bag of each object if possible + * + * --- Case 1 --- + * Let us consider the following code: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * + * %obj_1 = ... at %bag_1 + * %obj_2 = ... at %bag_2 + * + * ... + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle + * ... + * + * Note that the content of %bag_2 after shuffle is identical to a part of %bag_1, so + * the following code is identical to the above code + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * + * %obj_1 = ... at %bag_1 + * %obj_2 = ... at %bag_1 + * + * ... + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) + * ... + * + * --- Case 2 --- + * Let us consider the following code: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * %bag_3 = Bag(size: 1) + * + * %obj_1 = ... at %bag_2 + * %obj_2 = ... at %bag_3 + * + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle_1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- shuffle_2 + * + * Note that the content of %bag_3 after shuffle_2 is identical to that of %bag_2 after shuffle_1, + * so the following code is identical to the above one: + * + * %bag_1 = Bag(size: 4) + * %bag_2 = Bag(size: 1) + * %bag_3 = Bag(size: 1) + * + * %obj_1 = ... at %bag_2 + * %obj_2 = ... at %bag_2 <- HERE + * + * Shuffle(from: %bag_1, into: %bag_2, [0 -> 0]) <- shuffle_1 + * Shuffle(from: %bag_1, into: %bag_3, [0 -> 0]) <- shuffle_2 + * + * "hoist_object" optimization rewrites the former code as the latter one. + * + * NOTE "hoist_object" DOES NOT change any instruction. It just updates the base bag of objects of + * interest. + */ +void hoist_object(enco::Code *code); + +} // namespace enco + +#endif // __ENCO_OPTIMIZATIONS_H__ diff --git a/compiler/enco/core/src/Transforms/Split.cpp b/compiler/enco/core/src/Transforms/Split.cpp new file mode 100644 index 00000000000..b57b8f88295 --- /dev/null +++ b/compiler/enco/core/src/Transforms/Split.cpp @@ -0,0 +1,1233 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Split.h" +#include "Usage.h" +#include "Session.h" +#include "coex/IR.h" + +#include + +#include +#include + +#include +#include +#include + +using stdex::make_unique; + +namespace +{ + +std::map> _subnet_contexts; + +} // namespace + +namespace enco +{ + +const ANNContext *SubnetManager::context(const coco::Module *m) +{ + return _subnet_contexts.at(m).get(); +} + +} // namespace enco + +namespace +{ + +using Appender = std::function; + +struct ANNOpAppender +{ + virtual ~ANNOpAppender() = default; + + virtual void append(ANNBinder *binder) const = 0; +}; + +class ANNAddAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand(_left); + auto right = binder->addOperand(_right); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand(_out); + + binder->addOperation(ann::Operation::Code::ADD, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNMulAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand(_left); + auto right = binder->addOperand(_right); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand(_out); + + binder->addOperation(ann::Operation::Code::MUL, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +/** + * WARN The current implementation supports concatenation along depth only + */ +class ANNConcatAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand(_left); + auto right = binder->addOperand(_right); + auto axis = binder->addOperand(); + binder->setOperand(axis, 3 /* DEPTH */); + + auto out = binder->addOperand(_out); + + binder->addOperation(ann::Operation::Code::CONCAT, {left, right, axis}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNConv2DAppender final : public ANNOpAppender +{ +public: + void session(const enco::SessionID &sess) { _sess = sess; } + + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ker(coco::KernelObject *ker) { _ker = ker; } + // Q: Should we take a bias as a feature object? + // NOTE This interface is subject to change + void bias(coco::FeatureObject *bias) { _bias = bias; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto data = enco::data(_sess); + + auto ifm = binder->addOperand(_ifm); + auto ker = binder->addOperand(_ker); + + // Fill kernel data + { + auto ker_bag = _ker->bag(); + auto ker_weight = data->f32()->weight(ker_bag); + + assert(ker_weight.data() != nullptr); + + binder->setOperand(ker, ker_weight.data(), ker_weight.data() + ker_weight.size()); + } + + // Conv2D in coco IR has no bias, but bias is mandatory in Android NN API + auto bias = binder->addOperand(nncc::core::ADT::tensor::Shape{_ker->shape().count()}); + + // Fill bias data + if (_bias == nullptr) + { + // Use a fresh empty bias if "bias" is not specified + auto length = _ker->shape().count(); + + std::vector values; + values.resize(length, 0.0f); + + binder->setOperand(bias, values.begin(), values.end()); + } + else + { + // Use specified "bias" + auto bias_bag = _bias->bag(); + auto bias_weight = data->f32()->weight(bias_bag); + + assert(bias_weight.data() != nullptr); + assert(bias_weight.size() == _ker->shape().count()); + + binder->setOperand(bias, bias_weight.data(), bias_weight.data() + bias_weight.size()); + } + + auto left = binder->addOperand(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand(); + binder->setOperand(bottom, _pad.bottom()); + auto hstride = binder->addOperand(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand(); + binder->setOperand(vstride, _stride.vertical()); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::CONV_2D, + {ifm, ker, bias, left, right, top, bottom, hstride, vstride, fuse}, {ofm}); + } + +private: + enco::SessionID _sess; + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::KernelObject *_ker = nullptr; + coco::FeatureObject *_bias = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNDepthwiseConv2DAppender final : public ANNOpAppender +{ +public: + void session(const enco::SessionID &sess) { _sess = sess; } + + void multiplier(const uint32_t &multiplier) { _multiplier = multiplier; } + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ker(coco::KernelObject *ker) { _ker = ker; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + using namespace nncc::core::ADT; + + auto data = enco::data(_sess); + + const uint32_t ker_N = _ker->shape().count(); + const uint32_t ker_H = _ker->shape().height(); + const uint32_t ker_W = _ker->shape().width(); + + assert(ker_N % _multiplier == 0); + const uint32_t group = ker_N / _multiplier; + + auto ifm = binder->addOperand(_ifm); + auto ker = binder->addOperand(tensor::Shape{1, ker_H, ker_W, ker_N}); + + // Fill kernel data + { + auto obj = _ker; + auto shape = obj->shape(); + + auto ovl = data->f32()->read(obj); + assert(ovl != nullptr); + + // Flatten? + std::vector values; + + /** + * Android NN computes DEPTHWISE_CONV_2D as follows: + * + * output[b, i, j, k * channel_multiplier + q] = + * sum_{di, dj} ( + * input[b, strides[1] * i + di, strides[2] * j + dj, k] * + * filter[1, di, dj, k * channel_multiplier + q] + * ) + bias[k * channel_multiplier + q] + * + */ + for (uint32_t row = 0; row < shape.height(); ++row) + { + for (uint32_t col = 0; col < shape.width(); ++col) + { + for (uint32_t g = 0; g < group; ++g) + { + for (uint32_t m = 0; m < _multiplier; ++m) + { + const auto value = ovl->at(g * _multiplier + m, 0, row, col); + values.emplace_back(value); + } + } + } + } + + assert(values.size() == nncc::core::ADT::kernel::num_elements(shape)); + binder->setOperand(ker, values.begin(), values.end()); + } + + // Conv2D in coco IR has no bias, but bias is mandatory in Android NN API + auto bias = binder->addOperand(nncc::core::ADT::tensor::Shape{_ker->shape().count()}); + + // Fill bias data + { + auto length = _ker->shape().count(); + + std::vector values; + values.resize(length, 0.0f); + + binder->setOperand(bias, values.begin(), values.end()); + } + + auto left = binder->addOperand(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand(); + binder->setOperand(bottom, _pad.bottom()); + auto hstride = binder->addOperand(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand(); + binder->setOperand(vstride, _stride.vertical()); + auto multiplier = binder->addOperand(); + binder->setOperand(multiplier, _multiplier); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand(_ofm); + + binder->addOperation( + ann::Operation::Code::DEPTHWISE_CONV_2D, + {ifm, ker, bias, left, right, top, bottom, hstride, vstride, multiplier, fuse}, {ofm}); + } + +private: + enco::SessionID _sess; + +private: + uint32_t _multiplier; + coco::Padding2D _pad; + coco::Stride2D _stride; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::KernelObject *_ker = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNReLUAppender final : public ANNOpAppender +{ +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand(_ifm); + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::RELU, {ifm}, {ofm}); + } + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNReLU6Appender final : public ANNOpAppender +{ +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand(_ifm); + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::RELU6, {ifm}, {ofm}); + } + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNMaxPool2DAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + void window(const coco::Window2D *window) { _window = *window; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand(_ifm); + + // Set padding + auto left = binder->addOperand(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand(); + binder->setOperand(bottom, _pad.bottom()); + + // Set horizontal/vertical stride + auto hstride = binder->addOperand(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand(); + binder->setOperand(vstride, _stride.vertical()); + + // Set receptive field size + auto width = binder->addOperand(); + binder->setOperand(width, _window.width()); + auto height = binder->addOperand(); + binder->setOperand(height, _window.height()); + + // Set fuse code + // TODO Suport operation fusion + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::MAX_POOL_2D, + {ifm, left, right, top, bottom, hstride, vstride, width, height, fuse}, + {ofm}); + } + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + coco::Window2D _window; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNAvgPool2DAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + void stride(const coco::Stride2D *stride) { _stride = *stride; } + void window(const coco::Window2D *window) { _window = *window; } + + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + auto ifm = binder->addOperand(_ifm); + + // Set padding + auto left = binder->addOperand(); + binder->setOperand(left, _pad.left()); + auto right = binder->addOperand(); + binder->setOperand(right, _pad.right()); + auto top = binder->addOperand(); + binder->setOperand(top, _pad.top()); + auto bottom = binder->addOperand(); + binder->setOperand(bottom, _pad.bottom()); + + // Set horizontal/vertical stride + auto hstride = binder->addOperand(); + binder->setOperand(hstride, _stride.horizontal()); + auto vstride = binder->addOperand(); + binder->setOperand(vstride, _stride.vertical()); + + // Set receptive field size + auto width = binder->addOperand(); + binder->setOperand(width, _window.width()); + auto height = binder->addOperand(); + binder->setOperand(height, _window.height()); + + // Set fuse code + // TODO Suport operation fusion + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::AVG_POOL_2D, + {ifm, left, right, top, bottom, hstride, vstride, width, height, fuse}, + {ofm}); + } + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; + coco::Window2D _window; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNPadFAppender final : public ANNOpAppender +{ +public: + void pad(const coco::Padding2D *pad) { _pad = *pad; } + +public: + void ifm(coco::FeatureObject *ifm) { _ifm = ifm; } + void ofm(coco::FeatureObject *ofm) { _ofm = ofm; } + +public: + void append(ANNBinder *binder) const override + { + using nncc::core::ADT::tensor::Shape; + + auto ifm = binder->addOperand(_ifm); + auto pad = binder->addOperand(Shape{4, 2}); + { + std::vector values; + values.resize(8); + // For 'N' + values.at(0) = values.at(1) = 0; + // For 'H' + values.at(2) = _pad.top(); + values.at(3) = _pad.bottom(); + // For 'W' + values.at(4) = _pad.left(); + values.at(5) = _pad.right(); + // For 'C' + values.at(6) = values.at(7) = 0; + + binder->setOperand(pad, values.begin(), values.end()); + } + + auto ofm = binder->addOperand(_ofm); + + binder->addOperation(ann::Operation::Code::PAD, {ifm, pad}, {ofm}); + } + +private: + coco::Padding2D _pad; + +private: + coco::FeatureObject *_ifm = nullptr; + coco::FeatureObject *_ofm = nullptr; +}; + +class ANNOpFunctionalAppender final : public ANNOpAppender +{ +public: + ANNOpFunctionalAppender(const Appender &fun) : _fun{fun} + { + // DO NOTHING + } + +public: + void append(ANNBinder *binder) const { _fun(binder); } + +private: + Appender _fun; +}; + +class ANNSubAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand(_left); + auto right = binder->addOperand(_right); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand(_out); + + binder->addOperation(ann::Operation::Code::SUB, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNDivAppender final : public ANNOpAppender +{ +public: + void left(coco::FeatureObject *o) { _left = o; } + void right(coco::FeatureObject *o) { _right = o; } + void out(coco::FeatureObject *o) { _out = o; } + +public: + void append(ANNBinder *binder) const override + { + auto left = binder->addOperand(_left); + auto right = binder->addOperand(_right); + auto fuse = binder->addOperand(); + binder->setOperand(fuse, 0); + + auto out = binder->addOperand(_out); + + binder->addOperation(ann::Operation::Code::DIV, {left, right, fuse}, {out}); + } + +private: + coco::FeatureObject *_left = nullptr; + coco::FeatureObject *_right = nullptr; + coco::FeatureObject *_out = nullptr; +}; + +class ANNOpBuilder : public coco::Instr::Visitor> +{ +public: + std::unique_ptr visit(const coco::Eval *eval) + { + if (auto conv = eval->op()->asConv2D()) + { + if (auto load = conv->arg()->asLoad()) + { + auto sess = enco::session(eval->module()); + + auto ifm = load->object()->asFeature(); + auto ker = conv->ker(); + auto ofm = eval->out()->asFeature(); + + const auto group = conv->group(); + + if (group == 1) + { + auto app = make_unique(); + + app->session(sess); + + app->pad(conv->pad()); + app->stride(conv->stride()); + + app->ifm(ifm); + app->ofm(ofm); + app->ker(ker); + + return std::move(app); + } + else + { + assert(ifm->shape().depth() == group); + assert(ker->shape().count() % group == 0); + assert(ker->shape().depth() == 1); + + auto app = make_unique(); + + app->session(sess); + + app->multiplier(ker->shape().count() / group); + app->pad(conv->pad()); + app->stride(conv->stride()); + + app->ifm(ifm); + app->ofm(ofm); + app->ker(ker); + + return std::move(app); + } + } + } + else if (auto op = eval->op()->asAdd()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %ofm = eval(Add(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asMul()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %ofm = eval(Mul(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asPadF()) + { + if (auto load = op->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(PadF(Load(%ifm)) + // + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique(); + + app->pad(op->pad()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto maxpool = eval->op()->asMaxPool2D()) + { + if (auto load = maxpool->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(MaxPool2D(Load(%ifm)) + // + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique(); + + app->pad(maxpool->pad()); + app->stride(maxpool->stride()); + app->window(maxpool->window()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto avgpool = eval->op()->asAvgPool2D()) + { + if (auto load = avgpool->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(AvgPool2D(Load(%ifm)) + // + if (avgpool->divisor() == coco::AvgPool2D::Divisor::PaddingExcluded) + { + // When ANN runtime computes the average of each receptive field, + // it uses the number of valid(=non-padding) elements as a divisor. + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique(); + + app->pad(avgpool->pad()); + app->stride(avgpool->stride()); + app->window(avgpool->window()); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + } + else if (auto relu = eval->op()->asReLU()) + { + if (auto load = relu->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ReLU(Load(%ifm)) + // + // TODO Support objects of other kinds, such as Tensor + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique(); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto relu6 = eval->op()->asReLU6()) + { + if (auto load = relu6->arg()->asLoad()) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ReLU6(Load(%ifm)) + // + // TODO Support objects of other kinds, such as Tensor + auto ifm = load->object()->asFeature(); + auto ofm = eval->out()->asFeature(); + + assert(ifm != nullptr && ofm != nullptr); + + auto app = make_unique(); + + app->ifm(ifm); + app->ofm(ofm); + + return std::move(app); + } + } + else if (auto op = eval->op()->asConcatF()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load && (op->axis() == coco::ConcatF::Axis::Depth)) + { + // Let's compile the following code fragment: + // + // %ofm = eval(ConcatF(Depth, Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asSub()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %out = eval(Sub(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + else if (auto op = eval->op()->asDiv()) + { + auto left_load = op->left()->asLoad(); + auto right_load = op->right()->asLoad(); + + if (left_load && right_load) + { + // Let's compile the following code fragment: + // + // %out = eval(Div(Load(%left), Load(%right))) + // + auto left = left_load->object()->asFeature(); + auto right = right_load->object()->asFeature(); + assert(left != nullptr && right != nullptr); + + auto out = eval->out()->asFeature(); + assert(out != nullptr); + + auto app = make_unique(); + + app->left(left); + app->right(right); + app->out(out); + + return std::move(app); + } + } + + // Return nullptr if a given Eval instruction is incompatible + return nullptr; + } + +public: + std::unique_ptr visit(const coco::Shuffle *) { return nullptr; } +}; + +namespace +{ + +std::unique_ptr make_appender(coco::Instr *ins) +{ + ANNOpBuilder op_builder; + + if (auto eval = coco::safe_cast(ins)) + { + return eval->accept(op_builder); + } + + if (auto depth_concat = coco::safe_cast(ins)) + { + auto app = make_unique(); + + app->out(depth_concat->out()->asFeature()); + + app->left(depth_concat->fst()->asFeature()); + app->right(depth_concat->snd()->asFeature()); + + return std::move(app); + } + + // Build ANN IR from ANNConv2D instruction + if (auto conv2d = coco::safe_cast(ins)) + { + auto sess = enco::session(conv2d->module()); + auto app = make_unique(); + + app->session(sess); + + app->pad(conv2d->pad()); + app->stride(conv2d->stride()); + + app->ofm(conv2d->ofm()->asFeature()); + app->ifm(conv2d->ifm()->asFeature()); + app->ker(conv2d->ker()->asKernel()); + app->bias(coco::safe_cast(conv2d->bias())); + + return std::move(app); + } + + return nullptr; +} + +enum Compatibility +{ + COMPATIBLE, + INCOMPATIBLE +}; + +class ANNGroupBuilder +{ +public: + ANNGroupBuilder(ANNContext *ctx) : _ctx{ctx} + { + // DO NOTHING + } + +public: + Compatibility kind(const coco::Block *blk) const; + Compatibility kind(const std::unique_ptr &appender) const; + +public: + void build(enco::Code *code) const; + +private: + ANNContext *_ctx; +}; + +Compatibility ANNGroupBuilder::kind(const std::unique_ptr &app) const +{ + return app ? COMPATIBLE : INCOMPATIBLE; +} + +Compatibility ANNGroupBuilder::kind(const coco::Block *blk) const +{ + return (_ctx->find(blk) != nullptr) ? COMPATIBLE : INCOMPATIBLE; +} + +void ANNGroupBuilder::build(enco::Code *code) const +{ + auto m = code->module(); + + // ANNGroupBuilder will construct a sequence of blocks from the original block sequence, and + // a destination block (that dst_blk points to) is the tail of the generated sequence. + coco::Block *dst_blk = nullptr; + + auto append = [&](const Compatibility &t) { + auto blk = m->entity()->block()->create(); + + if (dst_blk == nullptr) + { + m->block()->prepend(blk); + } + else + { + blk->insertAfter(dst_blk); + } + + dst_blk = blk; + + if (COMPATIBLE == t) + { + _ctx->create(blk); + } + }; + + for (auto blk = m->block()->head(); blk;) + { + // Let's move instructions from a block of interest (referred to as source block) into + // a destination block + auto src_blk = blk; + blk = src_blk->next(); + src_blk->detach(); + + for (auto ins = src_blk->instr()->head(); ins;) + { + auto cur_ins = ins; + ins = cur_ins->next(); + cur_ins->detach(); + + auto cur_append = make_appender(cur_ins); + + // Create a new compatible block and use it as a destination block if the current + // destination block is absent or incompatible with the instruction of intereset. + if ((dst_blk == nullptr) || (kind(cur_append) != kind(dst_blk))) + { + append(kind(cur_append)); + } + + assert(dst_blk != nullptr); + assert(kind(cur_append) == kind(dst_blk)); + + // Append ins to the dst_blk block + dst_blk->instr()->append(cur_ins); + + if (cur_append) + { + // Update Android NN IR if the current instruction is compatible + auto binder = _ctx->find(dst_blk); + assert(binder != nullptr); + cur_append->append(binder); + } + } + + // Destroy the source block + assert(src_blk->instr()->empty()); + m->entity()->block()->destroy(src_blk); + } +} + +} // namespace + +class ANNModuleBuilder +{ +private: + std::set inputs(ANNBinder *binder) const; + std::set outputs(ANNBinder *binder) const; + +public: + void build(ANNContext *ann_ctx) const; +}; + +std::set ANNModuleBuilder::inputs(ANNBinder *binder) const +{ + std::set res; + + for (auto bag : binder->bags()) + { + auto u = enco::updaters(bag); + u.erase(binder->block()); + + /** + * A bag is the input of this block if + * 1. it is an input of the whole network, or + * 2. it is updated by preceding blocks during execution + */ + if (bag->isInput() || (u.size() > 0)) + { + res.insert(bag); + } + } + + return res; +} + +std::set ANNModuleBuilder::outputs(ANNBinder *binder) const +{ + std::set res; + + for (auto bag : binder->bags()) + { + auto u = enco::updaters(bag); + auto r = enco::readers(bag); + r.erase(binder->block()); + + /** + * Only a bag that this block updates can be the output of this block + */ + if (u.find(binder->block()) == u.end()) + { + continue; + } + + /** + * A bag is the output of this block if + * 1. it is an output of the whole network, or + * 2. it is read by following blocks during execution + */ + if (bag->isOutput() || (r.size() > 0)) + { + res.insert(bag); + } + } + + return res; +} + +void ANNModuleBuilder::build(ANNContext *ann_ctx) const +{ + for (uint32_t n = 0; n < ann_ctx->count(); ++n) + { + auto binder = ann_ctx->nth(n); + + // NOTE binder->module() returns an ANN IR module (not coco IR module) + auto m = binder->block()->module(); + auto d = enco::data(m); + + // Let's identify operands with initial values + for (auto bag : binder->bags()) + { + if (binder->associated(bag) && d->allocated(bag)) + { + // TODO Support other datatype + auto span = d->f32()->weight(bag); + assert(span.data() != nullptr); + + binder->setOperand(binder->operand(bag), span.data(), span.data() + span.size()); + } + } + + // Let's identify input/output bags + binder->identifyInputs(inputs(binder)); + binder->identifyOutputs(outputs(binder)); + } +} + +} // namespace + +namespace +{ + +class SplitPass +{ +public: + void runOnCode(enco::Code *code) const; +}; + +void SplitPass::runOnCode(enco::Code *code) const +{ + auto ann_ctx = make_unique(); + + ANNGroupBuilder group_builder{ann_ctx.get()}; + group_builder.build(code); + + ANNModuleBuilder module_builder; + module_builder.build(ann_ctx.get()); + + _subnet_contexts[code->module()] = std::move(ann_ctx); +} + +} // namespace + +namespace enco +{ + +void split_into_phases(enco::Code *code) +{ + SplitPass split; + split.runOnCode(code); +} + +} // namespace enco diff --git a/compiler/enco/core/src/Transforms/Split.h b/compiler/enco/core/src/Transforms/Split.h new file mode 100644 index 00000000000..b4e1d7bafbf --- /dev/null +++ b/compiler/enco/core/src/Transforms/Split.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __SPLIT_H__ +#define __SPLIT_H__ + +#include "Code.h" +#include "Pass.h" + +namespace enco +{ + +struct SubnetManager +{ + static const ANNContext *context(const coco::Module *m); +}; + +/** + * @brief Split instructions into a set of phases + */ +void split_into_phases(enco::Code *code); + +struct PhaseConstructionPass final : public Pass +{ + PASS_CTOR(PhaseConstructionPass) + { + // DO NOTHING + } + + void run(const SessionID &sess) const override { split_into_phases(code(sess)); } +}; + +} // namespace enco; + +#endif // __SPLIT_H__ diff --git a/compiler/enco/core/src/Usage.cpp b/compiler/enco/core/src/Usage.cpp new file mode 100644 index 00000000000..92ccba5a0b0 --- /dev/null +++ b/compiler/enco/core/src/Usage.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Usage.h" + +namespace enco +{ + +std::set readers(const coco::Bag *bag) +{ + std::set res; + + for (auto read : coco::readers(bag)) + { + assert(read != nullptr); + auto instr = read->loc(); + assert(instr != nullptr); + auto block = instr->parent(); + assert(block != nullptr); + + res.insert(block); + } + + return res; +} + +std::set updaters(const coco::Bag *bag) +{ + std::set res; + + for (auto update : coco::updaters(bag)) + { + assert(update != nullptr); + auto instr = update->loc(); + assert(instr != nullptr); + auto block = instr->parent(); + assert(block != nullptr); + + res.insert(block); + } + + return res; +} + +} // namespace enco diff --git a/compiler/enco/core/src/Usage.h b/compiler/enco/core/src/Usage.h new file mode 100644 index 00000000000..8fa05f9b93e --- /dev/null +++ b/compiler/enco/core/src/Usage.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_USAGE_H__ +#define __ENCO_USAGE_H__ + +#include "coco/IR.h" + +#include + +namespace enco +{ + +/// @brief Returns the set of blocks that reads a given bag +std::set readers(const coco::Bag *bag); +/// @brief Return the set of blocks that updates a given bag +std::set updaters(const coco::Bag *bag); + +} // namespace enco + +#endif // __ENCO_USAGE_H__ diff --git a/compiler/enco/core/src/coex/IR.h b/compiler/enco/core/src/coex/IR.h new file mode 100644 index 00000000000..e81943f18be --- /dev/null +++ b/compiler/enco/core/src/coex/IR.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ENCO_COEX_IR_H__ +#define __ENCO_COEX_IR_H__ + +#include + +/** + * @brief 2D Convolution through Andoird NN API + * + * TODO Support FusedActivation + */ +class ANNConv2D : public coco::Instr, public coco::Object::Producer, public coco::Object::Consumer +{ +public: + ANNConv2D() : _ofm{this}, _ifm{this}, _ker{this}, _bias{this} + { + // DO NOTHING + } + +public: + coco::Instr *loc(void) override { return this; } + +public: + coco::Object *ofm(void) const { return _ofm.value(); } + void ofm(coco::Object *o) { _ofm.value(o); } + + coco::Object *ifm(void) const { return _ifm.value(); } + void ifm(coco::Object *o) { _ifm.value(o); } + + coco::Object *ker(void) const { return _ker.value(); } + void ker(coco::Object *o) { _ker.value(o); } + + /** + * Currently, this "bias" is a Feature object with channel-wise layout + * + * NOTE This design is subject to change + */ + coco::Object *bias(void) const { return _bias.value(); } + void bias(coco::Object *o) { _bias.value(o); } + +public: + coco::Padding2D *pad(void) { return &_pad; } + const coco::Padding2D *pad(void) const { return &_pad; } + + coco::Stride2D *stride(void) { return &_stride; } + const coco::Stride2D *stride(void) const { return &_stride; } + +private: + coco::Def _ofm; + + coco::Use _ifm; + coco::Use _ker; + coco::Use _bias; + +private: + coco::Padding2D _pad; + coco::Stride2D _stride; +}; + +/** + * @brief Concatenate feature maps along "depth" dimension through Andoird NN API + */ +class ANNDepthConcatF : public coco::Instr, + public coco::Object::Producer, + public coco::Object::Consumer +{ +public: + ANNDepthConcatF() : _out{this}, _fst{this}, _snd{this} + { + // DO NOTHING + } + +public: + coco::Instr *loc(void) override { return this; } + +public: + coco::Object *out(void) const { return _out.value(); } + void out(coco::Object *o) { _out.value(o); } + + coco::Object *fst(void) const { return _fst.value(); } + void fst(coco::Object *o) { _fst.value(o); } + + coco::Object *snd(void) const { return _snd.value(); } + void snd(coco::Object *o) { _snd.value(o); } + +private: + coco::Def _out; + + // TODO Support variadic-length inputs + coco::Use _fst; + coco::Use _snd; +}; + +#endif // __ENCO_COEX_IR_H__ diff --git a/compiler/enco/core/src/coex/IR.test.cpp b/compiler/enco/core/src/coex/IR.test.cpp new file mode 100644 index 00000000000..e20cbe4fdba --- /dev/null +++ b/compiler/enco/core/src/coex/IR.test.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IR.h" + +#include + +TEST(IRTest, ANNConv2D_default_constructor) +{ + ANNConv2D ins; + + ASSERT_EQ(ins.ofm(), nullptr); + ASSERT_EQ(ins.ifm(), nullptr); + ASSERT_EQ(ins.ker(), nullptr); + ASSERT_EQ(ins.bias(), nullptr); +} + +TEST(IRTest, ANNDepthConcatF_default_constructor) +{ + ANNDepthConcatF ins; + + ASSERT_EQ(ins.out(), nullptr); + ASSERT_EQ(ins.fst(), nullptr); + ASSERT_EQ(ins.snd(), nullptr); +} diff --git a/compiler/enco/frontend/CMakeLists.txt b/compiler/enco/frontend/CMakeLists.txt new file mode 100644 index 00000000000..5ea6cdadd0e --- /dev/null +++ b/compiler/enco/frontend/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectories() diff --git a/compiler/enco/frontend/caffe/CMakeLists.txt b/compiler/enco/frontend/caffe/CMakeLists.txt new file mode 100644 index 00000000000..ce43a41d3b8 --- /dev/null +++ b/compiler/enco/frontend/caffe/CMakeLists.txt @@ -0,0 +1,39 @@ +nnas_find_package(CaffeProto QUIET) + +if(NOT CaffeProto_FOUND) + return() +endif(NOT CaffeProto_FOUND) + +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(enco_caffe_frontend SHARED ${SOURCES}) +target_include_directories(enco_caffe_frontend PRIVATE src) +target_link_libraries(enco_caffe_frontend coco_core) +target_link_libraries(enco_caffe_frontend coco_generic) +target_link_libraries(enco_caffe_frontend enco_intf_frontend) +target_link_libraries(enco_caffe_frontend enco_intf_cmdline) +target_link_libraries(enco_caffe_frontend morph) +target_link_libraries(enco_caffe_frontend caffeproto) +target_link_libraries(enco_caffe_frontend stdex) + +nnas_find_package(GTest QUIET) + +if(NOT GTest_FOUND) + return() +endif(NOT GTest_FOUND) + +nnas_find_package(Caffe QUIET) + +if(NOT Caffe_FOUND) + return() +endif(NOT Caffe_FOUND) + +add_executable(enco_caffe_frontend_test ${TESTS}) +target_include_directories(enco_caffe_frontend_test PRIVATE src) +target_link_libraries(enco_caffe_frontend_test gtest_main) +target_link_libraries(enco_caffe_frontend_test enco_caffe_frontend) +target_link_libraries(enco_caffe_frontend_test morph) +target_link_libraries(enco_caffe_frontend_test caffe) +add_test(enco_caffe_frontend_test enco_caffe_frontend_test) diff --git a/compiler/enco/frontend/caffe/src/ConcatSpec.cpp b/compiler/enco/frontend/caffe/src/ConcatSpec.cpp new file mode 100644 index 00000000000..b83a1f902ab --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConcatSpec.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConcatSpec.h" + +#include + +using namespace nncc::core::ADT::tensor; + +nncc::core::ADT::tensor::Shape ConcatSpec::forward(const ShapeList &inputs) const +{ + assert(inputs.size() > 0); + + Shape output_shape = inputs.at(0); + + for (uint32_t n = 1; n < inputs.size(); ++n) + { + // The current implementation assumes that "inputs" is well-formed + // TODO Verify whether "inputs" is really well-formed + const auto &input_shape = inputs.at(n); + output_shape.dim(_axis) += input_shape.dim(_axis); + } + + return output_shape; +} + +ConcatSpec concat_spec(uint32_t axis) { return ConcatSpec{axis}; } diff --git a/compiler/enco/frontend/caffe/src/ConcatSpec.h b/compiler/enco/frontend/caffe/src/ConcatSpec.h new file mode 100644 index 00000000000..cc636c77816 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConcatSpec.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONCAT_SPEC_H__ +#define __CONCAT_SPEC_H__ + +#include + +#include + +using ShapeList = std::vector; + +class ConcatSpec +{ +public: + explicit ConcatSpec(uint32_t axis) : _axis{axis} + { + // DO NOTHING + } + +public: + /** + * @brief Return the output shape when inputs of given shape are + * concatenated along _axis + */ + nncc::core::ADT::tensor::Shape forward(const ShapeList &) const; + +private: + uint32_t _axis; +}; + +ConcatSpec concat_spec(uint32_t axis); + +#endif // __CONCAT_SPEC_H__ diff --git a/compiler/enco/frontend/caffe/src/ConcatSpec.test.cpp b/compiler/enco/frontend/caffe/src/ConcatSpec.test.cpp new file mode 100644 index 00000000000..1cb2ea5af50 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConcatSpec.test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConcatSpec.h" + +#include + +using nncc::core::ADT::tensor::Shape; + +namespace +{ +class ConcatSpecTest : public ::testing::Test +{ + // FOR FUTURE USE +}; +} // namespace + +TEST_F(ConcatSpecTest, ifm_shape) +{ + const Shape in_1{1, 1, 4, 4}; + const Shape in_2{1, 2, 4, 4}; + const Shape in_3{1, 3, 4, 4}; + const Shape in_4{1, 4, 4, 4}; + + auto expected = Shape{1, 10, 4, 4}; + auto obtained = concat_spec(1).forward({in_1, in_2, in_3, in_4}); + + ASSERT_EQ(expected, obtained); +} diff --git a/compiler/enco/frontend/caffe/src/Context.cpp b/compiler/enco/frontend/caffe/src/Context.cpp new file mode 100644 index 00000000000..9f7204b257c --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Context.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @note: This cpp file exist to check compilation integrity + */ + +#include "Context.h" diff --git a/compiler/enco/frontend/caffe/src/Context.h b/compiler/enco/frontend/caffe/src/Context.h new file mode 100644 index 00000000000..aca57ce6f9c --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Context.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONTEXT_H__ +#define __CONTEXT_H__ + +#include + +#include +#include + +#include +#include +#include + +namespace caffeimport +{ + +using LayerName = std::string; +using BlobName = std::string; +// Note: these two maybe evolved to a class +using ShapeContext = std::map; +using StoreContext = std::map; + +class WeightContext +{ +public: + WeightContext(::caffe::NetParameter *caffemodel) : _caffemodel(caffemodel) + { + for (uint32_t n = 0; n < _caffemodel->layer_size(); ++n) + { + auto layer = _caffemodel->mutable_layer(n); + + if (layer->has_name()) + { + _data[layer->name()] = layer; + } + } + } + +public: + int blob_count(const LayerName &name) + { + if (_data.find(name) != _data.end()) + return _data.at(name)->blobs_size(); + + assert(false); + return 0; + } + + ::caffe::BlobProto *blob_get(const LayerName &name, uint32_t n) + { + if (_data.find(name) != _data.end()) + return _data.at(name)->mutable_blobs(n); + + assert(false); + return nullptr; + }; + +private: + ::caffe::NetParameter *_caffemodel; + std::map _data; +}; + +class GraphBuilderContext +{ +public: + explicit GraphBuilderContext(coco::Module *module, coco::Data *data, coco::Block *block, + ShapeContext &shape_ctx, StoreContext &bag_ctx, + WeightContext &weight_ctx) + : _module(module), _data(data), _block(block), _shape_ctx(shape_ctx), _bag_ctx(bag_ctx), + _weight_ctx(weight_ctx) + { + // DO NOTHING + } + + GraphBuilderContext(const GraphBuilderContext &) = delete; + GraphBuilderContext(GraphBuilderContext &&) = delete; + +public: + coco::Module *module() { return _module; } + coco::Data *data() { return _data; } + coco::Block *block() { return _block; } + ShapeContext &shape_ctx() { return _shape_ctx; } + StoreContext &bag_ctx() { return _bag_ctx; } + WeightContext &weight_ctx() { return _weight_ctx; } + +private: + coco::Module *_module; + coco::Data *_data; + coco::Block *_block; + ShapeContext &_shape_ctx; + StoreContext &_bag_ctx; + WeightContext &_weight_ctx; +}; + +} // namespace caffeimport + +#endif // __CONTEXT_H__ diff --git a/compiler/enco/frontend/caffe/src/Convert.cpp b/compiler/enco/frontend/caffe/src/Convert.cpp new file mode 100644 index 00000000000..d697b1bd8a4 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Convert.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Convert.h" + +using namespace nncc::core::ADT; + +namespace caffeimport +{ + +tensor::Shape as_tensor_shape(const ::caffe::BlobShape &blob_shape) +{ + const uint32_t rank = blob_shape.dim_size(); + + tensor::Shape res; + + res.resize(rank); + + for (uint32_t axis = 0; axis < rank; ++axis) + { + res.dim(axis) = blob_shape.dim(axis); + } + + return res; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Convert.h b/compiler/enco/frontend/caffe/src/Convert.h new file mode 100644 index 00000000000..9f6f9f104b7 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Convert.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + +#include + +#include + +namespace caffeimport +{ + +nncc::core::ADT::tensor::Shape as_tensor_shape(const ::caffe::BlobShape &blob_shape); + +inline nncc::core::ADT::tensor::Shape as_tensor_shape(const ::caffe::BlobProto *blob_proto) +{ + return as_tensor_shape(blob_proto->shape()); +} + +} // namespace caffeimport + +#endif // __CONVERT_H__ diff --git a/compiler/enco/frontend/caffe/src/ConvolutionSpec.cpp b/compiler/enco/frontend/caffe/src/ConvolutionSpec.cpp new file mode 100644 index 00000000000..e13ada836e0 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConvolutionSpec.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConvolutionSpec.h" +#include "PaddingUtils.h" +#include "ShapeQuery.h" + +#include + +ConvolutionSpec::ConvolutionSpec(const ::caffe::ConvolutionParameter ¶m) : _param(param) +{ + // NOTE Dilation is not supported, yet + // TODO Support dilation + assert(param.dilation().size() == 0); +} + +uint32_t ConvolutionSpec::group(void) const { return _param.group(); } + +uint32_t ConvolutionSpec::channel_axis(void) const +{ + return query_on(ifm_shape()).axis(axis_specifier(_param.axis())); +} + +uint32_t ConvolutionSpec::pad(uint32_t spatial_axis) const +{ + assert(spatial_axis < num_spatial_axes()); + + auto raw_padding = build_raw_padding().with(_param); + auto spatial_padding = build_spatial_padding(num_spatial_axes()).with(raw_padding); + + return spatial_padding.value(spatial_axis); +} + +uint32_t ConvolutionSpec::stride(uint32_t spatial_axis) const +{ + assert(spatial_axis < num_spatial_axes()); + + // TODO Support stride_h/stride_w parameters + assert(!_param.has_stride_h()); + assert(!_param.has_stride_w()); + + if (_param.stride().size() == 0) + { + // NOTE default stride is 1 + return 1; + } + + if (_param.stride().size() == 1) + { + return _param.stride(0); + } + + assert(_param.stride().size() == num_spatial_axes()); + return _param.stride(spatial_axis); +} + +uint32_t ConvolutionSpec::ker_dim(uint32_t spatial_axis) const +{ + assert(spatial_axis < num_spatial_axes()); + if (_param.kernel_size().size() == 0) + { + if (_param.has_kernel_h() && (spatial_axis == 0)) + { + assert(num_spatial_axes() == 2); + return _param.kernel_h(); + } + + if (_param.has_kernel_w() && (spatial_axis == 1)) + { + assert(num_spatial_axes() == 2); + return _param.kernel_w(); + } + + return 0; + } + + assert(!_param.has_kernel_h()); + assert(!_param.has_kernel_w()); + if (_param.kernel_size().size() == 1) + { + return _param.kernel_size(0); + } + else + { + assert(_param.kernel_size().size() == num_spatial_axes()); + return _param.kernel_size(spatial_axis); + } +} + +nncc::core::ADT::tensor::Shape ConvolutionSpec::ker_shape(void) const +{ + nncc::core::ADT::tensor::Shape res; + + res.resize(2 + num_spatial_axes()); + + res.dim(0) = ker_count(); + assert(ifm_dim(channel_axis()) % group() == 0); + res.dim(1) = ifm_dim(channel_axis()) / group(); + for (uint32_t axis = 0; axis < num_spatial_axes(); ++axis) + { + res.dim(2 + axis) = ker_dim(axis); + } + + return res; +} + +nncc::core::ADT::tensor::Shape ConvolutionSpec::ofm_shape(void) const +{ + nncc::core::ADT::tensor::Shape res; + + res.resize(num_batch_axes() + 1 + num_spatial_axes()); + + for (uint32_t axis = 0; axis < num_batch_axes(); ++axis) + { + res.dim(axis) = ifm_dim(axis); + } + + res.dim(num_batch_axes()) = ker_count(); + + for (uint32_t spatial_axis = 0; spatial_axis < num_spatial_axes(); ++spatial_axis) + { + const uint32_t full_axis = num_batch_axes() + 1 + spatial_axis; + + uint32_t dim = 0; + + dim += ifm_dim(full_axis) - ker_dim(spatial_axis) + 2 * pad(spatial_axis); + dim /= stride(spatial_axis); + dim += 1; + + res.dim(full_axis) = dim; + } + + return res; +} diff --git a/compiler/enco/frontend/caffe/src/ConvolutionSpec.h b/compiler/enco/frontend/caffe/src/ConvolutionSpec.h new file mode 100644 index 00000000000..c5c7c9024fd --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConvolutionSpec.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVOLUTION_SPEC_H__ +#define __CONVOLUTION_SPEC_H__ + +#include + +#include + +class ConvolutionSpec +{ +public: + ConvolutionSpec(const ::caffe::ConvolutionParameter ¶m); + +public: + uint32_t ifm_rank(void) const { return _ifm_shape.rank(); } + uint32_t ifm_dim(uint32_t axis) const { return _ifm_shape.dim(axis); } + + uint32_t group(void) const; + + uint32_t channel_axis(void) const; + + uint32_t num_batch_axes(void) const { return channel_axis(); } + uint32_t num_spatial_axes(void) const { return ifm_rank() - channel_axis() - 1; } + + uint32_t pad(uint32_t spatial_axis) const; + uint32_t stride(uint32_t spatial_axis) const; + uint32_t ker_dim(uint32_t spatial_axis) const; + +public: + const nncc::core::ADT::tensor::Shape &ifm_shape(void) const { return _ifm_shape; } + void ifm_shape(const nncc::core::ADT::tensor::Shape &shape) { _ifm_shape = shape; } + +public: + uint32_t ker_count(void) const { return _param.num_output(); } + nncc::core::ADT::tensor::Shape ker_shape(void) const; + +public: + nncc::core::ADT::tensor::Shape ofm_shape(void) const; + +private: + const ::caffe::ConvolutionParameter &_param; + nncc::core::ADT::tensor::Shape _ifm_shape; +}; +#endif // __CONVOLUTION_SPEC_H__ diff --git a/compiler/enco/frontend/caffe/src/ConvolutionSpec.test.cpp b/compiler/enco/frontend/caffe/src/ConvolutionSpec.test.cpp new file mode 100644 index 00000000000..02670b0ccfe --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ConvolutionSpec.test.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConvolutionSpec.h" +#include "Importer.h" + +#include + +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; + +#define STRING(content) #content + +namespace +{ +class ConvolutionSpecTest : public ::testing::Test +{ +protected: + tensor::Shape as_tensor_shape(const std::vector &dims) + { + const uint32_t rank = dims.size(); + + tensor::Shape res; + + res.resize(rank); + + for (uint32_t axis = 0; axis < rank; ++axis) + { + res.dim(axis) = dims.at(axis); + } + + return res; + } + + bool load(const std::string &prototxt, ::caffe::NetParameter ¶m) + { + std::stringstream ss{prototxt}; + + return from_txt(ss, param); + } +}; +} // namespace + +TEST_F(ConvolutionSpecTest, ifm_shape) +{ + ::caffe::ConvolutionParameter param; + ConvolutionSpec spec{param}; + + const tensor::Shape ifm_shape{1, 3, 244, 244}; + + spec.ifm_shape(ifm_shape); + + ASSERT_EQ(spec.ifm_shape(), ifm_shape); + ASSERT_EQ(spec.num_batch_axes(), 1); + ASSERT_EQ(spec.num_spatial_axes(), 2); +} + +namespace +{ +// clang-format off +const char *conv_0 = STRING( +layer { + name: "data" + type : "Input" + top : "data" + input_param { shape: { dim: 1 dim : 3 dim : 244 dim : 244 } } +} +layer{ + name : "conv" + type : "Convolution" + bottom : "data" + top : "conv" + convolution_param { + bias_term : false + num_output : 1 + kernel_size : 1 + } +}); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, conv_0) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(conv_0, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 244, 244}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'ker_shape' + { + auto expected = as_tensor_shape(net.layer_by_name("conv")->blobs().at(0)->shape()); + auto obtained = spec.ker_shape(); + + ASSERT_EQ(expected, obtained); + } + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +namespace +{ +// clang-format off +const char *conv_1 = STRING( +layer { + name: "data" + type : "Input" + top : "data" + input_param { shape: { dim: 1 dim : 3 dim : 244 dim : 244 } } +} +layer{ + name : "conv" + type : "Convolution" + bottom : "data" + top : "conv" + convolution_param { + bias_term : false + num_output : 1 + kernel_size : 1 + kernel_size : 3 + } +}); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, conv_1) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(conv_1, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 244, 244}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'ker_shape' + { + auto expected = as_tensor_shape(net.layer_by_name("conv")->blobs().at(0)->shape()); + auto obtained = spec.ker_shape(); + + ASSERT_EQ(expected, obtained); + } + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +namespace +{ +// NOTE This example is derived from conv1_3x3_s2 layer in reference inception v3 layer +// clang-format off +const char *conv_2 = STRING( +layer { + name: "data" + type: "Input" + top: "data" + input_param { + shape: { dim: 1 dim: 3 dim: 299 dim: 299 } + } +} +layer { + name: "conv" + type: "Convolution" + bottom: "data" + top: "conv" + convolution_param { + bias_term: false + num_output: 2 + stride: 2 + kernel_size: 3 + } +} +); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, conv_2) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(conv_2, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 299, 299}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'stride' + ASSERT_EQ(spec.stride(0), 2); + ASSERT_EQ(spec.stride(1), 2); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +namespace +{ +// clang-format off +const char *conv_pad = STRING( +layer { + name: "data" + type: "Input" + top: "data" + input_param { + shape: { dim: 1 dim: 3 dim: 16 dim: 16 } + } +} +layer { + name: "conv" + type: "Convolution" + bottom: "data" + top: "conv" + convolution_param { + bias_term: false + num_output: 2 + pad: 2 + kernel_size: 3 + } +} +); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, conv_pad) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(conv_pad, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 16, 16}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'pad' + ASSERT_EQ(spec.pad(0), 2); + ASSERT_EQ(spec.pad(1), 2); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +namespace +{ +// clang-format off +const char *conv_ker_hw = STRING( +layer { + name: "data" + type: "Input" + top: "data" + input_param { + shape: { dim: 1 dim: 3 dim: 16 dim: 16 } + } +} +layer { + name: "conv" + type: "Convolution" + bottom: "data" + top: "conv" + convolution_param { + bias_term: false + num_output: 2 + kernel_h: 3 + kernel_w: 1 + } +} +); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, conv_ker_hw) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(conv_ker_hw, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 16, 16}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'pad' + ASSERT_EQ(spec.ker_dim(0), 3); + ASSERT_EQ(spec.ker_dim(1), 1); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +namespace +{ +// clang-format off +const char *dconv = STRING( +layer { + name: "data" + type: "Input" + top: "data" + input_param { + shape: { dim: 1 dim: 3 dim: 16 dim: 16 } + } +} +layer { + name: "conv" + type: "Convolution" + bottom: "data" + top: "conv" + convolution_param { + bias_term: false + num_output: 3 + kernel_size: 3 + group: 3 + } +} +); +// clang-format on +} // namespace + +TEST_F(ConvolutionSpecTest, dconv) +{ + ::caffe::NetParameter param; + + ASSERT_TRUE(load(dconv, param)); + + ::caffe::Net net{param}; + + const tensor::Shape ifm_shape{1, 3, 16, 16}; + ConvolutionSpec spec{param.layer(1).convolution_param()}; + + spec.ifm_shape(ifm_shape); + + // Check 'ker_shape' + { + auto expected = as_tensor_shape(net.layer_by_name("conv")->blobs().at(0)->shape()); + auto obtained = spec.ker_shape(); + + ASSERT_EQ(expected, obtained); + } + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("conv")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} diff --git a/compiler/enco/frontend/caffe/src/Entry.cpp b/compiler/enco/frontend/caffe/src/Entry.cpp new file mode 100644 index 00000000000..2bdb73eac48 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Entry.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Frontend.h" +#include "Importer.h" + +#include + +#include + +#include +#include + +extern "C" std::unique_ptr make_frontend(const cmdline::View &cmdline) +{ + assert(cmdline.size() == 2); + + auto frontend = stdex::make_unique(); + + // Fill prototxt + { + std::ifstream ifs{cmdline.at(0)}; + if (!ifs.is_open()) + { + throw std::runtime_error("Prototxt file open fail"); + } + + if (!from_txt(ifs, *frontend->prototxt())) + { + throw std::runtime_error("Filling prototxt fail"); + } + } + + // Fill caffemodel + { + std::ifstream ifs{cmdline.at(1), std::ios::binary}; + if (!ifs.is_open()) + { + throw std::runtime_error("Caffemodel file open fail"); + } + + if (!from_bin(ifs, *frontend->caffemodel())) + { + throw std::runtime_error("Filling caffemodel fail"); + } + } + + return std::move(frontend); +} diff --git a/compiler/enco/frontend/caffe/src/Frontend.cpp b/compiler/enco/frontend/caffe/src/Frontend.cpp new file mode 100644 index 00000000000..7d2b3d36cb1 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Frontend.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Frontend.h" +#include "Context.h" +#include "GraphBuilderRegistry.h" + +#include +#include + +#include +#include +#include + +#include +#include + +using namespace nncc::core::ADT; + +using tensor::LexicalLayout; + +Frontend::Frontend() : _prototxt{new ::caffe::NetParameter}, _caffemodel{new ::caffe::NetParameter} +{ + // DO NOTHING +} + +enco::Bundle Frontend::load(void) const +{ + auto module = coco::Module::create(); + auto blk = module->entity()->block()->create(); + module->block()->append(blk); + + auto data = coco::Data::create(); + + // For weight access + caffeimport::WeightContext weight_ctx(_caffemodel.get()); + + // For inter-layer communication + std::map shape_ctx; + std::map bag_ctx; + + std::set bags; + std::map def_count; + std::map use_count; + + auto def = [&bags, &def_count, &use_count](const std::string &name) { + if (bags.find(name) == bags.end()) + { + bags.insert(name); + def_count[name] = 0; + use_count[name] = 0; + } + + def_count.at(name) += 1; + }; + + auto use = [&use_count](const std::string &name) { use_count.at(name) += 1; }; + + auto outputs = [&bags, &def_count, &use_count](void) { + std::set res; + + for (const auto &bag : bags) + { + if (def_count.at(bag) > use_count.at(bag)) + { + res.insert(bag); + } + } + + return res; + }; + + caffeimport::GraphBuilderContext opbuilder_context(module.get(), data.get(), blk, shape_ctx, + bag_ctx, weight_ctx); + + for (const auto &layer : _prototxt->layer()) + { + assert(layer.has_name()); + assert(layer.has_type()); + + for (uint32_t n = 0; n < layer.top().size(); ++n) + { + def(layer.top(n)); + } + + for (uint32_t n = 0; n < layer.bottom().size(); ++n) + { + use(layer.bottom(n)); + } + + if (const auto *graph_builder = caffeimport::GraphBuilderRegistry::get().lookup(layer.type())) + { + graph_builder->build(layer, &opbuilder_context); + } + else + { + throw std::runtime_error{"Not supported: " + layer.type()}; + } + } + + // Finalize: Create output for each top blob + for (const auto &name : outputs()) + { + const auto &shape = shape_ctx.at(name); + auto bag = bag_ctx.at(name); + + auto output = module->entity()->output()->create(shape); + + output->bag(bag); + output->name(name); + output->reorder(); + + module->output()->insert(output); + } + + enco::Bundle bundle; + + bundle.module(std::move(module)); + bundle.data(std::move(data)); + + return std::move(bundle); +} diff --git a/compiler/enco/frontend/caffe/src/Frontend.h b/compiler/enco/frontend/caffe/src/Frontend.h new file mode 100644 index 00000000000..34fe90eba23 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Frontend.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FRONTEND_H__ +#define __FRONTEND_H__ + +#include + +#include + +#include + +class Frontend final : public enco::Frontend +{ +public: + Frontend(); + +public: + ::caffe::NetParameter *prototxt(void) { return _prototxt.get(); } + ::caffe::NetParameter *caffemodel(void) { return _caffemodel.get(); } + +public: + enco::Bundle load(void) const override; + +private: + std::unique_ptr<::caffe::NetParameter> _prototxt; + std::unique_ptr<::caffe::NetParameter> _caffemodel; +}; + +#endif // __FRONTEND_H__ diff --git a/compiler/enco/frontend/caffe/src/GraphBuilder.cpp b/compiler/enco/frontend/caffe/src/GraphBuilder.cpp new file mode 100644 index 00000000000..18ba10c0894 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/GraphBuilder.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @note: This cpp file exist to check compilation integrity + */ + +#include "GraphBuilder.h" diff --git a/compiler/enco/frontend/caffe/src/GraphBuilder.h b/compiler/enco/frontend/caffe/src/GraphBuilder.h new file mode 100644 index 00000000000..04adb96f41a --- /dev/null +++ b/compiler/enco/frontend/caffe/src/GraphBuilder.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BUILDER_H__ +#define __GRAPH_BUILDER_H__ + +#include "Context.h" + +#include + +namespace caffeimport +{ + +class GraphBuilder +{ +public: + virtual void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const = 0; + virtual ~GraphBuilder() {} +}; + +} // namespace caffeimport + +#endif // __GRAPH_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.cpp b/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.cpp new file mode 100644 index 00000000000..e9db3117716 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "GraphBuilderRegistry.h" + +#include "Layer/Concatenation.h" +#include "Layer/Convolution.h" +#include "Layer/Eltwise.h" +#include "Layer/Input.h" +#include "Layer/Pooling.h" +#include "Layer/ReLU.h" +#include "Layer/Scale.h" +#include "Layer/BatchNorm.h" + +#include + +using stdex::make_unique; + +namespace caffeimport +{ + +GraphBuilderRegistry::GraphBuilderRegistry() +{ + _builder_map["Concat"] = make_unique(); + _builder_map["Convolution"] = make_unique(); + _builder_map["Eltwise"] = make_unique(); + _builder_map["Input"] = make_unique(); + _builder_map["Pooling"] = make_unique(); + _builder_map["ReLU"] = make_unique(); + _builder_map["Scale"] = make_unique(); + _builder_map["BatchNorm"] = make_unique(); +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.h b/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.h new file mode 100644 index 00000000000..035d32a4b2f --- /dev/null +++ b/compiler/enco/frontend/caffe/src/GraphBuilderRegistry.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BUILDER_REGISTRY_H__ +#define __GRAPH_BUILDER_REGISTRY_H__ + +#include "GraphBuilder.h" + +#include +#include + +namespace caffeimport +{ + +class GraphBuilderRegistry +{ +public: + const GraphBuilder *lookup(const std::string &layer) const + { + if (_builder_map.find(layer) == _builder_map.end()) + return nullptr; + + return _builder_map.at(layer).get(); + } + + static GraphBuilderRegistry &get() + { + static GraphBuilderRegistry me; + return me; + } + +private: + GraphBuilderRegistry(); + +private: + std::map> _builder_map; +}; + +} // namespace caffeimport + +#endif // __GRAPH_BUILDER_REGISTRY_H__ diff --git a/compiler/enco/frontend/caffe/src/IRBuilder.h b/compiler/enco/frontend/caffe/src/IRBuilder.h new file mode 100644 index 00000000000..fe34328af80 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/IRBuilder.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __IR_BUILDER_H__ +#define __IR_BUILDER_H__ + +#include "coco/IR/Module.h" + +#include + +/** + * coco IR builders + */ + +class OpBuilder +{ +public: + OpBuilder(coco::Module *module) : _module{module} + { + // module SHOULD BE valid + assert(_module != nullptr); + } + +public: + /** + * @brief Return true if the internal stack is empty + */ + bool empty(void) const { return _stack.empty(); } + + /** + * @brief Return the operation at the top of the internal stack + */ + coco::Op *top(void) const + { + assert(_stack.size() > 0); + return _stack.front(); + } + + /** + * @brief Push op onto the internal stack + * + * BEFORE| Stack + * AFTER | Op; Stack + */ + OpBuilder &push(coco::Op *op) + { + _stack.push_front(op); + return (*this); + } + + /** + * @brief Create "Load" op and push it onto the internal stack + * + * BEFORE| Stack + * AFTER | Load(obj); Stack + */ + OpBuilder &load(coco::Object *obj) + { + auto op = _module->entity()->op()->create(); + op->object(obj); + push(op); + return (*this); + } + + /** + * @brief Create "Add" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Add(Left, Right); Stack + */ + OpBuilder &add(void) { return binary(); } + + /** + * @brief Create "Sub" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Sub(Left, Right); Stack + */ + OpBuilder &sub(void) { return binary(); } + + /** + * @brief Create "Mul" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Mul(Left, Right); Stack + */ + OpBuilder &mul(void) { return binary(); } + + /** + * @brief Create "Div" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Div(Left, Right); Stack + */ + OpBuilder &div(void) { return binary(); } + + /** + * @brief Pop op from the internal stack + * + * BEFORE| Op; Stack + * AFTER | Stack + */ + coco::Op *pop(void) + { + assert(_stack.size() > 0); + auto op = _stack.front(); + _stack.pop_front(); + return op; + } + +private: + template OpBuilder &binary() + { + assert(_stack.size() >= 2); + auto left = pop(); + auto right = pop(); + + auto op = _module->entity()->op()->create(); + op->left(left); + op->right(right); + push(op); + + return (*this); + } + +private: + coco::Module *_module; + std::deque _stack; +}; + +inline OpBuilder op_builder(coco::Module *m) { return OpBuilder{m}; } +inline OpBuilder op_builder(const std::unique_ptr &m) { return op_builder(m.get()); } + +class InstrBuilder +{ +public: + InstrBuilder(coco::Module *module) : _module{module} + { + // NOTE _module SHOULD be valid + assert(_module != nullptr); + } + +public: + /** + * @brief Create "Eval" instruction with a given "Object" and "Op" + * + * @note "eval(out, op)" will create "%out <- Eval(op)" instruction + */ + coco::Eval *eval(coco::Object *out, coco::Op *op) const + { + auto ins = _module->entity()->instr()->create(); + ins->op(op); + ins->out(out); + return ins; + } + +private: + coco::Module *_module; +}; + +inline InstrBuilder instr_builder(coco::Module *m) { return InstrBuilder{m}; } +inline InstrBuilder instr_builder(const std::unique_ptr &m) +{ + return instr_builder(m.get()); +} + +#endif // __IR_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Importer.cpp b/compiler/enco/frontend/caffe/src/Importer.cpp new file mode 100644 index 00000000000..943a54e5d53 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Importer.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Importer.h" + +#include +#include +#include + +bool from_txt(std::istream &is, ::caffe::NetParameter ¶m) +{ + google::protobuf::io::IstreamInputStream iis{&is}; + + if (!google::protobuf::TextFormat::Parse(&iis, ¶m)) + { + return false; + } + + return true; +} + +bool from_bin(std::istream &is, ::caffe::NetParameter ¶m) +{ + google::protobuf::io::IstreamInputStream iis{&is}; + google::protobuf::io::CodedInputStream cis{&iis}; + + if (!param.ParseFromCodedStream(&cis)) + { + return false; + } + + return true; +} + +bool from_txt(std::istream &is, ::caffe::PoolingParameter ¶m) +{ + ::google::protobuf::io::IstreamInputStream iis{&is}; + return google::protobuf::TextFormat::Parse(&iis, ¶m); +} diff --git a/compiler/enco/frontend/caffe/src/Importer.h b/compiler/enco/frontend/caffe/src/Importer.h new file mode 100644 index 00000000000..ac83c0b2785 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Importer.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __IMPORTER_H__ +#define __IMPORTER_H__ + +#include + +#include + +bool from_txt(std::istream &is, ::caffe::NetParameter ¶m); +bool from_bin(std::istream &is, ::caffe::NetParameter ¶m); + +bool from_txt(std::istream &is, ::caffe::PoolingParameter ¶m); + +#endif // __IMPORTER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/BatchNorm.cpp b/compiler/enco/frontend/caffe/src/Layer/BatchNorm.cpp new file mode 100644 index 00000000000..ff1e86570cd --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/BatchNorm.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "BatchNorm.h" +#include "IRBuilder.h" + +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +using tensor::num_elements; + +namespace caffeimport +{ + +void BatchNormBuilder::build(const ::caffe::LayerParameter &layer, + GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Data *data = context->data(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + WeightContext &weight_ctx = context->weight_ctx(); + + assert(layer.bottom().size() == 1); + assert(layer.top().size() == 1); + + assert(layer.has_batch_norm_param()); + const auto ¶m = layer.batch_norm_param(); + + // TODO Support training case + assert(param.use_global_stats() == true); + + // Create an object for an input feature map + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + auto ifm_bag = bag_ctx.at(ifm_name); + auto ifm_obj = module->entity()->object()->create(); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + const auto ofm_name = layer.top(0); + const auto ofm_shape = ifm_shape; + auto ofm_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto ofm_obj = module->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + // Create an object for the scaled mean estimates data + auto mean_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto mean_obj = module->entity()->object()->create(); + + mean_obj->bag(mean_bag); + mean_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Create an object for the scaled variance estimates data + auto variance_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto variance_obj = module->entity()->object()->create(); + + variance_obj->bag(variance_bag); + variance_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + if (param.use_global_stats()) + { + // Use the stored mean/variance estimates. + assert(weight_ctx.blob_count(layer.name()) == 3); + + // Create an object for scale factor data + auto factor_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto factor_obj = module->entity()->object()->create(); + + factor_obj->bag(factor_bag); + factor_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Fill "scale factor" data + { + data->f32()->allocate(factor_bag); + + auto dst = data->f32()->weight(factor_bag); + // Calculate scale factor + auto blob = weight_ctx.blob_get(layer.name(), 2); + const auto scale_factor = blob->data(0) == 0 ? 0.f : 1 / blob->data(0); + + for (uint32_t ch = 0; ch < factor_obj->shape().depth(); ++ch) + { + dst[ch] = scale_factor; + } + } + + // Create an object for saved mean data + auto saved_mean_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto saved_mean_obj = module->entity()->object()->create(); + + saved_mean_obj->bag(saved_mean_bag); + saved_mean_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Fill "saved mean estimates" data + { + data->f32()->allocate(saved_mean_bag); + + auto dst = data->f32()->weight(saved_mean_bag); + auto blob = weight_ctx.blob_get(layer.name(), 0); + + for (uint32_t ch = 0; ch < saved_mean_obj->shape().depth(); ++ch) + { + dst[ch] = blob->data(ch); + } + } + + // Multiply scale factor to mean data + { + auto mul_op = op_builder(module).load(factor_obj).load(saved_mean_obj).mul().pop(); + auto mul_ins = instr_builder(module).eval(mean_obj, mul_op); + + blk->instr()->append(mul_ins); + } + + // Create an object for saved variance data + auto saved_variance_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto saved_variance_obj = module->entity()->object()->create(); + + saved_variance_obj->bag(saved_variance_bag); + saved_variance_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Fill "saved variance estimates" data + { + data->f32()->allocate(saved_variance_bag); + + auto dst = data->f32()->weight(saved_variance_bag); + auto blob = weight_ctx.blob_get(layer.name(), 1); + + for (uint32_t ch = 0; ch < saved_variance_obj->shape().depth(); ++ch) + { + dst[ch] = blob->data(ch); + } + } + + // Multiply scale factor to variance data + { + auto mul_op = op_builder(module).load(factor_obj).load(saved_variance_obj).mul().pop(); + auto mul_ins = instr_builder(module).eval(variance_obj, mul_op); + + blk->instr()->append(mul_ins); + } + } + else + { + // TODO use_global_stats() == false case + } + + // Create an object for subtraction + auto sub_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto sub_obj = module->entity()->object()->create(); + + sub_obj->bag(sub_bag); + sub_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + // Subtract mean + { + auto sub_op = op_builder(module).load(mean_obj).load(ifm_obj).sub().pop(); + auto sub_ins = instr_builder(module).eval(sub_obj, sub_op); + + blk->instr()->append(sub_ins); + } + + // Create an object for normalize variance data + auto norm_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto norm_obj = module->entity()->object()->create(); + + norm_obj->bag(norm_bag); + norm_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Normalize variance + { + // Create an object for epsilon data + auto eps_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto eps_obj = module->entity()->object()->create(); + + eps_obj->bag(eps_bag); + eps_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Fill "epsilon" data + { + data->f32()->allocate(eps_bag); + + auto dst = data->f32()->weight(eps_bag); + auto eps = param.eps(); + + for (uint32_t ch = 0; ch < eps_obj->shape().depth(); ++ch) + { + dst[ch] = eps; + } + } + + // Create a temp object + auto temp_bag = module->entity()->bag()->create(ofm_shape.dim(1)); + auto temp_obj = module->entity()->object()->create(); + + temp_obj->bag(temp_bag); + temp_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + // Add epsilon to variance + { + auto add_op = op_builder(module).load(variance_obj).load(eps_obj).add().pop(); + auto add_ins = instr_builder(module).eval(temp_obj, add_op); + + blk->instr()->append(add_ins); + } + + // Sqrt variance + { + auto load = op_builder(module).load(temp_obj).pop(); + auto sqrt_op = module->entity()->op()->create(); + sqrt_op->arg(load); + auto sqrt_ins = instr_builder(module).eval(norm_obj, sqrt_op); + + blk->instr()->append(sqrt_ins); + } + } + + // Replicate variance to input size + { + auto div_op = op_builder(module).load(norm_obj).load(sub_obj).div().pop(); + auto div_ins = instr_builder(module).eval(ofm_obj, div_op); + + blk->instr()->append(div_ins); + } + + // Update bag and shape context + bag_ctx[ofm_name] = ofm_bag; + shape_ctx[ofm_name] = ofm_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/BatchNorm.h b/compiler/enco/frontend/caffe/src/Layer/BatchNorm.h new file mode 100644 index 00000000000..613b6687e26 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/BatchNorm.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __BATCHNORM_BUILDER_H__ +#define __BATCHNORM_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class BatchNormBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __BATCHNORM_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Concatenation.cpp b/compiler/enco/frontend/caffe/src/Layer/Concatenation.cpp new file mode 100644 index 00000000000..f05f5908acd --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Concatenation.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Concatenation.h" +#include "IRBuilder.h" + +#include + +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +namespace caffeimport +{ + +void ConcatBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + + assert(layer.bottom().size() > 0); + assert(layer.top().size() == 1); + + // Assume default concat axis + // - Please refer to http://caffe.berkeleyvision.org/tutorial/layers/concat.html for details + // TODO Get concat axis from concat param + assert(!layer.has_concat_param()); + const uint32_t concat_axis = 1; + + // Construct a vector of input objects + std::vector input_objects; + + for (const auto &input_name : layer.bottom()) + { + const auto input_shape = as_feature_shape(shape_ctx.at(input_name)); + + auto input_bag = bag_ctx.at(input_name); + auto input_feature = module->entity()->object()->create(); + + input_feature->bag(input_bag); + input_feature->layout(coco::FeatureLayouts::BCHW::create(input_shape)); + + input_objects.emplace_back(input_feature); + } + + coco::FeatureObject *last_feature = input_objects.at(0); + + assert(last_feature != nullptr); + assert(last_feature->bag() != nullptr); + + // Update coco IR + // + // Given a sequence of input features %in[0] / %in[1] / ... / %in[N] + // the below code constructs a sequence of eval instructions + // - Load is omitted for simplicity + // + // %out[0] = eval(ConcatF(%in[0], %in[1])) + // %out[1] = eval(ConcatF(%out[0], %in[2])) + // ... + // %out[N - 1] = eval(ConcatF(%out[N - 2], %in[N])) + // + for (uint32_t n = 1; n < input_objects.size(); ++n) + { + auto const left_feature = last_feature; + auto const left_shape = left_feature->layout()->shape(); + + auto right_feature = input_objects.at(n); + auto right_shape = right_feature->layout()->shape(); + + // Batch is not supported, yet + assert(left_feature->layout()->batch() == 1); + assert(right_feature->layout()->batch() == 1); + + // Height and Width SHOULD BE IDENTICAL for depth concat + assert(left_shape.height() == right_shape.height()); + assert(left_shape.width() == right_shape.width()); + + const uint32_t C = left_shape.depth() + right_shape.depth(); + const uint32_t H = left_shape.height(); + const uint32_t W = left_shape.width(); + + const nncc::core::ADT::feature::Shape out_shape{C, H, W}; + + auto out_bag = module->entity()->bag()->create(num_elements(out_shape)); + auto out_feature = module->entity()->object()->create(); + + out_feature->bag(out_bag); + out_feature->layout(coco::FeatureLayouts::BCHW::create(out_shape)); + + auto left_load = op_builder(module).load(left_feature).pop(); + auto right_load = op_builder(module).load(right_feature).pop(); + + auto concat_f = module->entity()->op()->create(); + + concat_f->axis(coco::ConcatF::Axis::Depth); + concat_f->left(left_load); + concat_f->right(right_load); + + auto eval = instr_builder(module).eval(out_feature, concat_f); + + // Append the constructed Shuffle instruction + blk->instr()->append(eval); + + // Update 'last_feature' + last_feature = out_feature; + } + + assert(last_feature != nullptr); + assert(last_feature->bag() != nullptr); + + // Update bag and shape context + auto const out_name = layer.top(0); + auto const out_shape = as_tensor_shape(last_feature->layout()->shape()); + auto const out_bag = last_feature->bag(); + + bag_ctx[out_name] = out_bag; + shape_ctx[out_name] = out_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Concatenation.h b/compiler/enco/frontend/caffe/src/Layer/Concatenation.h new file mode 100644 index 00000000000..85e04000d78 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Concatenation.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONCAT_BUILDER_H__ +#define __CONCAT_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class ConcatBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __CONCAT_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Convolution.cpp b/compiler/enco/frontend/caffe/src/Layer/Convolution.cpp new file mode 100644 index 00000000000..9fb096d4900 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Convolution.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Convolution.h" +#include "ConvolutionSpec.h" +#include "Convert.h" +#include "IRBuilder.h" + +#include +#include + +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +using tensor::num_elements; + +namespace caffeimport +{ + +void ConvolutionBuilder::build(const ::caffe::LayerParameter &layer, + GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Data *data = context->data(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + WeightContext &weight_ctx = context->weight_ctx(); + + assert(layer.bottom().size() == 1); + assert(layer.top().size() == 1); + + assert(layer.has_convolution_param()); + const auto ¶m = layer.convolution_param(); + + ConvolutionSpec spec{param}; + { + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + spec.ifm_shape(ifm_shape); + } + + // NOTE The current implementation focuses on 2D convolution + // TODO Support general ND convolution + assert(spec.num_batch_axes() == 1); + assert(spec.num_spatial_axes() == 2); + + // Create an object for an input feature map + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + auto ifm_bag = bag_ctx.at(ifm_name); + auto ifm_obj = module->entity()->object()->create(); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + const auto ofm_name = layer.top(0); + const auto ofm_shape = spec.ofm_shape(); + auto ofm_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto ofm_obj = module->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + // Create an object for kernel + using namespace coco::KernelLayouts; + + const auto ker_shape = spec.ker_shape(); + auto ker_bag = module->entity()->bag()->create(num_elements(ker_shape)); + auto ker_obj = module->entity()->object()->create(); + + ker_obj->bag(ker_bag); + ker_obj->layout(NCHW::create(as_kernel_shape(ker_shape))); + + // Create a kernel overlay for the kernel object + data->f32()->allocate(ker_bag); + + // Initialize the kernel overlay + assert(weight_ctx.blob_count(layer.name()) >= 1); + auto ker_blob = weight_ctx.blob_get(layer.name(), 0); + + assert(ker_shape == caffeimport::as_tensor_shape(ker_blob)); + + auto ker_dst = data->f32()->access(ker_obj); + auto ker_src = kernel::OverlayFactory::make( + ker_obj->shape(), ker_blob->mutable_data()->begin()); + + for (uint32_t n = 0; n < ker_obj->shape().count(); ++n) + { + for (uint32_t ch = 0; ch < ker_obj->shape().depth(); ++ch) + { + for (uint32_t row = 0; row < ker_obj->shape().height(); ++row) + { + for (uint32_t col = 0; col < ker_obj->shape().width(); ++col) + { + ker_dst->at(n, ch, row, col) = ker_src.at(n, ch, row, col); + } + } + } + } + + // Create a Load op + auto load = op_builder(module).load(ifm_obj).pop(); + + // Create a Conv2D op + auto op = module->entity()->op()->create(); + + op->group(spec.group()); + + op->ker(ker_obj); + op->stride()->vertical(spec.stride(0)); + op->stride()->horizontal(spec.stride(1)); + + op->pad()->top(spec.pad(0)); + op->pad()->bottom(spec.pad(0)); + op->pad()->left(spec.pad(1)); + op->pad()->right(spec.pad(1)); + + op->arg(load); + + // Create an Eval instruction + auto ins = instr_builder(module).eval(ofm_obj, op); + + // Append the instruction to the block + blk->instr()->append(ins); + + // + // coco IR allows Conv2D fused with Add, but the current implementation of enco backend + // is unable to process such a tree. + // + // As a workaround, caffe frontend constructs a instruction for Conv2D and Add. + // + if (param.bias_term()) + { + assert(weight_ctx.blob_count(layer.name()) >= 2); + + // Create Bag & Object + auto bias_bag = module->entity()->bag()->create(ker_shape.dim(0)); + auto bias_obj = module->entity()->object()->create(); + + bias_obj->bag(bias_bag); + bias_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(ofm_shape))); + + auto added_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto added_obj = module->entity()->object()->create(); + + added_obj->bag(added_bag); + added_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + // Create Op + auto bias_add = op_builder(module).load(bias_obj).load(ofm_obj).add().pop(); + + // Create Instr + auto bias_add_ins = instr_builder(module).eval(added_obj, bias_add); + + // Append the instruction + blk->instr()->append(bias_add_ins); + + // Fill bias data + data->f32()->allocate(bias_bag); + + auto bias_span = data->f32()->weight(bias_bag); + auto bias_blob = weight_ctx.blob_get(layer.name(), 1); + + for (uint32_t ch = 0; ch < ker_obj->shape().count(); ++ch) + { + bias_span[ch] = bias_blob->data(ch); + } + + // Update output + ofm_bag = added_bag; + } + + // Update bag and shape context + bag_ctx[ofm_name] = ofm_bag; + shape_ctx[ofm_name] = ofm_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Convolution.h b/compiler/enco/frontend/caffe/src/Layer/Convolution.h new file mode 100644 index 00000000000..a944f12a322 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Convolution.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVOLUTION_BUILDER_H__ +#define __CONVOLUTION_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class ConvolutionBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __CONVOLUTION_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Eltwise.cpp b/compiler/enco/frontend/caffe/src/Layer/Eltwise.cpp new file mode 100644 index 00000000000..6a5d4f19673 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Eltwise.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Eltwise.h" +#include "IRBuilder.h" + +#include + +#include + +#include +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +namespace caffeimport +{ + +void EltwiseBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + + using coco::FeatureLayouts::BCHW; + + assert(layer.bottom().size() > 1); + assert(layer.top().size() == 1); + + assert(layer.has_eltwise_param()); + const auto ¶m = layer.eltwise_param(); + + using ::caffe::EltwiseParameter_EltwiseOp; + using ::caffe::EltwiseParameter_EltwiseOp_SUM; + using ::caffe::EltwiseParameter_EltwiseOp_PROD; + + using Reducer = std::function; + using ReducerRegistry = std::map; + + ReducerRegistry registry; + + // MAX are not supported, yet + registry[EltwiseParameter_EltwiseOp_SUM] = [](coco::Op *lhs, coco::Op *rhs) -> coco::Op * { + if (lhs == nullptr) + { + assert(rhs != nullptr); + return rhs; + } + + assert(lhs != nullptr && rhs != nullptr); + assert(lhs->module() == rhs->module()); + assert(lhs->module() != nullptr); + + auto m = lhs->module(); + return op_builder(m).push(rhs).push(lhs).add().pop(); + }; + + registry[EltwiseParameter_EltwiseOp_PROD] = [](coco::Op *lhs, coco::Op *rhs) -> coco::Op * { + if (lhs == nullptr) + { + assert(rhs != nullptr); + return rhs; + } + + assert(lhs != nullptr && rhs != nullptr); + assert(lhs->module() == rhs->module()); + assert(lhs->module() != nullptr); + + auto m = lhs->module(); + return op_builder(m).push(rhs).push(lhs).mul().pop(); + }; + + // coeff is not supported, yet + assert(!param.coeff().size()); + + // Decide appropriate reduce function + auto reduce = registry.at(param.operation()); + + coco::Op *op = nullptr; + + for (const auto &ifm_name : layer.bottom()) + { + auto ifm_shape = shape_ctx.at(ifm_name); + + // NOTE The current implementation does not work in general + auto ifm_bag = bag_ctx.at(ifm_name); + auto ifm_obj = module->entity()->object()->create(); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(BCHW::create(as_feature_shape(ifm_shape))); + + auto load = op_builder(module).load(ifm_obj).pop(); + + op = reduce(op, load); + } + + assert(op != nullptr); + + const auto ofm_name = layer.top(0); + const auto ofm_shape = shape_ctx.at(layer.bottom(0)); + + auto ofm_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto ofm_obj = module->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(BCHW::create(as_feature_shape(ofm_shape))); + + // Create "Eval" instruction + auto eval = instr_builder(module).eval(ofm_obj, op); + + // Append the instruction to the block + blk->instr()->append(eval); + + // Update bag and shape context + bag_ctx[ofm_name] = ofm_bag; + shape_ctx[ofm_name] = ofm_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Eltwise.h b/compiler/enco/frontend/caffe/src/Layer/Eltwise.h new file mode 100644 index 00000000000..e717077ec52 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Eltwise.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ELTWISE_BUILDER_H__ +#define __ELTWISE_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class EltwiseBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __ELTWISE_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Input.cpp b/compiler/enco/frontend/caffe/src/Layer/Input.cpp new file mode 100644 index 00000000000..39e44fa31d1 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Input.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Input.h" +#include "Convert.h" + +#include + +#include + +using namespace nncc::core::ADT; + +using tensor::num_elements; +using tensor::LexicalLayout; + +namespace caffeimport +{ + +void InputBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + + assert(layer.has_input_param()); + const auto ¶m = layer.input_param(); + + for (uint32_t n = 0; n < layer.top_size(); ++n) + { + const auto &name = layer.top(n); + const auto shape = as_tensor_shape(param.shape(n)); + + auto bag = module->entity()->bag()->create(num_elements(shape)); + auto input = module->entity()->input()->create(shape); + + input->bag(bag); + input->name(name); + input->reorder(); + + module->input()->insert(input); + + bag_ctx[name] = bag; + shape_ctx[name] = shape; + } +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Input.h b/compiler/enco/frontend/caffe/src/Layer/Input.h new file mode 100644 index 00000000000..2f464748d31 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Input.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __INPUT_BUILDER_H__ +#define __INPUT_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class InputBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __INPUT_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Pooling.cpp b/compiler/enco/frontend/caffe/src/Layer/Pooling.cpp new file mode 100644 index 00000000000..36220d841ad --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Pooling.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Pooling.h" +#include "PoolingSpec.h" +#include "IRBuilder.h" + +#include + +#include + +#include +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +namespace caffeimport +{ + +void PoolingBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + + assert(layer.bottom().size() == 1); + assert(layer.top().size() == 1); + + assert(layer.has_pooling_param()); + const auto ¶m = layer.pooling_param(); + + PoolingSpec spec{param}; + { + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + spec.ifm_shape(ifm_shape); + } + + // Create an object for an input feature map + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + auto ifm_bag = bag_ctx.at(ifm_name); + auto ifm_obj = module->entity()->object()->create(); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + const auto ofm_name = layer.top(0); + const auto ofm_shape = spec.ofm_shape(); + auto ofm_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto ofm_obj = module->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + using PoolingOpBuilder = std::function; + + std::map builders; + + // MaxPool2D op builder + builders[PoolingMethod::Max] = [ifm_obj](coco::Module *module, const PoolingSpec &spec) { + auto load = op_builder(module).load(ifm_obj).pop(); + + auto op = module->entity()->op()->create(); + + op->arg(load); + + op->window()->height(spec.window_height()); + op->window()->width(spec.window_width()); + + op->stride()->vertical(spec.vertical_stride()); + op->stride()->horizontal(spec.horizontal_stride()); + + op->pad()->top(spec.vertical_pad()); + op->pad()->bottom(spec.vertical_pad()); + op->pad()->left(spec.horizontal_pad()); + op->pad()->right(spec.horizontal_pad()); + + return op; + }; + + // AvgPool2D op builder + builders[PoolingMethod::Avg] = [ifm_obj](coco::Module *module, const PoolingSpec &spec) { + auto load = op_builder(module).load(ifm_obj).pop(); + + auto op = module->entity()->op()->create(); + + op->arg(load); + + // NOTE Caffe use static divisor on average pooling + op->divisor(coco::AvgPool2D::Divisor::Static); + + op->window()->height(spec.window_height()); + op->window()->width(spec.window_width()); + + op->stride()->vertical(spec.vertical_stride()); + op->stride()->horizontal(spec.horizontal_stride()); + + op->pad()->top(spec.vertical_pad()); + op->pad()->bottom(spec.vertical_pad()); + op->pad()->left(spec.horizontal_pad()); + op->pad()->right(spec.horizontal_pad()); + + return op; + }; + + // Create a pooling op + auto builder = builders.at(spec.method()); + auto op = builder(module, spec); + + // Create a UnitF instruction + auto ins = instr_builder(module).eval(ofm_obj, op); + + // Append the instruction to the block + blk->instr()->append(ins); + + // Update bag and shape context + bag_ctx[ofm_name] = ofm_bag; + shape_ctx[ofm_name] = ofm_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Pooling.h b/compiler/enco/frontend/caffe/src/Layer/Pooling.h new file mode 100644 index 00000000000..e72fd7aefe3 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Pooling.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __POOLING_BUILDER_H__ +#define __POOLING_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class PoolingBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __POOLING_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/ReLU.cpp b/compiler/enco/frontend/caffe/src/Layer/ReLU.cpp new file mode 100644 index 00000000000..61e206dc2d2 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/ReLU.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ReLU.h" +#include "IRBuilder.h" + +#include + +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +namespace caffeimport +{ + +void ReLUBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + + assert(layer.bottom().size() == 1); + assert(layer.top().size() == 1); + + // PReLU is not supported, yet + // TODO Support PReLU + assert(!layer.has_relu_param()); + + // NOTE The current implementation treats ReLU as Feature op + // TODO Support ReLU over general tensor + const auto ifm_name = layer.bottom(0); + const auto ifm_shape = shape_ctx.at(ifm_name); + auto ifm_bag = bag_ctx.at(ifm_name); + auto ifm_obj = module->entity()->object()->create(); + + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ifm_shape))); + + const auto ofm_name = layer.top(0); + const auto ofm_shape = ifm_shape; + auto ofm_bag = module->entity()->bag()->create(num_elements(ofm_shape)); + auto ofm_obj = module->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(ofm_shape))); + + // Create a Load Op + auto load = op_builder(module).load(ifm_obj).pop(); + + // Create a ReLU op + auto op = module->entity()->op()->create(); + + op->arg(load); + + // Create a Eval instruction + auto ins = instr_builder(module).eval(ofm_obj, op); + + // Append the instruction to the block + blk->instr()->append(ins); + + // Update bag and shape context + bag_ctx[ofm_name] = ofm_bag; + shape_ctx[ofm_name] = ofm_shape; +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/ReLU.h b/compiler/enco/frontend/caffe/src/Layer/ReLU.h new file mode 100644 index 00000000000..94836fd8ece --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/ReLU.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __RELU_BUILDER_H__ +#define __RELU_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class ReLUBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __RELU_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Layer/Scale.cpp b/compiler/enco/frontend/caffe/src/Layer/Scale.cpp new file mode 100644 index 00000000000..b9925978cfb --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Scale.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Scale.h" +#include "IRBuilder.h" + +#include + +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::caffe; + +namespace caffeimport +{ + +void ScaleBuilder::build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const +{ + coco::Module *module = context->module(); + coco::Data *data = context->data(); + coco::Block *blk = context->block(); + std::map &shape_ctx = context->shape_ctx(); + std::map &bag_ctx = context->bag_ctx(); + WeightContext &weight_ctx = context->weight_ctx(); + + // TODO Support Scale layer with 2 bottoms + assert(layer.bottom().size() == 1); + assert(layer.top().size() == 1); + + assert(layer.has_scale_param()); + const auto ¶m = layer.scale_param(); + + assert(param.axis() == 1); + assert(!param.has_num_axes()); + + assert(weight_ctx.blob_count(layer.name()) >= 1); + + // NOTE The shape of "Scale" output is same as that of its input + // NOTE The current implementation assumes that input/output is of feature type + // TODO Support generic tensor arguments + auto shape = shape_ctx.at(layer.bottom(0)); + + coco::Bag *last_bag = bag_ctx.at(layer.bottom(0)); + + // Create channel-wise multiplication + { + auto in_bag = last_bag; + auto in_obj = module->entity()->object()->create(); + + in_obj->bag(in_bag); + in_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(shape))); + + auto factor_bag = module->entity()->bag()->create(num_elements(shape)); + auto factor_obj = module->entity()->object()->create(); + + factor_obj->bag(factor_bag); + factor_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(shape))); + + auto out_bag = module->entity()->bag()->create(num_elements(shape)); + auto out_obj = module->entity()->object()->create(); + + out_obj->bag(out_bag); + out_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(shape))); + + auto mul_op = op_builder(module).load(factor_obj).load(in_obj).mul().pop(); + auto mul_ins = instr_builder(module).eval(out_obj, mul_op); + + blk->instr()->append(mul_ins); + + // Fill "factor" data + { + data->f32()->allocate(factor_bag); + + auto span = data->f32()->weight(factor_bag); + auto blob = weight_ctx.blob_get(layer.name(), 0); + + for (uint32_t ch = 0; ch < factor_obj->shape().depth(); ++ch) + { + span[ch] = blob->data(ch); + } + } + + // Update "last_bag" + last_bag = out_bag; + } + + assert(last_bag != nullptr); + + // Create bias addition (as channel-wise addition) + if (param.bias_term()) + { + assert(weight_ctx.blob_count(layer.name()) >= 2); + + auto in_bag = last_bag; /* Use the output of the last computation as an input */ + auto in_obj = module->entity()->object()->create(); + + in_obj->bag(in_bag); + in_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(shape))); + + auto bias_bag = module->entity()->bag()->create(num_elements(shape)); + auto bias_obj = module->entity()->object()->create(); + + bias_obj->bag(bias_bag); + bias_obj->layout(coco::FeatureLayouts::BC::create(as_feature_shape(shape))); + + auto out_bag = module->entity()->bag()->create(num_elements(shape)); + auto out_obj = module->entity()->object()->create(); + + out_obj->bag(out_bag); + out_obj->layout(coco::FeatureLayouts::BCHW::create(as_feature_shape(shape))); + + auto add_op = op_builder(module).load(bias_obj).load(in_obj).add().pop(); + auto add_ins = instr_builder(module).eval(out_obj, add_op); + + blk->instr()->append(add_ins); + + // Fill bias data + { + data->f32()->allocate(bias_bag); + + auto bias_span = data->f32()->weight(bias_bag); + auto bias_blob = weight_ctx.blob_get(layer.name(), 1); + + for (uint32_t ch = 0; ch < bias_obj->shape().depth(); ++ch) + { + bias_span[ch] = bias_blob->data(ch); + } + } + + // Update "last_bag" + last_bag = out_bag; + } + + // Update bag and shape context + { + const auto &out_name = layer.top(0); + const auto &out_bag = last_bag; + const auto &out_shape = shape; + + bag_ctx[out_name] = out_bag; + shape_ctx[out_name] = out_shape; + } +} + +} // namespace caffeimport diff --git a/compiler/enco/frontend/caffe/src/Layer/Scale.h b/compiler/enco/frontend/caffe/src/Layer/Scale.h new file mode 100644 index 00000000000..491cc31cfd6 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Layer/Scale.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __SCALE_BUILDER_H__ +#define __SCALE_BUILDER_H__ + +#include "GraphBuilder.h" + +#include "Context.h" + +namespace caffeimport +{ + +class ScaleBuilder final : public GraphBuilder +{ +public: + void build(const ::caffe::LayerParameter &layer, GraphBuilderContext *context) const override; +}; + +} // namespace caffeimport + +#endif // __SCALE_BUILDER_H__ diff --git a/compiler/enco/frontend/caffe/src/Padding.h b/compiler/enco/frontend/caffe/src/Padding.h new file mode 100644 index 00000000000..98b018117bb --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Padding.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @file Padding.h + * @brief This file declares padding-related data structures. + */ +#ifndef __PADDING_H__ +#define __PADDING_H__ + +#include +#include + +/** + * @brief A PaddingBase encapsulates common implementation for derived Padding classes + */ +template class PaddingBase +{ +public: + virtual ~PaddingBase() = default; + +public: + uint32_t count(void) const { return _values.size(); } + +public: + uint32_t &value(uint32_t n) { return _values.at(n); } + const uint32_t &value(uint32_t n) const { return _values.at(n); } + +public: + void resize(uint32_t len) { return _values.resize(len); } + +private: + std::vector _values; +}; + +/** + * @brief A RawPadding denotes padding values stored in Caffe model + * + * @note There may be a mismatch between the number of values in RawPadding and spatial rank + */ +struct RawPadding final : public PaddingBase +{ + // Empty +}; + +/** + * @brief A SpatialPadding denotes padding values for each "spatial" dimension + * + * @note The number of values in SpatialPadding should be matched with spatial rank + */ +struct SpatialPadding final : public PaddingBase +{ + // Empty +}; + +#endif // __PADDING_H__ diff --git a/compiler/enco/frontend/caffe/src/Padding.test.cpp b/compiler/enco/frontend/caffe/src/Padding.test.cpp new file mode 100644 index 00000000000..cb2495d0644 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/Padding.test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Padding.h" + +#include + +namespace +{ + +struct DerivedPadding : PaddingBase +{ + // Empty +}; + +} // namespace + +TEST(PaddingTest, PaddingBase) +{ + DerivedPadding pad; + + ASSERT_EQ(pad.count(), 0); + + pad.resize(2); + + ASSERT_EQ(pad.count(), 2); + ASSERT_EQ(pad.value(0), 0); + ASSERT_EQ(pad.value(1), 0); + + pad.value(1) = 4; + + ASSERT_EQ(pad.count(), 2); + ASSERT_EQ(pad.value(0), 0); + ASSERT_EQ(pad.value(1), 4); +} diff --git a/compiler/enco/frontend/caffe/src/PaddingUtils.cpp b/compiler/enco/frontend/caffe/src/PaddingUtils.cpp new file mode 100644 index 00000000000..ffb4bfbfd86 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/PaddingUtils.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "PaddingUtils.h" + +#include + +// +// Section: Raw Padding Builder +// +RawPadding RawPaddingBuilder::with(const ::caffe::ConvolutionParameter ¶m) const +{ + RawPadding res; + + if (param.has_pad_h() || param.has_pad_w()) + { + assert(param.pad().size() == 0); + assert(param.has_pad_h() && param.has_pad_w()); + + res.resize(2); + res.value(0) = param.pad_h(); + res.value(1) = param.pad_w(); + } + else + { + // NOTE pad and pad_h/pad_w cannot be specified at the same time + // + // Reference: BaseConvolutionLayer::LayerSetUp in base_conv_layer.cpp + assert(!param.has_pad_h() && !param.has_pad_w()); + + uint32_t rank = param.pad().size(); + + res.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + res.value(axis) = param.pad(axis); + } + } + + return res; +} + +RawPadding RawPaddingBuilder::with(const ::caffe::PoolingParameter ¶m) const +{ + RawPadding res; + + if (param.has_pad_h() || param.has_pad_w()) + { + assert(!param.has_pad()); + assert(param.has_pad_h() && param.has_pad_w()); + + res.resize(2); + res.value(0) = param.pad_h(); + res.value(1) = param.pad_w(); + } + else + { + // NOTE pad and pad_h/pad_w cannot be specified at the same time + // + // Reference: PoolingLayer::LayerSetUp in pooling_layer.cpp + assert(!param.has_pad_h() && !param.has_pad_w()); + + if (param.has_pad()) + { + res.resize(1); + res.value(0) = param.pad(); + } + } + + return res; +} + +RawPaddingBuilder build_raw_padding(void) { return RawPaddingBuilder{}; } + +// +// Section: Spatial Padding Builder +// +SpatialPadding SpatialPaddingBuilder::with(const RawPadding &raw) const +{ + const auto spatial_rank = _spatial_rank; + + SpatialPadding res; + + res.resize(spatial_rank); + + if (raw.count() == 0) + { + // NOTE default padding is 0 + for (uint32_t spatial_axis = 0; spatial_axis < spatial_rank; ++spatial_axis) + { + res.value(spatial_axis) = 0; + } + } + else if (raw.count() == 1) + { + // NOTE One-for-all scheme + for (uint32_t spatial_axis = 0; spatial_axis < spatial_rank; ++spatial_axis) + { + res.value(spatial_axis) = raw.value(0); + } + } + else + { + // NOTE One-to-one scheme + assert(raw.count() == spatial_rank); + for (uint32_t spatial_axis = 0; spatial_axis < spatial_rank; ++spatial_axis) + { + res.value(spatial_axis) = raw.value(spatial_axis); + } + } + + return res; +} + +SpatialPaddingBuilder build_spatial_padding(uint32_t spatial_rank) +{ + return SpatialPaddingBuilder{spatial_rank}; +} diff --git a/compiler/enco/frontend/caffe/src/PaddingUtils.h b/compiler/enco/frontend/caffe/src/PaddingUtils.h new file mode 100644 index 00000000000..81f32aaa8f2 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/PaddingUtils.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PADDING_UTILS_H__ +#define __PADDING_UTILS_H__ + +#include "Padding.h" + +#include + +/** + * @brief Construct a raw padding from each Layer parameter + * + * @note This class is an auxiliary class for build_raw_padding function below + */ +class RawPaddingBuilder +{ +public: + friend RawPaddingBuilder build_raw_padding(void); + +private: + RawPaddingBuilder() = default; + +public: + RawPadding with(const ::caffe::ConvolutionParameter &) const; + RawPadding with(const ::caffe::PoolingParameter &) const; +}; + +/** + * RawPaddingBuilder is introduced to support the following code pattern: + * + * auto raw_padding = build_raw_padding().with(conv_param); + * ... + */ +RawPaddingBuilder build_raw_padding(void); + +/** + * @brief Convert a raw padding to a spatial padding of a given spatial rank + * + * @note This class is an auxiliary class for build_raw_padding function below + */ +class SpatialPaddingBuilder +{ +public: + friend SpatialPaddingBuilder build_spatial_padding(uint32_t spatial_rank); + +private: + SpatialPaddingBuilder(uint32_t spatial_rank) : _spatial_rank{spatial_rank} + { + // DO NOTHING + } + +public: + SpatialPadding with(const RawPadding &raw) const; + +private: + uint32_t _spatial_rank = 0; +}; + +/** + * SpatialPaddingBuilder is introduced to support the following code pattern: + * + * auto raw_padding = build_raw_padding().with(conv_param); + * auto spatial_padding = build_spatial_padding(4).with(raw_padding); + */ +SpatialPaddingBuilder build_spatial_padding(uint32_t spatial_rank); + +#endif // __PADDING_UTILS_H__ diff --git a/compiler/enco/frontend/caffe/src/PoolingSpec.cpp b/compiler/enco/frontend/caffe/src/PoolingSpec.cpp new file mode 100644 index 00000000000..36216a2dad4 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/PoolingSpec.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "PoolingSpec.h" +#include "PaddingUtils.h" + +#include +#include + +PoolingSpec::PoolingSpec(const ::caffe::PoolingParameter ¶m) : _param(param) +{ + // DO NOTHING +} + +PoolingMethod PoolingSpec::method(void) const +{ + if (!_param.has_pool()) + { + // Default pooling method is MAX + // Reference: http://caffe.berkeleyvision.org/tutorial/layers/pooling.html + return PoolingMethod::Max; + } + + std::map<::caffe::PoolingParameter_PoolMethod, PoolingMethod> methods; + + // NOTE STOCHASTIC Pooling is not supported, yet + // TODO Support STOCHASTIC Pooling + methods[::caffe::PoolingParameter_PoolMethod_MAX] = PoolingMethod::Max; + methods[::caffe::PoolingParameter_PoolMethod_AVE] = PoolingMethod::Avg; + + assert(_param.has_pool()); + return methods.at(_param.pool()); +} + +uint32_t PoolingSpec::window_height(void) const +{ + // NOTE Global pooling is not supported, yet + // TODO Support global pooling + assert(!_param.global_pooling()); + + if (_param.has_kernel_h()) + { + return _param.kernel_h(); + } + + assert(_param.has_kernel_size()); + return _param.kernel_size(); +} + +uint32_t PoolingSpec::window_width(void) const +{ + // NOTE Global pooling is not supported, yet + // TODO Support global pooling + assert(!_param.global_pooling()); + + if (_param.has_kernel_w()) + { + return _param.kernel_w(); + } + + assert(_param.has_kernel_size()); + return _param.kernel_size(); +} + +uint32_t PoolingSpec::vertical_pad(void) const +{ + // NOTE The input of Pooling SHOULD BE a rank-4 tensor. + // Reference: PoolingLayer::Reshape in pooling_layer.cpp + auto raw_padding = build_raw_padding().with(_param); + auto spatial_padding = build_spatial_padding(2 /* SPATIAL RANK */).with(raw_padding); + return spatial_padding.value(0 /* H */); +} + +uint32_t PoolingSpec::horizontal_pad(void) const +{ + // NOTE The input of Pooling SHOULD BE a rank-4 tensor. + // Reference: PoolingLayer::Reshape in pooling_layer.cpp + auto raw_padding = build_raw_padding().with(_param); + auto spatial_padding = build_spatial_padding(2 /* SPATIAL RANK */).with(raw_padding); + return spatial_padding.value(1 /* W */); +} + +uint32_t PoolingSpec::vertical_stride(void) const +{ + if (_param.has_stride_h()) + { + return _param.stride_h(); + } + + if (_param.has_stride()) + { + return _param.stride(); + } + + return 1; +} + +uint32_t PoolingSpec::horizontal_stride(void) const +{ + if (_param.has_stride_w()) + { + return _param.stride_w(); + } + + if (_param.has_stride()) + { + return _param.stride(); + } + + return 1; +} + +nncc::core::ADT::tensor::Shape PoolingSpec::ofm_shape(void) const +{ + nncc::core::ADT::tensor::Shape res; + + // NOTE Caffe supports only pooling over rank-4 tensor + assert(_ifm_shape.rank() == 4); + res.resize(4); + + // N (= the number of bacths) SHOULD be same + res.dim(0) = _ifm_shape.dim(0); + // C (= the number of chaanels) SHOULD be same + res.dim(1) = _ifm_shape.dim(1); + + // H and W are derived from IFM, Window, and Padding + const auto effective_input_height = _ifm_shape.dim(2) + 2 * vertical_pad() - window_height(); + const auto effective_input_width = _ifm_shape.dim(3) + 2 * horizontal_pad() - window_width(); + // TODO Remove the following asserts + assert(effective_input_height % vertical_stride() == 0); + assert(effective_input_width % horizontal_stride() == 0); + res.dim(2) = effective_input_height / vertical_stride() + 1; + res.dim(3) = effective_input_width / horizontal_stride() + 1; + return res; +} diff --git a/compiler/enco/frontend/caffe/src/PoolingSpec.h b/compiler/enco/frontend/caffe/src/PoolingSpec.h new file mode 100644 index 00000000000..655a773bae0 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/PoolingSpec.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __POOLING_SPEC_H__ +#define __POOLING_SPEC_H__ + +#include + +#include + +enum class PoolingMethod +{ + Max, + Avg +}; + +class PoolingSpec +{ +public: + PoolingSpec(const ::caffe::PoolingParameter ¶m); + +public: + const nncc::core::ADT::tensor::Shape &ifm_shape(void) const { return _ifm_shape; } + void ifm_shape(const nncc::core::ADT::tensor::Shape &shape) { _ifm_shape = shape; } + +public: + PoolingMethod method(void) const; + +public: + uint32_t window_height(void) const; + uint32_t window_width(void) const; + +public: + uint32_t vertical_pad(void) const; + uint32_t horizontal_pad(void) const; + +public: + uint32_t vertical_stride(void) const; + uint32_t horizontal_stride(void) const; + +public: + nncc::core::ADT::tensor::Shape ofm_shape(void) const; + +private: + const ::caffe::PoolingParameter &_param; + nncc::core::ADT::tensor::Shape _ifm_shape; +}; + +#endif // __POOLING_SPEC_H__ diff --git a/compiler/enco/frontend/caffe/src/PoolingSpec.test.cpp b/compiler/enco/frontend/caffe/src/PoolingSpec.test.cpp new file mode 100644 index 00000000000..26bcaa09bda --- /dev/null +++ b/compiler/enco/frontend/caffe/src/PoolingSpec.test.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "PoolingSpec.h" +#include "Importer.h" + +#include + +#include + +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; + +#define STRING(content) #content + +bool from_txt(const std::string &txt, ::caffe::PoolingParameter &out) +{ + std::stringstream ss{txt}; + return from_txt(ss, out); +} + +namespace +{ + +class SequentialBuilder +{ +public: + SequentialBuilder(::caffe::NetParameter *net) : _net{net} + { + // DO NOTHING + } + +public: + bool addLayer(const std::string &prototxt) + { + auto layer = _net->add_layer(); + std::stringstream ss{prototxt}; + ::google::protobuf::io::IstreamInputStream iis{&ss}; + return google::protobuf::TextFormat::Parse(&iis, layer); + } + + bool addInputLayer(const tensor::Shape &shape) + { + auto param = new ::caffe::InputParameter; + { + auto s = param->add_shape(); + for (uint32_t n = 0; n < shape.rank(); ++n) + { + s->add_dim(shape.dim(n)); + } + } + + auto layer = _net->add_layer(); + + layer->set_name("data"); + layer->set_type("Input"); + layer->add_top("data"); + layer->set_allocated_input_param(param); + + return true; + } + +private: + ::caffe::NetParameter *_net; +}; + +} // namespace + +namespace +{ + +class PoolingSpecTest : public ::testing::Test +{ +protected: + tensor::Shape as_tensor_shape(const std::vector &dims) + { + const uint32_t rank = dims.size(); + + tensor::Shape res; + + res.resize(rank); + + for (uint32_t axis = 0; axis < rank; ++axis) + { + res.dim(axis) = dims.at(axis); + } + + return res; + } +}; +} // namespace + +TEST_F(PoolingSpecTest, ifm_shape) +{ + ::caffe::PoolingParameter param; + PoolingSpec spec{param}; + + const tensor::Shape ifm_shape{1, 3, 244, 244}; + + spec.ifm_shape(ifm_shape); + + ASSERT_EQ(spec.ifm_shape(), ifm_shape); +} + +namespace +{ +} // namespace + +TEST_F(PoolingSpecTest, kernel_size_same_for_all) +{ + const tensor::Shape ifm_shape{1, 3, 16, 16}; + + ::caffe::NetParameter param; + { + SequentialBuilder builder{¶m}; + + builder.addInputLayer(ifm_shape); + + // clang-format off + const char *prototxt = STRING( + name : "pool" + type : "Pooling" + bottom : "data" + top : "pool" + pooling_param { kernel_size : 3 } + ); + // clang-format on + + builder.addLayer(prototxt); + } + + ::caffe::Net net{param}; + + PoolingSpec spec{param.layer(1).pooling_param()}; + + spec.ifm_shape(ifm_shape); + + ASSERT_EQ(spec.window_height(), 3); + ASSERT_EQ(spec.window_width(), 3); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("pool")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +TEST_F(PoolingSpecTest, pad_for_all) +{ + const tensor::Shape ifm_shape{1, 3, 15, 15}; + + ::caffe::NetParameter param; + { + SequentialBuilder builder{¶m}; + + builder.addInputLayer(ifm_shape); + + // clang-format off + const char *prototxt = STRING( + name : "pool" + type : "Pooling" + bottom : "data" + top : "pool" + pooling_param { + pool: MAX + kernel_size : 3 + pad: 2 + } + ); + // clang-format on + + builder.addLayer(prototxt); + } + + ::caffe::Net net{param}; + + PoolingSpec spec{param.layer(1).pooling_param()}; + + spec.ifm_shape(ifm_shape); + + ASSERT_EQ(spec.vertical_pad(), 2); + ASSERT_EQ(spec.horizontal_pad(), 2); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("pool")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +TEST_F(PoolingSpecTest, stride_for_all) +{ + const tensor::Shape ifm_shape{1, 3, 15, 15}; + + ::caffe::NetParameter param; + { + SequentialBuilder builder{¶m}; + + builder.addInputLayer(ifm_shape); + + // clang-format off + const char *prototxt = STRING( + name : "pool" + type : "Pooling" + bottom : "data" + top : "pool" + pooling_param { + pool: MAX + kernel_size : 3 + stride: 2 + } + ); + // clang-format on + + builder.addLayer(prototxt); + } + + ::caffe::Net net{param}; + + PoolingSpec spec{param.layer(1).pooling_param()}; + + spec.ifm_shape(ifm_shape); + + ASSERT_EQ(spec.vertical_stride(), 2); + ASSERT_EQ(spec.horizontal_stride(), 2); + + // Check 'ofm_shape' + { + auto expected = as_tensor_shape(net.blob_by_name("pool")->shape()); + auto obtained = spec.ofm_shape(); + + ASSERT_EQ(expected, obtained); + } +} + +TEST_F(PoolingSpecTest, method_none) +{ + const char *prototxt = ""; + + ::caffe::PoolingParameter param; + from_txt(prototxt, param); + + PoolingSpec spec{param}; + + ASSERT_EQ(spec.method(), PoolingMethod::Max); +} + +TEST_F(PoolingSpecTest, method_max) +{ + const char *prototxt = "pool: MAX"; + + ::caffe::PoolingParameter param; + from_txt(prototxt, param); + + PoolingSpec spec{param}; + + ASSERT_EQ(spec.method(), PoolingMethod::Max); +} + +TEST_F(PoolingSpecTest, method_avg) +{ + const char *prototxt = "pool: AVE"; + + ::caffe::PoolingParameter param; + from_txt(prototxt, param); + + PoolingSpec spec{param}; + + ASSERT_EQ(spec.method(), PoolingMethod::Avg); +} diff --git a/compiler/enco/frontend/caffe/src/ShapeQuery.cpp b/compiler/enco/frontend/caffe/src/ShapeQuery.cpp new file mode 100644 index 00000000000..1166453b601 --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ShapeQuery.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ShapeQuery.h" + +#include + +// +// AxisSpecifier +// +AxisSpecifier axis_specifier(int32_t value) { return AxisSpecifier{value}; } + +// +// ShapeQuery +// +uint32_t ShapeQuery::axis(const AxisSpecifier &specifier) const +{ + if (specifier.value() > 0) + { + return static_cast(specifier.value()); + } + + assert(_shape->rank() >= static_cast(-specifier.value())); + return static_cast(_shape->rank() + specifier.value()); +} + +ShapeQuery query_on(const nncc::core::ADT::tensor::Shape &shape) { return ShapeQuery{&shape}; } diff --git a/compiler/enco/frontend/caffe/src/ShapeQuery.h b/compiler/enco/frontend/caffe/src/ShapeQuery.h new file mode 100644 index 00000000000..260b6ad4daf --- /dev/null +++ b/compiler/enco/frontend/caffe/src/ShapeQuery.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __SHAPE_QUERY_H__ +#define __SHAPE_QUERY_H__ + +#include + +/** + * @brief A wrapper class for an integer number that specifies axis + * + * Several Caffe layers includes 'axis' parameter (which may be negative) which specifies + * some axis required for operation. + * + * Here are several examples: + * - Convolution layer uses 'axis' parameter to specify "channel" axis + * (http://caffe.berkeleyvision.org/tutorial/layers/convolution.html) + * - Concat layer uses 'axis' parameter to specify axis to be concatenated + * (http://caffe.berkeleyvision.org/tutorial/layers/concat.html) + * + * AxisSpecifier class is introduced to distinguish this 'axis' parameter from other integers + * (to prevent possible mistake). + */ +class AxisSpecifier +{ +public: + explicit AxisSpecifier(int32_t value) : _value{value} + { + // DO NOTHING + } + +public: + int32_t value(void) const { return _value; } + +private: + int32_t _value = 1; +}; + +AxisSpecifier axis_specifier(int32_t value); + +/** + * @brief A wrapper class that allows additional queries over tensor shape. + */ +class ShapeQuery +{ +public: + explicit ShapeQuery(const nncc::core::ADT::tensor::Shape *shape) : _shape{shape} + { + // DO NOTHING + } + +public: + /// @brief Return the dimension number (axis) specified by a given axis specifier + uint32_t axis(const AxisSpecifier &) const; + +private: + const nncc::core::ADT::tensor::Shape *_shape; +}; + +ShapeQuery query_on(const nncc::core::ADT::tensor::Shape &); + +#endif // __SHAPE_QUERY_H__ diff --git a/compiler/enco/frontend/tflite/CMakeLists.txt b/compiler/enco/frontend/tflite/CMakeLists.txt new file mode 100644 index 00000000000..77159879ee6 --- /dev/null +++ b/compiler/enco/frontend/tflite/CMakeLists.txt @@ -0,0 +1,36 @@ +nnas_find_package(FlatBuffers QUIET) + +if(NOT FlatBuffers_FOUND) + return() +endif(NOT FlatBuffers_FOUND) + +FlatBuffers_Target(enco_tflite_schema + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated" + SCHEMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/schema" + SCHEMA_FILES schema.fbs) + +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(enco_tflite_frontend SHARED ${SOURCES}) +target_include_directories(enco_tflite_frontend PRIVATE src) +target_link_libraries(enco_tflite_frontend enco_intf_frontend) +target_link_libraries(enco_tflite_frontend enco_intf_cmdline) +target_link_libraries(enco_tflite_frontend flatbuffers) +target_link_libraries(enco_tflite_frontend enco_tflite_schema) +target_link_libraries(enco_tflite_frontend stdex) +target_link_libraries(enco_tflite_frontend morph) +target_link_libraries(enco_tflite_frontend cwrap) + +nnas_find_package(GTest QUIET) + +if(NOT GTest_FOUND) + return() +endif(NOT GTest_FOUND) + +add_executable(enco_tflite_frontend_test ${TESTS}) +target_include_directories(enco_tflite_frontend_test PRIVATE src) +target_link_libraries(enco_tflite_frontend_test gtest_main) +target_link_libraries(enco_tflite_frontend_test enco_tflite_frontend) +add_test(enco_tflite_frontend_test enco_tflite_frontend_test) diff --git a/compiler/enco/frontend/tflite/schema/schema.fbs b/compiler/enco/frontend/tflite/schema/schema.fbs new file mode 100644 index 00000000000..3045351f222 --- /dev/null +++ b/compiler/enco/frontend/tflite/schema/schema.fbs @@ -0,0 +1,734 @@ +// Copyright 2017 The TensorFlow Authors. All Rights Reserved. +// +// 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. + +// Revision History +// Version 0: Initial version. +// Version 1: Add subgraphs to schema. +// Version 2: Rename operators to conform to NN API. +// Version 3: Move buffer data from Model.Subgraph.Tensors to Model.Buffers. + +namespace tflite; + +// This corresponds to the version. +file_identifier "TFL3"; +// File extension of any written files. +file_extension "tflite"; + +// IMPORTANT: All new members of tables, enums and unions must be added at the +// end to ensure backwards compatibility. + +// The type of data stored in a tensor. +enum TensorType : byte { + FLOAT32 = 0, + FLOAT16 = 1, + INT32 = 2, + UINT8 = 3, + INT64 = 4, + STRING = 5, + BOOL = 6, + INT16 = 7, + COMPLEX64 = 8, +} + +// Parameters for converting a quantized tensor back to float. Given a +// quantized value q, the corresponding float value f should be: +// f = scale * (q - zero_point) +table QuantizationParameters { + min:[float]; // For importing back into tensorflow. + max:[float]; // For importing back into tensorflow. + scale:[float]; // For dequantizing the tensor's values. + zero_point:[long]; +} + +table Tensor { + // The tensor shape. The meaning of each entry is operator-specific but + // builtin ops use: [batch size, height, width, number of channels] (That's + // Tensorflow's NHWC). + shape:[int]; + type:TensorType; + // An index that refers to the buffers table at the root of the model. Or, + // if there is no data buffer associated (i.e. intermediate results), then + // this is 0 (which refers to an always existent empty buffer). + // + // The data_buffer itself is an opaque container, with the assumption that the + // target device is little-endian. In addition, all builtin operators assume + // the memory is ordered such that if `shape` is [4, 3, 2], then index + // [i, j, k] maps to data_buffer[i*3*2 + j*2 + k]. + buffer:uint; + name:string; // For debugging and importing back into tensorflow. + quantization:QuantizationParameters; // Optional. + + is_variable:bool = false; +} + +// A list of builtin operators. Builtin operators are slightly faster than custom +// ones, but not by much. Moreover, while custom operators accept an opaque +// object containing configuration parameters, builtins have a predetermined +// set of acceptable options. +enum BuiltinOperator : byte { + ADD = 0, + AVERAGE_POOL_2D = 1, + CONCATENATION = 2, + CONV_2D = 3, + DEPTHWISE_CONV_2D = 4, + // DEPTH_TO_SPACE = 5, + DEQUANTIZE = 6, + EMBEDDING_LOOKUP = 7, + FLOOR = 8, + FULLY_CONNECTED = 9, + HASHTABLE_LOOKUP = 10, + L2_NORMALIZATION = 11, + L2_POOL_2D = 12, + LOCAL_RESPONSE_NORMALIZATION = 13, + LOGISTIC = 14, + LSH_PROJECTION = 15, + LSTM = 16, + MAX_POOL_2D = 17, + MUL = 18, + RELU = 19, + // NOTE(aselle): RELU_N1_TO_1 used to be called RELU1, but it was renamed + // since different model developers use RELU1 in different ways. Never + // create another op called RELU1. + RELU_N1_TO_1 = 20, + RELU6 = 21, + RESHAPE = 22, + RESIZE_BILINEAR = 23, + RNN = 24, + SOFTMAX = 25, + SPACE_TO_DEPTH = 26, + SVDF = 27, + TANH = 28, + // TODO(aselle): Consider rename to CONCATENATE_EMBEDDINGS + CONCAT_EMBEDDINGS = 29, + SKIP_GRAM = 30, + CALL = 31, + CUSTOM = 32, + EMBEDDING_LOOKUP_SPARSE = 33, + PAD = 34, + UNIDIRECTIONAL_SEQUENCE_RNN = 35, + GATHER = 36, + BATCH_TO_SPACE_ND = 37, + SPACE_TO_BATCH_ND = 38, + TRANSPOSE = 39, + MEAN = 40, + SUB = 41, + DIV = 42, + SQUEEZE = 43, + UNIDIRECTIONAL_SEQUENCE_LSTM = 44, + STRIDED_SLICE = 45, + BIDIRECTIONAL_SEQUENCE_RNN = 46, + EXP = 47, + TOPK_V2 = 48, + SPLIT = 49, + LOG_SOFTMAX = 50, + // DELEGATE is a special op type for the operations which are delegated to + // other backends. + // WARNING: Experimental interface, subject to change + DELEGATE = 51, + BIDIRECTIONAL_SEQUENCE_LSTM = 52, + CAST = 53, + PRELU = 54, + MAXIMUM = 55, + ARG_MAX = 56, + MINIMUM = 57, + LESS = 58, + NEG = 59, + PADV2 = 60, + GREATER = 61, + GREATER_EQUAL = 62, + LESS_EQUAL = 63, + SELECT = 64, + SLICE = 65, + SIN = 66, + TRANSPOSE_CONV = 67, + SPARSE_TO_DENSE = 68, + TILE = 69, + EXPAND_DIMS = 70, + EQUAL = 71, + NOT_EQUAL = 72, + LOG = 73, + SUM = 74, + SQRT = 75, + RSQRT = 76, + SHAPE = 77, + POW = 78, + ARG_MIN = 79, + FAKE_QUANT = 80, + REDUCE_PROD = 81, + REDUCE_MAX = 82, + PACK = 83, + LOGICAL_OR = 84, + ONE_HOT = 85, + LOGICAL_AND = 86, + LOGICAL_NOT = 87, + UNPACK = 88, + REDUCE_MIN = 89, + FLOOR_DIV = 90, + REDUCE_ANY = 91, + SQUARE = 92, + ZEROS_LIKE = 93, + FILL = 94, + FLOOR_MOD = 95, + RANGE = 96, +} + +// Options for the builtin operators. +union BuiltinOptions { + Conv2DOptions, + DepthwiseConv2DOptions, + ConcatEmbeddingsOptions, + LSHProjectionOptions, + Pool2DOptions, + SVDFOptions, + RNNOptions, + FullyConnectedOptions, + SoftmaxOptions, + ConcatenationOptions, + AddOptions, + L2NormOptions, + LocalResponseNormalizationOptions, + LSTMOptions, + ResizeBilinearOptions, + CallOptions, + ReshapeOptions, + SkipGramOptions, + SpaceToDepthOptions, + EmbeddingLookupSparseOptions, + MulOptions, + PadOptions, + GatherOptions, + BatchToSpaceNDOptions, + SpaceToBatchNDOptions, + TransposeOptions, + ReducerOptions, + SubOptions, + DivOptions, + SqueezeOptions, + SequenceRNNOptions, + StridedSliceOptions, + ExpOptions, + TopKV2Options, + SplitOptions, + LogSoftmaxOptions, + CastOptions, + DequantizeOptions, + MaximumMinimumOptions, + ArgMaxOptions, + LessOptions, + NegOptions, + PadV2Options, + GreaterOptions, + GreaterEqualOptions, + LessEqualOptions, + SelectOptions, + SliceOptions, + TransposeConvOptions, + SparseToDenseOptions, + TileOptions, + ExpandDimsOptions, + EqualOptions, + NotEqualOptions, + ShapeOptions, + PowOptions, + ArgMinOptions, + FakeQuantOptions, + PackOptions, + LogicalOrOptions, + OneHotOptions, + LogicalAndOptions, + LogicalNotOptions, + UnpackOptions, + FloorDivOptions, + SquareOptions, + ZerosLikeOptions, + FillOptions, + BidirectionalSequenceLSTMOptions, + BidirectionalSequenceRNNOptions, + UnidirectionalSequenceLSTMOptions, + FloorModOptions, + RangeOptions, +} + +enum Padding : byte { SAME, VALID } + +enum ActivationFunctionType : byte { + NONE = 0, + RELU = 1, + RELU_N1_TO_1 = 2, + RELU6 = 3, + TANH = 4, + SIGN_BIT = 5, +} + +table Conv2DOptions { + padding:Padding; + stride_w:int; + stride_h:int; + fused_activation_function:ActivationFunctionType; + dilation_w_factor:int = 1; + dilation_h_factor:int = 1; +} + +table Pool2DOptions { + padding:Padding; + stride_w:int; + stride_h:int; + filter_width:int; + filter_height:int; + fused_activation_function:ActivationFunctionType; +} + +table DepthwiseConv2DOptions { + // Parameters for DepthwiseConv version 1 or above. + padding:Padding; + stride_w:int; + stride_h:int; + depth_multiplier:int; + fused_activation_function:ActivationFunctionType; + // Parameters for DepthwiseConv version 2 or above. + dilation_w_factor:int = 1; + dilation_h_factor:int = 1; +} + +table ConcatEmbeddingsOptions { + num_channels:int; + num_columns_per_channel:[int]; + embedding_dim_per_channel:[int]; // This could be inferred from parameters. +} + +enum LSHProjectionType: byte { + UNKNOWN = 0, + SPARSE = 1, + DENSE = 2, +} + +table LSHProjectionOptions { + type: LSHProjectionType; +} + +table SVDFOptions { + rank:int; + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow RNNCell. +table RNNOptions { + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow dynamic_rnn with RNNCell. +table SequenceRNNOptions { + time_major:bool; + fused_activation_function:ActivationFunctionType; +} + +// An implementation of TensorFlow bidrectional_dynamic_rnn with RNNCell. +table BidirectionalSequenceRNNOptions { + time_major:bool; + fused_activation_function:ActivationFunctionType; + merge_outputs: bool; +} + +enum FullyConnectedOptionsWeightsFormat: byte { + DEFAULT = 0, + SHUFFLED4x16INT8 = 1, +} + +// An implementation of TensorFlow fully_connected (a.k.a Dense) layer. +table FullyConnectedOptions { + // Parameters for FullyConnected version 1 or above. + fused_activation_function:ActivationFunctionType; + + // Parameters for FullyConnected version 2 or above. + weights_format:FullyConnectedOptionsWeightsFormat = DEFAULT; +} + +table SoftmaxOptions { + beta: float; +} + +// An implementation of TensorFlow concat. +table ConcatenationOptions { + axis:int; + fused_activation_function:ActivationFunctionType; +} + +table AddOptions { + fused_activation_function:ActivationFunctionType; +} + +table MulOptions { + fused_activation_function:ActivationFunctionType; +} + +table L2NormOptions { + fused_activation_function:ActivationFunctionType; +} + +table LocalResponseNormalizationOptions { + radius:int; + bias:float; + alpha:float; + beta:float; +} + +enum LSTMKernelType : byte { + // Full LSTM kernel which supports peephole and projection. + FULL = 0, + // Basic LSTM kernels. Equivalent to TensorFlow BasicLSTMCell. + BASIC = 1, +} + +// An implementation of TensorFlow LSTMCell and CoupledInputForgetGateLSTMCell +table LSTMOptions { + // Parameters for LSTM version 1 or above. + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // Parameters for LSTM version 2 or above. + // Basic kernel is only supported in version 2 or above. + kernel_type: LSTMKernelType = FULL; +} + +// An implementation of TensorFlow dynamic_rnn with LSTMCell. +table UnidirectionalSequenceLSTMOptions { + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // If true then first dimension is sequence, otherwise batch. + time_major:bool; +} + +table BidirectionalSequenceLSTMOptions { + fused_activation_function:ActivationFunctionType; + cell_clip: float; // Optional, 0.0 means no clipping + proj_clip: float; // Optional, 0.0 means no clipping + + // If true, store the outputs of both directions into the first output. + merge_outputs: bool; +} + +table ResizeBilinearOptions { + new_height: int (deprecated); + new_width: int (deprecated); + align_corners: bool; +} + +// A call operation options +table CallOptions { + // The subgraph index that needs to be called. + subgraph:uint; +} + +table PadOptions { +} + +table PadV2Options { +} + +table ReshapeOptions { + new_shape:[int]; +} + +table SpaceToBatchNDOptions { +} + +table BatchToSpaceNDOptions { +} + +table SkipGramOptions { + ngram_size: int; + max_skip_size: int; + include_all_ngrams: bool; +} + +table SpaceToDepthOptions { + block_size: int; +} + +table SubOptions { + fused_activation_function:ActivationFunctionType; +} + +table DivOptions { + fused_activation_function:ActivationFunctionType; +} + +table TopKV2Options { +} + +enum CombinerType : byte { + SUM = 0, + MEAN = 1, + SQRTN = 2, +} + +table EmbeddingLookupSparseOptions { + combiner:CombinerType; +} + +table GatherOptions { + axis: int; +} + +table TransposeOptions { +} + +table ExpOptions { +} + +table ReducerOptions { + keep_dims: bool; +} + +table SqueezeOptions { + squeeze_dims:[int]; +} + +table SplitOptions { + num_splits: int; +} + +table StridedSliceOptions { + begin_mask: int; + end_mask: int; + ellipsis_mask: int; + new_axis_mask: int; + shrink_axis_mask: int; +} + +table LogSoftmaxOptions { +} + +table CastOptions { + in_data_type: TensorType; + out_data_type: TensorType; +} + +table DequantizeOptions { +} + +table MaximumMinimumOptions { +} + +table TileOptions { +} + +table ArgMaxOptions { + output_type : TensorType; +} + +table ArgMinOptions { + output_type : TensorType; +} + +table GreaterOptions { +} + +table GreaterEqualOptions { +} + +table LessOptions { +} + +table LessEqualOptions { +} + +table NegOptions { +} + +table SelectOptions { +} + +table SliceOptions { +} + +table TransposeConvOptions { + padding:Padding; + stride_w:int; + stride_h:int; +} + +table ExpandDimsOptions { +} + +table SparseToDenseOptions { + validate_indices:bool; +} + +table EqualOptions { +} + +table NotEqualOptions { +} + +table ShapeOptions { + // Optional output type of the operation (int32 or int64). Defaults to int32. + out_type : TensorType; +} + +table PowOptions { +} + +table FakeQuantOptions { + // Parameters supported by version 1: + min:float; + max:float; + num_bits:int; + + // Parameters supported by version 2: + narrow_range:bool; +} + +table PackOptions { + values_count:int; + axis:int; +} + +table LogicalOrOptions { +} + +table OneHotOptions { + axis:int; +} + +table LogicalAndOptions { +} + +table LogicalNotOptions { +} + +table UnpackOptions { + num:int; + axis:int; +} + +table FloorDivOptions { +} + +table SquareOptions { +} + +table ZerosLikeOptions { +} + +table FillOptions { +} + +table FloorModOptions { +} + +table RangeOptions { +} + +// An OperatorCode can be an enum value (BuiltinOperator) if the operator is a +// builtin, or a string if the operator is custom. +table OperatorCode { + builtin_code:BuiltinOperator; + custom_code:string; + + // The version of the operator. The version need to be bumped whenever new + // parameters are introduced into an op. + version:int = 1; +} + +enum CustomOptionsFormat : byte { + FLEXBUFFERS = 0, +} + +// An operator takes tensors as inputs and outputs. The type of operation being +// performed is determined by an index into the list of valid OperatorCodes, +// while the specifics of each operations is configured using builtin_options +// or custom_options. +table Operator { + // Index into the operator_codes array. Using an integer here avoids + // complicate map lookups. + opcode_index:uint; + + // Optional input and output tensors are indicated by -1. + inputs:[int]; + outputs:[int]; + + builtin_options:BuiltinOptions; + custom_options:[ubyte]; + custom_options_format:CustomOptionsFormat; + + // A list of booleans indicating the input tensors which are being mutated by + // this operator.(e.g. used by RNN and LSTM). + // For example, if the "inputs" array refers to 5 tensors and the second and + // fifth are mutable variables, then this list will contain + // [false, true, false, false, true]. + // + // If the list is empty, no variable is mutated in this operator. + // The list either has the same length as `inputs`, or is empty. + mutating_variable_inputs:[bool]; +} + +// The root type, defining a subgraph, which typically represents an entire +// model. +table SubGraph { + // A list of all tensors used in this subgraph. + tensors:[Tensor]; + + // Indices of the tensors that are inputs into this subgraph. Note this is + // the list of non-static tensors that feed into the subgraph for inference. + inputs:[int]; + + // Indices of the tensors that are outputs out of this subgraph. Note this is + // the list of output tensors that are considered the product of the + // subgraph's inference. + outputs:[int]; + + // All operators, in execution order. + operators:[Operator]; + + // Name of this subgraph (used for debugging). + name:string; +} + +// Table of raw data buffers (used for constant tensors). Referenced by tensors +// by index. The generous alignment accommodates mmap-friendly data structures. +table Buffer { + data:[ubyte] (force_align: 16); +} + +table Model { + // Version of the schema. + version:uint; + + // A list of all operator codes used in this model. This is + // kept in order because operators carry an index into this + // vector. + operator_codes:[OperatorCode]; + + // All the subgraphs of the model. The 0th is assumed to be the main + // model. + subgraphs:[SubGraph]; + + // A description of the model. + description:string; + + // Buffers of the model. + // Note the 0th entry of this array must be an empty buffer (sentinel). + // This is a convention so that tensors without a buffer can provide 0 as + // their buffer. + buffers:[Buffer]; + + // Metadata about the model. Indirects into the existings buffers list. + metadata_buffer:[int]; +} + +root_type Model; diff --git a/compiler/enco/frontend/tflite/schema/schema.meta b/compiler/enco/frontend/tflite/schema/schema.meta new file mode 100644 index 00000000000..8cc1f4e62ab --- /dev/null +++ b/compiler/enco/frontend/tflite/schema/schema.meta @@ -0,0 +1,2 @@ +Commit: 24963954a84a3e85dc8dfe79a15a01dc33fedab4 +URL: https://github.com/tensorflow/tensorflow/blob/2496395/tensorflow/contrib/lite/schema/schema.fbs diff --git a/compiler/enco/frontend/tflite/src/Context.cpp b/compiler/enco/frontend/tflite/src/Context.cpp new file mode 100644 index 00000000000..ef030dc5df5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Context.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Context.h" + +#include "Convert.h" + +#include +#include + +#include +#include + +#include +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +void TensorContext::prepare(const tflite::SubGraph *graph) +{ + for (uint32_t tensor_id = 0; tensor_id < graph->tensors()->size(); ++tensor_id) + { + auto const tensor_info = graph->tensors()->Get(tensor_id); + auto const tensor_name = tensor_info->name()->str(); + auto const tensor_shape = as_tensor_shape(tensor_info->shape()); + auto const tensor_type = tensor_info->type(); + + _name_ctx[tensor_id] = tensor_name; + _shape_ctx[tensor_id] = tensor_shape; + _type_ctx[tensor_id] = tensor_type; + } +} + +TflOpCodeContext::TflOpCodeContext( + const flatbuffers::Vector> *opcodes) +{ + for (const tflite::OperatorCode *opcode : *opcodes) + { + _opcodes.push_back(opcode); + } +} + +tflite::BuiltinOperator TflOpCodeContext::builtin_code(const tflite::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _opcodes.size()); + const tflite::OperatorCode *opcode = _opcodes.at(index); + return opcode->builtin_code(); +} + +std::string TflOpCodeContext::opcode_name(const tflite::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _opcodes.size()); + const tflite::OperatorCode *opcode = _opcodes.at(index); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid: " << index << ")"; + return oss.str(); + } + + if (is_custom(opcode)) + { + if (!opcode->custom_code()) + return "(invalid custom)"; + + return opcode->custom_code()->c_str(); + } + + tflite::BuiltinOperator code = opcode->builtin_code(); + return EnumNameBuiltinOperator(code); +} + +bool TflOpCodeContext::is_valid(const tflite::OperatorCode *opcode) +{ + tflite::BuiltinOperator code = opcode->builtin_code(); + return (tflite::BuiltinOperator_MIN <= code && code <= tflite::BuiltinOperator_MAX); +} + +bool TflOpCodeContext::is_custom(const tflite::OperatorCode *opcode) +{ + tflite::BuiltinOperator code = opcode->builtin_code(); + return (code == tflite::BuiltinOperator_CUSTOM); +} + +TflBufferContext::TflBufferContext(const tflite::Model *tfl_model) +{ + const flatbuffers::Vector> *tfl_buffers; + + tfl_buffers = tfl_model->buffers(); + + for (uint32_t buffer_id = 0; buffer_id < tfl_buffers->size(); ++buffer_id) + { + _buffer_ctx[buffer_id] = (*tfl_buffers)[buffer_id]; + } +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Context.h b/compiler/enco/frontend/tflite/src/Context.h new file mode 100644 index 00000000000..f72385f9aa5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Context.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONTEXT_H__ +#define __CONTEXT_H__ + +#include "Convert.h" +#include "TensorBags.h" + +#include +#include + +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +/** + * @brief Extracts and holds operand(tensor) information such as name, shape, and type + */ +class TensorContext +{ +public: + void prepare(const tflite::SubGraph *graph); + + const std::string &name(uint32_t tensor_id) { return _name_ctx[tensor_id]; } + const tensor::Shape &shape(uint32_t tensor_id) { return _shape_ctx[tensor_id]; } + const tflite::TensorType &type(uint32_t tensor_id) { return _type_ctx[tensor_id]; } + +private: + std::map _name_ctx; + std::map _shape_ctx; + std::map _type_ctx; +}; + +/** + * @brief Class that holds operator codes and related methods + */ +class TflOpCodeContext +{ +public: + TflOpCodeContext(const flatbuffers::Vector> *opcodes); + + /** + * @brief Returns BuiltinOperator value of the operator + */ + tflite::BuiltinOperator builtin_code(const tflite::Operator *op) const; + + /** + * @brief Returns human readable name of the operator code of the operator + * + * @note TF lite InterpreterBuilder sets an error state and returns error code + * for invalid opcode. Here we just return human readable message as + * this method returns a name for the operator code. + */ + std::string opcode_name(const tflite::Operator *op) const; + +public: + static bool is_valid(const tflite::OperatorCode *opcode); + static bool is_custom(const tflite::OperatorCode *opcode); + +private: + std::vector _opcodes; +}; + +/** + * @brief Class to read and provide buffer information of tflite + */ +class TflBufferContext +{ +public: + template struct TflBuffer + { + TflBuffer(const T *p, size_t s) : ptr{p}, len{s} {}; + const T *ptr; + size_t len; + }; + +public: + explicit TflBufferContext(const tflite::Model *tfl_model); + +public: + template + TflBuffer tensor_buffer(const tflite::SubGraph *graph, uint32_t tensor_idx) const + { + TflBufferContext::TflBuffer res{nullptr, 0}; + const auto *tensor = graph->tensors()->Get(tensor_idx); + uint32_t tfl_buf_id = tensor->buffer(); + + assert(_buffer_ctx.size() > tfl_buf_id); + + const tflite::Buffer *tfl_buffer = _buffer_ctx.at(tfl_buf_id); + + if (auto *array = tfl_buffer->data()) + { + if (size_t size = array->size()) + { + assert(size % sizeof(T) == 0); + + res.len = size / sizeof(T); + res.ptr = reinterpret_cast(array->data()); + } + } + + return res; + } + +private: + std::map _buffer_ctx; +}; + +/** + * @brief Class to store context to build IR from tflite + */ +class GraphBuilderContext +{ +public: + explicit GraphBuilderContext(coco::Module *m, coco::Data *d, coco::Block *block, + TensorBags &tensor_bags, TensorContext &tensor_context, + TflBufferContext &buffer_context, const tflite::SubGraph *graph) + : _m(m), _d(d), _block(block), _tensor_bags(tensor_bags), _tensor_context(tensor_context), + _buffer_context(buffer_context), _graph(graph) + { + // DO NOTHING + } + + GraphBuilderContext() = delete; + GraphBuilderContext(const GraphBuilderContext &) = delete; + GraphBuilderContext(GraphBuilderContext &&) = delete; + +public: + coco::Module *m() { return _m; } + coco::Data *d() { return _d; } + coco::Block *block() { return _block; } + TensorContext &tensor() { return _tensor_context; } + TensorBags &bags() { return _tensor_bags; } + TflBufferContext &buffer() { return _buffer_context; } + const tflite::SubGraph *graph() { return _graph; } + +private: + coco::Module *_m; + coco::Data *_d; + coco::Block *_block; + TensorContext &_tensor_context; + TensorBags &_tensor_bags; + TflBufferContext &_buffer_context; + const tflite::SubGraph *_graph; +}; + +} // namespace tflimport + +#endif // __CONTEXT_H__ diff --git a/compiler/enco/frontend/tflite/src/Convert.cpp b/compiler/enco/frontend/tflite/src/Convert.cpp new file mode 100644 index 00000000000..ffae95d0147 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Convert.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Convert.h" + +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +IndexVector as_index_vector(const flatbuffers::Vector *array) +{ + const uint32_t size = array->size(); + + std::vector res(size); + + for (uint32_t i = 0; i < size; i++) + { + res[i] = array->Get(i); + } + + return res; +} + +tensor::Shape as_tensor_shape(const flatbuffers::Vector *shape) +{ + const uint32_t rank = shape->size(); + + tensor::Shape res; + + res.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + res.dim(axis) = shape->Get(axis); + } + + return res; +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Convert.h b/compiler/enco/frontend/tflite/src/Convert.h new file mode 100644 index 00000000000..fb4c248bfbc --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Convert.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +using IndexVector = std::vector; + +/** + * @brief Converts flatbuffers::Vector to IndexVector + */ +IndexVector as_index_vector(const flatbuffers::Vector *array); + +/** + * @brief Converts flatbuffers::Vector to nncc::core::ADT::tensor::Shape + */ +tensor::Shape as_tensor_shape(const flatbuffers::Vector *shape); + +} // namespace tflimport + +#endif // __CONVERT_H__ diff --git a/compiler/enco/frontend/tflite/src/Entry.cpp b/compiler/enco/frontend/tflite/src/Entry.cpp new file mode 100644 index 00000000000..c69e180743c --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Entry.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Frontend.h" +#include "RawModelLoader.h" + +#include + +#include + +#include +#include + +using stdex::make_unique; + +extern "C" std::unique_ptr make_frontend(const cmdline::View &cmdline) +{ + assert(cmdline.size() == 1); // tflite file name + + auto model = load_from(cmdline.at(0)); + + return make_unique(std::move(model)); +} diff --git a/compiler/enco/frontend/tflite/src/Frontend.cpp b/compiler/enco/frontend/tflite/src/Frontend.cpp new file mode 100644 index 00000000000..c64f181f462 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Frontend.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Frontend.h" +#include "Context.h" +#include "Convert.h" +#include "TensorBags.h" +#include "GraphBuilderRegistry.h" + +#include +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +/** + * @brief Set module input operands and its information + */ +void set_module_inputs(coco::Module *m, TensorContext &ctx, TensorBags &bags, + const IndexVector &inputs) +{ + for (uint32_t n = 0; n < inputs.size(); ++n) + { + auto const tensor_id = inputs.at(n); + + auto const tensor_name = ctx.name(tensor_id); + auto const tensor_shape = ctx.shape(tensor_id); + auto const tensor_bag = bags.bag(tensor_id); + + auto input = m->entity()->input()->create(tensor_shape); + + input->name(tensor_name); + input->bag(tensor_bag); + input->reorder(); + + m->input()->insert(input); + } +} + +/** + * @brief Set module output operands and its information + */ +void set_module_outputs(coco::Module *m, TensorContext &ctx, TensorBags &bags, + const IndexVector &outputs) +{ + for (uint32_t n = 0; n < outputs.size(); ++n) + { + auto const tensor_id = outputs.at(n); + + auto const tensor_name = ctx.name(tensor_id); + auto const tensor_shape = ctx.shape(tensor_id); + auto const tensor_bag = bags.bag(tensor_id); + + auto output = m->entity()->output()->create(tensor_shape); + + output->name(tensor_name); + output->bag(tensor_bag); + output->reorder(); + + m->output()->insert(output); + } +} + +/** + * @brief Copy values of tfl tensors into coco::Data if the data was not copied + */ +void copy_tensors(GraphBuilderContext *ctx) +{ + auto d = ctx->d(); + + // for each bag, check if bag is not allocated but tflite tensor has values + for (auto &iter : ctx->bags()) + { + auto tfl_tensor_id = iter.first; + auto bag = iter.second; + + auto tfl_buffer = ctx->buffer().tensor_buffer(ctx->graph(), tfl_tensor_id); + + // TODO remove this line when support int32 is ready + if (ctx->tensor().type(tfl_tensor_id) == tflite::TensorType::TensorType_INT32) + { + std::cout << "*** INT32 COPYING IS NOT SUPPORTED ***" << std::endl; + continue; + } + + assert(ctx->tensor().type(tfl_tensor_id) == tflite::TensorType::TensorType_FLOAT32); + + auto span = d->f32()->weight(bag); // TODO support other type + + if (!(span.data() == nullptr && span.size() == 0)) // already allocated + continue; + + if (tfl_buffer.ptr == nullptr || tfl_buffer.len == 0) // no data to copy + continue; + + d->f32()->allocate(bag); + + auto ifm_span = d->f32()->weight(bag); + for (uint32_t idx = 0; idx < tfl_buffer.len; ++idx) + { + ifm_span[idx] = tfl_buffer.ptr[idx]; + } + } +} + +} // namespace tflimport + +Frontend::Frontend(std::unique_ptr &&raw) : _raw{std::move(raw)} +{ + // DO NOTHING +} + +enco::Bundle Frontend::load(void) const +{ + auto model = _raw->model(); + + assert(model->version() == 3); + assert(model->subgraphs()->size() == 1); + + auto graph = model->subgraphs()->Get(0); + + auto m = coco::Module::create(); + auto d = coco::Data::create(); + + tflimport::TensorContext tensor_context; + tflimport::TensorBags tensor_bags; + + tensor_context.prepare(graph); + tensor_bags.prepare(graph, m); + + auto inputs = tflimport::as_index_vector(graph->inputs()); + auto outputs = tflimport::as_index_vector(graph->outputs()); + + tflimport::set_module_inputs(m.get(), tensor_context, tensor_bags, inputs); + tflimport::set_module_outputs(m.get(), tensor_context, tensor_bags, outputs); + + auto blk = m->entity()->block()->create(); + m->block()->append(blk); + + auto opcodes = model->operator_codes(); + + tflimport::TflBufferContext buffer_context(model); + tflimport::TflOpCodeContext opcode_context(opcodes); + + auto operators = graph->operators(); + + tflimport::GraphBuilderContext opbuilder_context(m.get(), d.get(), blk, tensor_bags, + tensor_context, buffer_context, graph); + + for (int i = 0; i < operators->Length(); ++i) + { + const auto *op = operators->Get(i); + tflite::BuiltinOperator builtincode = opcode_context.builtin_code(op); + + if (const auto *graph_builder = tflimport::GraphBuilderRegistry::get().lookup(builtincode)) + { + if (!graph_builder->validate(op)) + { + throw std::runtime_error{"Invalid operator"}; + } + + graph_builder->build(op, &opbuilder_context); + } + else + { + std::string opcodename = opcode_context.opcode_name(op); + throw std::runtime_error{"Not supported: " + opcodename}; + } + + // copying unfilled tensor value + copy_tensors(&opbuilder_context); + } + + // Create "Bundle" + enco::Bundle bundle; + + bundle.module(std::move(m)); + bundle.data(std::move(d)); + + return std::move(bundle); +} diff --git a/compiler/enco/frontend/tflite/src/Frontend.h b/compiler/enco/frontend/tflite/src/Frontend.h new file mode 100644 index 00000000000..bb0c9cd2c00 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Frontend.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FRONTEND_H__ +#define __FRONTEND_H__ + +#include "RawModel.h" + +#include + +#include + +#include + +class Frontend final : public enco::Frontend +{ +public: + Frontend(std::unique_ptr &&raw); + +public: + enco::Bundle load(void) const override; + +private: + std::unique_ptr _raw; +}; + +#endif // __FRONTEND_H__ diff --git a/compiler/enco/frontend/tflite/src/Frontend.test.cpp b/compiler/enco/frontend/tflite/src/Frontend.test.cpp new file mode 100644 index 00000000000..aee6099e733 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Frontend.test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Frontend.h" + +#include + +#include + +using stdex::make_unique; + +namespace +{ + +struct MockRawModel final : public RawModel +{ + const tflite::Model *model(void) const override { return nullptr; } +}; + +} // namespace + +TEST(FrontendTest, constructor) +{ + // Let's test whether Frontend is actually constructible. + auto frontend = make_unique(make_unique()); + + ASSERT_NE(frontend, nullptr); +} diff --git a/compiler/enco/frontend/tflite/src/GraphBuilder.h b/compiler/enco/frontend/tflite/src/GraphBuilder.h new file mode 100644 index 00000000000..f2cb5784840 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/GraphBuilder.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BUILDER_H__ +#define __GRAPH_BUILDER_H__ + +#include "Context.h" + +#include + +namespace tflimport +{ + +/** + * @brief Parent class of tflite operation graph builders (e.g., Conv2DGraphBuilder) + */ +class GraphBuilder +{ +public: + /** + * TODO Declare "validate" method as a pure virtual method + * + * Q: Is it possible to validate T/F Lite model only with this interface? + */ + virtual bool validate(const tflite::Operator *) const { return true; } + + virtual void build(const tflite::Operator *op, GraphBuilderContext *context) const = 0; + virtual ~GraphBuilder() {} +}; + +} // namespace tflimport + +#endif // __GRAPH_BUILDER_H__ diff --git a/compiler/enco/frontend/tflite/src/GraphBuilderRegistry.h b/compiler/enco/frontend/tflite/src/GraphBuilderRegistry.h new file mode 100644 index 00000000000..1ae882e899f --- /dev/null +++ b/compiler/enco/frontend/tflite/src/GraphBuilderRegistry.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BUILDER_REGISTRY_H__ +#define __GRAPH_BUILDER_REGISTRY_H__ + +#include "Op/Conv2D.h" +#include "Op/DepthwiseConv2D.h" +#include "Op/AveragePool2D.h" +#include "Op/MaxPool2D.h" +#include "Op/Concatenation.h" +#include "Op/ReLU.h" +#include "Op/ReLU6.h" +#include "Op/Reshape.h" +#include "Op/Sub.h" +#include "Op/Div.h" + +#include +#include + +#include + +using stdex::make_unique; + +namespace tflimport +{ + +/** + * @brief Class to return graph builder for passed tflite::builtinOperator + */ +class GraphBuilderRegistry +{ +public: + /** + * @brief Returns registered GraphBuilder pointer for BuiltinOperator or + * nullptr if not registered + */ + const GraphBuilder *lookup(tflite::BuiltinOperator op) const + { + if (_builder_map.find(op) == _builder_map.end()) + return nullptr; + + return _builder_map.at(op).get(); + } + + static GraphBuilderRegistry &get() + { + static GraphBuilderRegistry me; + return me; + } + +private: + GraphBuilderRegistry() + { + // add GraphBuilder for each tflite operation. + _builder_map[tflite::BuiltinOperator_CONV_2D] = make_unique(); + _builder_map[tflite::BuiltinOperator_DEPTHWISE_CONV_2D] = + make_unique(); + _builder_map[tflite::BuiltinOperator_AVERAGE_POOL_2D] = make_unique(); + _builder_map[tflite::BuiltinOperator_MAX_POOL_2D] = make_unique(); + _builder_map[tflite::BuiltinOperator_CONCATENATION] = make_unique(); + _builder_map[tflite::BuiltinOperator_RELU] = make_unique(); + _builder_map[tflite::BuiltinOperator_RELU6] = make_unique(); + _builder_map[tflite::BuiltinOperator_RESHAPE] = make_unique(); + _builder_map[tflite::BuiltinOperator_SUB] = make_unique(); + _builder_map[tflite::BuiltinOperator_DIV] = make_unique(); + } + +private: + std::map> _builder_map; +}; + +} // namespace tflimport + +#endif // __GRAPH_BUILDER_REGISTRY_H__ diff --git a/compiler/enco/frontend/tflite/src/IRBuilder.h b/compiler/enco/frontend/tflite/src/IRBuilder.h new file mode 100644 index 00000000000..edfe247e16c --- /dev/null +++ b/compiler/enco/frontend/tflite/src/IRBuilder.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @file IRBuilder.h + * @brief coco IR builders. This is code is copied from enco caffe frontend. + */ +#ifndef __IR_BUILDER_H__ +#define __IR_BUILDER_H__ + +#include "coco/IR/Module.h" + +#include + +using namespace nncc::core::ADT; + +class OpBuilder +{ +public: + OpBuilder(coco::Module *module) : _module{module} + { + // module SHOULD BE valid + assert(_module != nullptr); + } + +public: + /** + * @brief Return true if the internal stack is empty + */ + bool empty(void) const { return _stack.empty(); } + + /** + * @brief Return the operation at the top of the internal stack + */ + coco::Op *top(void) const + { + assert(_stack.size() > 0); + return _stack.front(); + } + + /** + * @brief Push op onto the internal stack + * + * BEFORE| Stack + * AFTER | Op; Stack + */ + OpBuilder &push(coco::Op *op) + { + _stack.push_front(op); + return (*this); + } + + /** + * @brief Create "Load" op and push it onto the internal stack + * + * BEFORE| Stack + * AFTER | Load(obj); Stack + */ + OpBuilder &load(coco::Object *obj) + { + auto op = _module->entity()->op()->create(); + op->object(obj); + push(op); + return (*this); + } + + /** + * @brief Create "Add" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Add(Left, Right); Stack + */ + OpBuilder &add(void) { return binary(); } + + /** + * @brief Create "Mul" op and push it onto the internal stack + * + * BEFORE| Left; Right; Stack + * AFTER | Mul(Left, Right); Stack + */ + OpBuilder &mul(void) { return binary(); } + + /** + * @brief Pop op from the internal stack + * + * BEFORE| Op; Stack + * AFTER | Stack + */ + coco::Op *pop(void) + { + assert(_stack.size() > 0); + auto op = _stack.front(); + _stack.pop_front(); + return op; + } + +private: + template OpBuilder &binary() + { + assert(_stack.size() >= 2); + auto left = pop(); + auto right = pop(); + + auto op = _module->entity()->op()->create(); + op->left(left); + op->right(right); + push(op); + + return (*this); + } + +private: + coco::Module *_module; + std::deque _stack; +}; + +inline OpBuilder op_builder(coco::Module *m) { return OpBuilder{m}; } +inline OpBuilder op_builder(const std::unique_ptr &m) { return op_builder(m.get()); } + +class InstrBuilder +{ +public: + InstrBuilder(coco::Module *module) : _module{module} + { + // NOTE _module SHOULD be valid + assert(_module != nullptr); + } + +public: + /** + * @brief Create "Eval" instruction with a given "Object" and "Op" + * + * @note "eval(out, op)" will create "%out <- Eval(op)" instruction + */ + coco::Eval *eval(coco::Object *out, coco::Op *op) const + { + auto ins = _module->entity()->instr()->create(); + ins->op(op); + ins->out(out); + return ins; + } + + /** + * @brief Create "Copy" instruction with given two "Object" + * + * @note "copy(into, from)" will create "%into <- Copy(%from)" instruction + */ + coco::Copy *copy(coco::Object *into, coco::Object *from) const + { + auto ins = _module->entity()->instr()->create(); + ins->from(from); + ins->into(into); + return ins; + } + +private: + coco::Module *_module; +}; + +using ModuleHandle = std::unique_ptr; + +inline InstrBuilder instr_builder(coco::Module *m) { return InstrBuilder{m}; } +inline InstrBuilder instr_builder(const ModuleHandle &m) { return instr_builder(m.get()); } + +#endif // __IR_BUILDER_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Activation.cpp b/compiler/enco/frontend/tflite/src/Op/Activation.cpp new file mode 100644 index 00000000000..d6215ba34b5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Activation.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Activation.h" + +#include + +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +coco::FeatureObject *build_activation(tflite::ActivationFunctionType act, coco::Block *block, + coco::FeatureObject *ifm) +{ + assert(ifm != nullptr && ifm->asFeature() != nullptr); // support feature only in this version + + coco::Module *m = block->module(); + + auto shape = ifm->asFeature()->shape(); + + // creates output object + auto output_obj = m->entity()->object()->create(); + auto output_bag = m->entity()->bag()->create(num_elements(shape)); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(shape)); + + switch (act) + { + case tflite::ActivationFunctionType::ActivationFunctionType_NONE: + { + // Create Copy Instr (copying from ifm to output_obj), + // redundant layer but optimized by backend + auto copy_ins = instr_builder(m).copy(output_obj, ifm); + + // Append the instruction to the block + block->instr()->append(copy_ins); + break; + } + case tflite::ActivationFunctionType::ActivationFunctionType_RELU: + { + // Create Eval(output_obj, ReLU(load(ifm))) + auto load_op = op_builder(m).load(ifm).pop(); + auto relu_op = m->entity()->op()->create(); + relu_op->arg(load_op); + + auto eval_ins = instr_builder(m).eval(output_obj, relu_op); + + // Append the instruction to the block + block->instr()->append(eval_ins); + break; + } + case tflite::ActivationFunctionType::ActivationFunctionType_RELU6: + { + // Create Eval(output_obj, ReLU6(load(ifm))) + auto load_op = op_builder(m).load(ifm).pop(); + auto relu6_op = m->entity()->op()->create(); + relu6_op->arg(load_op); + + auto eval_ins = instr_builder(m).eval(output_obj, relu6_op); + + // Append the instruction to the block + block->instr()->append(eval_ins); + break; + } + default: + // TODO support other fused activations + assert(false); + break; + } + + return output_obj; +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Activation.h b/compiler/enco/frontend/tflite/src/Op/Activation.h new file mode 100644 index 00000000000..05306dd410c --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Activation.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_ACTIVATION_H__ +#define __OP_ACTIVATION_H__ + +#include +#include + +#include + +namespace tflimport +{ + +/** + * @brief Add coco::Eval for fused activation. + * This method creates an ofm object, appends Eval(ofm object, RELU(...)) into block, + * and returns ofm object. + */ +coco::FeatureObject *build_activation(tflite::ActivationFunctionType act, coco::Block *block, + coco::FeatureObject *ifm); +} // namespace tflimport + +#endif // __OP_ACTIVATION_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/AveragePool2D.cpp b/compiler/enco/frontend/tflite/src/Op/AveragePool2D.cpp new file mode 100644 index 00000000000..16f68fcdbf7 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/AveragePool2D.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "AveragePool2D.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Padding.h" +#include "Activation.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +bool AvgPool2DGraphBuilder::validate(const tflite::Operator *op) const +{ + auto const options = op->builtin_options_as_Pool2DOptions(); + + if ((options->stride_h() == 0) || (options->stride_w() == 0)) + { + return false; + } + + return true; +} + +void AvgPool2DGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // output index 0 : output feature + assert(opinputs.size() == 1); + assert(opoutputs.size() == 1); + + int ifm_idx = opinputs.at(0); + int ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + + // Create an object for an input feature map + coco::FeatureObject *ifm_obj = m->entity()->object()->create(); + coco::Bag *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + coco::FeatureObject *ofm_obj = m->entity()->object()->create(); + coco::Bag *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create a Load op + auto coco_load = op_builder(m).load(ifm_obj).pop(); + + // Create a AvgPool2D + auto coco_avgpool2d = m->entity()->op()->create(); + auto *params = op->builtin_options_as_Pool2DOptions(); + + // NOTE For Tensorflow lite, PaddingExcluded is needed + coco_avgpool2d->divisor(coco::AvgPool2D::Divisor::PaddingExcluded); + + coco_avgpool2d->window()->height(params->filter_height()); + coco_avgpool2d->window()->width(params->filter_width()); + + coco_avgpool2d->stride()->vertical(params->stride_h()); + coco_avgpool2d->stride()->horizontal(params->stride_w()); + + coco::Padding2D padding = + pool2D_padding(params, ifm_shape, params->filter_width(), params->filter_height()); + + coco_avgpool2d->pad()->top(padding.top()); + coco_avgpool2d->pad()->bottom(padding.bottom()); + coco_avgpool2d->pad()->left(padding.left()); + coco_avgpool2d->pad()->right(padding.right()); + + // Link ops + coco_avgpool2d->arg(coco_load); + + // Create an Eval instruction + auto ins = instr_builder(m).eval(ofm_obj, coco_avgpool2d); + + // Append the instruction to the block + blk->instr()->append(ins); + + // TODO activation, e.g., relu + assert(params->fused_activation_function() == + tflite::ActivationFunctionType::ActivationFunctionType_NONE); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/AveragePool2D.h b/compiler/enco/frontend/tflite/src/Op/AveragePool2D.h new file mode 100644 index 00000000000..3e37e3cadb5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/AveragePool2D.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_AVERAGEPOOL2D_H__ +#define __OP_AVERAGEPOOL2D_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for AvgPool2D operator + */ +class AvgPool2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const tflite::Operator *op) const override; + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_AVERAGEPOOL2D_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Concatenation.cpp b/compiler/enco/frontend/tflite/src/Op/Concatenation.cpp new file mode 100644 index 00000000000..ce0f47b21e6 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Concatenation.cpp @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Concatenation.h" + +#include "IRBuilder.h" +#include "GraphBuilder.h" + +#include +#include + +#include +#include + +#include +#include + +using namespace nncc::core::ADT; + +namespace +{ + +/** + * @brief Convert a numeric tensor axis as a ConcatF FeatureAxis value + */ +coco::ConcatF::Axis as_ConcatF_axis(uint32_t axis) +{ + // NOTE The feature map (in TensorFlow) is a rank-4 (NHWC) tensor + assert(axis < 4); + + coco::ConcatF::Axis res = coco::ConcatF::Axis::Unknown; + + switch (axis) + { + case 0: + res = coco::ConcatF::Axis::Batch; + break; + case 1: + res = coco::ConcatF::Axis::Height; + break; + case 2: + res = coco::ConcatF::Axis::Width; + break; + case 3: + res = coco::ConcatF::Axis::Depth; + break; + default: + break; + } + + return res; +} + +/** + * @brief Convert a coco FeatureShape as an array of 'uint32_t' values + */ +std::array as_dims(const coco::FeatureShape &shape) +{ + std::array res; + + res[0] = shape.batch(); + res[1] = shape.height(); + res[2] = shape.width(); + res[3] = shape.depth(); + + return res; +} + +/** + * @brief Convert a tensor shape as a coco FeatureShape + */ +coco::FeatureShape as_feature_shape(const tensor::Shape &shape) +{ + assert(shape.rank() == 4); + + auto const B = shape.dim(0); + auto const C = shape.dim(3); + auto const H = shape.dim(1); + auto const W = shape.dim(2); + + return coco::FeatureShape{B, C, H, W}; +} + +} // namespace + +namespace tflimport +{ + +void ConcatenationGraphBuilder::build(const tflite::Operator *op, + GraphBuilderContext *context) const +{ + assert(context != nullptr); + + coco::Module *m = context->m(); + coco::Data *d = context->d(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 ~ N : any number of input features + // output index 0 : one output feature + assert(opinputs.size() > 0); + assert(opoutputs.size() == 1); + + // Default parameter values are referenced from schema_generated.h + int32_t concat_axis = 0; + tflite::ActivationFunctionType activation = tflite::ActivationFunctionType_NONE; + + if (auto *concatenation_params = op->builtin_options_as_ConcatenationOptions()) + { + activation = concatenation_params->fused_activation_function(); + concat_axis = concatenation_params->axis(); + + const int32_t rank = static_cast(tensor_context.shape(opinputs.at(0)).rank()); + if (concat_axis < 0) + { + concat_axis += rank; + } + assert(concat_axis >= 0); + assert(concat_axis < rank); + } + assert(as_ConcatF_axis(concat_axis) != coco::ConcatF::Axis::Unknown); + assert(activation == tflite::ActivationFunctionType_NONE); + + // Construct a vector of input objects + std::vector input_objects; + + for (auto &input_index : opinputs) + { + const tensor::Shape &input_shape = tensor_context.shape(input_index); + coco::FeatureObject *input_obj = m->entity()->object()->create(); + coco::Bag *input_bag = bags.bag(input_index); + input_obj->bag(input_bag); + input_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(input_shape))); + + input_objects.emplace_back(input_obj); + } + + coco::FeatureObject *last_feature = input_objects.at(0); + + assert(last_feature != nullptr); + assert(last_feature->bag() != nullptr); + + // Update coco IR + // + // Given a sequence of input features %in[0] / %in[1] / ... / %in[N] + // the below code constructs a sequence of eval instructions + // - Load is omitted for simplicity + // + // %tmp = eval(ConcatF(%in[0], %in[1])) + // %tmp = eval(ConcatF(%tmp, %in[2])) + // ... + // %tmp = eval(ConcatF(%tmp, %in[N])) + // %out[0] = copy(%tmp) + // + for (uint32_t n = 1; n < input_objects.size(); ++n) + { + auto const left_feature = last_feature; + auto const left_shape = left_feature->layout()->shape(); + + auto right_feature = input_objects.at(n); + auto right_shape = right_feature->layout()->shape(); + + // Compute output dimensionalities + auto compute_out_dims = [&left_shape, &right_shape, concat_axis](void) { + std::array out_dims; + + const auto left_dims = as_dims(left_shape); + const auto right_dims = as_dims(right_shape); + + for (uint32_t axis = 0; axis < 4 /* FEATURE MAP RANK */; ++axis) + { + // The dimensionality of all the axises except 'concat' axis SHOULD BE INDETICAL + assert((concat_axis == axis) || (left_dims[axis] == right_dims[axis])); + + out_dims[axis] = left_dims[axis]; + if (axis == concat_axis) + { + out_dims[axis] += right_dims[axis]; + } + } + + return out_dims; + }; + + const auto out_dims = compute_out_dims(); + + const uint32_t B = out_dims[0 /* BATCH */]; + const uint32_t C = out_dims[3 /* DEPTH */]; + const uint32_t H = out_dims[1 /* HEIGHT */]; + const uint32_t W = out_dims[2 /* WIDTH */]; + + const coco::FeatureShape out_shape{B, C, H, W}; + + auto out_bag = m->entity()->bag()->create(B * num_elements(out_shape)); + auto out_feature = m->entity()->object()->create(); + + out_feature->bag(out_bag); + out_feature->layout(coco::FeatureLayouts::BHWC::create(out_shape)); + + auto left_load = op_builder(m).load(left_feature).pop(); + auto right_load = op_builder(m).load(right_feature).pop(); + + auto concat_f = m->entity()->op()->create(); + + concat_f->axis(as_ConcatF_axis(concat_axis)); + concat_f->left(left_load); + concat_f->right(right_load); + + auto eval = instr_builder(m).eval(out_feature, concat_f); + + // Append the constructed Shuffle instruction + blk->instr()->append(eval); + + // Update 'last_feature' + last_feature = out_feature; + } + + // Insert copy instruction from last_feature to output operand + int const ofm_idx = opoutputs.at(0); + auto const ofm_shape = tensor_context.shape(ofm_idx); + + auto ofm_bag = bags.bag(ofm_idx); + auto ofm_obj = m->entity()->object()->create(); + + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create a Copy instruction from last into ofm + auto copy_ins = instr_builder(m).copy(ofm_obj, last_feature); + + // Append the instruction + blk->instr()->append(copy_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Concatenation.h b/compiler/enco/frontend/tflite/src/Op/Concatenation.h new file mode 100644 index 00000000000..eb7625a85f5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Concatenation.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_CONCATENATION_H__ +#define __OP_CONCATENATION_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for Concatenation operator + */ +class ConcatenationGraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_CONCATENATION_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Conv2D.cpp b/compiler/enco/frontend/tflite/src/Op/Conv2D.cpp new file mode 100644 index 00000000000..e9516c0e923 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Conv2D.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Conv2D.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Padding.h" +#include "Activation.h" + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +bool Conv2DGraphBuilder::validate(const tflite::Operator *op) const +{ + auto const options = op->builtin_options_as_Conv2DOptions(); + + if ((options->stride_h() == 0) || (options->stride_w() == 0)) + { + return false; + } + + return true; +} + +void Conv2DGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); + + // preparation + coco::Module *m = context->m(); + coco::Data *d = context->d(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + TflBufferContext &buffer_context = context->buffer(); + const tflite::SubGraph *graph = context->graph(); + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // input index 1 : kernel + // input index 2 : bias (optional) + bool hasBias = (opinputs.size() == 3); + assert(opinputs.size() == 2 || hasBias); + assert(opoutputs.size() == 1); + + int ifm_idx = opinputs.at(0); + int ker_idx = opinputs.at(1); + int ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + const tensor::Shape &ker_shape = tensor_context.shape(ker_idx); + + // Create an input feature map object + auto *ifm_obj = m->entity()->object()->create(); + auto *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an an output feature map object + auto *ofm_obj = m->entity()->object()->create(); + auto *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create an kernel object + auto *ker_obj = m->entity()->object()->create(); + auto *ker_bag = bags.bag(ker_idx); + ker_obj->bag(ker_bag); + ker_obj->layout(coco::KernelLayouts::NHWC::create(as_kernel_shape(ker_shape))); + + // Create a Load op + auto load = op_builder(m).load(ifm_obj).pop(); + + // Create a Conv2D op + auto coco_conv2d = m->entity()->op()->create(); + + // populating Conv2D objects and options such as stride and padding + coco_conv2d->ker(ker_obj); + + auto *conv_params = op->builtin_options_as_Conv2DOptions(); + + coco_conv2d->stride()->vertical(conv_params->stride_h()); + coco_conv2d->stride()->horizontal(conv_params->stride_w()); + + // conv_params->padding() to left, top, right, bottom + coco::Padding2D padding = conv2D_padding(conv_params, ifm_shape, ker_shape); + + coco_conv2d->pad()->top(padding.top()); + coco_conv2d->pad()->bottom(padding.bottom()); + coco_conv2d->pad()->left(padding.left()); + coco_conv2d->pad()->right(padding.right()); + + // Link ops + coco_conv2d->arg(load); + + // Object to store Conv2D output + auto *conv2d_obj = m->entity()->object()->create(); + auto *conv2d_bag = m->entity()->bag()->create(num_elements(ofm_shape)); + conv2d_obj->bag(conv2d_bag); + conv2d_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create an Eval instruction for Conv2D + auto conv2d_ins = instr_builder(m).eval(conv2d_obj, coco_conv2d); + + // Append the instruction to the block + blk->instr()->append(conv2d_ins); + + // Last Object to make a copy to Output Object + coco::FeatureObject *last_obj = conv2d_obj; + + if (hasBias) + { + // When there is a bias, use btmp_obj as bias add output + // Bias is adding last_obj with bias weight values + auto *btmp_obj = m->entity()->object()->create(); + auto *btmp_bag = m->entity()->bag()->create(num_elements(ofm_shape)); + btmp_obj->bag(btmp_bag); + btmp_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_obj->shape())); + + int bias_idx = opinputs.at(2); + + // Create an object for bias + auto bias_obj = m->entity()->object()->create(); + coco::Bag *bias_bag = bags.bag(bias_idx); + bias_obj->bag(bias_bag); + bias_obj->layout(coco::FeatureLayouts::BC::create(ofm_obj->shape())); + + // Create Op of conv2d output (last_obj) + bias values(bias_obj) + auto bias_add = op_builder(m).load(last_obj).load(bias_obj).add().pop(); + + // Create Instr as bias add result write to btmp_obj + auto bias_add_ins = instr_builder(m).eval(btmp_obj, bias_add); + + // Append the instruction + blk->instr()->append(bias_add_ins); + + // Update last_obj to btmp_obj + last_obj = btmp_obj; + } + + // fused activation + coco::FeatureObject *act_output = + build_activation(conv_params->fused_activation_function(), blk, last_obj); + + // Create Copy Instr of last_obj to Output Object + auto copy_ins = instr_builder(m).copy(ofm_obj, act_output); + blk->instr()->append(copy_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Conv2D.h b/compiler/enco/frontend/tflite/src/Op/Conv2D.h new file mode 100644 index 00000000000..018815bd417 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Conv2D.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_CONV2D_H__ +#define __OP_CONV2D_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for Conv2D operator + */ +class Conv2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const tflite::Operator *op) const override; + void build(const tflite::Operator *op, GraphBuilderContext *context) const override; +}; + +} // namespace tflimport + +#endif // __OP_CONV2D_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.cpp b/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.cpp new file mode 100644 index 00000000000..e3d7b263e11 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DepthwiseConv2D.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Padding.h" +#include "Activation.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +bool DepthwiseConv2DGraphBuilder::validate(const tflite::Operator *op) const +{ + auto const options = op->builtin_options_as_DepthwiseConv2DOptions(); + + if ((options->stride_h() == 0) || (options->stride_w() == 0)) + { + return false; + } + + return true; +} + +void DepthwiseConv2DGraphBuilder::build(const tflite::Operator *op, + GraphBuilderContext *context) const +{ + assert(context != nullptr); + + // preparation + coco::Module *m = context->m(); + coco::Data *d = context->d(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + TflBufferContext &buffer_context = context->buffer(); + const tflite::SubGraph *graph = context->graph(); + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // input index 1 : kernel + // input index 2 : bias (optional) + bool hasBias = (opinputs.size() == 3); + assert(opinputs.size() == 2 || hasBias); + assert(opoutputs.size() == 1); + + int ifm_idx = opinputs.at(0); + int ker_idx = opinputs.at(1); + int ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + tensor::Shape &ker_shape = const_cast(tensor_context.shape(ker_idx)); + + assert(ifm_shape.rank() == 4); + assert(ofm_shape.rank() == 4); + assert(ker_shape.rank() == 4); + + assert(ker_shape.dim(0) == 1); // value > 1 was not tested. This value seems 1 in DepthwiseConv2D + assert(ifm_shape.dim(3) == ofm_shape.dim(3)); + assert(ofm_shape.dim(3) == ker_shape.dim(3)); + + // Create an input feature map object + auto *ifm_obj = m->entity()->object()->create(); + auto *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an an output feature map object + auto *ofm_obj = m->entity()->object()->create(); + auto *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create an kernel object + auto *ker_obj = m->entity()->object()->create(); + auto *ker_bag = bags.bag(ker_idx); + ker_obj->bag(ker_bag); + + // Adjust tflite kernel shape [1, h, w, channel_out] for coco::Kernel. + // coco::Kernel will have kernel.count = channel_out, kernel.depth = 1 ( == ker_shape.dim(0)) + kernel::Shape new_shape{ker_shape.dim(3), 1, ker_shape.dim(1), ker_shape.dim(2)}; + ker_obj->layout(coco::KernelLayouts::NHWC::create(new_shape)); + + // Create a kernel overlay for the kernel object + // TODO : support for other types + d->f32()->allocate(ker_bag); + + TflBufferContext::TflBuffer buffer = buffer_context.tensor_buffer(graph, ker_idx); + + auto ker_spn = d->f32()->weight(ker_bag); + + // Copy data from tflBuffer of [1, h, w, channel_out] shape to coco::Data, which will be accessed + // by coco::KernelLayouts::NHWC + for (auto n = 0; n < new_shape.count(); n++) + { + auto tfl_c = n; + for (auto h = 0; h < new_shape.height(); h++) + { + for (auto w = 0; w < new_shape.width(); w++) + { + auto hw = new_shape.height() * new_shape.width(); + for (auto c = 0; c < new_shape.depth(); c++) + { + auto tfl_n = c; + auto hwc = hw * new_shape.depth(); + auto wc = new_shape.width() * new_shape.depth(); + + ker_spn[n * hwc + h * wc + w * new_shape.depth() + c] = + buffer.ptr[tfl_n * hw * new_shape.count() + /* new_shape.count() is old c */ + h * new_shape.width() * new_shape.count() + w * new_shape.count() + tfl_c]; + } + } + } + } + + // Create a Load op + auto load = op_builder(m).load(ifm_obj).pop(); + + // Create a coco::Conv2D op for DepthwiseConv2D + auto coco_dconv2d = m->entity()->op()->create(); + + // populating objects and options such as stride and padding for DepthwiseConv2D + coco_dconv2d->ker(ker_obj); + + // setting params passed from TFLITE DepthwiseConv2DOptions + auto dconv_params = op->builtin_options_as_DepthwiseConv2DOptions(); + + assert(dconv_params->depth_multiplier() == 1); // other depth_multiplier was not tested + + coco_dconv2d->group(ifm_obj->asFeature()->shape().depth()); + + coco_dconv2d->stride()->vertical(dconv_params->stride_h()); + coco_dconv2d->stride()->horizontal(dconv_params->stride_w()); + + coco::Padding2D padding = depthwiseConv2D_padding(dconv_params, ifm_shape, ker_shape); + coco_dconv2d->pad()->top(padding.top()); + coco_dconv2d->pad()->bottom(padding.bottom()); + coco_dconv2d->pad()->left(padding.left()); + coco_dconv2d->pad()->right(padding.right()); + + // Link ops + coco_dconv2d->arg(load); + + // Object to store output for DepthwiseConv2D + auto *dconv2d_obj = m->entity()->object()->create(); + auto *dconv2d_bag = m->entity()->bag()->create(num_elements(ofm_shape)); + dconv2d_obj->bag(dconv2d_bag); + dconv2d_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create an Eval instruction for DepthwiseConv2D + auto dconv2d_ins = instr_builder(m).eval(dconv2d_obj, coco_dconv2d); + + // Append the instruction to the block + blk->instr()->append(dconv2d_ins); + + // Last Object to make a copy to Output Object + coco::FeatureObject *last_obj = dconv2d_obj; + + if (hasBias) + { + // When there is a bias, use btmp_obj as bias add output + // Bias is adding last_obj with bias weight values + auto *btmp_obj = m->entity()->object()->create(); + auto *btmp_bag = m->entity()->bag()->create(num_elements(ofm_shape)); + btmp_obj->bag(btmp_bag); + btmp_obj->layout(coco::FeatureLayouts::BHWC::create(ofm_obj->shape())); + + int bias_idx = opinputs.at(2); + + // Create an object for bias + auto bias_obj = m->entity()->object()->create(); + coco::Bag *bias_bag = bags.bag(bias_idx); + bias_obj->bag(bias_bag); + bias_obj->layout(coco::FeatureLayouts::BC::create(ofm_obj->shape())); + + // Create Op of conv2d output (last_obj) + bias values(bias_obj) + auto bias_add = op_builder(m).load(last_obj).load(bias_obj).add().pop(); + + // Create Instr as bias add result write to btmp_obj + auto bias_add_ins = instr_builder(m).eval(btmp_obj, bias_add); + + // Append the instruction + blk->instr()->append(bias_add_ins); + + // Update last_obj to btmp_obj + last_obj = btmp_obj; + } + + // fused activation + coco::FeatureObject *act_output = + build_activation(dconv_params->fused_activation_function(), blk, last_obj); + + // Create Copy Instr of last_obj to Output Object + auto copy_ins = instr_builder(m).copy(ofm_obj, act_output); + blk->instr()->append(copy_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.h b/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.h new file mode 100644 index 00000000000..b36b36b8f09 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/DepthwiseConv2D.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_DEPTHWISECONV2D_H__ +#define __OP_DEPTHWISECONV2D_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for DepthwiseConv2D operator + */ +class DepthwiseConv2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const tflite::Operator *op) const override; + void build(const tflite::Operator *op, GraphBuilderContext *context) const override; +}; + +} // namespace tflimport + +#endif // __OP_DEPTHWISECONV2D_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Div.cpp b/compiler/enco/frontend/tflite/src/Op/Div.cpp new file mode 100644 index 00000000000..6b71be2e614 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Div.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Div.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Padding.h" +#include "Activation.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +void DivGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : numerator + // input index 1 : denominator + // output index 0 : result + assert(opinputs.size() == 2); + assert(opoutputs.size() == 1); + + tflite::ActivationFunctionType activation; + if (auto *options = op->builtin_options_as_DivOptions()) + { + activation = options->fused_activation_function(); + } + else + { + activation = tflite::ActivationFunctionType_NONE; + } + + // TODO activation, e.g. ReLU + assert(activation == tflite::ActivationFunctionType_NONE); + + auto num_idx = opinputs.at(0); + auto denom_idx = opinputs.at(1); + auto out_idx = opoutputs.at(0); + + const tensor::Shape &num_shape = tensor_context.shape(num_idx); + const tensor::Shape &denom_shape = tensor_context.shape(denom_idx); + const tensor::Shape &out_shape = tensor_context.shape(out_idx); + + // TODO Now input/output assumes Feature map, but Div should support generic object type + // Create an object for an input + auto *num_obj = m->entity()->object()->create(); + auto *num_bag = bags.bag(num_idx); + num_obj->bag(num_bag); + num_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(num_shape))); + + auto *denom_obj = m->entity()->object()->create(); + auto *denom_bag = bags.bag(denom_idx); + denom_obj->bag(denom_bag); + denom_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(denom_shape))); + + // Create an object for an output + auto *out_obj = m->entity()->object()->create(); + auto *out_bag = bags.bag(out_idx); + out_obj->bag(out_bag); + out_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(out_shape))); + + // Create a Load ops for each input + auto coco_load_num = op_builder(m).load(num_obj).pop(); + auto coco_load_denom = op_builder(m).load(denom_obj).pop(); + + // Create a Div op + auto coco_div = m->entity()->op()->create(); + + // Link ops + coco_div->left(coco_load_num); + coco_div->right(coco_load_denom); + + // Create an Eval instruction + auto eval_ins = instr_builder(m).eval(out_obj, coco_div); + + // Append the instruction to the block + blk->instr()->append(eval_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Div.h b/compiler/enco/frontend/tflite/src/Op/Div.h new file mode 100644 index 00000000000..053d1a44122 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Div.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_DIV_H__ +#define __OP_DIV_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for Div operator + */ +class DivGraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_DIV_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/MaxPool2D.cpp b/compiler/enco/frontend/tflite/src/Op/MaxPool2D.cpp new file mode 100644 index 00000000000..ee440642516 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/MaxPool2D.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MaxPool2D.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Padding.h" +#include "Activation.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +bool MaxPool2DGraphBuilder::validate(const tflite::Operator *op) const +{ + auto const options = op->builtin_options_as_Pool2DOptions(); + + if ((options->stride_h() == 0) || (options->stride_w() == 0)) + { + return false; + } + + return true; +} + +void MaxPool2DGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // output index 0 : output feature + assert(opinputs.size() == 1); + assert(opoutputs.size() == 1); + + int ifm_idx = opinputs.at(0); + int ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + + // Create an object for an input feature map + coco::FeatureObject *ifm_obj = m->entity()->object()->create(); + coco::Bag *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + coco::FeatureObject *ofm_obj = m->entity()->object()->create(); + coco::Bag *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create a Load op + coco::Op *coco_load = op_builder(m).load(ifm_obj).pop(); + + // Create a MaxPool2D + coco::MaxPool2D *coco_maxpool2d = m->entity()->op()->create(); + const tflite::Pool2DOptions *params = op->builtin_options_as_Pool2DOptions(); + + coco_maxpool2d->window()->height(params->filter_height()); + coco_maxpool2d->window()->width(params->filter_width()); + + coco_maxpool2d->stride()->vertical(params->stride_h()); + coco_maxpool2d->stride()->horizontal(params->stride_w()); + + coco::Padding2D padding = + pool2D_padding(params, ifm_shape, params->filter_width(), params->filter_height()); + + coco_maxpool2d->pad()->top(padding.top()); + coco_maxpool2d->pad()->bottom(padding.bottom()); + coco_maxpool2d->pad()->left(padding.left()); + coco_maxpool2d->pad()->right(padding.right()); + + // Link ops + coco_maxpool2d->arg(coco_load); + + // Create an Eval instruction + coco::Eval *ins = instr_builder(m).eval(ofm_obj, coco_maxpool2d); + + // Append the instruction to the block + blk->instr()->append(ins); + + // TODO activation, e.g., relu + assert(params->fused_activation_function() == + tflite::ActivationFunctionType::ActivationFunctionType_NONE); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/MaxPool2D.h b/compiler/enco/frontend/tflite/src/Op/MaxPool2D.h new file mode 100644 index 00000000000..06a8285280f --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/MaxPool2D.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_MAXPOOL2D_H__ +#define __OP_MAXPOOL2D_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for AvgPool2D operator + */ +class MaxPool2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const tflite::Operator *op) const override; + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_MAXPOOL2D_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Padding.cpp b/compiler/enco/frontend/tflite/src/Op/Padding.cpp new file mode 100644 index 00000000000..9a0e4ef417d --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Padding.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Padding.h" + +#include "Convert.h" +#include "TensorBags.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +coco::Padding2D get_padding(const tensor::Shape &ifm_shape, const int kernel_w, const int kernel_h, + tflite::Padding padding, int stride_w, int stride_h, + int dilation_w_factor, int dilation_h_factor) +{ + assert(stride_w != 0); + assert(stride_h != 0); + assert(ifm_shape.rank() == 4); + + /** + * Compute [top padding + bottom padding] (or [left padding + right padding]). + * If this returns an even number, top = return value / 2 and bottom = return value - top + * If this returns an odd number, top = return value / 2 and bottom = return value - top (so, + * bottom = top + 1) + * + * Code based on https://www.tensorflow.org/api_guides/python/nn#Convolution + */ + auto compute_padding = [](tflite::Padding padding, int stride, int dilation_rate, int in_size, + int filter_size) { + int effective_filter_size = (filter_size - 1) * dilation_rate + 1; + if (padding == tflite::Padding_SAME) + { + if (in_size % stride == 0) + return std::max(effective_filter_size - stride, 0); + else + return std::max(effective_filter_size - (in_size % stride), 0); + } + else // padding == VALID + { + return 0; + } + }; + + // ifm shape is from order of NHWC. ifm W = dim(2), ifm H = dim(1) + int padding_w = compute_padding(padding, stride_w, dilation_w_factor, ifm_shape.dim(2), kernel_w); + int padding_h = compute_padding(padding, stride_h, dilation_h_factor, ifm_shape.dim(1), kernel_h); + + coco::Padding2D coco_padding; + coco_padding.top(padding_h / 2).bottom(padding_h - padding_h / 2); + coco_padding.left(padding_w / 2).right(padding_w - padding_w / 2); + + return coco_padding; +} + +coco::Padding2D pool2D_padding(const tflite::Pool2DOptions *options, const tensor::Shape &ifm_shape, + const int filter_w, const int filter_h) +{ + return get_padding(ifm_shape, filter_w, filter_h, options->padding(), options->stride_w(), + options->stride_h(), 1, 1); +} + +coco::Padding2D conv2D_padding(const tflite::Conv2DOptions *options, const tensor::Shape &ifm_shape, + const tensor::Shape &kernel_shape) +{ + return get_padding(ifm_shape, kernel_shape.dim(2), kernel_shape.dim(1), /* kernel layout: NHWC */ + options->padding(), options->stride_w(), options->stride_h(), + options->dilation_w_factor(), options->dilation_h_factor()); +} + +coco::Padding2D depthwiseConv2D_padding(const tflite::DepthwiseConv2DOptions *options, + const tensor::Shape &ifm_shape, + const tensor::Shape &kernel_shape) +{ + return get_padding(ifm_shape, kernel_shape.dim(2), kernel_shape.dim(1), /* kernel layout: NHWC */ + options->padding(), options->stride_w(), options->stride_h(), + options->dilation_w_factor(), options->dilation_h_factor()); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Padding.h b/compiler/enco/frontend/tflite/src/Op/Padding.h new file mode 100644 index 00000000000..ac84adeb763 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Padding.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_PADDING_H__ +#define __OP_PADDING_H__ + +#include +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +coco::Padding2D pool2D_padding(const tflite::Pool2DOptions *options, const tensor::Shape &ifm_shape, + const int filter_w, const int filter_h); + +coco::Padding2D conv2D_padding(const tflite::Conv2DOptions *options, const tensor::Shape &ifm_shape, + const tensor::Shape &kernel_shape); + +coco::Padding2D depthwiseConv2D_padding(const tflite::DepthwiseConv2DOptions *options, + const tensor::Shape &ifm_shape, + const tensor::Shape &kernel_shape); + +} // namespace tflimport + +#endif // __OP_PADDING_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/ReLU.cpp b/compiler/enco/frontend/tflite/src/Op/ReLU.cpp new file mode 100644 index 00000000000..4922f4d1f80 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/ReLU.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ReLU.h" + +#include "IRBuilder.h" +#include "GraphBuilder.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +void ReLUGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // output index 0 : output feature + assert(opinputs.size() == 1); + assert(opoutputs.size() == 1); + + auto ifm_idx = opinputs.at(0); + auto ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + + // Create an object for an input feature map + coco::FeatureObject *ifm_obj = m->entity()->object()->create(); + coco::Bag *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + coco::FeatureObject *ofm_obj = m->entity()->object()->create(); + coco::Bag *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create a Load op + auto coco_load = op_builder(m).load(ifm_obj).pop(); + + // Create a ReLU + auto coco_relu = m->entity()->op()->create(); + + // Link ops + coco_relu->arg(coco_load); + + // Create an Eval instruction + auto eval_ins = instr_builder(m).eval(ofm_obj, coco_relu); + + // Append the instruction to the block + blk->instr()->append(eval_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/ReLU.h b/compiler/enco/frontend/tflite/src/Op/ReLU.h new file mode 100644 index 00000000000..c78400d7ee9 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/ReLU.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_RELU_H__ +#define __OP_RELU_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for ReLU operator + */ +class ReLUGraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_RELU_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/ReLU6.cpp b/compiler/enco/frontend/tflite/src/Op/ReLU6.cpp new file mode 100644 index 00000000000..936fda3e2c7 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/ReLU6.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ReLU6.h" + +#include "IRBuilder.h" +#include "GraphBuilder.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +void ReLU6GraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // output index 0 : output feature + assert(opinputs.size() == 1); + assert(opoutputs.size() == 1); + + int ifm_idx = opinputs.at(0); + int ofm_idx = opoutputs.at(0); + + const tensor::Shape &ifm_shape = tensor_context.shape(ifm_idx); + const tensor::Shape &ofm_shape = tensor_context.shape(ofm_idx); + + // Create an object for an input feature map + coco::FeatureObject *ifm_obj = m->entity()->object()->create(); + coco::Bag *ifm_bag = bags.bag(ifm_idx); + ifm_obj->bag(ifm_bag); + ifm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ifm_shape))); + + // Create an object for an output feature map + coco::FeatureObject *ofm_obj = m->entity()->object()->create(); + coco::Bag *ofm_bag = bags.bag(ofm_idx); + ofm_obj->bag(ofm_bag); + ofm_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(ofm_shape))); + + // Create a Load op + auto coco_load = op_builder(m).load(ifm_obj).pop(); + + // Create a ReLU6 + auto coco_relu6 = m->entity()->op()->create(); + + // Link ops + coco_relu6->arg(coco_load); + + // Create an Eval instruction + auto eval_ins = instr_builder(m).eval(ofm_obj, coco_relu6); + + // Append the instruction to the block + blk->instr()->append(eval_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/ReLU6.h b/compiler/enco/frontend/tflite/src/Op/ReLU6.h new file mode 100644 index 00000000000..10bcd4f71b5 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/ReLU6.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_RELU6_H__ +#define __OP_RELU6_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for ReLU6 operator + */ +class ReLU6GraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_RELU6_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Reshape.cpp b/compiler/enco/frontend/tflite/src/Op/Reshape.cpp new file mode 100644 index 00000000000..9bd473fa9e2 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Reshape.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Reshape.h" + +#include "IRBuilder.h" +#include "GraphBuilder.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +void ReshapeGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : input feature + // input index 1 : output shape (int32_t), (optional or not, is not clear) + // output index 0 : output feature + assert(opinputs.size() == 1 || opinputs.size() == 2); + assert(opoutputs.size() == 1); + + // Note: there are actually 3 places where we can get output shape from + // current TF lite implementation. From output operand shape, second input, + // and ReshapeOption (new_shape). Here we use output operand shape + int ifm_idx = opinputs.at(0); + int ofm_idx = opoutputs.at(0); + + auto ifm_bag = bags.bag(ifm_idx); + auto ofm_bag = bags.bag(ofm_idx); + + // TODO: move to InstrBuilder as 'shuffle_elements()' + // Create a 1:1 shuffle instruction from ifm into ofm + // Note: Reshape is change of shape information and there is no value change + // in the bag itself. We implement this as just make a element wise copy of + // the bag from input to output. So there is no need of 'reshape' operator + auto shuffle_ins = m->entity()->instr()->create(); + auto num_elem = ifm_bag->size(); + + assert(num_elem == ofm_bag->size()); + + shuffle_ins->from(ifm_bag); + shuffle_ins->into(ofm_bag); + + for (uint32_t n = 0; n < num_elem; ++n) + { + const auto from = coco::ElemID(n); + const auto into = coco::ElemID(n); + + shuffle_ins->insert(from, into); + } + + // Append the instruction + blk->instr()->append(shuffle_ins); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Reshape.h b/compiler/enco/frontend/tflite/src/Op/Reshape.h new file mode 100644 index 00000000000..7447b56c8ee --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Reshape.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_RESHAPE_H__ +#define __OP_RESHAPE_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for Reshape operator + */ +class ReshapeGraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_RESHAPE_H__ diff --git a/compiler/enco/frontend/tflite/src/Op/Sub.cpp b/compiler/enco/frontend/tflite/src/Op/Sub.cpp new file mode 100644 index 00000000000..62973bb22df --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Sub.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Sub.h" + +#include "Convert.h" +#include "IRBuilder.h" +#include "GraphBuilder.h" +#include "Activation.h" + +#include +#include +#include + +#include +#include + +#include + +using namespace nncc::core::ADT; +using namespace morph::tflite; + +namespace tflimport +{ + +void SubGraphBuilder::build(const tflite::Operator *op, GraphBuilderContext *context) const +{ + assert(context != nullptr); // check if init(..) is called + + coco::Module *m = context->m(); + coco::Block *blk = context->block(); + TensorContext &tensor_context = context->tensor(); + TensorBags &bags = context->bags(); + + IndexVector opinputs = as_index_vector(op->inputs()); + IndexVector opoutputs = as_index_vector(op->outputs()); + + // these are fixed in tflite + // input index 0 : left input feature + // input index 1 : right input feature + // output index 0 : output feature + assert(opinputs.size() == 2); + assert(opoutputs.size() == 1); + + // Default parameter values are referenced from schema_generated.h + auto *params = op->builtin_options_as_SubOptions(); + tflite::ActivationFunctionType activation = tflite::ActivationFunctionType_NONE; + + if (auto *params = op->builtin_options_as_SubOptions()) + { + activation = params->fused_activation_function(); + } + assert(activation == tflite::ActivationFunctionType_NONE); + + // Construct a vector of input objects + std::vector input_objects; + + for (auto &input_index : opinputs) + { + // Add objects for input feature map + const tensor::Shape &input_shape = tensor_context.shape(input_index); + coco::FeatureObject *input_obj = m->entity()->object()->create(); + coco::Bag *input_bag = bags.bag(input_index); + input_obj->bag(input_bag); + input_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(input_shape))); + + input_objects.emplace_back(input_obj); + } + + // Create an object for an output feature map + int const output_index = opoutputs.at(0); + const tensor::Shape &output_shape = tensor_context.shape(output_index); + coco::FeatureObject *output_obj = m->entity()->object()->create(); + coco::Bag *output_bag = bags.bag(output_index); + output_obj->bag(output_bag); + output_obj->layout(coco::FeatureLayouts::BHWC::create(as_feature_shape(output_shape))); + + // Create Load ops + auto left_load = op_builder(m).load(input_objects[0]).pop(); + auto right_load = op_builder(m).load(input_objects[1]).pop(); + + // Create a Sub + auto coco_sub = m->entity()->op()->create(); + + coco_sub->left(left_load); + coco_sub->right(right_load); + + // Create an Eval instruction + auto eval = instr_builder(m).eval(output_obj, coco_sub); + + // Append the instruction to the block + blk->instr()->append(eval); + + // TODO activation, e.g., relu + assert(params->fused_activation_function() == + tflite::ActivationFunctionType::ActivationFunctionType_NONE); +} + +} // namespace tflimport diff --git a/compiler/enco/frontend/tflite/src/Op/Sub.h b/compiler/enco/frontend/tflite/src/Op/Sub.h new file mode 100644 index 00000000000..580d8baa3c3 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/Op/Sub.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OP_SUB_H__ +#define __OP_SUB_H__ + +#include "GraphBuilder.h" + +#include + +namespace tflimport +{ + +/** + * @brief GraphBuilder for Sub operator + */ +class SubGraphBuilder : public GraphBuilder +{ +public: + void build(const tflite::Operator *op, GraphBuilderContext *) const override; +}; + +} // namespace tflimport + +#endif // __OP_SUB_H__ diff --git a/compiler/enco/frontend/tflite/src/RawModel.h b/compiler/enco/frontend/tflite/src/RawModel.h new file mode 100644 index 00000000000..02946f1d750 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/RawModel.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __RAW_MODEL_H__ +#define __RAW_MODEL_H__ + +#include "schema_generated.h" + +struct RawModel +{ + virtual ~RawModel() = default; + + virtual const tflite::Model *model(void) const = 0; +}; + +#endif // __RAW_MODEL_H__ diff --git a/compiler/enco/frontend/tflite/src/RawModelLoader.cpp b/compiler/enco/frontend/tflite/src/RawModelLoader.cpp new file mode 100644 index 00000000000..5c127f37ced --- /dev/null +++ b/compiler/enco/frontend/tflite/src/RawModelLoader.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "RawModelLoader.h" + +#include "cwrap/Fildes.h" + +#include +#include +#include +#include + +namespace +{ + +class MemoryMappedRawModel final : public RawModel +{ +public: + /** + * @require fd and data SHOULD be valid + */ + explicit MemoryMappedRawModel(int fd, void *data, size_t size) : _fd{fd}, _data{data}, _size{size} + { + // DO NOTHING + } + +public: + ~MemoryMappedRawModel() + { + munmap(_data, _size); + close(_fd); + } + +public: + MemoryMappedRawModel(const MemoryMappedRawModel &) = delete; + MemoryMappedRawModel(MemoryMappedRawModel &&) = delete; + +public: + const tflite::Model *model(void) const override { return tflite::GetModel(_data); } + +private: + int _fd = -1; + void *_data = nullptr; + size_t _size = 0; +}; + +} // namespace + +std::unique_ptr load_from(const std::string &path) +{ + cwrap::Fildes fildes{open(path.c_str(), O_RDONLY)}; + + if (fildes.get() == -1) + { + // Return nullptr on open failure + return nullptr; + } + + struct stat st; + if (fstat(fildes.get(), &st) == -1) + { + // Return nullptr on fstat failure + return nullptr; + } + + auto size = st.st_size; + auto data = mmap(nullptr, size, PROT_READ, MAP_SHARED, fildes.get(), 0); + + if (data == MAP_FAILED) + { + // Return nullptr on mmap failure + return nullptr; + } + + return std::unique_ptr{new MemoryMappedRawModel(fildes.release(), data, size)}; +} diff --git a/compiler/enco/frontend/tflite/src/RawModelLoader.h b/compiler/enco/frontend/tflite/src/RawModelLoader.h new file mode 100644 index 00000000000..5d93528de88 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/RawModelLoader.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __RAW_MODEL_LOADER_H__ +#define __RAW_MODEL_LOADER_H__ + +#include "RawModel.h" + +/** + * @brief Load TensorFlow Lite model (as a RawModel) from a given path + * + * @note May return a nullptr + */ +std::unique_ptr load_from(const std::string &path); + +#endif // __RAW_MODEL_LOADER_H__ diff --git a/compiler/enco/frontend/tflite/src/TensorBags.h b/compiler/enco/frontend/tflite/src/TensorBags.h new file mode 100644 index 00000000000..29558b85ee7 --- /dev/null +++ b/compiler/enco/frontend/tflite/src/TensorBags.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TENSOR_BAGS_H__ +#define __TENSOR_BAGS_H__ + +#include "Convert.h" + +#include +#include + +#include + +#include + +using namespace nncc::core::ADT; + +namespace tflimport +{ + +/** + * @brief Pre-creates coco:Bags for each operands(tensors) + */ +class TensorBags +{ +public: + void prepare(const tflite::SubGraph *graph, std::unique_ptr &m) + { + for (uint32_t tensor_id = 0; tensor_id < graph->tensors()->size(); ++tensor_id) + { + auto const tensor_info = graph->tensors()->Get(tensor_id); + auto const tensor_shape = as_tensor_shape(tensor_info->shape()); + auto const tensor_bag = m->entity()->bag()->create(num_elements(tensor_shape)); + + _bag_ctx[tensor_id] = tensor_bag; + } + } + + coco::Bag *bag(int32_t tensor_id) { return _bag_ctx[tensor_id]; } + +public: + std::map::iterator begin() { return _bag_ctx.begin(); } + + std::map::iterator end() { return _bag_ctx.end(); } + +private: + std::map _bag_ctx; +}; + +} // namespace tflimport + +#endif // __TENSOR_BAGS_H__ diff --git a/compiler/enco/requires.cmake b/compiler/enco/requires.cmake new file mode 100644 index 00000000000..fee0e18e56c --- /dev/null +++ b/compiler/enco/requires.cmake @@ -0,0 +1,8 @@ +require("coco") +require("caffegen") +require("tflchef") +require("ann-api") +require("ann-ref") +require("nnkit") +require("cwrap") +require("enco-intf") diff --git a/compiler/enco/test/CMakeLists.txt b/compiler/enco/test/CMakeLists.txt new file mode 100644 index 00000000000..5ea6cdadd0e --- /dev/null +++ b/compiler/enco/test/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectories() diff --git a/compiler/enco/test/basic/000/CMakeLists.txt b/compiler/enco/test/basic/000/CMakeLists.txt new file mode 100644 index 00000000000..20ba3c57138 --- /dev/null +++ b/compiler/enco/test/basic/000/CMakeLists.txt @@ -0,0 +1,26 @@ +### +### This test first generates C++ code from an empty model, and check whether is has compile error +### +set(PREFIX enco-basic-test-000) +set(GENERATED_CPP ${PREFIX}.cpp) +set(GENERATED_ASM ${PREFIX}.embed.S) +set(GENERATED_BIN ${PREFIX}.bin) +set(SOURCE_TARGET ${PREFIX}-src) +set(LIB_TARGET ${PREFIX}-lib) + +add_library(${PREFIX}-frontend SHARED enco.test.cpp) +target_link_libraries(${PREFIX}-frontend enco_intf_cmdline) +target_link_libraries(${PREFIX}-frontend enco_intf_frontend) +target_link_libraries(${PREFIX}-frontend stdex) + +# NOTE BYPRODUCTS are not specified in order to enforce source code generation +add_custom_command(OUTPUT ${GENERATED_CPP} ${GENERATED_ASM} ${GENERATED_BIN} + COMMAND $ + --frontend $ + --backend-arg ${PREFIX} + DEPENDS enco-cli ${PREFIX}-frontend) +set_source_files_properties(${GENERATED_ASM} PROPERTIES GENERATED TRUE LANGUAGE C) +add_library(${LIB_TARGET} SHARED ${GENERATED_CPP} ${GENERATED_ASM}) +# NOTE This line is necessary to compile the generated assembly (it includes the generated bin file) +target_include_directories(${LIB_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(${LIB_TARGET} PRIVATE ann_api) diff --git a/compiler/enco/test/basic/000/enco.test.cpp b/compiler/enco/test/basic/000/enco.test.cpp new file mode 100644 index 00000000000..3dbf9661312 --- /dev/null +++ b/compiler/enco/test/basic/000/enco.test.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include + +#include + +#include + +using namespace nncc::core::ADT; + +namespace +{ + +// +// Dummy frontend for testing +// +struct Frontend final : public enco::Frontend +{ + enco::Bundle load(void) const override + { + auto m = coco::Module::create(); + auto d = coco::Data::create(); + + // Create an input + { + const tensor::Shape shape{1, 3, 3, 1}; + + auto bag = m->entity()->bag()->create(9); + auto input = m->entity()->input()->create(shape); + + input->bag(bag); + input->name("input"); + input->reorder(); + + m->input()->insert(input); + } + + // Create an output + { + const tensor::Shape shape{1, 3, 3, 1}; + + auto bag = m->entity()->bag()->create(9); + auto output = m->entity()->output()->create(shape); + + output->bag(bag); + output->name("output"); + output->reorder(); + + m->output()->insert(output); + } + + enco::Bundle bundle; + + bundle.module(std::move(m)); + bundle.data(std::move(d)); + + return std::move(bundle); + } +}; + +} // namespace + +extern "C" std::unique_ptr make_frontend(const cmdline::View &cmdline) +{ + return stdex::make_unique(); +} diff --git a/compiler/enco/test/basic/CMakeLists.txt b/compiler/enco/test/basic/CMakeLists.txt new file mode 100644 index 00000000000..5ea6cdadd0e --- /dev/null +++ b/compiler/enco/test/basic/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectories() diff --git a/compiler/enco/test/binder.cpp b/compiler/enco/test/binder.cpp new file mode 100644 index 00000000000..c8c72fc8b75 --- /dev/null +++ b/compiler/enco/test/binder.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +// +// Generated API +// +struct Network; + +Network *Network_construct(); +void Network_destruct(Network *net); + +unsigned Network_input_count(const Network *); +const char *Network_input_name(const Network *, unsigned n); +unsigned Network_input_rank(const Network *, unsigned n); +unsigned Network_input_dim(const Network *, unsigned n, unsigned axis); +void Network_input_bind(Network *net, unsigned n, const void *ptr, unsigned len); + +unsigned Network_output_count(const Network *net); +const char *Network_output_name(const Network *, unsigned n); +unsigned Network_output_rank(const Network *, unsigned n); +unsigned Network_output_dim(const Network *, unsigned n, unsigned axis); +void Network_output_bind(Network *net, unsigned n, void *ptr, unsigned len); + +void Network_invoke(Network *net); + +// +// nnkit backend +// +#include +#include +#include + +#include +#include + +#include + +using stdex::make_unique; +using namespace nncc::core::ADT; + +namespace +{ + +class TensorContext final : public nnkit::TensorContext +{ +public: + TensorContext() = default; + +public: + void allocate(const std::string &name, const tensor::Shape &shape) + { + using nncc::core::ADT::tensor::num_elements; + + auto blob = make_unique>(); + blob->resize(num_elements(shape) * sizeof(float)); + + _names.emplace_back(name); + _shapes.emplace_back(shape); + _blobs.emplace_back(std::move(blob)); + } + +public: + uint8_t *base(uint32_t n) const { return _blobs.at(n)->data(); } + +public: + uint32_t size(void) const override { return _blobs.size(); } + +public: + std::string name(uint32_t n) const override { return _names.at(n); } + +public: + tensor::Shape shape(uint32_t n) const override { return _shapes.at(n); } + +public: + uint32_t size(uint32_t n) const { return _blobs.at(n)->size(); } + +public: + // Float (fp32) tensor support + bool isFloatTensor(uint32_t n) const override { return true; } + void getMutableFloatTensor(uint32_t n, const TensorContext::TypedAccessor &f) override + { + using nncc::core::ADT::tensor::LexicalLayout; + using nncc::core::ADT::tensor::make_overlay; + + auto base = reinterpret_cast(this->base(n)); + auto view = make_overlay(shape(n), base); + + f(*this, n, view); + } + + void getConstFloatTensor(uint32_t n, const TensorContext::TypedReader &f) const override + { + using nncc::core::ADT::tensor::LexicalLayout; + using nncc::core::ADT::tensor::make_overlay; + + auto base = reinterpret_cast(this->base(n)); + auto view = make_overlay(shape(n), base); + + f(*this, n, view); + } + +private: + std::vector _names; + std::vector _shapes; + std::vector>> _blobs; +}; + +class Backend final : public nnkit::Backend +{ +public: + Backend() + { + _net = Network_construct(); + + // Allocate and bind inputs + for (uint32_t n = 0; n < Network_input_count(_net); ++n) + { + const uint32_t rank = Network_input_rank(_net, n); + const std::string name = Network_input_name(_net, n); + + tensor::Shape shape; + + shape.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + shape.dim(axis) = Network_input_dim(_net, n, axis); + } + + _inputs.allocate(name, shape); + + Network_input_bind(_net, n, reinterpret_cast(_inputs.base(n)), _inputs.size(n)); + } + + // Allocate and bind outputs + for (uint32_t n = 0; n < Network_output_count(_net); ++n) + { + const uint32_t rank = Network_output_rank(_net, n); + const std::string name = Network_output_name(_net, n); + + tensor::Shape shape; + + shape.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + shape.dim(axis) = Network_output_dim(_net, n, axis); + } + + _outputs.allocate(name, shape); + + Network_output_bind(_net, n, reinterpret_cast(_outputs.base(n)), _outputs.size(n)); + } + } + +public: + ~Backend() { Network_destruct(_net); } + +public: + void prepare(const std::function &f) override { f(_inputs); } + void run(void) override { Network_invoke(_net); } + void teardown(const std::function &f) override { f(_outputs); } + +private: + Network *_net; + +private: + TensorContext _inputs; + TensorContext _outputs; +}; + +} // namespace + +extern "C" std::unique_ptr make_backend(const nnkit::CmdlineArguments &args) +{ + return make_unique<::Backend>(); +} diff --git a/compiler/enco/test/caffe/CMakeLists.txt b/compiler/enco/test/caffe/CMakeLists.txt new file mode 100644 index 00000000000..ee49b6b281a --- /dev/null +++ b/compiler/enco/test/caffe/CMakeLists.txt @@ -0,0 +1,141 @@ +option(ENCO_CAFFE_TEST "Enable enco test for caffe" ON) + +if(NOT ENCO_CAFFE_TEST) + return() +endif(NOT ENCO_CAFFE_TEST) + +# TODO Use REQUIRED if supported +nncc_find_resource(BVLCCaffeTests) + +if(NOT BVLCCaffeTests_FOUND) + message(FATAL_ERROR "Fail to find BVLCCaffeTests") +endif(NOT BVLCCaffeTests_FOUND) + +# TESTCASE_BASE_DIR indicates where all the testcases are located +set(TESTCASE_BASE_DIR "${BVLCCaffeTests_DIR}") + +### +### Common function(s) +### +function(get_test_configuration PREFIX) + set(PROTOTXT_FILE "${PREFIX}.prototxt") + set(PROTOTXT_FILE "${PROTOTXT_FILE}" PARENT_SCOPE) + set(PROTOTXT_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PROTOTXT_FILE}" PARENT_SCOPE) + set(CAFFEMODEL_FILE "${PREFIX}.caffemodel") + set(CAFFEMODEL_FILE "${CAFFEMODEL_FILE}" PARENT_SCOPE) + set(CAFFEMODEL_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CAFFEMODEL_FILE}" PARENT_SCOPE) + set(SOURCE_FILE ${PREFIX}.cpp) + set(SOURCE_FILE "${SOURCE_FILE}" PARENT_SCOPE) + set(SOURCE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${SOURCE_FILE}" PARENT_SCOPE) + set(ASM_FILE ${PREFIX}.embed.S) + set(ASM_FILE "${ASM_FILE}" PARENT_SCOPE) + set(ASM_PATH "${CMAKE_CURRENT_BINARY_DIR}/${ASM_FILE}" PARENT_SCOPE) + set(BIN_FILE ${PREFIX}.bin) + set(BIN_FILE "${BIN_FILE}" PARENT_SCOPE) + set(BIN_PATH "${CMAKE_CURRENT_BINARY_DIR}/${BIN_FILE}" PARENT_SCOPE) +endfunction(get_test_configuration) + +### +### Prepare test(s) +### +if(NOT TARGET caffegen) + return() +endif(NOT TARGET caffegen) + +if(NOT TARGET enco_caffe_frontend) + return() +endif(NOT TARGET enco_caffe_frontend) + +# TODO Use "whitelist" instead +# +# WHY? +# +# Tests are now shared by multiple frameworks (not private), and thus +# some tests may be unsupported. +# +file(GLOB MODELS RELATIVE "${TESTCASE_BASE_DIR}" "${TESTCASE_BASE_DIR}/*/test.prototxt") + +foreach(MODEL IN ITEMS ${MODELS}) + get_filename_component(PREFIX ${MODEL} DIRECTORY) + get_test_configuration(${PREFIX}) + + set(MODEL_FILE ${TESTCASE_BASE_DIR}/${MODEL}) + + # Copy prototxt + # TODO Fix indentation + add_custom_command(OUTPUT ${PROTOTXT_PATH} + COMMAND ${CMAKE_COMMAND} -E copy "${MODEL_FILE}" "${PROTOTXT_PATH}" + DEPENDS "${MODEL_FILE}" + COMMENT "Generating ${PROTOTXT_FILE}") + + # Generate caffemodel + # TODO Fix indentation + add_custom_command(OUTPUT ${CAFFEMODEL_PATH} + COMMAND cat ${PROTOTXT_PATH} + | GLOG_minloglevel=2 $ init + | GLOG_minloglevel=2 $ encode + > ${CAFFEMODEL_PATH} + DEPENDS caffegen ${PROTOTXT_PATH} + COMMENT "Generating ${CAFFEMODEL_FILE}") + + # Generate C++ code + # TODO Fix indentation + add_custom_command(OUTPUT ${SOURCE_PATH} ${ASM_PATH} ${BIN_PATH} + COMMAND $ + --frontend $ + --frontend-arg ${PROTOTXT_FILE} + --frontend-arg ${CAFFEMODEL_FILE} + --backend-arg ${PREFIX} + DEPENDS enco-cli enco_caffe_frontend ${CAFFEMODEL_PATH} + COMMENT "Generating ${SOURCE_FILE}") + set_source_files_properties(${ASM_PATH} PROPERTIES GENERATED TRUE LANGUAGE C) + + list(APPEND CANDIDATES ${PREFIX}) +endforeach(MODEL) + +### +### Inference test +### +if(NOT TARGET ann_ref_static) + return() +endif(NOT TARGET ann_ref_static) + +find_program(H5DIFF h5diff) + +if (NOT H5DIFF) + return() +endif(NOT H5DIFF) + +message(STATUS "Enable enco(caffe) inference test") + +foreach(PREFIX IN ITEMS ${CANDIDATES}) + if(NOT EXISTS "${TESTCASE_BASE_DIR}/${PREFIX}/INFERENCE") + continue() + endif() + + get_test_configuration(${PREFIX}) + + set(BINDER_TARGET enco_caffe_test_${PREFIX}_binder) + + # Compile nnkit binder (from generated C++ code) + add_library(${BINDER_TARGET} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/../binder.cpp ${SOURCE_PATH} ${ASM_PATH}) + target_include_directories(${BINDER_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(${BINDER_TARGET} nnkit_intf_backend) + target_link_libraries(${BINDER_TARGET} ann_api) + target_link_libraries(${BINDER_TARGET} ann_ref_static) + target_link_libraries(${BINDER_TARGET} stdex) + set_target_properties(${BINDER_TARGET} PROPERTIES OUTPUT_NAME ${PREFIX}) + + list(APPEND TESTS ${PREFIX}) +endforeach(PREFIX) + +# Run tests +add_test(NAME enco_test_caffe + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/runall.sh" + $ + $ + $ + $ + $ + "${CMAKE_CURRENT_BINARY_DIR}" + ${TESTS}) diff --git a/compiler/enco/test/caffe/runall.sh b/compiler/enco/test/caffe/runall.sh new file mode 100755 index 00000000000..3b18f1c6bbb --- /dev/null +++ b/compiler/enco/test/caffe/runall.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +if [[ $# -le 6 ]]; then + echo "USAGE: $0 [nnkit-run path] [reference backend path] [randomize action path] [HDF5 export action path] [HDF5 import action path] [WORKDIR] [Prefix1] [Prefix2]..." + exit 255 +fi + +NNKIT_RUN_PATH="$1"; shift +REFERENCE_BACKEND_PATH="$1"; shift +RANDOMIZE_ACTION_PATH="$1"; shift +HDF5_EXPORT_ACTION_PATH="$1"; shift +HDF5_IMPORT_ACTION_PATH="$1"; shift +WORKDIR="$1"; shift + +echo "-- Found nnkit-run: ${NNKIT_RUN_PATH}" +echo "-- Found reference backend: ${REFERENCE_BACKEND_PATH}" +echo "-- Found randomize action: ${RANDOMIZE_ACTION_PATH}" +echo "-- Found HDF5 export action: ${HDF5_EXPORT_ACTION_PATH}" +echo "-- Found HDF5 import action: ${HDF5_IMPORT_ACTION_PATH}" +echo "-- Found workdir: ${WORKDIR}" + +TESTED=() +PASSED=() +FAILED=() + +pushd "${WORKDIR}" +while [[ $# -ne 0 ]]; do + PREFIX="$1"; shift + + TESTED+=("${PREFIX}") + + PASSED_TAG="${PREFIX}.passed" + + rm -f "${PASSED_TAG}" + + cat > "${PREFIX}.log" <( + exec 2>&1 + + echo "-- Found prototxt: ${PREFIX}.prototxt" + echo "-- Found caffemodel: ${PREFIX}.caffemodel" + echo "-- Found backend: lib${PREFIX}.so" + + "${NNKIT_RUN_PATH}" \ + --backend "${REFERENCE_BACKEND_PATH}" \ + --backend-arg "${WORKDIR}/${PREFIX}.prototxt" \ + --backend-arg "${WORKDIR}/${PREFIX}.caffemodel" \ + --pre "${RANDOMIZE_ACTION_PATH}" \ + --pre "${HDF5_EXPORT_ACTION_PATH}" \ + --pre-arg "${PREFIX}.input.h5" \ + --post "${HDF5_EXPORT_ACTION_PATH}" \ + --post-arg "${PREFIX}.expected.h5" + + "${NNKIT_RUN_PATH}" \ + --backend "./lib${PREFIX}.so" \ + --pre "${HDF5_IMPORT_ACTION_PATH}" \ + --pre-arg "${PREFIX}.input.h5" \ + --post "${HDF5_EXPORT_ACTION_PATH}" \ + --post-arg "${PREFIX}.obtained.h5" + + h5diff -d 0.001 "${PREFIX}.expected.h5" "${PREFIX}.obtained.h5" + + if [[ $? -eq 0 ]]; then + touch "${PASSED_TAG}" + fi + ) + + if [[ -f "${PASSED_TAG}" ]]; then + PASSED+=("$PREFIX") + else + FAILED+=("$PREFIX") + fi +done +popd + +if [[ ${#TESTED[@]} -ne ${#PASSED[@]} ]]; then + echo "FAILED" + for TEST in "${FAILED[@]}" + do + echo "- ${TEST}" + done + exit 255 +fi + +echo "PASSED" +exit 0 diff --git a/compiler/enco/test/tflite/AveragePool2D_000/INFERENCE b/compiler/enco/test/tflite/AveragePool2D_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/AveragePool2D_000/test.recipe b/compiler/enco/test/tflite/AveragePool2D_000/test.recipe new file mode 100644 index 00000000000..746c3433425 --- /dev/null +++ b/compiler/enco/test/tflite/AveragePool2D_000/test.recipe @@ -0,0 +1,24 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 1 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 7 dim: 7 dim: 1 } +} +operation { + type: "AveragePool2D" + averagepool2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + filter_width: 2 + filter_height: 2 + } + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/AveragePool2D_001/INFERENCE b/compiler/enco/test/tflite/AveragePool2D_001/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/AveragePool2D_001/test.recipe b/compiler/enco/test/tflite/AveragePool2D_001/test.recipe new file mode 100644 index 00000000000..36bbda78cf0 --- /dev/null +++ b/compiler/enco/test/tflite/AveragePool2D_001/test.recipe @@ -0,0 +1,24 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 5 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 5 } +} +operation { + type: "AveragePool2D" + averagepool2d_options { + padding: SAME + stride_w: 1 + stride_h: 1 + filter_width: 3 + filter_height: 3 + } + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/CMakeLists.txt b/compiler/enco/test/tflite/CMakeLists.txt new file mode 100644 index 00000000000..d5a96a6da3a --- /dev/null +++ b/compiler/enco/test/tflite/CMakeLists.txt @@ -0,0 +1,108 @@ +option(ENCO_TFLITE_TEST "Enable enco test for TFLite" ON) + +if(NOT ENCO_TFLITE_TEST) + return() +endif(NOT ENCO_TFLITE_TEST) + +### +### Common function(s) +### +function(get_test_configuration PREFIX) + set(RECIPE_FILE "${PREFIX}.recipe" PARENT_SCOPE) + set(TFLITEMODEL_FILE "${PREFIX}.tflite" PARENT_SCOPE) + set(SOURCE_FILE ${PREFIX}.cpp PARENT_SCOPE) + set(ASM_FILE ${PREFIX}.embed.S PARENT_SCOPE) + set(BIN_FILE ${PREFIX}.bin PARENT_SCOPE) +endfunction(get_test_configuration) + +### +### Prepare test(s) +### +if(NOT TARGET tflchef-file) + return() +endif(NOT TARGET tflchef-file) + +if(NOT TARGET enco_tflite_frontend) + return() +endif(NOT TARGET enco_tflite_frontend) + +file(GLOB MODELS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/test.recipe") + +foreach(MODEL IN ITEMS ${MODELS}) + get_filename_component(PREFIX ${MODEL} DIRECTORY) + get_test_configuration(${PREFIX}) + + set(MODEL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${MODEL}) + + # Copy recipe + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${RECIPE_FILE} + COMMAND ${CMAKE_COMMAND} -E copy "${MODEL_FILE}" + "${CMAKE_CURRENT_BINARY_DIR}/${RECIPE_FILE}" + DEPENDS "${MODEL_FILE}" + COMMENT "Copying ${RECIPE_FILE}") + + # Generate tflitemodel + add_custom_command(OUTPUT ${TFLITEMODEL_FILE} + COMMAND $ ${RECIPE_FILE} ${TFLITEMODEL_FILE} + DEPENDS tflchef ${CMAKE_CURRENT_BINARY_DIR}/${RECIPE_FILE} + COMMENT "Generating ${TFLITEMODEL_FILE}") + + # Generate C++ code + add_custom_command(OUTPUT ${SOURCE_FILE} ${ASM_FILE} ${BIN_FILE} + COMMAND $ + --frontend $ + --frontend-arg ${TFLITEMODEL_FILE} + --backend-arg ${PREFIX} + DEPENDS enco-cli enco_caffe_frontend ${TFLITEMODEL_FILE} + COMMENT "Generating ${SOURCE_FILE}") + set_source_files_properties(${ASM_FILE} PROPERTIES GENERATED TRUE LANGUAGE C) + + list(APPEND CANDIDATES ${PREFIX}) +endforeach(MODEL) + +### +### Inference test +### +if(NOT TARGET ann_ref_static) + return() +endif(NOT TARGET ann_ref_static) + +find_program(H5DIFF h5diff) + +if (NOT H5DIFF) + return() +endif(NOT H5DIFF) + +message(STATUS "Enable enco(tflite) inference test") + +foreach(PREFIX IN ITEMS ${CANDIDATES}) + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${PREFIX}/INFERENCE") + continue() + endif() + + get_test_configuration(${PREFIX}) + + set(BINDER_TARGET enco_tflite_test_${PREFIX}_binder) + + # Compile nnkit binder (from generated C++ code) + add_library(${BINDER_TARGET} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/../binder.cpp ${SOURCE_FILE} ${ASM_FILE}) + target_include_directories(${BINDER_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(${BINDER_TARGET} nnkit_intf_backend) + target_link_libraries(${BINDER_TARGET} ann_api) + target_link_libraries(${BINDER_TARGET} ann_ref_static) + target_link_libraries(${BINDER_TARGET} stdex) + set_target_properties(${BINDER_TARGET} PROPERTIES OUTPUT_NAME ${PREFIX}) + + list(APPEND TESTS ${PREFIX}) +endforeach(PREFIX) + +# Run tests +add_test(NAME enco_test_tflite + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/runall.sh" + $ + $ + $ + $ + $ + "${CMAKE_CURRENT_BINARY_DIR}" + ${TESTS}) diff --git a/compiler/enco/test/tflite/Concat_000/INFERENCE b/compiler/enco/test/tflite/Concat_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Concat_000/test.recipe b/compiler/enco/test/tflite/Concat_000/test.recipe new file mode 100644 index 00000000000..35641bd07a4 --- /dev/null +++ b/compiler/enco/test/tflite/Concat_000/test.recipe @@ -0,0 +1,28 @@ +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 1 } +} +operand { + name: "ifm2" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 2 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } +} +operation { + type: "Concatenation" + concatenation_options { + axis: 3 + activation: NONE + } + input: "ifm1" + input: "ifm2" + output: "ofm" +} +input: "ifm1" +input: "ifm2" +output: "ofm" diff --git a/compiler/enco/test/tflite/Concat_001/INFERENCE b/compiler/enco/test/tflite/Concat_001/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Concat_001/test.recipe b/compiler/enco/test/tflite/Concat_001/test.recipe new file mode 100644 index 00000000000..7adaf164562 --- /dev/null +++ b/compiler/enco/test/tflite/Concat_001/test.recipe @@ -0,0 +1,29 @@ +# Concatenate two feature maps along "width" dimension +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 1 } +} +operand { + name: "ifm2" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 2 dim: 1 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 3 dim: 1 } +} +operation { + type: "Concatenation" + concatenation_options { + axis: 2 + activation: NONE + } + input: "ifm1" + input: "ifm2" + output: "ofm" +} +input: "ifm1" +input: "ifm2" +output: "ofm" diff --git a/compiler/enco/test/tflite/Concat_002/INFERENCE b/compiler/enco/test/tflite/Concat_002/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Concat_002/test.recipe b/compiler/enco/test/tflite/Concat_002/test.recipe new file mode 100644 index 00000000000..918cb13d3f4 --- /dev/null +++ b/compiler/enco/test/tflite/Concat_002/test.recipe @@ -0,0 +1,29 @@ +# Concatenate two feature maps along "height" dimension +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 1 } +} +operand { + name: "ifm2" + type: FLOAT32 + shape { dim: 1 dim: 2 dim: 1 dim: 1 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 1 dim: 1 } +} +operation { + type: "Concatenation" + concatenation_options { + axis: 1 + activation: NONE + } + input: "ifm1" + input: "ifm2" + output: "ofm" +} +input: "ifm1" +input: "ifm2" +output: "ofm" diff --git a/compiler/enco/test/tflite/Concat_003/INFERENCE b/compiler/enco/test/tflite/Concat_003/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Concat_003/test.recipe b/compiler/enco/test/tflite/Concat_003/test.recipe new file mode 100644 index 00000000000..8f1b64ea68f --- /dev/null +++ b/compiler/enco/test/tflite/Concat_003/test.recipe @@ -0,0 +1,29 @@ +# Concatenate two feature maps along "batch" dimension +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 1 } +} +operand { + name: "ifm2" + type: FLOAT32 + shape { dim: 2 dim: 1 dim: 1 dim: 1 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 3 dim: 1 dim: 1 dim: 1 } +} +operation { + type: "Concatenation" + concatenation_options { + axis: 0 + activation: NONE + } + input: "ifm1" + input: "ifm2" + output: "ofm" +} +input: "ifm1" +input: "ifm2" +output: "ofm" diff --git a/compiler/enco/test/tflite/Conv2D_000/INFERENCE b/compiler/enco/test/tflite/Conv2D_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Conv2D_000/test.recipe b/compiler/enco/test/tflite/Conv2D_000/test.recipe new file mode 100644 index 00000000000..9f084181950 --- /dev/null +++ b/compiler/enco/test/tflite/Conv2D_000/test.recipe @@ -0,0 +1,45 @@ +# Test for basic case: VALID padding, no activation layer, stride=[1,1] +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Conv2D_001/INFERENCE b/compiler/enco/test/tflite/Conv2D_001/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Conv2D_001/test.recipe b/compiler/enco/test/tflite/Conv2D_001/test.recipe new file mode 100644 index 00000000000..d9d4904da69 --- /dev/null +++ b/compiler/enco/test/tflite/Conv2D_001/test.recipe @@ -0,0 +1,45 @@ +# Test for SAME padding +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 5 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 5 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: SAME + stride_w: 1 + stride_h: 1 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Conv2D_002/INFERENCE b/compiler/enco/test/tflite/Conv2D_002/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Conv2D_002/test.recipe b/compiler/enco/test/tflite/Conv2D_002/test.recipe new file mode 100644 index 00000000000..55976c9b9fd --- /dev/null +++ b/compiler/enco/test/tflite/Conv2D_002/test.recipe @@ -0,0 +1,46 @@ +# Test for RELU activation layer +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + activation: RELU + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Conv2D_003/INFERENCE b/compiler/enco/test/tflite/Conv2D_003/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Conv2D_003/test.recipe b/compiler/enco/test/tflite/Conv2D_003/test.recipe new file mode 100644 index 00000000000..30c9473b7d2 --- /dev/null +++ b/compiler/enco/test/tflite/Conv2D_003/test.recipe @@ -0,0 +1,45 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + activation: RELU6 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Conv2D_004/INFERENCE b/compiler/enco/test/tflite/Conv2D_004/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Conv2D_004/test.recipe b/compiler/enco/test/tflite/Conv2D_004/test.recipe new file mode 100644 index 00000000000..20f4a99087d --- /dev/null +++ b/compiler/enco/test/tflite/Conv2D_004/test.recipe @@ -0,0 +1,45 @@ +# Conv2D with ifm w, h = 14, 14 && ofm w, h = 7, 7 && stride = 2, 2 && padding = SAME (similar case from Mobile) +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 14 dim: 14 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 7 dim: 7 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: SAME + stride_w: 2 + stride_h: 2 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/DepthwiseConv2D_000/INFERENCE b/compiler/enco/test/tflite/DepthwiseConv2D_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/DepthwiseConv2D_000/test.recipe b/compiler/enco/test/tflite/DepthwiseConv2D_000/test.recipe new file mode 100644 index 00000000000..27bc767fc97 --- /dev/null +++ b/compiler/enco/test/tflite/DepthwiseConv2D_000/test.recipe @@ -0,0 +1,48 @@ +# SAME padding, stride = [1,1], activation=RELU6. +# In mobilenet, there are two cases using depthwiseConv2D : A case like this one, and another case with stride=[2,2] +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 5 dim: 5 dim: 4 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 4 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 4 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 5 dim: 5 dim: 4 } +} +operation { + type: "DepthwiseConv2D" + depthwiseconv2d_options { + padding: SAME + stride_w: 1 + stride_h: 1 + depth_multiplier: 1 + activation: RELU6 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/DepthwiseConv2D_001/INFERENCE b/compiler/enco/test/tflite/DepthwiseConv2D_001/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/DepthwiseConv2D_001/test.recipe b/compiler/enco/test/tflite/DepthwiseConv2D_001/test.recipe new file mode 100644 index 00000000000..0166474d8c0 --- /dev/null +++ b/compiler/enco/test/tflite/DepthwiseConv2D_001/test.recipe @@ -0,0 +1,46 @@ +# depthwiseConv2D with ifm w, h = 14, 14 && ofm w, h = 7, 7 && stride = 2, 2 && padding = SAME (similar case from Mobile) +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 14 dim: 14 dim: 5 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 5 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 5 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 7 dim: 7 dim: 5 } +} +operation { + type: "DepthwiseConv2D" + depthwiseconv2d_options { + padding: SAME + stride_w: 2 + stride_h: 2 + activation: RELU6 + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Div_000/INFERENCE b/compiler/enco/test/tflite/Div_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Div_000/test.recipe b/compiler/enco/test/tflite/Div_000/test.recipe new file mode 100644 index 00000000000..a6335de4608 --- /dev/null +++ b/compiler/enco/test/tflite/Div_000/test.recipe @@ -0,0 +1,27 @@ +operand { + name: "ifm0" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } +} +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } +} +operation { + type: "Div" + input: "ifm0" + input: "ifm1" + output: "ofm" + div_options { + activation: NONE + } +} +input: "ifm0" +input: "ifm1" +output: "ofm" diff --git a/compiler/enco/test/tflite/MaxPool2D_000/INFERENCE b/compiler/enco/test/tflite/MaxPool2D_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/MaxPool2D_000/test.recipe b/compiler/enco/test/tflite/MaxPool2D_000/test.recipe new file mode 100644 index 00000000000..718630f08f5 --- /dev/null +++ b/compiler/enco/test/tflite/MaxPool2D_000/test.recipe @@ -0,0 +1,24 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 1 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 7 dim: 7 dim: 1 } +} +operation { + type: "MaxPool2D" + maxpool2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + filter_width: 2 + filter_height: 2 + } + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/ReLU6_000/INFERENCE b/compiler/enco/test/tflite/ReLU6_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/ReLU6_000/test.recipe b/compiler/enco/test/tflite/ReLU6_000/test.recipe new file mode 100644 index 00000000000..226593593c3 --- /dev/null +++ b/compiler/enco/test/tflite/ReLU6_000/test.recipe @@ -0,0 +1,17 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operation { + type: "ReLU6" + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/ReLU_000/INFERENCE b/compiler/enco/test/tflite/ReLU_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/ReLU_000/test.recipe b/compiler/enco/test/tflite/ReLU_000/test.recipe new file mode 100644 index 00000000000..8eaa3602f44 --- /dev/null +++ b/compiler/enco/test/tflite/ReLU_000/test.recipe @@ -0,0 +1,17 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operation { + type: "ReLU" + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Regression_0000/INFERENCE b/compiler/enco/test/tflite/Regression_0000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Regression_0000/test.recipe b/compiler/enco/test/tflite/Regression_0000/test.recipe new file mode 100644 index 00000000000..2f3c0367020 --- /dev/null +++ b/compiler/enco/test/tflite/Regression_0000/test.recipe @@ -0,0 +1,84 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 2 } +} +operand { + name: "ker_0" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias_0" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "0.1" + } +} +operand { + name: "ofm_0" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + activation: NONE + } + input: "ifm" + input: "ker_0" + input: "bias_0" + output: "ofm_0" +} +operand { + name: "ker_1" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias_1" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "0.1" + } +} +operand { + name: "ofm_1" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + activation: NONE + } + input: "ifm" + input: "ker_1" + input: "bias_1" + output: "ofm_1" +} +input: "ifm" +output: "ofm_0" +output: "ofm_1" diff --git a/compiler/enco/test/tflite/Regression_0001/INFERENCE b/compiler/enco/test/tflite/Regression_0001/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Regression_0001/test.recipe b/compiler/enco/test/tflite/Regression_0001/test.recipe new file mode 100644 index 00000000000..e6f4eca8fd2 --- /dev/null +++ b/compiler/enco/test/tflite/Regression_0001/test.recipe @@ -0,0 +1,50 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { tag: "gaussian" arg: "0.0" arg: "1.0" } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { tag: "gaussian" arg: "0.0" arg: "1.0" } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 1 } +} +operand { + name: "arr" + type: FLOAT32 + shape { dim: 1 dim: 9 } +} +operand { + name: "shape" + type: INT32 + shape { dim: 2 } + filler { tag: "explicit" arg: "-1" arg: "9" } +} +operation { + type: "Conv2D" + conv2d_options { padding: VALID stride_w: 1 stride_h: 1 activation: RELU6 } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +operation { + type: "Reshape" + input: "ofm" + input: "shape" + output: "arr" + reshape_options { new_shape: [-1, 9] } +} +input: "ifm" +output: "arr" diff --git a/compiler/enco/test/tflite/Regression_0002/INFERENCE b/compiler/enco/test/tflite/Regression_0002/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Regression_0002/test.recipe b/compiler/enco/test/tflite/Regression_0002/test.recipe new file mode 100644 index 00000000000..8234c79969e --- /dev/null +++ b/compiler/enco/test/tflite/Regression_0002/test.recipe @@ -0,0 +1,45 @@ +# Compilation SHOULD NOT fail even when there is no effective calcualtion +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 2 } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "1.0" + } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { + tag: "gaussian" + arg: "0.0" + arg: "0.1" + } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 8 dim: 8 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { + padding: VALID + stride_w: 1 + stride_h: 1 + activation: NONE + } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +input: "ifm" diff --git a/compiler/enco/test/tflite/Regression_0003/INFERENCE b/compiler/enco/test/tflite/Regression_0003/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Regression_0003/test.recipe b/compiler/enco/test/tflite/Regression_0003/test.recipe new file mode 100644 index 00000000000..693c455431b --- /dev/null +++ b/compiler/enco/test/tflite/Regression_0003/test.recipe @@ -0,0 +1,33 @@ +# Compilation SHOULD NOT fail even if all the inputs are constant +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 2 } + filler { tag: "constant" arg: "0.1" } +} +operand { + name: "ker" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 2 } + filler { tag: "constant" arg: "0.2" } +} +operand { + name: "bias" + type: FLOAT32 + shape { dim: 1 } + filler { tag: "constant" arg: "0.3" } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 3 dim: 3 dim: 1 } +} +operation { + type: "Conv2D" + conv2d_options { padding: VALID } + input: "ifm" + input: "ker" + input: "bias" + output: "ofm" +} +output: "ofm" diff --git a/compiler/enco/test/tflite/Regression_0004/INFERENCE b/compiler/enco/test/tflite/Regression_0004/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Regression_0004/test.recipe b/compiler/enco/test/tflite/Regression_0004/test.recipe new file mode 100644 index 00000000000..80705efd5bf --- /dev/null +++ b/compiler/enco/test/tflite/Regression_0004/test.recipe @@ -0,0 +1,27 @@ +operand { + name: "ifm0" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } + filler { tag: "constant" arg: "0.1" } +} +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } + filler { tag: "constant" arg: "0.1" } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 4 dim: 4 dim: 3 } +} +operation { + type: "Div" + input: "ifm0" + input: "ifm1" + output: "ofm" + div_options { + activation: NONE + } +} +output: "ofm" diff --git a/compiler/enco/test/tflite/Reshape_000/INFERENCE b/compiler/enco/test/tflite/Reshape_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Reshape_000/test.recipe b/compiler/enco/test/tflite/Reshape_000/test.recipe new file mode 100644 index 00000000000..bb7ce48a95d --- /dev/null +++ b/compiler/enco/test/tflite/Reshape_000/test.recipe @@ -0,0 +1,21 @@ +operand { + name: "ifm" + type: FLOAT32 + shape { dim: 1 dim: 1 dim: 1 dim: 10 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 10 } +} +operation { + type: "Reshape" + reshape_options { + new_shape: -1 + new_shape: 10 + } + input: "ifm" + output: "ofm" +} +input: "ifm" +output: "ofm" diff --git a/compiler/enco/test/tflite/Sub_000/INFERENCE b/compiler/enco/test/tflite/Sub_000/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/Sub_000/test.recipe b/compiler/enco/test/tflite/Sub_000/test.recipe new file mode 100644 index 00000000000..0397c9c2bb0 --- /dev/null +++ b/compiler/enco/test/tflite/Sub_000/test.recipe @@ -0,0 +1,27 @@ +operand { + name: "ifm1" + type: FLOAT32 + shape { dim: 1 dim: 5 dim:2 dim:3 } +} +operand { + name: "ifm2" + type: FLOAT32 + shape { dim: 1 dim: 5 dim:2 dim:3 } +} +operand { + name: "ofm" + type: FLOAT32 + shape { dim: 1 dim: 5 dim:2 dim:3 } +} +operation { + type: "Sub" + sub_options { + activation: NONE + } + input: "ifm1" + input: "ifm2" + output: "ofm" +} +input: "ifm1" +input: "ifm2" +output: "ofm" diff --git a/compiler/enco/test/tflite/empty/INFERENCE b/compiler/enco/test/tflite/empty/INFERENCE new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/empty/test.recipe b/compiler/enco/test/tflite/empty/test.recipe new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/enco/test/tflite/runall.sh b/compiler/enco/test/tflite/runall.sh new file mode 100755 index 00000000000..c274f724b21 --- /dev/null +++ b/compiler/enco/test/tflite/runall.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +if [[ $# -le 6 ]]; then + echo "USAGE: $0 [nnkit-run path] [reference backend path] [randomize action path] [HDF5 export action path] [HDF5 import action path] [WORKDIR] [Prefix1] [Prefix2] ..." + exit 255 +fi + +NNKIT_RUN_PATH="$1"; shift +REFERENCE_BACKEND_PATH="$1"; shift +RANDOMIZE_ACTION_PATH="$1"; shift +HDF5_EXPORT_ACTION_PATH="$1"; shift +HDF5_IMPORT_ACTION_PATH="$1"; shift +WORKDIR="$1"; shift + +echo "-- Found nnkit-run: ${NNKIT_RUN_PATH}" +echo "-- Found reference backend: ${REFERENCE_BACKEND_PATH}" +echo "-- Found randomize action: ${RANDOMIZE_ACTION_PATH}" +echo "-- Found HDF5 export action: ${HDF5_EXPORT_ACTION_PATH}" +echo "-- Found HDF5 import action: ${HDF5_IMPORT_ACTION_PATH}" +echo "-- Found workdir: ${WORKDIR}" + +TESTED=() +PASSED=() +FAILED=() + +pushd "${WORKDIR}" +while [[ $# -ne 0 ]]; do + PREFIX="$1"; shift + + TESTED+=("${PREFIX}") + + PASSED_TAG="${PREFIX}.passed" + + rm -f "${PASSED_TAG}" + + cat > "${PREFIX}.log" <( + exec 2>&1 + + echo "-- Found tflite: ${PREFIX}.tflite" + echo "-- Found backend: lib${PREFIX}.so" + + "${NNKIT_RUN_PATH}" \ + --backend "${REFERENCE_BACKEND_PATH}" \ + --backend-arg "${WORKDIR}/${PREFIX}.tflite" \ + --pre "${RANDOMIZE_ACTION_PATH}" \ + --pre "${HDF5_EXPORT_ACTION_PATH}" \ + --pre-arg "${PREFIX}.input.h5" \ + --post "${HDF5_EXPORT_ACTION_PATH}" \ + --post-arg "${PREFIX}.expected.h5" + + "${NNKIT_RUN_PATH}" \ + --backend "./lib${PREFIX}.so" \ + --pre "${HDF5_IMPORT_ACTION_PATH}" \ + --pre-arg "${PREFIX}.input.h5" \ + --post "${HDF5_EXPORT_ACTION_PATH}" \ + --post-arg "${PREFIX}.obtained.h5" + + h5diff -d 0.001 "${PREFIX}.expected.h5" "${PREFIX}.obtained.h5" + + if [[ $? -eq 0 ]]; then + touch "${PASSED_TAG}" + fi + ) + + if [[ -f "${PASSED_TAG}" ]]; then + PASSED+=("$PREFIX") + else + FAILED+=("$PREFIX") + fi +done +popd + +if [[ ${#TESTED[@]} -ne ${#PASSED[@]} ]]; then + echo "FAILED" + for TEST in "${FAILED[@]}" + do + echo "- ${TEST}" + done + exit 255 +fi + +echo "PASSED" +exit 0 diff --git a/compiler/encodump/CMakeLists.txt b/compiler/encodump/CMakeLists.txt new file mode 100644 index 00000000000..58fe17a51fd --- /dev/null +++ b/compiler/encodump/CMakeLists.txt @@ -0,0 +1,17 @@ +if(NOT TARGET enco_intf_frontend) + return() +endif(NOT TARGET enco_intf_frontend) + +if(NOT TARGET enco_core) + return() +endif(NOT TARGET enco_core) + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(encodump ${SOURCES}) +target_include_directories(encodump PRIVATE src) +target_link_libraries(encodump enco_intf_frontend) +target_link_libraries(encodump enco_core) +target_link_libraries(encodump safemain) +target_link_libraries(encodump stdex) +target_link_libraries(encodump dl) diff --git a/compiler/encodump/README.md b/compiler/encodump/README.md new file mode 100644 index 00000000000..1a2b4496967 --- /dev/null +++ b/compiler/encodump/README.md @@ -0,0 +1,69 @@ +# encodump + +_encodump_ is a dumper for coco IR generated by enco + +## How to use +Sources for _encodump_ are: +1. enco frontend library `*.so` file +1. model description file for matching to enco frontend +``` +$ path/to/encodump \ + --frontend [enco frontend library .so file] + --frontend-arg [model file] ... +``` + +Currently supported enco frontends are Caffe and tensorflow lite. For Caffe, both `*.prototxt` and `*.caffemodel` are required, and for TFlite, `*.tflite` flatbuffers file is required. + +Output is dumped into terminal. + +## Example +``` +nncc$ ./build/compiler/encodump/encodump \ + --frontend ./build/compiler/enco/frontend/tflite/libenco_tflite_frontend.so \ + --frontend-arg ./build/compiler/enco/test/tflite/Conv2D_000.tflite +``` + +Output: +``` + + (index: 0) + : + Eval (0x10cfa90) + out: 0x10cf960 + : + Load(0x10cf600, obj: 0x10cd670) + Conv2D(0x10cf8a0, ker obj: 0x10cf2d0, padding [T/B/L/R=0,0,0,0], stride [V/H = 1,1]) + : + Eval (0x10cff80) + out: 0x10cfb20 + : + Load(0x10cfe70, obj: 0x10cfcc0) + Load(0x10cfdd0, obj: 0x10cf960) + Add + : + Copy (0x10d0120) + from: 0x10cfb20 + into: 0x10cfff0 + : + Copy (0x10d01f0) + from: 0x10cfff0 + into: 0x10cf210 + : bag 0x10ce650, name=ifm + : bag 0x10ce9c0, name=ofm + : + 0x10ce650, obj: [0x10cd670], size: 18, input, const, reader: [x], updater: [x], + 0x10ce770, obj: [0x10cf2d0], size: 2, const, reader: [x], updater: [x], + 0x10ce890, obj: [0x10cfcc0], size: 1, const, reader: [x], updater: [x], + 0x10ce9c0, obj: [0x10cf210], size: 9, output, const, reader: [x], updater: [x], + 0x10cf9d0, obj: [0x10cf960], size: 9, const, reader: [x], updater: [x], + 0x10cfbe0, obj: [0x10cfb20], size: 9, const, reader: [x], updater: [x], + 0x10d0060, obj: [0x10cfff0], size: 9, const, reader: [x], updater: [x], + : + 0x10cd670, bag: 0x10ce650, kind: Feature, Shape [H/W/D=3,3,2], producer: x, comsumer: [op: 0x10cf600] + 0x10cf210, bag: 0x10ce9c0, kind: Feature, Shape [H/W/D=3,3,1], producer: instr: 0x10d01f0, comsumer: [x] + 0x10cf2d0, bag: 0x10ce770, kind: Kernel, Shape [N/H/W/D=1,1,1,2], producer: x, comsumer: [op: 0x10cf8a0] + 0x10cf960, bag: 0x10cf9d0, kind: Feature, Shape [H/W/D=3,3,1], producer: instr: 0x10cfa90, comsumer: [op: 0x10cfdd0] + 0x10cfb20, bag: 0x10cfbe0, kind: Feature, Shape [H/W/D=3,3,1], producer: instr: 0x10cff80, comsumer: [inst: 0x10d0120] + 0x10cfcc0, bag: 0x10ce890, kind: Feature, Shape [H/W/D=3,3,1], producer: x, comsumer: [op: 0x10cfe70] + 0x10cfff0, bag: 0x10d0060, kind: Feature, Shape [H/W/D=3,3,1], producer: instr: 0x10d0120, comsumer: [inst: 0x10d01f0] +``` diff --git a/compiler/encodump/requires.cmake b/compiler/encodump/requires.cmake new file mode 100644 index 00000000000..3d1ca094e72 --- /dev/null +++ b/compiler/encodump/requires.cmake @@ -0,0 +1 @@ +require("safemain") diff --git a/compiler/encodump/src/Driver.cpp b/compiler/encodump/src/Driver.cpp new file mode 100644 index 00000000000..f27cbe90486 --- /dev/null +++ b/compiler/encodump/src/Driver.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include + +#include + +#include +#include + +#include + +#include "Dump.h" + +namespace cmdline +{ + +// TODO Extract this helper class +class Vector : public cmdline::View +{ +public: + uint32_t size(void) const { return _args.size(); } + +public: + const char *at(uint32_t nth) const { return _args.at(nth).c_str(); } + +public: + Vector &append(const std::string &arg) + { + _args.emplace_back(arg); + return (*this); + } + +private: + std::vector _args; +}; + +} // namespace cmdline + +namespace +{ + +class Zone +{ +public: + Zone() = default; + +public: + const cmdline::View *args(void) const { return &_args; } + +public: + void append(const std::string &arg) { _args.append(arg); } + +private: + cmdline::Vector _args; +}; + +} // namespace + +#include + +namespace +{ + +class FrontendFactory +{ +public: + FrontendFactory(const std::string &path) + { + _handle = dlopen(path.c_str(), RTLD_LAZY); + assert(_handle != nullptr); + } + +public: + // Copy is not allowed to avoid double close + FrontendFactory(const FrontendFactory &) = delete; + FrontendFactory(FrontendFactory &&) = delete; + +public: + ~FrontendFactory() { dlclose(_handle); } + +private: + using Entry = std::unique_ptr (*)(const cmdline::View &); + +private: + Entry entry(void) const + { + auto entry = reinterpret_cast(dlsym(_handle, "make_frontend")); + assert(entry != nullptr); + return entry; + } + +public: + std::unique_ptr make(const cmdline::View *args) const + { + auto fn = entry(); + return fn(*args); + } + +private: + void *_handle; +}; + +} // namespace + +namespace +{ + +class FrontendZone : public Zone +{ +public: + FrontendZone(const std::string &path) : _factory{path} + { + // DO NOTHING + } + +public: + const FrontendFactory *factory(void) const { return &_factory; } + +private: + FrontendFactory _factory; +}; + +} // namespace + +#include + +#include + +#include +#include + +/** + * @brief Dump IR for given arguments + * + * Call example: + * $ ./build/compiler/encodump/encodump \ + * --frontend build/compiler/enco/frontend/caffe/libenco_caffe_frontend.so \ + * --frontend-arg build/compiler/enco/test/caffe/Convolution_003.prototxt \ + * --frontend-arg build/compiler/enco/test/caffe/Convolution_003.caffemodel + */ +int entry(int argc, char **argv) +{ + // Usage: + // [Command] --frontend [Frontend .so path] --frontend-arg ... + std::unique_ptr frontend_zone; + + // Simple argument parser (based on map) + std::map> argparse; + + argparse["--frontend"] = [&](const std::string &path) { + frontend_zone = stdex::make_unique(path); + }; + + argparse["--frontend-arg"] = [&](const std::string &arg) { frontend_zone->append(arg); }; + + if (argc < 2) + { + std::cerr << "Usage:" << std::endl; + std::cerr << "[Command] --frontend [.so path]" << std::endl; + std::cerr << " --frontend-arg [argument] ..." << std::endl; + return 255; + } + + for (int n = 1; n < argc; n += 2) + { + const std::string tag{argv[n]}; + const std::string arg{argv[n + 1]}; + + auto it = argparse.find(tag); + + if (it == argparse.end()) + { + std::cerr << "Option '" << tag << "' is not supported" << std::endl; + return 255; + } + + it->second(arg); + } + + assert(frontend_zone != nullptr); + + auto frontend = frontend_zone->factory()->make(frontend_zone->args()); + + auto bundle = frontend->load(); + + // dump + dump(bundle.module()); + + // todo : dump data + + return 0; +} diff --git a/compiler/encodump/src/Dump.cpp b/compiler/encodump/src/Dump.cpp new file mode 100644 index 00000000000..7ec00e2e231 --- /dev/null +++ b/compiler/encodump/src/Dump.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @file Dump.cpp + * @brief Print coco IR produced from enco frontend + * + * @note Some object inherits multiple parents. + * For example, coco:Conv2D inherits coco::Consumer and more. Assume that op is an instance + * of coco::Conv2D. In this case, the printing results of the following may be different: + * 1) cout << op; // printing address of type coco::Conv2D + * 2) cout << reinterpret_cast(op); + * 3) cout << object->consumer(); // assume that this object->consumer() returns op + * 4) cout << dynamic_cast(op); + * 1) and 2) prints same address. 3) and 4) prints same address but different from 1) and 2). + * For details, refer to + * https://stackoverflow.com/questions/22256620/why-pointers-to-the-same-object-have-different-values + * For dumping, we will use 3), 4) + */ +#include "Dump.h" + +#include +#include + +std::string tab(int n) { return std::string(n * 2, ' '); } + +struct OpPrinter final : public coco::Op::Visitor +{ +public: + OpPrinter(std::ostream &os, int indent) : _os(os), _indent(indent) {} + +public: + void visit(const coco::Load *op) override + { + _os << tab(_indent) << "Load(" << dynamic_cast(op) + << ", obj: " << op->object() << ")" << std::endl; + } + + void visit(const coco::PadF *op) override + { + op->arg()->accept(this); + _os << tab(_indent) << "PadF" << std::endl; + } + + void visit(const coco::Conv2D *op) override + { + op->arg()->accept(this); + const coco::Padding2D *pad = op->pad(); + const coco::Stride2D *stride = op->stride(); + + _os << tab(_indent) << "Conv2D(" << dynamic_cast(op) + << ", ker obj: " << op->ker() << ", padding [T/B/L/R=" << pad->top() << "," << pad->bottom() + << "," << pad->left() << "," << pad->right() << "]" + << ", stride [V/H = " << stride->vertical() << "," << stride->horizontal() << "]" + << ")" << std::endl; + } + + void visit(const coco::MaxPool2D *op) override + { + op->arg()->accept(this); + _os << tab(_indent) << "MaxPool2D" << std::endl; + } + + void visit(const coco::AvgPool2D *op) override + { + op->arg()->accept(this); + _os << tab(_indent) << "AvgPool2D" << std::endl; + } + + void visit(const coco::Add *op) override + { + op->left()->accept(this); + op->right()->accept(this); + _os << tab(_indent) << "Add" << std::endl; + } + + void visit(const coco::Mul *op) override + { + op->left()->accept(this); + op->right()->accept(this); + _os << tab(_indent) << "Mul" << std::endl; + } + + void visit(const coco::ReLU *op) override + { + op->arg()->accept(this); + _os << tab(_indent) << "ReLU" << std::endl; + } + + void visit(const coco::ReLU6 *op) override + { + op->arg()->accept(this); + _os << tab(_indent) << "ReLU6" << std::endl; + } + + void visit(const coco::Sub *op) override + { + op->left()->accept(this); + op->right()->accept(this); + _os << tab(_indent) << "Sub" << std::endl; + } + + void visit(const coco::ConcatF *op) override + { + op->left()->accept(this); + op->right()->accept(this); + _os << tab(_indent) << "ConcatF" << std::endl; + } + + void visit(const coco::Div *op) override + { + op->left()->accept(this); + op->right()->accept(this); + _os << tab(_indent) << "Div" << std::endl; + } + +private: + std::ostream &_os; + +private: + int _indent; +}; + +struct InstrPrinter final : public coco::Instr::Visitor +{ +public: + InstrPrinter() = delete; + + InstrPrinter(int indent) : _indent(indent) {} + + void visit(const coco::Eval *ins) override + { + std::cout << tab(_indent) << "Eval (" << dynamic_cast(ins) << ")" + << std::endl; + std::cout << tab(_indent + 1) << "out: " << ins->out() << std::endl; + std::cout << tab(_indent + 1) << ": " << std::endl; + { + OpPrinter prn(std::cout, _indent + 2); + ins->op()->accept(prn); + } + } + + void visit(const coco::Copy *ins) override + { + // copy is Producer and also Customer. We will use address for Producer + std::cout << tab(_indent) << "Copy (" << dynamic_cast(ins) << ")" + << std::endl; + std::cout << tab(_indent) << " from: " << ins->from() << std::endl; + std::cout << tab(_indent) << " into: " << ins->into() << std::endl; + } + + void visit(const coco::Shuffle *ins) override + { + std::cout << tab(_indent) << "Shuffle (" << dynamic_cast(ins) << ")" + << std::endl; + std::cout << tab(_indent) << " from: " << ins->from() << std::endl; + std::cout << tab(_indent) << " into: " << ins->into() << std::endl; + } + +private: + int _indent; +}; + +void dump(const coco::Op *op, int indent) +{ + OpPrinter prn(std::cout, indent); + op->accept(prn); +} + +void dump(const coco::Instr *ins, int indent) +{ + std::cout << tab(indent) << ":" << std::endl; + + static InstrPrinter prn(indent + 1); + + ins->accept(prn); +} + +void dump(const coco::Block *B, int indent) +{ + std::cout << tab(indent) << " (index: " << B->index().value() << ")" << std::endl; + for (auto I = B->instr()->head(); I != nullptr; I = I->next()) + { + dump(I, indent + 1); + } +} + +void dump(const coco::BlockList *L, int indent) +{ + for (auto B = L->head(); B != nullptr; B = B->next()) + { + dump(B, indent); + } +} + +template +void dump(std::string header, SetT set, EntityF print_addr_f) +{ + std::cout << header << ": ["; + if (set->size() == 0) + std::cout << "x"; + else + { + int idx = 0; + for (auto entity : *set) + { + if (idx++ != 0) + std::cout << ", "; + print_addr_f(entity); + } + } + std::cout << "]"; +} + +void dump(const coco::BagManager *l, int indent) +{ + std::cout << tab(indent) << ":" << std::endl; + + for (auto n = 0; n < l->size(); ++n) + { + auto bag = l->at(n); + + std::cout << tab(indent + 1) << bag << ", "; + + // print objects in bag->deps() + auto print_dep_object = [](coco::Dep *dep) { std::cout << dep->object(); }; + dump("obj", bag->deps(), print_dep_object); + std::cout << ", "; + + std::cout << "size: " << bag->size() << ", "; + + if (bag->isInput()) + std::cout << "input, "; + if (bag->isOutput()) + std::cout << "output, "; + if ((!bag->isInput()) || (!bag->isOutput())) + std::cout << "const, "; + + // print readers in bag->reads() + auto print_read_reader = [](coco::Read *read) { + if (coco::Op *op = dynamic_cast(read->reader())) + std::cout << "op: " << op; + else if (coco::Instr *instr = dynamic_cast(read->reader())) + std::cout << "instr: " << instr; + else + std::cout << "x"; + }; + dump("reader", bag->reads(), print_read_reader); + std::cout << ", "; + + // print updaters in bag->updates() + auto print_update_updater = [](coco::Update *update) { + if (coco::Op *op = dynamic_cast(update->updater())) + std::cout << "op: " << op; + else if (coco::Instr *instr = dynamic_cast(update->updater())) + std::cout << "instr: " << instr; + else + std::cout << "x"; + }; + dump("updater", bag->updates(), print_update_updater); + std::cout << ", "; + + std::cout << std::endl; + } +} + +void dump(coco::FeatureObject *feature_ob) +{ + auto shape = feature_ob->shape(); + std::cout << "kind: Feature, Shape [H/W/D=" << shape.height() << "," << shape.width() << "," + << shape.depth() << "]"; +} + +void dump(coco::KernelObject *kernel_ob) +{ + auto shape = kernel_ob->shape(); + std::cout << "kind: Kernel, Shape [N/H/W/D=" << shape.count() << "," << shape.height() << "," + << shape.width() << "," << shape.depth() << "]"; +} + +void dump(const coco::ObjectManager *l, int indent) +{ + std::cout << tab(indent) << ":" << std::endl; + for (auto n = 0; n < l->size(); ++n) + { + auto obj = l->at(n); + std::cout << tab(indent + 1) << obj << ", bag: " << obj->bag() << ", "; + + using ObDumpers = std::function; + + std::map ob_dumpers; + + ob_dumpers[coco::Object::Kind::Feature] = [](coco::Object *ob) { dump(ob->asFeature()); }; + ob_dumpers[coco::Object::Kind::Kernel] = [](coco::Object *ob) { dump(ob->asKernel()); }; + ob_dumpers[coco::Object::Kind::Unknown] = [](coco::Object *ob) { + std::cout << "kind: Unknown"; + }; + + ob_dumpers[obj->kind()](obj); + + std::cout << ", producer: "; + auto def = obj->def(); + if (def) + { + if (coco::Op *op = dynamic_cast(def->producer())) + std::cout << "op: " << op; + else if (coco::Instr *instr = dynamic_cast(def->producer())) + std::cout << "instr: " << instr; + else + std::cout << "x"; + } + else + std::cout << "x"; + std::cout << ", "; + + // print consumers in obj->uses() + auto print_consumer = [](coco::Use *use) { + if (coco::Op *op = dynamic_cast(use->consumer())) + std::cout << "op: " << op; + else if (coco::Instr *instr = dynamic_cast(use->consumer())) + std::cout << "inst: " << instr; + else + std::cout << "x"; + }; + dump("comsumer", obj->uses(), print_consumer); + std::cout << std::endl; + } +} + +template void head(int indent); + +template <> void head(int indent) { std::cout << tab(indent) << ": "; } + +template <> void head(int indent) { std::cout << tab(indent) << ": "; } + +template void dump(const coco::PtrList *list, int indent) +{ + head(indent); + for (int n = 0; n < list->size(); n++) + { + const PtrItemT *item = list->at(n); + if (n != 0) + std::cout << ", "; + std::cout << "bag " << item->bag() << ", name=" << item->name(); + } + std::cout << std::endl; +} + +void dump(const coco::Module *module) +{ + std::cout << "" << std::endl; + + dump(module->block(), 1); + dump(module->input(), 1); + dump(module->output(), 1); + dump(module->entity()->bag(), 1); + dump(module->entity()->object(), 1); +} diff --git a/compiler/encodump/src/Dump.h b/compiler/encodump/src/Dump.h new file mode 100644 index 00000000000..6ea69b978ec --- /dev/null +++ b/compiler/encodump/src/Dump.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DUMP_H__ +#define __DUMP_H__ + +#include + +void dump(const coco::Module *module); + +#endif // __DUMP_H__ diff --git a/compiler/exo/CMakeLists.txt b/compiler/exo/CMakeLists.txt new file mode 100644 index 00000000000..79c75ef2efa --- /dev/null +++ b/compiler/exo/CMakeLists.txt @@ -0,0 +1,73 @@ +nnas_find_package(FlatBuffers QUIET) + +if(NOT FlatBuffers_FOUND) + message(STATUS "Build exo: FALSE (missing FlatBuffers)") + return() +endif(NOT FlatBuffers_FOUND) + +nnas_find_package(TensorFlowSource EXACT 1.14 QUIET) + +if(NOT TensorFlowSource_FOUND) + message(STATUS "Build exo: FALSE (missing TensorFlowSource)") + return() +endif(NOT TensorFlowSource_FOUND) + +message(STATUS "Build exo: TRUE") + +set(TFLITE_SCHEMA_DIR "${TensorFlowSource_DIR}/tensorflow/lite/schema") +set(CIRCLE_SCHEMA_DIR "${NNAS_PROJECT_SOURCE_DIR}/nnpackage/schema") + +FlatBuffers_Target(exo_tflite_fbs + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen" + SCHEMA_DIR "${TFLITE_SCHEMA_DIR}" + SCHEMA_FILES schema.fbs +) + +FlatBuffers_Target(exo_circle_fbs + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen" + SCHEMA_DIR "${CIRCLE_SCHEMA_DIR}" + SCHEMA_FILES circle_schema.fbs +) + +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(exo SHARED ${SOURCES}) +target_include_directories(exo PUBLIC include) +target_include_directories(exo PRIVATE src) +target_link_libraries(exo PUBLIC exo_tflite_fbs) +target_link_libraries(exo PUBLIC exo_circle_fbs) +target_link_libraries(exo PUBLIC loco) +target_link_libraries(exo PRIVATE stdex) +target_link_libraries(exo PRIVATE pepper_str) +target_link_libraries(exo PRIVATE pepper_strcast) +target_link_libraries(exo PRIVATE locoex_customop) +target_link_libraries(exo PRIVATE locop) +target_link_libraries(exo PRIVATE hermes_std) +target_link_libraries(exo PRIVATE logo) +target_link_libraries(exo PRIVATE oops) +install(TARGETS exo DESTINATION lib) + +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(exo PRIVATE nncc_common) + +if (NOT ENABLE_TEST) + return() +endif (NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(exo_test ${TESTS}) +target_include_directories(exo_test PRIVATE src) +target_link_libraries(exo_test stdex) +target_link_libraries(exo_test pepper_str) +target_link_libraries(exo_test exo) +target_link_libraries(exo_test hermes_std) +target_link_libraries(exo_test logo) +target_link_libraries(exo_test oops) +target_link_libraries(exo_test locoex_customop) diff --git a/compiler/exo/README.md b/compiler/exo/README.md new file mode 100644 index 00000000000..bfa7fcfd34a --- /dev/null +++ b/compiler/exo/README.md @@ -0,0 +1,12 @@ +# exo + +_exo_ includes _loco_-to-_T/F Lite_ exporter (as a library). + +## How to add a new TFL node + +1. Add a new TFL node into `TFLNodes.lst` and `TFLNodes.h` +1. Define a knob in `Knob.lst` if you need a knob. +1. Add appropriate methods in `TFLShapeInferenceRule.cpp` and `TFLTypeInferenceRule.cpp` +1. Add a new converter under `Conversion` directory +1. Add an appropriate method in `OperationExporter.cpp` +1. Register the converter into `Convert.cpp` diff --git a/compiler/exo/include/exo/CircleExporter.h b/compiler/exo/include/exo/CircleExporter.h new file mode 100644 index 00000000000..7ec15930355 --- /dev/null +++ b/compiler/exo/include/exo/CircleExporter.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXO_CIRCLE_EXPORTER_H__ +#define __EXO_CIRCLE_EXPORTER_H__ + +#include + +#include + +namespace exo +{ + +/** + * HOW TO USE: + * + * loco::Graph *g = ...; + * + * CircleExporter e(g); + * e.dumpToFile("model.circle"); + * + * HOW TO USE (simplified): + * + * CircleExporter(g).dumpToFile("model.circle"); + * + */ +class CircleExporter +{ +public: + class Impl; + +public: + explicit CircleExporter(loco::Graph *graph); + ~CircleExporter(); + + /** + * @brief write to a file + * @param path path to file where to write data + * @throws any file related exceptions + */ + void dumpToFile(const char *path) const; + +private: + std::unique_ptr _impl; +}; + +} // namespace exo + +#endif // __EXO_CIRCLE_EXPORTER_H__ diff --git a/compiler/exo/include/exo/LoggingContext.h b/compiler/exo/include/exo/LoggingContext.h new file mode 100644 index 00000000000..5f10ceb9343 --- /dev/null +++ b/compiler/exo/include/exo/LoggingContext.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXO_LOGGING_CONTEXT_H__ +#define __EXO_LOGGING_CONTEXT_H__ + +#include + +namespace exo +{ + +/** + * @brief Global logging context + */ +struct LoggingContext +{ + static hermes::Context *get(void); +}; + +} // namespace exo + +#endif // __EXO_LOGGING_CONTEXT_H__ diff --git a/compiler/exo/include/exo/TFLExporter.h b/compiler/exo/include/exo/TFLExporter.h new file mode 100644 index 00000000000..49cce2af51e --- /dev/null +++ b/compiler/exo/include/exo/TFLExporter.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXO_TFL_EXPORTER_H__ +#define __EXO_TFL_EXPORTER_H__ + +#include + +#include + +namespace exo +{ + +/** + * HOW TO USE: + * + * loco::Graph *g = ...; + * + * TFLExporter e(g); + * e.dumpToFile("model.tflite"); + * + * HOW TO USE (simplified): + * + * TFLExporter(g).dumpToFile("model.tflite"); + * + */ +class TFLExporter +{ +public: + class Impl; + +public: + explicit TFLExporter(loco::Graph *graph); + ~TFLExporter(); + + /** + * @brief write to a file + * @param path path to file where to write data + * @throws any file related exceptions + */ + void dumpToFile(const char *path) const; + +private: + std::unique_ptr _impl; +}; + +} // namespace exo + +#endif // __EXO_TFL_EXPORTER_H__ diff --git a/compiler/exo/requires.cmake b/compiler/exo/requires.cmake new file mode 100644 index 00000000000..6378b942de3 --- /dev/null +++ b/compiler/exo/requires.cmake @@ -0,0 +1,6 @@ +require("stdex") +require("loco") +require("locoex-customop") +require("logo") +require("pepper-str") +require("oops") diff --git a/compiler/exo/src/Check.h b/compiler/exo/src/Check.h new file mode 100644 index 00000000000..79dac50dd4a --- /dev/null +++ b/compiler/exo/src/Check.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CHECK_H__ +#define __CHECK_H__ + +#include + +#include +#include +#include + +// TODO Add macro for Release version + +#define EXO_ASSERT(condition, msg) \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[assert failed] " << (msg) << ". " << std::endl; \ + assert((condition)); \ + } \ + } + +#endif // __CHECK_H__ diff --git a/compiler/exo/src/Circle/CircleExporter.cpp b/compiler/exo/src/Circle/CircleExporter.cpp new file mode 100644 index 00000000000..797749090e6 --- /dev/null +++ b/compiler/exo/src/Circle/CircleExporter.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "exo/CircleExporter.h" + +#include "CircleExporterImpl.h" + +#include + +#include + +#include + +namespace exo +{ + +CircleExporter::CircleExporter(loco::Graph *graph) : _impl(stdex::make_unique(graph)) +{ + // NOTHING TO DO +} + +CircleExporter::~CircleExporter() = default; + +void CircleExporter::dumpToFile(const char *path) const +{ + const char *ptr = _impl->getBufferPointer(); + const size_t size = _impl->getBufferSize(); + + if (!ptr) + INTERNAL_EXN("Graph was not serialized by FlatBuffer for some reason"); + + std::ofstream file(path, std::ofstream::binary); + file.write(ptr, size); +} + +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleExporterImpl.cpp b/compiler/exo/src/Circle/CircleExporterImpl.cpp new file mode 100644 index 00000000000..4cba33da14a --- /dev/null +++ b/compiler/exo/src/Circle/CircleExporterImpl.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleExporterImpl.h" + +#include "Convert.h" +#include "ExoOptimize.h" + +#include "CircleTensorExporter.h" +#include "CircleOperationExporter.h" +#include "CircleExporterUtils.h" + +#include "Log.h" +#include "Knob.h" + +#include + +#include +#include +#include +#include + +namespace +{ + +using namespace exo::circle_detail; + +void registerGraphInputTensors(loco::Graph *graph, SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->inputs()->size(); ++n) + { + auto node = loco::pull_node(graph, n); + assert(node != nullptr); + ctx._inputs.push_back(get_tensor_index(node)); + } +} + +void registerGraphOutputTensors(loco::Graph *graph, SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->outputs()->size(); ++n) + { + auto push = loco::push_node(graph, n); + assert(push != nullptr); + auto node = push->from(); + assert(node != nullptr); + ctx._outputs.push_back(get_tensor_index(node)); + } +} + +} // namespace + +namespace +{ + +using namespace circle; +using namespace flatbuffers; + +Offset>> +encodeOperatorCodes(FlatBufferBuilder &builder, std::unordered_map &opcodes, + std::unordered_map &custom_opcodes) +{ + std::vector> operator_codes_vec(opcodes.size()); + for (auto it : opcodes) + { + uint32_t idx = it.second; + if (it.first.opcode != BuiltinOperator_CUSTOM) + { + operator_codes_vec[idx] = CreateOperatorCode(builder, it.first.opcode); + } + else // custom op + { + auto opCode = it.first; + auto custom_code = custom_opcodes.find(opCode); + if (custom_code == custom_opcodes.end()) + INTERNAL_EXN("Cannot find code for customop even though opcode is BuiltinOperator_CUSTOM"); + + operator_codes_vec[idx] = + CreateOperatorCode(builder, it.first.opcode, builder.CreateString(custom_code->second)); + } + } + return builder.CreateVector(operator_codes_vec); +} + +} // namespace + +namespace exo +{ + +using namespace exo::circle_detail; +using namespace circle; +using namespace flatbuffers; + +CircleExporter::Impl::Impl(loco::Graph *graph) { exportGraph(graph); } + +::flatbuffers::Offset<::circle::SubGraph> +CircleExporter::Impl::exportSubgraph(SerializedModelData &gd) +{ + auto tensors = _builder.CreateVector(gd._tensors); + auto inputs = _builder.CreateVector(gd._inputs); + auto outputs = _builder.CreateVector(gd._outputs); + auto operators = _builder.CreateVector(gd._operators); + auto df = gd._data_format; + auto subgraph = CreateSubGraph(_builder, tensors, inputs, outputs, operators, df); + return subgraph; +} + +void CircleExporter::Impl::exportGraph(loco::Graph *graph) +{ + LOGGER(l); + + // IR-level conversion and optimization + { + convert_to_TFLNodes(graph); + set(Dialect::CIRCLE); + optimize(graph); + } + + _builder.Clear(); + + SerializedModelData gd; + + // This version is taken from comment in fbs + constexpr uint32_t version = 0; + + registerGraphIOName(graph, gd); + + // parse graph into SerializedModelData structure + exportOpDefinedTensors(graph, _builder, gd); + + // NOTE Invoke these register functions only after each node is annotated with its tensor_index + registerGraphInputTensors(graph, gd); + registerGraphOutputTensors(graph, gd); + + exportNodes(graph, _builder, gd); + + // encode operator codes + auto operator_codes = + encodeOperatorCodes(_builder, gd._operator_codes, gd._custom_operator_codes); + + // Subgraphs + Offset subgraph = exportSubgraph(gd); + auto subgraphs = _builder.CreateVector(std::vector>{subgraph}); + + // Description + std::string description_str = "nnpackage"; + auto description = _builder.CreateString(description_str); + + // create array of buffers + auto buffers = _builder.CreateVector(gd._buffers); + + // empty metadata + std::vector metadata_buffer_vec; + auto metadata_buffer = _builder.CreateVector(metadata_buffer_vec); + + // Model + auto model_offset = CreateModel(_builder, version, operator_codes, subgraphs, description, + buffers, metadata_buffer); + FinishModelBuffer(_builder, model_offset); +} + +const char *CircleExporter::Impl::getBufferPointer() const +{ + return reinterpret_cast(_builder.GetBufferPointer()); +} + +size_t CircleExporter::Impl::getBufferSize() const { return _builder.GetSize(); } + +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleExporterImpl.h b/compiler/exo/src/Circle/CircleExporterImpl.h new file mode 100644 index 00000000000..b1138fbad43 --- /dev/null +++ b/compiler/exo/src/Circle/CircleExporterImpl.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_EXPORTER_IMPL_H__ +#define __CIRCLE_EXPORTER_IMPL_H__ + +#include "exo/CircleExporter.h" +#include "circle_schema_generated.h" + +#include + +namespace exo +{ + +namespace circle_detail +{ + +struct SerializedModelData; + +} // namespace circle_detail + +using namespace circle_detail; + +/** + * internal implementation of interface exporter class + */ +class CircleExporter::Impl +{ +public: + Impl() = delete; + ~Impl() = default; + + explicit Impl(loco::Graph *graph); + + /** + * @return pointer to buffer with serialized graph + */ + const char *getBufferPointer() const; + + /** + * @return size of buffer with serialized graph + */ + size_t getBufferSize() const; + +private: + /** + * @brief create Subgraph using data stored in SerializedModelData + * @param gd information about serializer parts of model + * @return offset in buffer corresponding to serialized subgraph + */ + flatbuffers::Offset exportSubgraph(SerializedModelData &gd); + + /** + * @brief root function that writes graph into internal buffer + * @param graph + */ + void exportGraph(loco::Graph *graph); + +private: + flatbuffers::FlatBufferBuilder _builder; +}; + +} // namespace exo + +#endif // __CIRCLE_EXPORTER_IMPL_H__ diff --git a/compiler/exo/src/Circle/CircleExporterUtils.cpp b/compiler/exo/src/Circle/CircleExporterUtils.cpp new file mode 100644 index 00000000000..12b204ce7eb --- /dev/null +++ b/compiler/exo/src/Circle/CircleExporterUtils.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleExporterUtils.h" + +#include + +namespace exo +{ + +circle::ActivationFunctionType to_circle_actfunc(locoex::FusedActFunc func) +{ + switch (func) + { + case locoex::FusedActFunc::NONE: + return circle::ActivationFunctionType_NONE; + case locoex::FusedActFunc::RELU: + return circle::ActivationFunctionType_RELU; + case locoex::FusedActFunc::RELU6: + return circle::ActivationFunctionType_RELU6; + default: + INTERNAL_EXN_V("trying to convert unsupported locoex::FusedActFunc", oops::to_uint32(func)); + } +} + +} // namespace exo + +namespace exo +{ +namespace circle_detail +{ + +uint32_t SerializedModelData::registerBuiltinOpcode(circle::BuiltinOperator builtin_code) +{ + auto it = _operator_codes.find(OpCode{builtin_code}); + if (it != _operator_codes.end()) + { + return it->second; + } + auto idx = static_cast(_operator_codes.size()); + _operator_codes.emplace(OpCode{builtin_code}, idx); + return idx; +} + +uint32_t SerializedModelData::registerCustomOpcode(const std::string &custom_op) +{ + circle::BuiltinOperator custom_code = circle::BuiltinOperator_CUSTOM; + auto idx = registerBuiltinOpcode(custom_code); + _custom_operator_codes.emplace(OpCode{custom_code}, custom_op); + return idx; +} + +circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm) +{ + // VALID padding + if (pad->top() == 0 && pad->bottom() == 0 && pad->left() == 0 && pad->right() == 0) + return circle::Padding_VALID; + + // SAME padding + // + // For same padding, by definition, following equation should hold: + // O = floor((I - 1) / S) + 1 + // where input size I, output size O, stride S + // + // NOTE input and output 'feature' map are shape of NHWC + bool same_padding_criterion_1 = + (static_cast(ofm._dims[1]) == (ifm._dims[1] - 1) / stride->vertical() + 1) && + (static_cast(ofm._dims[2]) == (ifm._dims[2] - 1) / stride->horizontal() + 1); + + // For same padding, rear padding is same or bigger than front padding by at most 1 + bool same_padding_criterion_2 = + (pad->top() <= pad->bottom()) && (pad->bottom() <= pad->top() + 1) && + (pad->left() <= pad->right()) && (pad->right() <= pad->left() + 1); + + if (same_padding_criterion_1 && same_padding_criterion_2) + return circle::Padding_SAME; + + INTERNAL_EXN("Unsupported padding criteria"); +} + +circle::Padding getOpPadding(const locoex::Padding pad) +{ + if (pad == locoex::Padding::VALID) + return circle::Padding_VALID; + if (pad == locoex::Padding::SAME) + return circle::Padding_SAME; + + INTERNAL_EXN_V("Unsupported locoex::Padding", oops::to_uint32(pad)); +} + +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd) +{ + for (uint32_t in = 0; in < graph->inputs()->size(); ++in) + { + auto pull = loco::pull_node(graph, in); + auto name = graph->inputs()->at(in)->name(); + + gd._pull_to_name[pull] = name; + } + for (uint32_t out = 0; out < graph->outputs()->size(); ++out) + { + auto push = loco::push_node(graph, out); + auto name = graph->outputs()->at(out)->name(); + + gd._push_to_name[push] = name; + } + + // TODO set this value properly + gd._data_format = circle::DataFormat::DataFormat_CHANNELS_LAST; +} + +#include + +#include + +namespace +{ + +class TFLTensorIndexAnnotation final : public loco::NodeAnnotation +{ +public: + TFLTensorIndexAnnotation(const TFLTensorIndex &index) : _index{index} + { + // DO NOTHING + } + +public: + const TFLTensorIndex &index(void) const { return _index; } + +private: + TFLTensorIndex _index; +}; + +} // namespace + +void set_tensor_index(loco::Node *node, const TFLTensorIndex &tensor_id) +{ + assert(node->annot() == nullptr); + node->annot(stdex::make_unique(tensor_id)); +} + +TFLTensorIndex get_tensor_index(loco::Node *node) +{ + assert(node->annot() != nullptr); + return node->annot()->index(); +} + +} // namespace circle_detail +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleExporterUtils.h b/compiler/exo/src/Circle/CircleExporterUtils.h new file mode 100644 index 00000000000..fdd162baeca --- /dev/null +++ b/compiler/exo/src/Circle/CircleExporterUtils.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_EXPORTER_UTILS_H__ +#define __CIRCLE_EXPORTER_UTILS_H__ + +#include "ExporterUtils.h" + +#include "circle_schema_generated.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +#include + +namespace exo +{ +namespace circle_detail +{ + +struct OpCode +{ + circle::BuiltinOperator opcode; + + bool operator==(const OpCode &rhs) const { return opcode == rhs.opcode; } +}; + +} // namespace circle_detail +} // namespace exo + +namespace exo +{ + +circle::ActivationFunctionType to_circle_actfunc(locoex::FusedActFunc func); + +} // namespace exo + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const exo::circle_detail::OpCode &x) const { return hash()(x.opcode); } +}; + +} // namespace std + +namespace exo +{ +namespace circle_detail +{ + +/** + * @breif Record the information of T/F Lite SubGraph and its mapping to loco + */ +struct SubGraphContext +{ + /// @brief SubGraph input tensor id + std::vector _inputs; + /// @brief SubGraph output tensor id + std::vector _outputs; + /// @DataFormat for SubGraph + circle::DataFormat _data_format{circle::DataFormat::DataFormat_CHANNELS_LAST}; +}; + +// Prerequisites for circle::Model object creation +struct SerializedModelData final : public SubGraphContext +{ + SerializedModelData() = default; + SerializedModelData(const SerializedModelData &) = delete; + + std::unordered_map _operator_codes; + std::unordered_map _custom_operator_codes; + std::vector> _operators; + std::vector> _tensors; + std::vector> _buffers; + + // Graph input and output names + std::unordered_map _pull_to_name; + std::unordered_map _push_to_name; + + /** + * @brief if opcode is not registered in table of opcodes add it + * @param builtin_code + * @return idx of opcode in table of opcodes (see schema) + */ + uint32_t registerBuiltinOpcode(circle::BuiltinOperator builtin_code); + uint32_t registerCustomOpcode(const std::string &custom_op); +}; + +circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm); +circle::Padding getOpPadding(const locoex::Padding pad); + +/// @brief Register graph input and output names to SerializedModelData +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd); + +using TFLTensorIndex = int32_t; + +void set_tensor_index(loco::Node *node, const TFLTensorIndex &tensor_id); +TFLTensorIndex get_tensor_index(loco::Node *node); + +} // namespace circle_detail +} // namespace exo + +#endif // __TFL_EXPORTER_UTILS_H__ diff --git a/compiler/exo/src/Circle/CircleOperationExporter.cpp b/compiler/exo/src/Circle/CircleOperationExporter.cpp new file mode 100644 index 00000000000..390e2ec99b9 --- /dev/null +++ b/compiler/exo/src/Circle/CircleOperationExporter.cpp @@ -0,0 +1,1228 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleOperationExporter.h" +#include "CircleExporterUtils.h" +#include "ShapeInference.h" + +#include "Dialect/IR/TFLNode.h" +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include "Dialect/IR/CircleNode.h" +#include "Dialect/IR/CircleNodes.h" +#include "Dialect/IR/CircleNodeVisitor.h" + +#include "Check.h" + +#include +#include +#include +#include + +#include + +#include + +using namespace flatbuffers; +using namespace circle; + +namespace +{ + +using namespace exo; +using namespace exo::circle_detail; + +class OperationExporter final : public locoex::TFLNodeMutableVisitor, + public locoex::CircleNodeMutableVisitor, + public loco::CanonicalNodeMutableVisitor +{ +public: + OperationExporter(FlatBufferBuilder &fbb, SerializedModelData &ctx) : builder{fbb}, gd{ctx} + { + // DO NOTHING + } + +public: + // FOR TFLNodes + void visit(locoex::TFLAdd *) final; + void visit(locoex::TFLAveragePool2D *) final; + void visit(locoex::TFLConcatenation *) final; + void visit(locoex::TFLConst *) final{/* skip, everything is done in exportOpDefinedTensors */}; + void visit(locoex::TFLConv2D *) final; + void visit(locoex::TFLDepthwiseConv2D *) final; + void visit(locoex::TFLDiv *) final; + void visit(locoex::TFLFullyConnected *) final; + void visit(locoex::TFLMaximum *) final; + void visit(locoex::TFLMaxPool2D *) final; + void visit(locoex::TFLMean *) final; + void visit(locoex::TFLMul *) final; + void visit(locoex::TFLRelu *) final; + void visit(locoex::TFLRelu6 *) final; + // TODO TFLReshape + void visit(locoex::TFLRsqrt *) final; + // TODO TFLSoftmax + void visit(locoex::TFLSqrt *) final; + void visit(locoex::TFLSquaredDifference *) final; + void visit(locoex::TFLSub *) final; + // TODO TFLTanh + void visit(locoex::TFLTranspose *) final; + void visit(locoex::TFLTransposeConv *) final; + + // FOR CircleNodes + void visit(locoex::CircleInstanceNorm *) final; + + // FOR canonical nodes. These will be removed later + void visit(loco::ReLU *) final; + void visit(loco::ReLU6 *) final; + void visit(loco::Tanh *) final; + void visit(loco::Push *) final { /* DO NOTHING */} + void visit(loco::Pull *) final { /* DO NOTHING */} + void visit(loco::FeatureEncode *) final; + void visit(loco::FeatureDecode *) final; + void visit(loco::FilterEncode *) final; + void visit(loco::DepthwiseFilterEncode *) final; + void visit(loco::ConstGen *) final { /* skip, everything is done in exportOpDefinedTensors */} + void visit(loco::MaxPool2D *) final; + void visit(loco::AvgPool2D *) final; + void visit(loco::Conv2D *) final; + void visit(loco::TransposedConv2D *) final; + void visit(loco::DepthwiseConv2D *) final; + void visit(loco::TensorConcat *) final; + void visit(loco::TensorReduce *) final; + void visit(loco::TensorSoftmax *) final; + void visit(loco::BiasEncode *) final; + void visit(loco::TensorBiasAdd *) final; + void visit(loco::FeatureBiasAdd *) final; + void visit(loco::EltwiseAdd *) final; + void visit(loco::EltwiseMax *) final; + void visit(loco::EltwiseMul *) final; + void visit(loco::EltwiseSub *) final; + void visit(loco::EltwiseDiv *) final; + void visit(loco::EltwiseSqrt *) final; + void visit(loco::FixedReshape *) final; + void visit(loco::TensorBroadcast *) final; + void visit(loco::TensorConstantPad *) final; + + void visit(locoex::COpCall *); + +private: + /** + * @brief Exports TFLMaxPool2D or TFLAveragePool2D + * + * @note TFLPool2D should be one of TFLMaxPool2D or TFLAveragePool2D + */ + template + void export_pool_2d(TFLPool2D *node, circle::BuiltinOperator builtin_op); + +private: + FlatBufferBuilder &builder; + SerializedModelData &gd; +}; + +void OperationExporter::visit(locoex::TFLAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLAveragePool2D *node) +{ + export_pool_2d(node, circle::BuiltinOperator_AVERAGE_POOL_2D); +} + +void OperationExporter::visit(locoex::TFLConcatenation *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION); + std::vector inputs_vec; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + + for (uint32_t i = 0; i < node->numValues(); ++i) + inputs_vec.push_back(get_tensor_index(node->values(i))); + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder, node->axis(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ConcatenationOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = CreateConv2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + to_circle_actfunc(node->fusedActivationFunction())); + + // Make CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Conv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLDepthwiseConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = CreateDepthwiseConv2DOptions(builder, padding, node->stride()->w(), + node->stride()->h(), node->depthMultiplier(), + to_circle_actfunc(node->fusedActivationFunction())); + + // Make DEPTHWISE_CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLDiv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DIV); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateDivOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DivOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLFullyConnected *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_FULLY_CONNECTED); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->weights()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = + CreateFullyConnectedOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + + // Make FULLY_CONNECTED operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_FullyConnectedOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMaximum *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMaximumMinimumOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MaximumMinimumOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMaxPool2D *node) +{ + export_pool_2d(node, circle::BuiltinOperator_MAX_POOL_2D); +} + +void OperationExporter::visit(locoex::TFLMean *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MEAN); + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->reduction_indices())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateReducerOptions(builder, node->keep_dims()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ReducerOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMul *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MUL); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMulOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MulOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLRelu *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLRelu6 *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU6); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO TFLReshape + +void OperationExporter::visit(locoex::TFLRsqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RSQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO TFLSoftmax + +void OperationExporter::visit(locoex::TFLSqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLSquaredDifference *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQUARED_DIFFERENCE); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSquaredDifferenceOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SquaredDifferenceOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLSub *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SUB); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSubOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SubOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +// TODO TFLTanh + +void OperationExporter::visit(locoex::TFLTranspose *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE); + std::vector inputs_vec{get_tensor_index(node->arg(0)), get_tensor_index(node->arg(1))}; + std::vector outputs_vec{get_tensor_index(node)}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateTransposeOptions(builder); + + auto op_offset = + CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions::BuiltinOptions_TransposeOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLTransposeConv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->inputSizes()), + get_tensor_index(node->filter()), + get_tensor_index(node->outBackprop())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = + CreateTransposeConvOptions(builder, padding, node->stride()->w(), node->stride()->h()); + + // Make TRANSPOSE_CONV operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_TransposeConvOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +template +void OperationExporter::export_pool_2d(TFLPool2D *node, circle::BuiltinOperator builtin_op) +{ + EXO_ASSERT(builtin_op == circle::BuiltinOperator_MAX_POOL_2D || + builtin_op == circle::BuiltinOperator_AVERAGE_POOL_2D, + "should be maxpool or avgpool"); + EXO_ASSERT(node->padding() != locoex::Padding::UNDEFINED, "Padding is not set"); + + uint32_t op_idx = gd.registerBuiltinOpcode(builtin_op); + std::vector inputs_vec{get_tensor_index(node->value())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + circle::Padding padding = getOpPadding(node->padding()); + + auto options = CreatePool2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + node->filter()->w(), node->filter()->h(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::CircleInstanceNorm *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_INSTANCE_NORM); + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->gamma()), + get_tensor_index(node->beta())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateInstanceNormOptions(builder, node->epsilon(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_InstanceNormOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::ReLU *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::ReLU6 *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU6); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::Tanh *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TANH); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::MaxPool2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAX_POOL_2D); + std::vector inputs_vec{get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreatePool2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), node->window()->horizontal(), + node->window()->vertical()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::AvgPool2D *node) +{ + // Circle only support Valid convention of average pooling + assert(node->convention() == loco::AvgPool2D::Convention::Valid); + + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_AVERAGE_POOL_2D); + std::vector inputs_vec{get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreatePool2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), node->window()->horizontal(), + node->window()->vertical()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::Conv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D); + + // Third input of CONV_2D of Circle should be bias. We will make (and register to gd) dummy zero + // bias. Bias would be rank 1, have size of output kernel count, and have all zero values, i.e. + // zero bias. + auto *ker = dynamic_cast(node->ker()); + assert(ker); + int32_t bias_vec_size = ShapeInference::get(ker)._dims[0]; // output kernel count + + auto bias_vec_shape_offset = builder.CreateVector(std::vector{bias_vec_size}); + size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t); + + std::vector bias_vec_data(bias_vec_size); // initialized as zero vector + + auto bias_vec_offset = + builder.CreateVector(reinterpret_cast(bias_vec_data.data()), raw_bias_vec_size); + + auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset); + + const auto bias_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(bias_buffer_offset); + + auto bias_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id)); + + auto bias_tensor_offset = + CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset); + gd._tensors.push_back(bias_tensor_offset); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()), + bias_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreateConv2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical()); + + // Make CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Conv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TransposedConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV); + + // TRANSPOSE_CONV's first input is output shape array. + const int32_t outshape_vec_size = 4; + auto outshape_vec_shape_offset = builder.CreateVector(std::vector{outshape_vec_size}); + size_t raw_outshape_vec_size = outshape_vec_size * sizeof(int32_t); + + std::vector outshape_vec_data(outshape_vec_size); + { + // Copy inferred output shape of node + auto out_feature_shape = loco::shape_get(node).as(); + + // Feature tensor in Circle is NHWC + outshape_vec_data.at(0) = out_feature_shape.count().value(); + outshape_vec_data.at(1) = out_feature_shape.height().value(); + outshape_vec_data.at(2) = out_feature_shape.width().value(); + outshape_vec_data.at(3) = out_feature_shape.depth().value(); + } + + auto outshape_vec_offset = builder.CreateVector( + reinterpret_cast(outshape_vec_data.data()), raw_outshape_vec_size); + + auto outshape_buffer_offset = CreateBuffer(builder, outshape_vec_offset); + + const auto outshape_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(outshape_buffer_offset); + + auto outshape_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(outshape_tensor_id)); + + auto outshape_tensor_offset = CreateTensor(builder, outshape_vec_shape_offset, TensorType_INT32, + outshape_buffer_id, name_offset); + gd._tensors.push_back(outshape_tensor_offset); + + // Make input, output and options for operator + std::vector inputs_vec{outshape_tensor_id, get_tensor_index(node->ker()), + get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + // NOTE input and output is inversed to use this function + circle::Padding padding = getOpPadding(node->pad(), node->stride(), ShapeInference::get(node), + ShapeInference::get(node->ifm())); + auto options = CreateTransposeConvOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical()); + + // Make TRANSPOSE_CONV operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_TransposeConvOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::DepthwiseConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D); + + // Third input of DEPTHWISE_CONV2D of Circle should be bias. We will make (and register to gd) + // dummy zero bias. Bias would be rank 1, have size of output kernel count, and have all zero + // values, i.e. zero bias. + auto *ker = dynamic_cast(node->ker()); + assert(ker); + + int32_t bias_vec_size = ShapeInference::get(ker)._dims[3]; // output_size(C*M) + auto bias_vec_shape_offset = builder.CreateVector(std::vector{bias_vec_size}); + + size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t); + std::vector bias_vec_data(bias_vec_size); + auto bias_vec_offset = + builder.CreateVector(reinterpret_cast(bias_vec_data.data()), raw_bias_vec_size); + + auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset); + + const auto bias_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(bias_buffer_offset); + + auto bias_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id)); + + auto bias_tensor_offset = + CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset); + gd._tensors.push_back(bias_tensor_offset); + + std::vector inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()), + bias_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + + int32_t ifm_channel_size = ShapeInference::get(node->ifm())._dims[3]; + // multiplier = bias_vec_size(output_size)/ifm_channel_size + auto options = + CreateDepthwiseConv2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), bias_vec_size / ifm_channel_size); + + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TensorReduce *node) +{ + uint32_t op_idx; + + switch (node->func()) + { + case loco::ReduceFunc::Mean: + op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MEAN); + break; + + // TODO Support more reduce type operation + default: + INTERNAL_EXN_V("Unsupported reduce type", oops::to_uint32(node->func())); + } + + // Create a vector for axes data + std::vector axes_vec; + auto rank = ShapeInference::get(node->input())._dims.size(); + for (uint32_t i = 0; i < rank; ++i) + if (node->axes()->defined(i)) + axes_vec.push_back(i); + + int32_t axes_vec_size = axes_vec.size(); + auto axes_vec_shape_offset = builder.CreateVector(std::vector{axes_vec_size}); + + size_t raw_axes_vec_size = axes_vec_size * sizeof(int32_t); + auto axes_vec_offset = + builder.CreateVector(reinterpret_cast(axes_vec.data()), raw_axes_vec_size); + + auto axes_buffer_offset = CreateBuffer(builder, axes_vec_offset); + + const auto axes_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(axes_buffer_offset); + + auto axes_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(axes_tensor_id)); + + auto axes_tensor_offset = + CreateTensor(builder, axes_vec_shape_offset, TensorType_INT32, axes_buffer_id, name_offset); + gd._tensors.push_back(axes_tensor_offset); + + std::vector inputs_vec{get_tensor_index(node->input()), axes_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateReducerOptions(builder, true); // true is for keep_dims option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ReducerOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TensorSoftmax *node) +{ + // TODO Support when the input rank of TensorSoftmax is not 2 + assert(ShapeInference::get(node->input())._dims.size() == 2); + + // NOTE Circle only accepts axis when the value is last dimension + assert(node->axis() == ShapeInference::get(node->input())._dims.size() - 1); + + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SOFTMAX); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSoftmaxOptions(builder, 1.0f); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SoftmaxOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +/// @brief Export given node into identity, i.e. CONCATENATION with one input +template +void exportIdentity(NodeT *node, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION); + std::vector inputs_vec{get_tensor_index(node->arg(0))}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder); // use dummy 0 axis and NONE activation + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ConcatenationOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +/// @brief Export loco nodes as TRANSPOSE +void exportAsTranspose(loco::Node *node, FlatBufferBuilder &builder, + std::vector &perm_vec_data, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE); + + auto options = CreateTransposeOptions(builder); + + // Create constant tensor with perm vector + constexpr int perm_vec_size = 4; + assert(perm_vec_data.size() == perm_vec_size); + auto perm_vec_shape_offset = builder.CreateVector(std::vector{perm_vec_size}); + constexpr size_t raw_perm_vec_size = perm_vec_size * sizeof(int32_t); + + auto perm_vec_offset = + builder.CreateVector(reinterpret_cast(perm_vec_data.data()), raw_perm_vec_size); + + auto perm_buffer_offset = CreateBuffer(builder, perm_vec_offset); + + const auto perm_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(perm_buffer_offset); + + auto perm_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(perm_tensor_id)); + + auto perm_tensor_offset = + CreateTensor(builder, perm_vec_shape_offset, TensorType_INT32, perm_buffer_id, name_offset); + gd._tensors.push_back(perm_tensor_offset); + + // Create permutation node + + std::vector inputs_vec{get_tensor_index(node->arg(0)), perm_tensor_id}; + std::vector outputs_vec{get_tensor_index(node)}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + constexpr auto options_type = circle::BuiltinOptions::BuiltinOptions_TransposeOptions; + + auto transpose_offset = + CreateOperator(builder, op_idx, inputs, outputs, options_type, options.Union()); + gd._operators.push_back(transpose_offset); +} + +void OperationExporter::visit(loco::FeatureEncode *node) +{ + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + + if (isNHWC(perm)) + { + // Note that Circle represents feature as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + perm_vec_data[0] = perm->axis(loco::FeatureAxis::Count); + perm_vec_data[1] = perm->axis(loco::FeatureAxis::Height); + perm_vec_data[2] = perm->axis(loco::FeatureAxis::Width); + perm_vec_data[3] = perm->axis(loco::FeatureAxis::Depth); + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void OperationExporter::visit(loco::FeatureDecode *node) +{ + auto decoder = dynamic_cast *>(node->decoder()); + auto perm = decoder->perm(); + + if (isNHWC(perm)) + { + // Note that Circle represents feature as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + perm_vec_data[perm->axis(loco::FeatureAxis::Count)] = 0; + perm_vec_data[perm->axis(loco::FeatureAxis::Height)] = 1; + perm_vec_data[perm->axis(loco::FeatureAxis::Width)] = 2; + perm_vec_data[perm->axis(loco::FeatureAxis::Depth)] = 3; + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void OperationExporter::visit(loco::FilterEncode *node) +{ + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + + if (isNHWC(perm)) + { + // Note that Circle represents filter as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + // NOTE In Circle, all tensors means NHWC, so 0 = N, 1 = H, 2 = W, 3 = C + perm_vec_data[0] = perm->axis(loco::FilterAxis::Count); + perm_vec_data[1] = perm->axis(loco::FilterAxis::Height); + perm_vec_data[2] = perm->axis(loco::FilterAxis::Width); + perm_vec_data[3] = perm->axis(loco::FilterAxis::Depth); + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void exportAsReshape(loco::Node *node, FlatBufferBuilder &builder, + std::vector &new_shape_vec, SerializedModelData &gd) +{ + // NOTE Circle currently follows TFLite for this. + // NOTE TFLite has two ways to get new shape paramter, + // one is by attribute 'new_shape' and the other is by input 'shape'. + // Therefore TFLite interpreter calculates Reshape operation correctly + // if one of them is valid. + // However, since NN runtime usually get new shape parameter by input 'shape', + // passing new shape only by attribute can cause some problems. + // Of course, the opposite situation can be occurred in the future. + // To prevent those problems, we pass new shape parameter not only by attribute + // but also by input. + + auto input_shape_shape_vec_offset = + builder.CreateVector(std::vector{(int32_t)new_shape_vec.size()}); + + size_t input_shape_vec_size = new_shape_vec.size() * sizeof(int32_t); + auto input_shape_input_vec_offset = + builder.CreateVector(reinterpret_cast(new_shape_vec.data()), input_shape_vec_size); + auto input_shape_buffer_offset = CreateBuffer(builder, input_shape_input_vec_offset); + + const auto input_shape_buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(input_shape_buffer_offset); + + auto input_shape_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(input_shape_tensor_id)); + auto input_shape_tensor_offset = CreateTensor( + builder, input_shape_shape_vec_offset, TensorType_INT32, input_shape_buffer_id, name_offset); + gd._tensors.push_back(input_shape_tensor_offset); + + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RESHAPE); + + std::vector inputs_vec{get_tensor_index(node->arg(0)), input_shape_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + auto new_shape_vec_offset = builder.CreateVector(new_shape_vec); + auto options = CreateReshapeOptions(builder, new_shape_vec_offset); + + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ReshapeOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::DepthwiseFilterEncode *node) +{ + auto ker = node->input(); // [H, W, C, M] + + // Circle represents filter as [1, H, W, C*M] where M is multiplier. + std::vector new_shape_vec(4); + new_shape_vec[0] = 1; + new_shape_vec[1] = ShapeInference::get(ker)._dims[0]; + new_shape_vec[2] = ShapeInference::get(ker)._dims[1]; + new_shape_vec[3] = ShapeInference::get(ker)._dims[2] * ShapeInference::get(ker)._dims[3]; + + exportAsReshape(node, builder, new_shape_vec, gd); +} + +void OperationExporter::visit(loco::BiasAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::FeatureBiasAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +/// @brief Export CONCATENATION of **TWO** tensors only +void OperationExporter::visit(loco::TensorConcat *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder, node->axis()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ConcatenationOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::BiasEncode *encode) { exportIdentity(encode, builder, gd); } + +void OperationExporter::visit(loco::EltwiseAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseMax *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMaximumMinimumOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MaximumMinimumOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseMul *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MUL); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMulOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MulOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseSub *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SUB); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSubOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SubOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseDiv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DIV); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateDivOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DivOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseSqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQRT); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::FixedReshape *node) +{ + std::vector new_shape_vec; + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + assert(node->dim(axis).known()); + new_shape_vec.push_back(node->dim(axis).value()); + } + + exportAsReshape(node, builder, new_shape_vec, gd); +} + +void OperationExporter::visit(loco::TensorBroadcast *) +{ + INTERNAL_EXN("loco graph has loco::TensorBroadcast, which should not exist in the graph"); +} + +void OperationExporter::visit(loco::TensorConstantPad *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_PAD); + + // make padding attribute an input + auto padding = node->padding(); + // get padding vector size + int32_t padding_vec_size = padding->rank(); + // get byte size of vector + size_t padding_vec_byte_size = padding_vec_size * sizeof(int32_t) * 2; // [rank, 2] + // create vector for data + std::vector padding_vec_data(padding_vec_size * 2); + // set data + for (int32_t i = 0; i < padding_vec_size; i++) + { + padding_vec_data.at(i * 2) = padding->front(i); + padding_vec_data.at(i * 2 + 1) = padding->back(i); + } + // create FlatBuffer vector + auto padding_vec_ptr = builder.CreateVector(reinterpret_cast(padding_vec_data.data()), + padding_vec_byte_size); + + // create buffer + auto padding_buffer_ptr = CreateBuffer(builder, padding_vec_ptr); + // get buffer id + const auto padding_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(padding_buffer_ptr); + + // create padding shape vector + auto padding_shape_vec_ptr = builder.CreateVector(std::vector{padding_vec_size, 2}); + // create tensor + auto padding_tensor_ptr = + CreateTensor(builder, padding_shape_vec_ptr, TensorType_INT32, padding_buffer_id); + // get tensor id + const auto padding_tensor_id = static_cast(gd._tensors.size()); + + gd._tensors.push_back(padding_tensor_ptr); + + std::vector inputs_vec{get_tensor_index(node->input()), padding_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +inline flatbuffers::Offset> +CreateCOpCallOptions(flatbuffers::FlatBufferBuilder &fbb, locoex::COpCall *copCall) +{ + // read attrs in FlexBuffer format and pass them to FlatBuffer builder + flexbuffers::Builder flexbuf; + { + size_t map_start = flexbuf.StartMap(); + + // Note: among attrs of COpCall, 'op' and 'name' won't be included into tflite file + auto names = copCall->attr_names(); + for (auto name : names) + { + if (auto int_val = copCall->attr(name)) + flexbuf.Int(name.c_str(), int_val->val()); + else if (auto float_val = copCall->attr(name)) + flexbuf.Float(name.c_str(), float_val->val()); + else + // TODO Support more attribute types + INTERNAL_EXN_V("Unsupported dtype while writing flexbuffer for customop attr", name); + } + + flexbuf.EndMap(map_start); + flexbuf.Finish(); + } + + auto offset = fbb.CreateVector(flexbuf.GetBuffer()); + + return offset; +} + +void OperationExporter::visit(locoex::COpCall *call) +{ + // Registering this custom op name into tflite Operator Codes table + uint32_t op_idx = gd.registerCustomOpcode(call->op()); + + std::vector inputs_vec; + { + inputs_vec.resize(call->arity()); + for (uint32_t i = 0; i < call->arity(); i++) + inputs_vec[i] = get_tensor_index(call->arg(i)); + } + + std::vector outputs_vec{get_tensor_index(static_cast(call))}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + auto custom_options = CreateCOpCallOptions(builder, call); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_NONE, // builtin_options_type + 0, // built-in option + custom_options, // custom options + circle::CustomOptionsFormat_FLEXBUFFERS); + + gd._operators.push_back(op_offset); +} + +void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &data) +{ + // TODO Use explicit tagging to prevent possible mistake + auto isNoOp = [](loco::Node *node) { + if (node->arity() == 1) + { + assert(node->arg(0) != nullptr); + return get_tensor_index(node) == get_tensor_index(node->arg(0)); + } + return false; + }; + + if (isNoOp(node)) + { + // Skip if a given node is marked as NoOp (op with no effect) before + return; + } + + if (auto canonical_node = dynamic_cast(node)) + { // TODO Consider removing this later + OperationExporter exporter{builder, data}; + canonical_node->accept(&exporter); + } + else if (auto tfl_node = dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + tfl_node->accept(&exporter); + } + else if (auto circle_node = dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + circle_node->accept(&exporter); + } + else if (dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + exporter.visit(dynamic_cast(node)); + } + else + { + INTERNAL_EXN("Node with unsupported dialect found"); + } +} + +} // namespace + +namespace exo +{ +namespace circle_detail +{ + +void exportNodes(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + exportNode(node, builder, gd); + } +} + +} // namespace circle_detail +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleOperationExporter.h b/compiler/exo/src/Circle/CircleOperationExporter.h new file mode 100644 index 00000000000..19dadbfd1aa --- /dev/null +++ b/compiler/exo/src/Circle/CircleOperationExporter.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_OPERATION_EXPORTER_H__ +#define __CIRCLE_OPERATION_EXPORTER_H__ + +#include "CircleExporterUtils.h" + +#include + +namespace exo +{ +namespace circle_detail +{ + +/** + * @brief create Operators corresponding to model nodes + * @param nodes container with nodes + * @param gd information about serializer parts of model + */ +void exportNodes(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, SerializedModelData &gd); + +} // namespace circle_detail +} // namespace exo + +#endif // __CIRCLE_OPERATION_EXPORTER_H__ diff --git a/compiler/exo/src/Circle/CircleTensorExporter.cpp b/compiler/exo/src/Circle/CircleTensorExporter.cpp new file mode 100644 index 00000000000..efceae55d25 --- /dev/null +++ b/compiler/exo/src/Circle/CircleTensorExporter.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleTensorExporter.h" +#include "CircleTypeInference.h" +#include "ShapeInference.h" + +// TODO Fix include style +#include "loco/IR/Algorithm.h" +#include "loco/IR/CanonicalNode.h" +#include "loco/IR/CanonicalNodeVisitor.h" +#include "loco/IR/DataTypeTraits.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +using namespace circle; +using namespace flatbuffers; + +namespace +{ + +using namespace exo; +using namespace exo::circle_detail; + +class TFLTensorInfo +{ +public: + TFLTensorInfo() = default; + +public: + void name(const std::string &name) { _name = name; } + const std::string &name(void) const { return _name; } + +public: + const circle::TensorType &dtype(void) const { return _dtype; } + void dtype(const circle::TensorType &dtype) { _dtype = dtype; } + + const ShapeDescription &shape(void) const { return _shape; } + void shape(const ShapeDescription &shape) { _shape = shape; } + +public: + locoex::TFLConst *tfl_content(void) const { return _tfl_content; } + void tfl_content(locoex::TFLConst *c) { _tfl_content = c; } + +private: + std::string _name; + + circle::TensorType _dtype; + ShapeDescription _shape; + + // TODO Find a better design + loco::ConstGen *_content = nullptr; // TODO deprecate + locoex::TFLConst *_tfl_content = nullptr; +}; + +using TFLTensorContext = std::vector; + +struct NoOpDetector final : public loco::CanonicalNodeMutableVisitor +{ + bool visit(loco::BiasEncode *) final + { + // BiasEncode is always noop + return true; + } + + bool visit(loco::FilterEncode *node) final + { + auto encoder = dynamic_cast *>(node->encoder()); + if (encoder != nullptr) + { + auto perm = encoder->perm(); + return isNHWC(perm); + } + return false; + } + + bool visit(loco::FeatureEncode *node) final + { + auto encoder = dynamic_cast *>(node->encoder()); + if (encoder != nullptr) + { + auto perm = encoder->perm(); + return isNHWC(perm); + } + return false; + } + + bool visit(loco::FeatureDecode *node) final + { + auto decoder = dynamic_cast *>(node->decoder()); + if (decoder != nullptr) + { + auto perm = decoder->perm(); + return isNHWC(perm); + } + return false; + } + + // Return false by default + bool visit(loco::Node *) final { return false; } +}; + +bool isNoOp(loco::Node *node) +{ + if (auto canonical_node = dynamic_cast(node)) + { + NoOpDetector d; + return canonical_node->accept(&d); + } + return false; +} + +void allocateCircleTensor(loco::Node *node, TFLTensorContext &ctx) +{ + if (isNoOp(node)) + { + assert(node->arity() == 1 && node->arg(0) != nullptr); + set_tensor_index(node, get_tensor_index(node->arg(0))); + return; + } + + auto tensor_index = static_cast(ctx.size()); + // TODO Use Graph-level metadata for Input & Output + auto tensor_name = "t_" + std::to_string(tensor_index); + + TFLTensorInfo tensor_info; + + tensor_info.name(tensor_name); + tensor_info.dtype(TypeInference::get(node)); + tensor_info.shape(ShapeInference::get(node)); + + tensor_info.tfl_content(dynamic_cast(node)); + + set_tensor_index(node, tensor_index); + + ctx.emplace_back(tensor_info); +} + +} // namespace + +namespace +{ + +flatbuffers::Offset> encodeShape(FlatBufferBuilder &builder, + const ShapeDescription &shape) +{ + assert(shape._rank_known && "unknown number of dimensions is not supported"); + return builder.CreateVector(shape._dims); +} + +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, NodeT *) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBufferByDType(FlatBufferBuilder &builder, + locoex::TFLConst *c) +{ + using NativeType = typename loco::DataTypeImpl
::Type; + + std::vector raw_data; + const uint32_t size = c->size
(); + raw_data.reserve(size); + for (uint32_t i = 0; i < size; ++i) + { + raw_data.push_back(c->at
(i)); + } + const size_t raw_size = size * sizeof(NativeType); + auto array_offset = builder.CreateVector(reinterpret_cast(raw_data.data()), raw_size); + return CreateBuffer(builder, array_offset); +} + +template <> +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, locoex::TFLConst *c) +{ + if (c->dtype() == loco::DataType::FLOAT32) + { + return encodeOpBufferByDType(builder, c); + } + else if (c->dtype() == loco::DataType::S32) + { + return encodeOpBufferByDType(builder, c); + } + + INTERNAL_EXN_V("Unsupported datatype", oops::to_uint32(c->dtype())); +} + +} // namespace + +namespace exo +{ +namespace circle_detail +{ + +void exportOpDefinedTensor(const TFLTensorInfo &info, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + // Create and register output tensor shape + auto shape_offset = encodeShape(builder, info.shape()); + + // encode and register output tensor buffer + auto buffer = info.tfl_content() == nullptr ? encodeOpBuffer(builder) + : encodeOpBuffer(builder, info.tfl_content()); + + auto buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(buffer); + + auto name_offset = builder.CreateString(info.name()); + auto tensor_offset = CreateTensor(builder, shape_offset, info.dtype(), buffer_id, name_offset, + /*quantization*/ 0, /*is_variable*/ false); + gd._tensors.push_back(tensor_offset); +} + +void exportOpDefinedTensors(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + TFLTensorContext tensor_ctx; + + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + allocateCircleTensor(node, tensor_ctx); + } + + // add one empty buffer + // note: this follows TFLite + // note: there's a comment in tflite fbs file + // - Note the 0th entry of this array must be an empty buffer (sentinel). + // - This is a convention so that tensors without a buffer can provide 0 as + // - their buffer. + auto buffer = encodeOpBuffer(builder); + gd._buffers.push_back(buffer); + + for (const auto &tensor_info : tensor_ctx) + { + exportOpDefinedTensor(tensor_info, builder, gd); + } +} + +} // namespace circle_detail +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleTensorExporter.h b/compiler/exo/src/Circle/CircleTensorExporter.h new file mode 100644 index 00000000000..39d8e1b8652 --- /dev/null +++ b/compiler/exo/src/Circle/CircleTensorExporter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_TENSOR_EXPORTER_H__ +#define __CIRCLE_TENSOR_EXPORTER_H__ + +#include "CircleExporterUtils.h" + +#include + +#include + +namespace exo +{ +namespace circle_detail +{ + +/** + * @brief create Tensors corresponding to results of all nodes in graph + * @param computational graph + * @param gd information about serialized parts of model + */ +void exportOpDefinedTensors(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &gd); + +} // namespace circle_detail +} // namespace exo + +#endif // __CIRCLE_TENSOR_EXPORTER_H__ diff --git a/compiler/exo/src/Circle/CircleTypeInference.cpp b/compiler/exo/src/Circle/CircleTypeInference.cpp new file mode 100644 index 00000000000..a1e92b88444 --- /dev/null +++ b/compiler/exo/src/Circle/CircleTypeInference.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleTypeInference.h" + +#include "circle_schema_generated.h" + +#include "Dialect/Service/TFLTypeInferenceRule.h" +#include "Dialect/IR/TFLDialect.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace +{ + +circle::TensorType translateLocoTypeToCircle(loco::DataType dtype) +{ + switch (dtype) + { + case loco::DataType::U8: + return circle::TensorType_UINT8; + // case loco::DataType::U16: unsupported + // case loco::DataType::U32: unsupported + // case loco::DataType::U64: unsupported + case loco::DataType::S8: + return circle::TensorType_INT8; + case loco::DataType::S16: + return circle::TensorType_INT16; + case loco::DataType::S32: + return circle::TensorType_INT32; + case loco::DataType::S64: + return circle::TensorType_INT64; + case loco::DataType::FLOAT16: + return circle::TensorType_FLOAT16; + case loco::DataType::FLOAT32: + return circle::TensorType_FLOAT32; + // case loco::DataType::FLOAT64: unsupported + default: + break; + } + + INTERNAL_EXN_V("Invalid loco dtype", oops::to_uint32(dtype)); +} + +} // namespace + +namespace exo +{ +namespace circle_detail +{ + +circle::TensorType TypeInference::get(loco::Node *node) +{ + assert(loco::dtype_known(node)); + return translateLocoTypeToCircle(loco::dtype_get(node)); +} + +} // namespace circle_detail +} // namespace exo diff --git a/compiler/exo/src/Circle/CircleTypeInference.h b/compiler/exo/src/Circle/CircleTypeInference.h new file mode 100644 index 00000000000..9c17302333b --- /dev/null +++ b/compiler/exo/src/Circle/CircleTypeInference.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_TYPE_INFERENCE_H__ +#define __CIRCLE_TYPE_INFERENCE_H__ + +#include "CircleExporterUtils.h" + +#include + +namespace exo +{ +namespace circle_detail +{ + +/** + * @brief Get the type of each node as NodeAnnotation + * + * HOW TO USE + * + * TypeInference::get(g->nodes()->at(0)); + * TypeInference::get(g->nodes()->at(...)); + */ +struct TypeInference +{ + static circle::TensorType get(loco::Node *node); +}; + +} // namespace circle_detail +} // namespace exo + +#endif // __CIRCLE_TYPE_INFERENCE_H__ diff --git a/compiler/exo/src/Conversion/AvgPool2DConverter.cpp b/compiler/exo/src/Conversion/AvgPool2DConverter.cpp new file mode 100644 index 00000000000..a95518ac661 --- /dev/null +++ b/compiler/exo/src/Conversion/AvgPool2DConverter.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "AvgPool2DConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include + +namespace exo +{ +/** + * @brief Converts loco::AvgPool2D to locoex::TFLAveragePool2D + * + * How it works: (note: ten->fea means input: tensor, output: feature) + * + * Before: + * Foo ---- FeatureEncode ---- AvgPool2D ---- FeatureDecode ---- Bar + * ten->ten ten->fea fea->fea fea->ten ten->ten + * + * After: AvgPool2D + * / + * Foo -- FeatureEncode - FeatureDecode - TFLAvgPool2D - FeatureEncode - FeatureDecode -- Bar + * ten->ten ten->fea fea->ten ten->ten ten->fea fea->ten ten->ten + * + * @note This method replaces AvgPool2D with "FeatureDecode -- TFLAvgPool2D -- FeatureEncode". + * Redundant nodes will be removed during transforms. + */ +bool AvgPool2DConverter::convert(loco::AvgPool2D *origin) +{ + auto *graph = origin->graph(); + + auto dec = make_feature_decode(origin->ifm()); + auto tfl_average = graph->nodes()->create(); + { + tfl_average->value(dec); + + // set attributes + tfl_average->stride()->w(origin->stride()->horizontal()); + tfl_average->stride()->h(origin->stride()->vertical()); + + tfl_average->filter()->w(origin->window()->horizontal()); + tfl_average->filter()->h(origin->window()->vertical()); + + auto pad = origin->pad(); + if (pad->bottom() == 0 && pad->top() == 0 && pad->left() == 0 && pad->right() == 0) + tfl_average->padding(locoex::Padding::VALID); + else + // TODO This is necessary, but not sufficient condition. More rigorous check required + tfl_average->padding(locoex::Padding::SAME); + + tfl_average->fusedActivationFunction(locoex::FusedActFunc::NONE); + } + auto enc = make_feature_encode(tfl_average); + + // replace canonical node + loco::replace(origin).with(enc); + origin->ifm(nullptr); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/AvgPool2DConverter.h b/compiler/exo/src/Conversion/AvgPool2DConverter.h new file mode 100644 index 00000000000..f66d02eb621 --- /dev/null +++ b/compiler/exo/src/Conversion/AvgPool2DConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_AVGPOOL2D_CONVERTER__ +#define __CONVERSION_AVGPOOL2D_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::AvgPool2D to locoex::TFLAveragePool2D + */ +class AvgPool2DConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::AvgPool2DConverter"; } + +public: + bool convert(loco::AvgPool2D *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_AVGPOOL2D_CONVERTER__ diff --git a/compiler/exo/src/Conversion/CanonicalNodeConverter.cpp b/compiler/exo/src/Conversion/CanonicalNodeConverter.cpp new file mode 100644 index 00000000000..4daf905f83c --- /dev/null +++ b/compiler/exo/src/Conversion/CanonicalNodeConverter.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CanonicalNodeConverter.h" + +// This file is to make sure compilation of "CanonicalNodeConverter.h" diff --git a/compiler/exo/src/Conversion/CanonicalNodeConverter.h b/compiler/exo/src/Conversion/CanonicalNodeConverter.h new file mode 100644 index 00000000000..76f73d888d2 --- /dev/null +++ b/compiler/exo/src/Conversion/CanonicalNodeConverter.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_CANONICAL_NODE_CONVERTER_H__ +#define __CONVERSION_CANONICAL_NODE_CONVERTER_H__ + +#include "Convert.h" + +#include +#include +#include + +namespace exo +{ + +/** + * @brief Class to convert a canonical node to TFL node + * + * TODO Find a better name + */ +template class CanonicalNodeConverter : public logo::Pass +{ +public: + virtual const char *name(void) const { return nullptr; } + +public: + bool run(loco::Graph *graph); + +protected: + virtual bool convert(CanonicalType *node) = 0; +}; + +template +bool CanonicalNodeConverter::run(loco::Graph *graph) +{ + auto active_nodes = loco::active_nodes(loco::output_nodes(graph)); + bool changed = false; + + for (auto node : active_nodes) + { + // TODO Generalize this to all loco dialects + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto the_node = dynamic_cast(node); + if (the_node != nullptr) + { + if (convert(the_node)) + changed = true; + } + } + } + + return changed; +} + +} // namespace exo + +#endif //__CONVERSION_CANONICAL_NODE_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/ConstGenConverter.cpp b/compiler/exo/src/Conversion/ConstGenConverter.cpp new file mode 100644 index 00000000000..b2e2b4bdbdc --- /dev/null +++ b/compiler/exo/src/Conversion/ConstGenConverter.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConstGenConverter.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Check.h" + +#include + +#include + +namespace exo +{ + +bool ConstGenConverter::convert(loco::ConstGen *constgen) +{ + auto *graph = constgen->graph(); + + auto tfl_const = graph->nodes()->create(); + { + if (constgen->dtype() == loco::DataType::FLOAT32) + { + tfl_const->dtype(loco::DataType::FLOAT32); + + tfl_const->rank(constgen->rank()); + for (uint32_t axis = 0; axis < constgen->rank(); axis++) + tfl_const->dim(axis) = constgen->dim(axis); + + auto size = constgen->size(); + tfl_const->size(size); + + for (uint32_t i = 0; i < size; ++i) + { + tfl_const->at(i) = constgen->at(i); + } + } + else + INTERNAL_EXN_V("Unsupported DataType", oops::to_uint32(constgen->dtype())); + } + + loco::replace(constgen).with(tfl_const); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/ConstGenConverter.h b/compiler/exo/src/Conversion/ConstGenConverter.h new file mode 100644 index 00000000000..613ccd0e6da --- /dev/null +++ b/compiler/exo/src/Conversion/ConstGenConverter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_CONSTGEN_CONVERTER_H__ +#define __CONVERSION_CONSTGEN_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +class ConstGenConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::ConstGenConverter"; } + +public: + bool convert(loco::ConstGen *constgen) final; +}; + +} // namespace exo + +#endif // __CONVERSION_CONSTGEN_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/ConstGenConverter.test.cpp b/compiler/exo/src/Conversion/ConstGenConverter.test.cpp new file mode 100644 index 00000000000..f7a5772426b --- /dev/null +++ b/compiler/exo/src/Conversion/ConstGenConverter.test.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ConstGenConverter.h" +#include "ReluConverter.h" + +#include "Dialect/IR/TFLNodes.h" +#include "TestGraph.h" +#include "TestHelper.h" + +#include + +#include + +TEST(TFLConstGenConverterTest, ConstGen_Relu) +{ + exo::test::ExampleGraph g; + + // set constgen + { + g.constgen->dtype(loco::DataType::FLOAT32); + g.constgen->shape({2, 1}); + g.constgen->size(2); + + g.constgen->at(0) = 0.5; + g.constgen->at(1) = -0.5; + } + + // let's convert + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.add_pass(); + + test_phase.run(g.graph()); + } + + auto tfl_const = exo::test::find_first_node_bytype(g.graph()); + auto tfl_relu = exo::test::find_first_node_bytype(g.graph()); + + ASSERT_TRUE(tfl_const != nullptr and tfl_relu != nullptr); + ASSERT_TRUE(tfl_relu->features() == tfl_const); + + ASSERT_TRUE(tfl_const->rank() == g.constgen->rank()); + ASSERT_TRUE(tfl_const->dim(0) == g.constgen->dim(0)); + ASSERT_TRUE(tfl_const->dim(1) == g.constgen->dim(1)); + ASSERT_TRUE(tfl_const->at(0) == + g.constgen->at(0)); + ASSERT_TRUE(tfl_const->at(1) == + g.constgen->at(1)); +} diff --git a/compiler/exo/src/Conversion/Conv2DConverter.cpp b/compiler/exo/src/Conversion/Conv2DConverter.cpp new file mode 100644 index 00000000000..c8120171d49 --- /dev/null +++ b/compiler/exo/src/Conversion/Conv2DConverter.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Conv2DConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include +#include +#include + +namespace exo +{ +/** + * @brief Converts loco::Conv2D to locoex::TFLConv2D + * @note Because TFLConv2D accepts input and filter of loco::Domain::Tensor, + * loco::FeatureDecode and loco::FilterDecode will be inserted as an inputs + * to meet domain invariant. + * Please refer to the comment in AvgPool2DConvert. + */ +bool Conv2DConverter::convert(loco::Conv2D *origin) +{ + auto *graph = origin->graph(); + + assert(origin->ifm()); + assert(origin->ker()); + + auto tfl_conv2d = graph->nodes()->create(); + { + tfl_conv2d->stride()->w(origin->stride()->horizontal()); + tfl_conv2d->stride()->h(origin->stride()->vertical()); + + auto pad = origin->pad(); + if (pad->bottom() == 0 && pad->top() == 0 && pad->left() == 0 && pad->right() == 0) + tfl_conv2d->padding(locoex::Padding::VALID); + else + // TODO This is necessary, but not sufficient condition. More rigorous check required + tfl_conv2d->padding(locoex::Padding::SAME); + + tfl_conv2d->fusedActivationFunction(locoex::FusedActFunc::NONE); + } + + // let's create a new graph connection with tfl_conv2d + { + // input + auto feature_dec = make_feature_decode(origin->ifm()); + tfl_conv2d->input(feature_dec); + + // filter + auto filter_dec = make_filter_decode(origin->ker()); + tfl_conv2d->filter(filter_dec); + + // bias + auto zero_const = graph->nodes()->create(); + { + assert(loco::shape_known(origin)); + assert(loco::dtype_known(origin) && loco::dtype_get(origin) == loco::DataType::FLOAT32); + + auto output_depth = loco::shape_get(origin->ker()).as().count(); + + zero_const->dtype(loco::DataType::FLOAT32); + zero_const->rank(1); + zero_const->dim(0) = output_depth; + zero_const->size(output_depth.value()); + for (uint32_t x = 0; x < output_depth.value(); x++) + zero_const->at(x) = 0.0; + } + tfl_conv2d->bias(zero_const); + + // output + auto feature_enc = make_feature_encode(tfl_conv2d); + + // replace canonical node + loco::replace(origin).with(feature_enc); + origin->ifm(nullptr); + } + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/Conv2DConverter.h b/compiler/exo/src/Conversion/Conv2DConverter.h new file mode 100644 index 00000000000..95b3fbfaed3 --- /dev/null +++ b/compiler/exo/src/Conversion/Conv2DConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_CONV2D_CONVERTER__ +#define __CONVERSION_CONV2D_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::Conv2D to locoex::TFLConv2D + */ +class Conv2DConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::Conv2DConverter"; } + +public: + bool convert(loco::Conv2D *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_CONV2D_CONVERTER__ diff --git a/compiler/exo/src/Conversion/DepthwiseConv2DConverter.cpp b/compiler/exo/src/Conversion/DepthwiseConv2DConverter.cpp new file mode 100644 index 00000000000..5959fcc4528 --- /dev/null +++ b/compiler/exo/src/Conversion/DepthwiseConv2DConverter.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "DepthwiseConv2DConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include +#include +#include + +namespace exo +{ + +bool DepthwiseConv2DConverter::convert(loco::DepthwiseConv2D *origin) +{ + // Filter shape is required + if (not loco::shape_known(origin->ker())) + return false; + + auto filter_shape = loco::shape_get(origin->ker()).as(); + + if ((origin->ifm() == nullptr) or (origin->ker() == nullptr)) + return false; + + auto *graph = origin->graph(); + + auto tfl_dw_conv2d = graph->nodes()->create(); + { + tfl_dw_conv2d->stride()->w(origin->stride()->horizontal()); + tfl_dw_conv2d->stride()->h(origin->stride()->vertical()); + + auto pad = origin->pad(); + if (pad->bottom() == 0 && pad->top() == 0 && pad->left() == 0 && pad->right() == 0) + tfl_dw_conv2d->padding(locoex::Padding::VALID); + else + // TODO This is necessary, but not sufficient condition. More rigorous check required + tfl_dw_conv2d->padding(locoex::Padding::SAME); + + tfl_dw_conv2d->fusedActivationFunction(locoex::FusedActFunc::NONE); + + uint32_t multiplier = filter_shape.multiplier().value(); + EXO_ASSERT(multiplier < std::numeric_limits::max(), + "Multiplier is too big that casting may occur unintended behavior") + + tfl_dw_conv2d->depthMultiplier(static_cast(multiplier)); + } + + // let's create a new graph connection with tfl_dw_conv2d + { + // ifm --- feature_dec --- tfl_dw_conv2d + auto feature_dec = make_feature_decode(origin->ifm()); + tfl_dw_conv2d->input(feature_dec); + + // ker --- filter_dec(H x W x C x M) --- reshape(1 x H x W x CM) --- tfl_dw_conv2d + auto filter_dec = make_dw_filter_decode(origin->ker()); + + auto reshape = graph->nodes()->create(); + reshape->tensor(filter_dec); + + int32_t new_shape[4] = { + 1, static_cast(filter_shape.height().value()), + static_cast(filter_shape.width().value()), + static_cast(filter_shape.depth().value() * filter_shape.multiplier().value())}; + locoex::set_new_shape(reshape, new_shape, 4); + + tfl_dw_conv2d->filter(reshape); + + // bias + auto zero_const = graph->nodes()->create(); + { + assert(loco::shape_known(origin)); + assert(loco::dtype_known(origin) && loco::dtype_get(origin) == loco::DataType::FLOAT32); + + // bias size is C * M + uint32_t bias_size = filter_shape.depth().value() * filter_shape.multiplier().value(); + + zero_const->dtype(loco::DataType::FLOAT32); + zero_const->rank(1); + zero_const->dim(0) = bias_size; + zero_const->size(bias_size); + for (uint32_t x = 0; x < bias_size; x++) + zero_const->at(x) = 0.0; + } + tfl_dw_conv2d->bias(zero_const); + + // output + auto feature_enc = make_feature_encode(tfl_dw_conv2d); + + // replace canonical node + loco::replace(origin).with(feature_enc); + origin->ifm(nullptr); + } + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/DepthwiseConv2DConverter.h b/compiler/exo/src/Conversion/DepthwiseConv2DConverter.h new file mode 100644 index 00000000000..57cc01e5e39 --- /dev/null +++ b/compiler/exo/src/Conversion/DepthwiseConv2DConverter.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_DEPTHWISECONV2D_CONVERTER__ +#define __CONVERSION_DEPTHWISECONV2D_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::DepthwiseConv2D to locoex::TFLDepthwiseConv2D and auxiliary + * + * + * + * + * IFM -------- DepthwiseConv2D --- Out + * [Feature] / [Feature] + * / + * KER ------- + * [DWFilter] + * + * + * + * TFLConst (bias) --------------------------- + * \ + * IFM ------ FeatureDecode ------------------ TFLDepthwiseConv2D --- FeatureEncode --- Out + * [Feature] [Tensor] / [Tensor] [Feature] + * / + * KER ------- DepthwiseFilterDecode --- TFLReshape + * [DWFilter] [Tensor / H W C M] [Tensor / 1 H W CM] + * + */ +class DepthwiseConv2DConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::DepthwiseConv2DConverter"; } + +public: + bool convert(loco::DepthwiseConv2D *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_DEPTHWISECONV2D_CONVERTER__ diff --git a/compiler/exo/src/Conversion/EltwiseAddConverter.cpp b/compiler/exo/src/Conversion/EltwiseAddConverter.cpp new file mode 100644 index 00000000000..557f47944d0 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseAddConverter.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseAddConverter.h" + +#include "EltwiseBinaryConverter.h" + +namespace exo +{ + +bool EltwiseAddConverter::convert(loco::EltwiseAdd *origin) +{ + return EltwiseBinaryConvert(origin); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseAddConverter.h b/compiler/exo/src/Conversion/EltwiseAddConverter.h new file mode 100644 index 00000000000..97e1071b581 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseAddConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISEADD_CONVERTER_H__ +#define __CONVERSION_ELTWISEADD_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseAdd to TFLAdd + */ +class EltwiseAddConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseAddConverter"; } + +public: + bool convert(loco::EltwiseAdd *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_ELTWISEADD_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseBinaryConverter.h b/compiler/exo/src/Conversion/EltwiseBinaryConverter.h new file mode 100644 index 00000000000..095da9e5c1d --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseBinaryConverter.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISEBINARY_CONVERTER_H__ +#define __CONVERSION_ELTWISEBINARY_CONVERTER_H__ + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +#include + +namespace +{ + +template +class EltwiseBinInputHandler : public exo::InputHandler +{ +public: + void handover(ELTWISEBIN *origin, TFLBIN *replacer) override + { + assert(origin && replacer); + replacer->x(origin->lhs()); + replacer->y(origin->rhs()); + } + + std::vector getInputsToConvert(ELTWISEBIN *origin) override + { + assert(origin); + std::vector inputs({origin->lhs(), origin->rhs()}); + return inputs; + } + + void set(TFLBIN *replacer, std::vector &to) override + { + assert(to.size() == 2); + + replacer->x(to.at(0)); + replacer->y(to.at(1)); + } + + void nullify(ELTWISEBIN *origin) override + { + assert(origin); + origin->lhs(nullptr); + origin->rhs(nullptr); + } +}; + +template void init_fused_act_func(TFLBIN *); + +template <> inline void init_fused_act_func(locoex::TFLAdd *node) +{ + node->fusedActivationFunction(locoex::FusedActFunc::NONE); +} + +template <> inline void init_fused_act_func(locoex::TFLMul *node) +{ + node->fusedActivationFunction(locoex::FusedActFunc::NONE); +} + +template <> inline void init_fused_act_func(locoex::TFLSub *node) +{ + node->fusedActivationFunction(locoex::FusedActFunc::NONE); +} + +template <> inline void init_fused_act_func(locoex::TFLDiv *node) +{ + node->fusedActivationFunction(locoex::FusedActFunc::NONE); +} + +} // namespace + +namespace exo +{ + +template bool EltwiseBinaryConvert(ELTWISEBIN *origin) +{ + EltwiseBinInputHandler input_handler; + exo::DomainConverter domain_converter; + + auto tfl_node = domain_converter.template convert(origin, input_handler); + + if (tfl_node == nullptr) + return false; + + init_fused_act_func(tfl_node); + + return true; +} + +} // namespace exo + +#endif // __CONVERSION_ELTWISEBINARY_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseDivConverter.cpp b/compiler/exo/src/Conversion/EltwiseDivConverter.cpp new file mode 100644 index 00000000000..dc8eae46116 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseDivConverter.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseDivConverter.h" + +#include "EltwiseBinaryConverter.h" + +namespace exo +{ + +bool EltwiseDivConverter::convert(loco::EltwiseDiv *origin) +{ + return EltwiseBinaryConvert(origin); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseDivConverter.h b/compiler/exo/src/Conversion/EltwiseDivConverter.h new file mode 100644 index 00000000000..06b2d685b39 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseDivConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISEDIV_CONVERTER_H__ +#define __CONVERSION_ELTWISEDIV_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseDiv to TFLDiv + */ +class EltwiseDivConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseDivConverter"; } + +public: + bool convert(loco::EltwiseDiv *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_ELTWISEDIV_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseMaxConverter.cpp b/compiler/exo/src/Conversion/EltwiseMaxConverter.cpp new file mode 100644 index 00000000000..dd7d34440bf --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseMaxConverter.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseMaxConverter.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace +{ + +class EltwiseMaxInputHandler : public exo::InputHandler +{ +public: + void handover(loco::EltwiseMax *origin, locoex::TFLMaximum *replacer) override + { + replacer->x(origin->lhs()); + replacer->y(origin->rhs()); + } + + std::vector getInputsToConvert(loco::EltwiseMax *origin) override + { + std::vector inputs({origin->lhs(), origin->rhs()}); + return inputs; + } + + void set(locoex::TFLMaximum *replacer, std::vector &to) override + { + assert(to.size() == 2); + + replacer->x(to.at(0)); + replacer->y(to.at(1)); + } + + void nullify(loco::EltwiseMax *origin) override + { + assert(origin); + origin->lhs(nullptr); + origin->rhs(nullptr); + } +}; + +} // namespace + +namespace exo +{ + +bool EltwiseMaxConverter::convert(loco::EltwiseMax *origin) +{ + EltwiseMaxInputHandler input_handler; + exo::DomainConverter domain_converter; + + auto tfl_new = domain_converter.convert(origin, input_handler); + + return (tfl_new != nullptr); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseMaxConverter.h b/compiler/exo/src/Conversion/EltwiseMaxConverter.h new file mode 100644 index 00000000000..70874541975 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseMaxConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISEMAX_CONVERTER_H__ +#define __CONVERSION_ELTWISEMAX_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseMax to TFLMaximum + */ +class EltwiseMaxConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseMaxConverter"; } + +public: + bool convert(loco::EltwiseMax *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_ELTWISEMAX_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseMulConverter.cpp b/compiler/exo/src/Conversion/EltwiseMulConverter.cpp new file mode 100644 index 00000000000..f7a4b8298d5 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseMulConverter.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseMulConverter.h" + +#include "EltwiseBinaryConverter.h" + +namespace exo +{ + +bool EltwiseMulConverter::convert(loco::EltwiseMul *origin) +{ + return EltwiseBinaryConvert(origin); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseMulConverter.h b/compiler/exo/src/Conversion/EltwiseMulConverter.h new file mode 100644 index 00000000000..4f73484c0bf --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseMulConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISEMUL_CONVERTER_H__ +#define __CONVERSION_ELTWISEMUL_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseMul to TFLMul + */ +class EltwiseMulConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseMulConverter"; } + +public: + bool convert(loco::EltwiseMul *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_ELTWISEMUL_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseSqrtConverter.cpp b/compiler/exo/src/Conversion/EltwiseSqrtConverter.cpp new file mode 100644 index 00000000000..6dead7dc638 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseSqrtConverter.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseSqrtConverter.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace +{ + +class EltwiseSqrtInputHandler : public exo::InputHandler +{ +public: + void handover(loco::EltwiseSqrt *origin, locoex::TFLSqrt *replacer) override + { + replacer->x(origin->input()); + } + + std::vector getInputsToConvert(loco::EltwiseSqrt *origin) override + { + std::vector inputs({origin->input()}); + return inputs; + } + + void set(locoex::TFLSqrt *replacer, std::vector &to) override + { + assert(to.size() == 1); + + replacer->x(to.at(0)); + } + + void nullify(loco::EltwiseSqrt *origin) override { origin->input(nullptr); } +}; + +} // namespace + +namespace exo +{ + +bool EltwiseSqrtConverter::convert(loco::EltwiseSqrt *origin) +{ + EltwiseSqrtInputHandler input_handler; + exo::DomainConverter domain_converter; + + auto tfl_new = domain_converter.convert(origin, input_handler); + + return (tfl_new != nullptr); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseSqrtConverter.h b/compiler/exo/src/Conversion/EltwiseSqrtConverter.h new file mode 100644 index 00000000000..5ee3185ff3f --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseSqrtConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __ELTWISE_SQRT_CONVERTER_H__ +#define __ELTWISE_SQRT_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseSqrt to TFLSqrt + */ +class EltwiseSqrtConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseSqrtConverter"; } + +public: + bool convert(loco::EltwiseSqrt *origin) final; +}; + +} // namespace exo + +#endif // __ELTWISE_SQRT_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/EltwiseSubConverter.cpp b/compiler/exo/src/Conversion/EltwiseSubConverter.cpp new file mode 100644 index 00000000000..5647c47a289 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseSubConverter.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "EltwiseSubConverter.h" + +#include "EltwiseBinaryConverter.h" + +namespace exo +{ + +bool EltwiseSubConverter::convert(loco::EltwiseSub *origin) +{ + return EltwiseBinaryConvert(origin); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/EltwiseSubConverter.h b/compiler/exo/src/Conversion/EltwiseSubConverter.h new file mode 100644 index 00000000000..d61b76ec086 --- /dev/null +++ b/compiler/exo/src/Conversion/EltwiseSubConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_ELTWISESUB_CONVERTER_H__ +#define __CONVERSION_ELTWISESUB_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::EltwiseSub to TFLSub + */ +class EltwiseSubConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::EltwiseSubConverter"; } + +public: + bool convert(loco::EltwiseSub *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_ELTWISESUB_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/FeatureBiasAddConverter.cpp b/compiler/exo/src/Conversion/FeatureBiasAddConverter.cpp new file mode 100644 index 00000000000..b9aaf140b7c --- /dev/null +++ b/compiler/exo/src/Conversion/FeatureBiasAddConverter.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FeatureBiasAddConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" + +#include +#include + +#include + +namespace +{ + +inline void init_fused_act_func(locoex::TFLAdd *node) +{ + node->fusedActivationFunction(locoex::FusedActFunc::NONE); +} + +} // namespace + +namespace exo +{ + +/** + * @brief Converts loco::FeatureBiasAdd to locoex::TFLAdd + * + * Before: + * Foo ---+ + * | + * loco::FeatureBiasAdd - FeatureDecode - ... + * | + * Bar - BiasEncode --+ + * + * After: + * + * Foo - loco::FeatureDecode --+ loco::FeatureBiasAdd + * |(x) + * TFLAdd -- loco::FeatureEncode - FeatureDecode - ... + * |(y) + * Bar - BiasEncode - loco::BiasDecode --+ + */ +bool FeatureBiasAddConverter::convert(loco::FeatureBiasAdd *origin) +{ + auto *graph = origin->graph(); + + auto tfl_add = graph->nodes()->create(); + + // handling input x + assert(loco::shape_get(origin->value()).domain() == loco::Domain::Feature); + + auto fea_dec = make_feature_decode(origin->value()); + tfl_add->x(fea_dec); + + // handling input y + auto bias_dec = graph->nodes()->create(); + assert(bias_dec != nullptr); + + bias_dec->input(origin->bias()); + + tfl_add->y(bias_dec); + + // fused activation function + init_fused_act_func(tfl_add); + + // handling output + auto fea_enc = make_feature_encode(tfl_add); + + loco::replace(origin).with(fea_enc); + origin->value(nullptr); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/FeatureBiasAddConverter.h b/compiler/exo/src/Conversion/FeatureBiasAddConverter.h new file mode 100644 index 00000000000..5c4f1021346 --- /dev/null +++ b/compiler/exo/src/Conversion/FeatureBiasAddConverter.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_FEATUREBIASADD_CONVERTER__ +#define __CONVERSION_FEATUREBIASADD_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +class FeatureBiasAddConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::TFLAddConverter"; } + +public: + bool convert(loco::FeatureBiasAdd *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_FEATUREBIASADD_CONVERTER__ diff --git a/compiler/exo/src/Conversion/FeatureBiasAddConverter.test.cpp b/compiler/exo/src/Conversion/FeatureBiasAddConverter.test.cpp new file mode 100644 index 00000000000..f3c4a5f81d5 --- /dev/null +++ b/compiler/exo/src/Conversion/FeatureBiasAddConverter.test.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FeatureBiasAddConverter.h" + +#include "GraphBlock.h" +#include "Dialect/IR/TFLNodes.h" + +#include "TestGraph.h" +#include "TestHelper.h" + +#include + +#include + +TEST(FeatureBiasAddConverterTest, basic_test) +{ + exo::test::ExampleGraph g; + + { // attrib setting + // pull + g.pull->dtype(loco::DataType::FLOAT32); + g.pull->shape({1, 2, 2, 3}); + + // bias value + g.constgen->dtype(loco::DataType::FLOAT32); + g.constgen->shape({3}); + g.constgen->size(3); + + g.constgen->at(0) = 0.5; + g.constgen->at(1) = 1; + g.constgen->at(2) = 1.5; + } + + EXO_TEST_ASSERT_NODE_COUNT({g.push}, 7); // sanity check + + // let's convert!! + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + + test_phase.run(g.graph()); + + /* + Expected: + + Pull - FeatureEncoder - FeatureDecode - TFLAdd - FeatureEncode - FeatureDecode - Push + | + ConstGen - BiasEncode - BiasDecode ---+ + */ + } + + // check surroundings + auto tfl_add = exo::test::find_first_node_bytype(g.graph()); + { + ASSERT_TRUE(tfl_add != nullptr); + + // input x and its pred + { + auto actual_fea_dec = dynamic_cast(tfl_add->x()); + ASSERT_TRUE(actual_fea_dec != nullptr); + + auto actual_fea_enc = dynamic_cast(actual_fea_dec->input()); + ASSERT_TRUE(actual_fea_enc != nullptr); + ASSERT_TRUE(actual_fea_enc == g.fea_enc); + } + + // input y and its pred + { + auto actual_bias_dec = dynamic_cast(tfl_add->y()); + ASSERT_TRUE(actual_bias_dec != nullptr); + + auto actual_bias_enc = dynamic_cast(actual_bias_dec->input()); + ASSERT_TRUE(actual_bias_enc != nullptr); + ASSERT_TRUE(actual_bias_enc == g.bias_enc); + } + + // output check + { + auto actual_fea_enc = exo::test::get_only_succ(tfl_add); + ASSERT_TRUE(actual_fea_enc != nullptr); + + auto actual_fea_dec = exo::test::get_only_succ(actual_fea_enc); + ASSERT_TRUE(actual_fea_dec != nullptr); + ASSERT_TRUE(actual_fea_dec == g.fea_dec); + } + } +} diff --git a/compiler/exo/src/Conversion/MatMulConverter.cpp b/compiler/exo/src/Conversion/MatMulConverter.cpp new file mode 100644 index 00000000000..b1158b73d2d --- /dev/null +++ b/compiler/exo/src/Conversion/MatMulConverter.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MatMulConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include +#include +#include + +namespace exo +{ +/** + * @brief Converts loco::MatMul to locoex::TFLFullyConnected + * @note Because TFLFullyConnected accepts input and weights of loco::Domain::Matrix, + * loco::MatrixDecode will be inserted as an input and weights + * to meet domain invariant. + * + * How it works: + * + * Before: + * Foo1 ---- MatrixEncode ---- MatMul ---- MatrixDecode ---- Bar + * Foo2 ---- MatrixEncode ----/ + * + * After: + * + * Foo1 - MatrixEncode - MatrixDecode - TFLFullyConnected - MatrixEncode - MatrixDecode - Bar + * Foo2 - MatrixEncode - MatrixDecode -/ + * + * @note This method replaces MatMul with "- MatrixDecode - TFLFullyConnected - MatrixEncode -". + * - MatrixDecode -/ + * Redundant nodes will be removed during transforms. + * + * @ref + * https://github.com/tensorflow/tensorflow/blob/v1.13.1/tensorflow/lite/kernels/internal/reference/fully_connected.h + */ +bool MatMulConverter::convert(loco::MatMul *origin) +{ + auto *graph = origin->graph(); + + assert(origin->lhs()); + assert(origin->rhs()); + + auto tfl_fc = graph->nodes()->create(); + tfl_fc->fusedActivationFunction(locoex::FusedActFunc::NONE); + + // let's create a new graph connection with tfl_fc + { + // input + auto lhs_matrix_dec = make_matrix_decode(origin->lhs()); + tfl_fc->input(lhs_matrix_dec); + + // weights (WH format on TFLite) + auto rhs_matrix_dec = make_matrix_decode(origin->rhs()); + tfl_fc->weights(rhs_matrix_dec); + + // bias + auto zero_const = graph->nodes()->create(); + { // TODO Create optimization pass which fuse additional Add into bias of Conv or FC + assert(loco::shape_known(origin)); + assert(loco::dtype_known(origin) && loco::dtype_get(origin) == loco::DataType::FLOAT32); + + auto output_depth = loco::shape_get(origin->rhs()).as().width(); + // TODO Fix it with type inference + zero_const->dtype(loco::DataType::FLOAT32); + zero_const->rank(1); + zero_const->dim(0) = output_depth; + zero_const->size(output_depth.value()); + for (uint32_t x = 0; x < output_depth.value(); x++) + zero_const->at(x) = 0.0; + } + tfl_fc->bias(zero_const); + + // output + auto matrix_enc = make_matrix_encode(tfl_fc); + + // replace canonical node + loco::replace(origin).with(matrix_enc); + origin->lhs(nullptr); + origin->rhs(nullptr); + } + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/MatMulConverter.h b/compiler/exo/src/Conversion/MatMulConverter.h new file mode 100644 index 00000000000..e64c4a0f242 --- /dev/null +++ b/compiler/exo/src/Conversion/MatMulConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_FULLY_CONNECTED_CONVERTER__ +#define __CONVERSION_FULLY_CONNECTED_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::MatMul to locoex::TFLFullyConnected + */ +class MatMulConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::MatMulConverter"; } + +public: + bool convert(loco::MatMul *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_FULLY_CONNECTED_CONVERTER__ diff --git a/compiler/exo/src/Conversion/MaxPool2DConverter.cpp b/compiler/exo/src/Conversion/MaxPool2DConverter.cpp new file mode 100644 index 00000000000..67e5ab833bd --- /dev/null +++ b/compiler/exo/src/Conversion/MaxPool2DConverter.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MaxPool2DConverter.h" + +#include "Dialect/IR/TFLNodes.h" +#include "GraphBlock.h" + +#include + +namespace exo +{ + +/** + * @brief Converts loco::MaxPool2D to locoex::TFLMaxPool2D + * + * @note This works similar to AvgPool2DConverter. Please refer to the comment in + * AvgPool2DConverter. + */ +bool MaxPool2DConverter::convert(loco::MaxPool2D *origin) +{ + auto *graph = origin->graph(); + + auto dec = make_feature_decode(origin->ifm()); + auto tfl_max = graph->nodes()->create(); + { + tfl_max->value(dec); + + // set attributes + tfl_max->stride()->w(origin->stride()->horizontal()); + tfl_max->stride()->h(origin->stride()->vertical()); + + tfl_max->filter()->w(origin->window()->horizontal()); + tfl_max->filter()->h(origin->window()->vertical()); + + auto pad = origin->pad(); + if (pad->bottom() == 0 && pad->top() == 0 && pad->left() == 0 && pad->right() == 0) + tfl_max->padding(locoex::Padding::VALID); + else + // TODO This is necessary, but not sufficient condition. More rigorous check required + tfl_max->padding(locoex::Padding::SAME); + + tfl_max->fusedActivationFunction(locoex::FusedActFunc::NONE); + } + + auto enc = make_feature_encode(tfl_max); + + loco::replace(origin).with(enc); + origin->ifm(nullptr); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/MaxPool2DConverter.h b/compiler/exo/src/Conversion/MaxPool2DConverter.h new file mode 100644 index 00000000000..3f526d88f95 --- /dev/null +++ b/compiler/exo/src/Conversion/MaxPool2DConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_MAXPOOL2D_CONVERTER__ +#define __CONVERSION_MAXPOOL2D_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::MaxPool2D to locoex::TFLMaxPool2D + */ +class MaxPool2DConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::MaxPool2DConverter"; } + +public: + bool convert(loco::MaxPool2D *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_MAXPOOL2D_CONVERTER__ diff --git a/compiler/exo/src/Conversion/Relu6Converter.cpp b/compiler/exo/src/Conversion/Relu6Converter.cpp new file mode 100644 index 00000000000..b694511f505 --- /dev/null +++ b/compiler/exo/src/Conversion/Relu6Converter.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Relu6Converter.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace +{ + +class Relu6InputHandler : public exo::InputHandler +{ +public: + void handover(loco::ReLU6 *origin, locoex::TFLRelu6 *replacer) override + { + replacer->features(origin->input()); + } + + std::vector getInputsToConvert(loco::ReLU6 *origin) override + { + std::vector inputs({origin->input()}); + return inputs; + } + + void set(locoex::TFLRelu6 *replacer, std::vector &to) override + { + assert(to.size() == 1); + + replacer->features(to.at(0)); + } + + void nullify(loco::ReLU6 *origin) override { origin->input(nullptr); } +}; + +} // namespace + +namespace exo +{ + +bool Relu6Converter::convert(loco::ReLU6 *origin) +{ + Relu6InputHandler input_handler; + exo::DomainConverter domain_converter; + + auto tfl_node = domain_converter.convert(origin, input_handler); + + return (tfl_node != nullptr); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/Relu6Converter.h b/compiler/exo/src/Conversion/Relu6Converter.h new file mode 100644 index 00000000000..d987b42d08b --- /dev/null +++ b/compiler/exo/src/Conversion/Relu6Converter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_RELU6_CONVERTER_H__ +#define __CONVERSION_RELU6_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::Relu6 to TFLRelu6 + */ +class Relu6Converter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::Relu6Converter"; } + +public: + bool convert(loco::ReLU6 *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_RELU6_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/ReluConverter.cpp b/compiler/exo/src/Conversion/ReluConverter.cpp new file mode 100644 index 00000000000..92adef94d95 --- /dev/null +++ b/compiler/exo/src/Conversion/ReluConverter.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ReluConverter.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace +{ + +class ReluInputHandler : public exo::InputHandler +{ +public: + void handover(loco::ReLU *origin, locoex::TFLRelu *replacer) override + { + replacer->features(origin->input()); + } + + std::vector getInputsToConvert(loco::ReLU *origin) override + { + std::vector inputs({origin->input()}); + return inputs; + } + + void set(locoex::TFLRelu *replacer, std::vector &to) override + { + assert(to.size() == 1); + + replacer->features(to.at(0)); + } + + void nullify(loco::ReLU *origin) override { origin->input(nullptr); } +}; + +} // namespace + +namespace exo +{ + +bool ReluConverter::convert(loco::ReLU *origin) +{ + ReluInputHandler input_handler; + exo::DomainConverter domain_converter; + + auto tfl_node = domain_converter.convert(origin, input_handler); + + return (tfl_node != nullptr); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/ReluConverter.h b/compiler/exo/src/Conversion/ReluConverter.h new file mode 100644 index 00000000000..e1e82ae4b54 --- /dev/null +++ b/compiler/exo/src/Conversion/ReluConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_RELU_CONVERTER_H__ +#define __CONVERSION_RELU_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::Relu to TFLRelu + */ +class ReluConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::ReluConverter"; } + +public: + bool convert(loco::ReLU *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_RELU_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/ReluConverter.test.cpp b/compiler/exo/src/Conversion/ReluConverter.test.cpp new file mode 100644 index 00000000000..f53d656b4af --- /dev/null +++ b/compiler/exo/src/Conversion/ReluConverter.test.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ReluConverter.h" + +#include "GraphBlock.h" +#include "Dialect/IR/TFLNodes.h" + +#include "TestHelper.h" +#include "TestGraph.h" + +#include + +TEST(ReluConverterTest, relu_tensor_inout) +{ + exo::test::TestGraph graph; + { + auto tanh = graph.append(graph.pull); + auto relu = graph.append(tanh); + auto relu6 = graph.append(relu); + graph.complete(); + + auto pull = graph.pull; + { + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2, 2}); + } + } + + // let's convert + exo::test::TypeShapeReadyPhase test_phase; + { + test_phase.add_pass(); + test_phase.run(graph.g.get()); + } + + loco::Node *node = exo::test::find_first_node_bytype(graph.g.get()); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); +} + +TEST(ReluConverterTest, relu_feature_inout) +{ + // g = Pull - FeatureEncode - Relu - FeatureDecode - Push + exo::test::TestGraph graph; + { + auto enc = exo::make_feature_encode(graph.pull); + auto relu = graph.append(enc); + auto dec = exo::make_feature_decode(relu); + graph.complete(dec); + } + + auto pull = graph.pull; + { + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1, 2, 3, 4}); + } + + exo::test::TypeShapeReadyPhase test_phase; + { + test_phase.add_pass(); + test_phase.run(graph.g.get()); + } + + // now, g = Pull - FeatureEncode - FeatureDecode - TFLRelu - FeatureEncode - FeatureDecode - Push + + // Check + EXO_TEST_ASSERT_NODE_COUNT({graph.push}, 7); + + // Check [FeatureEncode - FeatureDecode - TFLRelu - FeatureEncode - FeatureDecode] chunk + loco::Node *node = exo::test::find_first_node_bytype(graph.g.get()); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); + node = exo::test::get_only_succ(node); + ASSERT_TRUE(node != nullptr); +} diff --git a/compiler/exo/src/Conversion/TensorBroadcastConverter.cpp b/compiler/exo/src/Conversion/TensorBroadcastConverter.cpp new file mode 100644 index 00000000000..53233274297 --- /dev/null +++ b/compiler/exo/src/Conversion/TensorBroadcastConverter.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TensorBroadcastConverter.h" + +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include +#include +#include + +#include + +namespace +{ + +template loco::TensorBroadcast *input_as_tbc(T *node) +{ + loco::TensorBroadcast *tbc = dynamic_cast(node->x()); + if (tbc == nullptr) + tbc = dynamic_cast(node->y()); + + return tbc; +} + +struct Collector final : public locoex::TFLNodeMutableVisitor +{ + using NodePair = std::pair; + + void visit(locoex::TFLAdd *node) final + { + if (auto tbc = input_as_tbc(node)) + { + NodePair pair(tbc, node); + candidates.insert(pair); + } + } + + void visit(locoex::TFLDiv *node) final + { + if (auto tbc = input_as_tbc(node)) + { + NodePair pair(tbc, node); + candidates.insert(pair); + } + } + + void visit(locoex::TFLMul *node) final + { + if (auto tbc = input_as_tbc(node)) + { + NodePair pair(tbc, node); + candidates.insert(pair); + } + } + + void visit(locoex::TFLSub *node) final + { + if (auto tbc = input_as_tbc(node)) + { + NodePair pair(tbc, node); + candidates.insert(pair); + } + } + + void visit(locoex::TFLMaximum *node) final + { + if (auto tbc = input_as_tbc(node)) + { + NodePair pair(tbc, node); + candidates.insert(pair); + } + } + + void visit(locoex::TFLNode *) final { return; } + + std::set candidates; +}; + +bool mapping_condition(Collector::NodePair &) +{ + // TODO fill condition + + return true; +} + +template void jump_connection(loco::TensorBroadcast *tbc, T *tflnode) +{ + if (tflnode->x() == tbc) + tflnode->x(tbc->input()); + else if (tflnode->y() == tbc) + tflnode->y(tbc->input()); + else + assert(false); + + tbc->input(nullptr); +} + +} // namespace + +namespace exo +{ + +/** + * @brief Disconnects loco::TensorBroadcast from the graph if following node + * is one of binary node: TFLAdd, TFLSub, TFLMul, TFLDiv, TFLMaximum + * and meets condition (TBA) + * @note + * Before: + * x --- TensorBroadcast --- TFLXXX --- output + * y ----------------------/ + * + * After: + * --- TensorBroadcast --- + * x --- TFLXXX --- output + * y --/ + */ +bool TensorBroadcastConverter::run(loco::Graph *graph) +{ + Collector collector; + + auto active_nodes = loco::active_nodes(loco::output_nodes(graph)); + + for (auto node : active_nodes) + { + if (node->dialect() == locoex::TFLDialect::get()) + { + auto tfl_node = dynamic_cast(node); + tfl_node->accept(&collector); + } + } + + bool changed = false; + + for (auto pair : collector.candidates) + { + if (mapping_condition(pair)) + { + loco::TensorBroadcast *tensorbroadcast = pair.first; + if (auto tfladd = dynamic_cast(pair.second)) + { + jump_connection(tensorbroadcast, tfladd); + changed = true; + } + else if (auto tfldiv = dynamic_cast(pair.second)) + { + jump_connection(tensorbroadcast, tfldiv); + changed = true; + } + else if (auto tflmul = dynamic_cast(pair.second)) + { + jump_connection(tensorbroadcast, tflmul); + changed = true; + } + else if (auto tflsub = dynamic_cast(pair.second)) + { + jump_connection(tensorbroadcast, tflsub); + changed = true; + } + else if (auto tflmaximum = dynamic_cast(pair.second)) + { + jump_connection(tensorbroadcast, tflmaximum); + changed = true; + } + else + { + assert(false); + } + } + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/TensorBroadcastConverter.h b/compiler/exo/src/Conversion/TensorBroadcastConverter.h new file mode 100644 index 00000000000..3cf79b0bae0 --- /dev/null +++ b/compiler/exo/src/Conversion/TensorBroadcastConverter.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TENSOR_BROADCAST_CONVERTER_H__ +#define __TENSOR_BROADCAST_CONVERTER_H__ + +#include +#include + +namespace exo +{ + +/** + * @brief Pass to resolve TensorBroadcast IR + */ +class TensorBroadcastConverter : public logo::Pass +{ +public: + virtual const char *name(void) const { return "exo::TensorBroadcastConverter"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace exo + +#endif //__TENSOR_BROADCAST_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/TensorConcatConverter.cpp b/compiler/exo/src/Conversion/TensorConcatConverter.cpp new file mode 100644 index 00000000000..1c36b11f80c --- /dev/null +++ b/compiler/exo/src/Conversion/TensorConcatConverter.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TensorConcatConverter.h" + +#include "GraphBlock.h" +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace exo +{ +/** + * @brief Converts loco::TensorConcat to locoex::TFLConcatenate + * + * Before: + * input:0 ----- loco::TensorConcat ------- C + * input:1 ----/ + * + * After: + * input:0 ----- locoex::TFLConcatenate --- C + * input:1 ----/ + * + * input:0 ----- loco::TensorConcat --- + * input:1 ----/ + * + */ +bool TensorConcatConverter::convert(loco::TensorConcat *origin) +{ + assert(loco::shape_get(origin).domain() == loco::Domain::Tensor); + + if (!loco::shape_known(origin)) + { + return false; + } + + auto tfl_concat = origin->graph()->nodes()->create(2); + tfl_concat->values(0, origin->lhs()); + tfl_concat->values(1, origin->rhs()); + tfl_concat->axis(origin->axis()); + tfl_concat->fusedActivationFunction(locoex::FusedActFunc::NONE); + + loco::replace(origin).with(tfl_concat); + + origin->lhs(nullptr); + origin->rhs(nullptr); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/TensorConcatConverter.h b/compiler/exo/src/Conversion/TensorConcatConverter.h new file mode 100644 index 00000000000..6b90f4731c5 --- /dev/null +++ b/compiler/exo/src/Conversion/TensorConcatConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_TENSORCONCAT_CONVERTER_H__ +#define __CONVERSION_TENSORCONCAT_CONVERTER_H__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::TensorConcat to TFLConcatenate + */ +class TensorConcatConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::TensorConcatConverter"; } + +public: + bool convert(loco::TensorConcat *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_TENSORCONCAT_CONVERTER_H__ diff --git a/compiler/exo/src/Conversion/TensorReduceConverter.cpp b/compiler/exo/src/Conversion/TensorReduceConverter.cpp new file mode 100644 index 00000000000..8fcb1682d8f --- /dev/null +++ b/compiler/exo/src/Conversion/TensorReduceConverter.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TensorReduceConverter.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Check.h" + +#include + +#include +#include + +namespace +{ + +/** + * @brief Convert given TensorReduce as TFLMean + * + * + * In --- loco::TensorReduce --- Out(s) + * + * + * In -------- locoex::TFLMean --- Out(s) + * / + * TFLConst --- + * (reduction indices) + */ +bool convert_as_mean(loco::TensorReduce *origin) +{ + EXO_ASSERT(origin->func() == loco::ReduceFunc::Mean, "func should be Mean for this helper"); + EXO_ASSERT(origin->input(), "TensorReduce has no input"); + + auto *graph = origin->graph(); + + // Make reduction indicies TFLConst node + auto reduction = graph->nodes()->create(); + { + auto input_rank = loco::shape_get(origin->input()).as().rank(); + + std::vector red_vec; + for (uint32_t axis = 0; axis < input_rank; ++axis) + if (origin->axes()->defined(axis)) + red_vec.push_back(static_cast(axis)); + + const loco::DataType S32 = loco::DataType::S32; + + reduction->dtype(S32); + reduction->rank(1); + reduction->dim(0) = red_vec.size(); + reduction->size(red_vec.size()); + for (uint32_t i = 0; i < red_vec.size(); ++i) + reduction->at(i) = red_vec.at(i); + } + + // Make TFLMean node to replace + auto mean = graph->nodes()->create(); + mean->input(origin->input()); + mean->reduction_indices(reduction); + mean->keep_dims(true); // Canonical TensorReduce always keep dimensions + + // replace canonical node + loco::replace(origin).with(mean); + origin->input(nullptr); + + return true; +} + +} // namespace + +namespace exo +{ + +bool TensorReduceConverter::convert(loco::TensorReduce *origin) +{ + if (origin->func() == loco::ReduceFunc::Mean) + return convert_as_mean(origin); + else + INTERNAL_EXN_V("Unsupported ReduceFunc", oops::to_uint32(origin->func())); +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/TensorReduceConverter.h b/compiler/exo/src/Conversion/TensorReduceConverter.h new file mode 100644 index 00000000000..dfd65ad2d4a --- /dev/null +++ b/compiler/exo/src/Conversion/TensorReduceConverter.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TENSOR_REDUCE_CONVERTER__ +#define __TENSOR_REDUCE_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::TensorReduce to appropriate TFL reduce operation + * @note loco::TensorReduce always keep dimensions + * + * Currently support: + * - When loco::TensorReduce::func() == Mean, convert to TFLMean + TFLConst + * - TODO Support other cases + */ +class TensorReduceConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::TensorReduceConverter"; } + +public: + bool convert(loco::TensorReduce *origin) final; +}; + +} // namespace exo + +#endif // __TENSOR_REDUCE_CONVERTER__ diff --git a/compiler/exo/src/Conversion/TensorTransposeConverter.cpp b/compiler/exo/src/Conversion/TensorTransposeConverter.cpp new file mode 100644 index 00000000000..25c27fe7ea3 --- /dev/null +++ b/compiler/exo/src/Conversion/TensorTransposeConverter.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TensorTransposeConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include +#include + +#include + +#include +#include +#include + +namespace +{ + +void validate_perm(loco::TensorTranspose *origin) +{ + // check perm values are correct + std::vector base_perms; // such as {0, 1, 2, 3, ... } + std::vector perms; // perm values in TensorTranspose + + base_perms.resize(origin->perm()->size()); + perms.resize(origin->perm()->size()); + for (loco::TensorAxis x = 0; x < origin->perm()->size(); x++) + { + base_perms[x] = x; + perms[x] = origin->perm()->axis(x); + } + + if (!std::is_permutation(base_perms.begin(), base_perms.end(), perms.begin())) + INTERNAL_EXN("wrong perm value"); +} + +} // namespace + +namespace exo +{ +/** + * @brief Converts loco::TensorTranspose to locoex::TFLTranspose + */ +bool TensorTransposeConverter::convert(loco::TensorTranspose *origin) +{ + auto *graph = origin->graph(); + + auto tfl_transpose = graph->nodes()->create(); + { + // validation + { + assert(origin->input() != nullptr); + + auto input_rank = loco::shape_get(origin->input()).as().rank(); + if (input_rank != origin->perm()->size()) + INTERNAL_EXN_V("perm size should be same with input rank", + oops::to_uint32(origin->perm()->size())); + + validate_perm(origin); + } + + tfl_transpose->a(origin->input()); + + // perm : set TFLConst + auto perm_const = graph->nodes()->create(); + { + perm_const->dtype(loco::DataType::S32); + perm_const->rank(1); + perm_const->dim(0) = origin->perm()->size(); + perm_const->size(origin->perm()->size()); + + // add perm values into perm TFLConst + for (loco::TensorAxis x = 0; x < origin->perm()->size(); x++) + { + perm_const->at(x) = origin->perm()->axis(x); + } + } + tfl_transpose->perm(perm_const); + } + + // replace canonical node + loco::replace(origin).with(tfl_transpose); + origin->input(nullptr); + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/TensorTransposeConverter.h b/compiler/exo/src/Conversion/TensorTransposeConverter.h new file mode 100644 index 00000000000..9b61ff38d93 --- /dev/null +++ b/compiler/exo/src/Conversion/TensorTransposeConverter.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_TENSORTRANSPOSE_CONVERTER__ +#define __CONVERSION_TENSORTRANSPOSE_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::TensorTranspose to locoex::TFLTranspose + */ +class TensorTransposeConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::TensorTransposeConverter"; } + +public: + bool convert(loco::TensorTranspose *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_TENSORTRANSPOSE_CONVERTER__ diff --git a/compiler/exo/src/Conversion/TransposedConv2DConverter.cpp b/compiler/exo/src/Conversion/TransposedConv2DConverter.cpp new file mode 100644 index 00000000000..c03b64f4813 --- /dev/null +++ b/compiler/exo/src/Conversion/TransposedConv2DConverter.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TransposedConv2DConverter.h" + +#include "Dialect/IR/TFLNodes.h" + +#include "GraphBlock.h" + +#include +#include + +namespace exo +{ + +bool TransposedConv2DConverter::convert(loco::TransposedConv2D *origin) +{ + // Shape is required to set origin->inputSizes() + if (not loco::shape_known(origin)) + return false; + + if ((origin->ifm() == nullptr) or (origin->ker() == nullptr)) + return false; + + auto *graph = origin->graph(); + + auto tfl_tr_conv = graph->nodes()->create(); + { + tfl_tr_conv->stride()->w(origin->stride()->horizontal()); + tfl_tr_conv->stride()->h(origin->stride()->vertical()); + + auto pad = origin->pad(); + if (pad->bottom() == 0 && pad->top() == 0 && pad->left() == 0 && pad->right() == 0) + tfl_tr_conv->padding(locoex::Padding::VALID); + else + // TODO This is necessary, but not sufficient condition. More rigorous check required + tfl_tr_conv->padding(locoex::Padding::SAME); + } + + // let's create a new graph connection with tfl_tr_conv + { + // Make inputSizes from shape of origin + auto input_sizes_const = graph->nodes()->create(); + auto origin_shape = loco::shape_get(origin).as(); + + const loco::DataType S32 = loco::DataType::S32; + + input_sizes_const->dtype(S32); + input_sizes_const->rank(1); + input_sizes_const->dim(0) = 4; + input_sizes_const->size(4); + // Note that NHWC is layout for inputSizes determined by tflite format + input_sizes_const->at(0) = origin_shape.count().value(); // N + input_sizes_const->at(1) = origin_shape.height().value(); // H + input_sizes_const->at(2) = origin_shape.width().value(); // W + input_sizes_const->at(3) = origin_shape.depth().value(); // C + + tfl_tr_conv->inputSizes(input_sizes_const); + + // filter + auto filter_dec = make_filter_decode(origin->ker()); + tfl_tr_conv->filter(filter_dec); + + // outBackprop + auto feature_dec = make_feature_decode(origin->ifm()); + tfl_tr_conv->outBackprop(feature_dec); + + // output + auto feature_enc = make_feature_encode(tfl_tr_conv); + + // replace canonical node + loco::replace(origin).with(feature_enc); + origin->ifm(nullptr); + } + + return true; +} + +} // namespace exo diff --git a/compiler/exo/src/Conversion/TransposedConv2DConverter.h b/compiler/exo/src/Conversion/TransposedConv2DConverter.h new file mode 100644 index 00000000000..f51e0a5bc5e --- /dev/null +++ b/compiler/exo/src/Conversion/TransposedConv2DConverter.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSION_TRANSPOSEDCONV2D_CONVERTER__ +#define __CONVERSION_TRANSPOSEDCONV2D_CONVERTER__ + +#include "CanonicalNodeConverter.h" + +#include + +namespace exo +{ + +/** + * @brief Convert loco::TransposedConv2D to locoex::TFLTransposeConv and auxiliary + * + * + * + * + * IFM ------- TransposedConv2D --- OFM + * (Feature) / (Feature) + * / + * KER ------ + * (Filter) + * + * + * + * + * out_backprop : IFM ------- FeatureDecode --- TFLTransposeConv --- FeatureEncode --- OFM + * [Feature] [Tensor] / / [Tensor] [Feature] + * / / + * filter: KER ------- FilterDecode --- / + * [Filter] [Tensor] / + * / + * input_sizes : TFLConst (new) ------------ + * [Tensor] + */ +class TransposedConv2DConverter : public CanonicalNodeConverter +{ +public: + const char *name(void) const final { return "exo::TransposedConv2DConverter"; } + +public: + bool convert(loco::TransposedConv2D *origin) final; +}; + +} // namespace exo + +#endif // __CONVERSION_TRANSPOSEDCONV2D_CONVERTER__ diff --git a/compiler/exo/src/Conversions.h b/compiler/exo/src/Conversions.h new file mode 100644 index 00000000000..8eb4ed2e4c8 --- /dev/null +++ b/compiler/exo/src/Conversions.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERSIONS_H__ +#define __CONVERSIONS_H__ + +#include "Conversion/AvgPool2DConverter.h" +#include "Conversion/ConstGenConverter.h" +#include "Conversion/Conv2DConverter.h" +#include "Conversion/DepthwiseConv2DConverter.h" +// TODO loco::DepthwiseFilterEncode +#include "Conversion/EltwiseAddConverter.h" +#include "Conversion/EltwiseDivConverter.h" +#include "Conversion/EltwiseMaxConverter.h" +#include "Conversion/EltwiseMulConverter.h" +#include "Conversion/EltwiseSqrtConverter.h" +#include "Conversion/EltwiseSubConverter.h" +#include "Conversion/FeatureBiasAddConverter.h" +// TODO loco::FixedReshape +#include "Conversion/MatMulConverter.h" +#include "Conversion/MaxPool2DConverter.h" +#include "Conversion/ReluConverter.h" +#include "Conversion/Relu6Converter.h" +// TODO loco::Tanh +#include "Conversion/TensorConcatConverter.h" +// TODO loco::TensorBiasAdd +#include "Conversion/TensorBroadcastConverter.h" +#include "Conversion/TensorReduceConverter.h" +// TODO loco::TensorSoftmax +#include "Conversion/TensorTransposeConverter.h" +#include "Conversion/TransposedConv2DConverter.h" + +#endif // __CONVERSIONS_H__ diff --git a/compiler/exo/src/Convert.cpp b/compiler/exo/src/Convert.cpp new file mode 100644 index 00000000000..45f0481f404 --- /dev/null +++ b/compiler/exo/src/Convert.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Convert.h" + +#include "Conversions.h" +#include "Pass/ShapeInferencePass.h" +#include "Pass/TypeInferencePass.h" +#include "ProgressReporter.h" +#include "Knob.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace exo +{ + +void convert_to_TFLNodes(loco::Graph *graph) +{ + // run Shape and Type inference must be run before conversion + loco::CanonicalShapeInferenceRule shape_rule; + loco::apply(&shape_rule).to(graph); + + loco::CanonicalTypeInferenceRule type_rule; + loco::apply(&type_rule).to(graph); + + logo::Phase phase; + { + // prepare type and shape before conversion + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + + // Add converters for canonical nodes. Note: Not all loco canonical nodes are listed. + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + // TODO loco::DepthwiseFilterEncode + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + // TODO loco::FixedReshape + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + // TODO loco::Tanh + phase.emplace_back(stdex::make_unique()); + // TODO loco::TensorBiasAdd + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + // TODO loco::TensorSoftmax + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + + // Add optimization below + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + } + + logo::PhaseRunner phase_runner{graph}; + + ProgressReporter prog(graph, logo::PhaseStrategy::Restart); + phase_runner.attach(&prog); + phase_runner.run(phase); + + // TODO Assert if all canonical nodes are converted to TFL node +} + +} // namespace exo diff --git a/compiler/exo/src/Convert.h b/compiler/exo/src/Convert.h new file mode 100644 index 00000000000..7038f9cf7eb --- /dev/null +++ b/compiler/exo/src/Convert.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + +#include + +namespace exo +{ + +void convert_to_TFLNodes(loco::Graph *graph); + +} // namespace exo + +#endif // __CONVERT_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleDialect.cpp b/compiler/exo/src/Dialect/IR/CircleDialect.cpp new file mode 100644 index 00000000000..ecd43b0a3f2 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleDialect.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleDialect.h" + +namespace locoex +{ + +loco::Dialect *CircleDialect::get(void) +{ + static CircleDialect d; + return &d; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/IR/CircleDialect.h b/compiler/exo/src/Dialect/IR/CircleDialect.h new file mode 100644 index 00000000000..9857d9e6d95 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleDialect.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLEDIALECT_H__ +#define __LOCOEX_IR_CIRCLEDIALECT_H__ + +#include + +namespace locoex +{ + +class CircleDialect final : public loco::Dialect +{ +private: + CircleDialect() = default; + +public: + CircleDialect(const CircleDialect &) = delete; + CircleDialect(CircleDialect &&) = delete; + +public: + static loco::Dialect *get(void); +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLEDIALECT_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleDialect.test.cpp b/compiler/exo/src/Dialect/IR/CircleDialect.test.cpp new file mode 100644 index 00000000000..6132eb361af --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleDialect.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleDialect.h" + +#include + +TEST(CircleDialectTest, get) +{ + using locoex::CircleDialect; + + auto d = CircleDialect::get(); + + // get() SHOULD return a valid(non-null) pointer + ASSERT_NE(d, nullptr); + // The return value SHOULD be stable across multiple invocations + ASSERT_EQ(d, CircleDialect::get()); +} diff --git a/compiler/exo/src/Dialect/IR/CircleNode.cpp b/compiler/exo/src/Dialect/IR/CircleNode.cpp new file mode 100644 index 00000000000..cdcd434ea7f --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNode.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleNode.h" + +#include "CircleDialect.h" + +namespace locoex +{ + +const loco::Dialect *CircleNode::dialect(void) const { return CircleDialect::get(); } + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/IR/CircleNode.h b/compiler/exo/src/Dialect/IR/CircleNode.h new file mode 100644 index 00000000000..1ae9d38bdce --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNode.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODE_H__ +#define __LOCOEX_IR_CIRCLENODE_H__ + +#include "CircleNodeDecl.h" +#include "CircleNodeImpl.h" + +#endif // __LOCOEX_IR_CIRCLENODE_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodeDecl.h b/compiler/exo/src/Dialect/IR/CircleNodeDecl.h new file mode 100644 index 00000000000..358b1f0ce91 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodeDecl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODEDECL_H__ +#define __LOCOEX_IR_CIRCLENODEDECL_H__ + +#include +#include + +#include "CircleOpcode.h" +#include "CircleNodeVisitor.forward.h" + +namespace locoex +{ + +struct CircleNode : public loco::Node +{ + virtual ~CircleNode() = default; + + const loco::Dialect *dialect(void) const final; + virtual CircleOpcode opcode(void) const = 0; + + template T accept(CircleNodeVisitorBase *) const; + template T accept(CircleNodeMutableVisitorBase *); +}; + +template struct CircleNodeImpl : public CircleNode +{ + virtual ~CircleNodeImpl() = default; + + uint32_t opnum(void) const final { return static_cast(Code); } + CircleOpcode opcode(void) const final { return Code; } +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLENODEDECL_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodeImpl.h b/compiler/exo/src/Dialect/IR/CircleNodeImpl.h new file mode 100644 index 00000000000..d9f48711180 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodeImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODEIMPL_H__ +#define __LOCOEX_IR_CIRCLENODEIMPL_H__ + +#include "CircleNodes.h" +#include "CircleNodeVisitor.h" + +#include + +#include + +namespace locoex +{ + +template T CircleNode::accept(CircleNodeVisitorBase *v) const +{ + switch (this->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + \ + case CircleOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + default: + break; + } + + INTERNAL_EXN("CircleNode::accept(CircleNodeVisitorBase) not handled"); +} + +template T CircleNode::accept(CircleNodeMutableVisitorBase *v) +{ + switch (this->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + \ + case CircleOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + default: + break; + } + + INTERNAL_EXN("CircleNode::accept(CircleNodeMutableVisitorBase) not handled"); +} + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLENODEIMPL_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodeVisitor.forward.h b/compiler/exo/src/Dialect/IR/CircleNodeVisitor.forward.h new file mode 100644 index 00000000000..8ae28abf3d7 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodeVisitor.forward.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODE_VISITOR_FORWARD_H__ +#define __LOCOEX_IR_CIRCLENODE_VISITOR_FORWARD_H__ + +namespace locoex +{ + +// NOTE These forward declarations SHOULD BE aligned with Node delcarations in +// "CircleNodeVisitor.h" +template struct CircleNodeVisitorBase; +template struct CircleNodeMutableVisitorBase; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLENODE_VISITOR_FORWARD_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodeVisitor.h b/compiler/exo/src/Dialect/IR/CircleNodeVisitor.h new file mode 100644 index 00000000000..fc70c9ebc3f --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodeVisitor.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODE_VISITOR_H__ +#define __LOCOEX_IR_CIRCLENODE_VISITOR_H__ + +#include "CircleNode.h" +#include "CircleNodes.h" + +#include + +namespace locoex +{ + +/** + * DO NOT use this class. Use CircleNodeVisitor instead. + */ +template struct CircleNodeVisitorBase +{ + virtual ~CircleNodeVisitorBase() = default; + +#define CIRCLE_NODE(OPCODE, Circle_CLASS) virtual T visit(const Circle_CLASS *) = 0; + +#include "CircleNodes.lst" +#undef CIRCLE_NODE +}; + +template struct CircleNodeVisitor : public CircleNodeVisitorBase +{ + virtual ~CircleNodeVisitor() = default; + +#define CIRCLE_NODE(OPCODE, Circle_CLASS) \ + \ + virtual T visit(const Circle_CLASS *node) { return visit(static_cast(node)); } + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + /// @brief Default fallback + virtual T visit(const CircleNode *) { INTERNAL_EXN("CircleNodeVisistor: NYI node"); } +}; + +/** + * DO NOT use this class. Use CircleNodeMutableVisitor instead. + */ +template struct CircleNodeMutableVisitorBase +{ + virtual ~CircleNodeMutableVisitorBase() = default; + +#define CIRCLE_NODE(OPCODE, Circle_CLASS) virtual T visit(Circle_CLASS *) = 0; + +#include "CircleNodes.lst" +#undef CIRCLE_NODE +}; + +template struct CircleNodeMutableVisitor : public CircleNodeMutableVisitorBase +{ + virtual ~CircleNodeMutableVisitor() = default; + +#define CIRCLE_NODE(OPCODE, Circle_CLASS) \ + \ + virtual T visit(Circle_CLASS *node) { return visit(static_cast(node)); } + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + /// @brief Default fallback + virtual T visit(CircleNode *) { INTERNAL_EXN("CircleMutableNodeVisistor: NYI node"); } +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLENODE_VISITOR_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodes.cpp b/compiler/exo/src/Dialect/IR/CircleNodes.cpp new file mode 100644 index 00000000000..bba59ff4d48 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodes.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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 is to validate CircleNodes.h +#include "CircleNodes.h" diff --git a/compiler/exo/src/Dialect/IR/CircleNodes.h b/compiler/exo/src/Dialect/IR/CircleNodes.h new file mode 100644 index 00000000000..7be09310366 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodes.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLENODES_H__ +#define __LOCOEX_IR_CIRCLENODES_H__ + +#include "CircleNodeDecl.h" +#include "CircleOpcode.h" + +#include "FusedActFunc.h" +#include "NodeMixins.h" // FixedArityNode + +#include + +namespace locoex +{ + +/// @brief enumeration of mixin class +enum class CircleNodeTrait +{ + FusedActFunc, +}; + +template class CircleNodeMixin; + +template <> class CircleNodeMixin +{ +public: + CircleNodeMixin() = default; + +public: + FusedActFunc fusedActivationFunction() const { return _fused_act_fun; } + void fusedActivationFunction(FusedActFunc fused_act_fun) { _fused_act_fun = fused_act_fun; } + +private: + FusedActFunc _fused_act_fun = FusedActFunc::UNDEFINED; +}; + +/** + * @brief INSTANCE_NORM in circle + */ +class CircleInstanceNorm final + : public FixedArityNode<3, CircleNodeImpl>, + public CircleNodeMixin +{ +public: + /// @note Currently only support FLOAT32 as input node + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *gamma(void) const { return at(1)->node(); } + void gamma(loco::Node *node) { at(1)->node(node); } + + loco::Node *beta(void) const { return at(2)->node(); } + void beta(loco::Node *node) { at(2)->node(node); } + + float epsilon() const { return _epsilon; } + void epsilon(float epsilon) { _epsilon = epsilon; } + +private: + float _epsilon = 1e-05; +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLENODES_H__ diff --git a/compiler/exo/src/Dialect/IR/CircleNodes.lst b/compiler/exo/src/Dialect/IR/CircleNodes.lst new file mode 100644 index 00000000000..96baf291778 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodes.lst @@ -0,0 +1,8 @@ +#ifndef CIRCLE_NODE +#error "Define CIRCLE_NODE" +#endif // CIRCLE_NODE + +// +// PLEASE SORT NODE DECLS IN ALPHABETICAL ORDER +// +CIRCLE_NODE(INSTANCE_NORM, locoex::CircleInstanceNorm) diff --git a/compiler/exo/src/Dialect/IR/CircleNodes.test.cpp b/compiler/exo/src/Dialect/IR/CircleNodes.test.cpp new file mode 100644 index 00000000000..b63e7ccaefb --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleNodes.test.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleNodes.h" + +#include "CircleDialect.h" +#include "CircleOpcode.h" + +#include + +TEST(CircleInstanceNormTest, constructor) +{ + locoex::CircleInstanceNorm instance_norm; + + ASSERT_EQ(instance_norm.dialect(), locoex::CircleDialect::get()); + ASSERT_EQ(instance_norm.opcode(), locoex::CircleOpcode::INSTANCE_NORM); + + ASSERT_EQ(instance_norm.input(), nullptr); + ASSERT_EQ(instance_norm.gamma(), nullptr); + ASSERT_EQ(instance_norm.beta(), nullptr); + ASSERT_FLOAT_EQ(instance_norm.epsilon(), 1e-05); + ASSERT_EQ(instance_norm.fusedActivationFunction(), locoex::FusedActFunc::UNDEFINED); +} diff --git a/compiler/exo/src/Dialect/IR/CircleOpcode.h b/compiler/exo/src/Dialect/IR/CircleOpcode.h new file mode 100644 index 00000000000..264304049fa --- /dev/null +++ b/compiler/exo/src/Dialect/IR/CircleOpcode.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_CIRCLEOPCODE_H__ +#define __LOCOEX_IR_CIRCLEOPCODE_H__ + +namespace locoex +{ + +enum class CircleOpcode +{ +#define CIRCLE_NODE(OPCODE, CLASS) OPCODE, +#include "CircleNodes.lst" +#undef CIRCLE_NODE +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_CIRCLEOPCODE_H__ diff --git a/compiler/exo/src/Dialect/IR/FusedActFunc.h b/compiler/exo/src/Dialect/IR/FusedActFunc.h new file mode 100644 index 00000000000..b73a0799e79 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/FusedActFunc.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DIALECT_IR_FUSEDACTFUNC_H__ +#define __DIALECT_IR_FUSEDACTFUNC_H__ + +namespace locoex +{ + +// TODO Divide into TFL version and Circle version when they go different approach +enum class FusedActFunc +{ + UNDEFINED, // This is not defined by TFLite or Circle. This was added to + // prevent programming error. + NONE, + RELU, + RELU6 +}; + +} // namespace locoex + +#endif // __DIALECT_IR_FUSEDACTFUNC_H__ diff --git a/compiler/exo/src/Dialect/IR/NodeMixins.cpp b/compiler/exo/src/Dialect/IR/NodeMixins.cpp new file mode 100644 index 00000000000..cdfe0d8d110 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/NodeMixins.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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 is to validate NodeMixins.h +#include "NodeMixins.h" diff --git a/compiler/exo/src/Dialect/IR/NodeMixins.h b/compiler/exo/src/Dialect/IR/NodeMixins.h new file mode 100644 index 00000000000..c35daebc634 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/NodeMixins.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __DIALECT_IR_NODEMIXINS_H__ +#define __DIALECT_IR_NODEMIXINS_H__ + +#include + +namespace locoex +{ + +/** + * @brief Nodes with the fixed number of inputs + * + * TODO Deprecated this class, and use loco::FixedArity instead + */ +template class FixedArityNode : public Base +{ +public: + FixedArityNode() + { + for (uint32_t n = 0; n < N; ++n) + { + _args[n] = std::unique_ptr(new loco::Use{this}); + } + } + + virtual ~FixedArityNode() = default; + +public: + unsigned arity(void) const final { return N; } + + loco::Node *arg(uint32_t n) const final { return _args.at(n)->node(); } + + void drop(void) final + { + for (uint32_t n = 0; n < N; ++n) + { + _args.at(n)->node(nullptr); + } + } + +protected: + // This API allows inherited classes to access "_args" field. + loco::Use *at(unsigned n) const { return _args.at(n).get(); } + +private: + std::array, N> _args; +}; + +} // namespace locoex + +#endif // __DIALECT_IR_NODEMIXINS_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLDialect.cpp b/compiler/exo/src/Dialect/IR/TFLDialect.cpp new file mode 100644 index 00000000000..8cbf9a364be --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLDialect.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLDialect.h" + +namespace locoex +{ + +loco::Dialect *TFLDialect::get(void) +{ + static TFLDialect d; + return &d; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/IR/TFLDialect.h b/compiler/exo/src/Dialect/IR/TFLDialect.h new file mode 100644 index 00000000000..96463a9f97c --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLDialect.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLDIALECT_H__ +#define __LOCOEX_IR_TFLDIALECT_H__ + +#include + +namespace locoex +{ + +class TFLDialect final : public loco::Dialect +{ +private: + TFLDialect() = default; + +public: + TFLDialect(const TFLDialect &) = delete; + TFLDialect(TFLDialect &&) = delete; + +public: + static loco::Dialect *get(void); +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLDIALECT_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLDialect.test.cpp b/compiler/exo/src/Dialect/IR/TFLDialect.test.cpp new file mode 100644 index 00000000000..136721e2df0 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLDialect.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLDialect.h" + +#include + +TEST(TFLDialectTest, get) +{ + using locoex::TFLDialect; + + auto d = TFLDialect::get(); + + // get() SHOULD return a valid(non-null) pointer + ASSERT_NE(d, nullptr); + // The return value SHOULD be stable across multiple invocations + ASSERT_EQ(d, TFLDialect::get()); +} diff --git a/compiler/exo/src/Dialect/IR/TFLNode.cpp b/compiler/exo/src/Dialect/IR/TFLNode.cpp new file mode 100644 index 00000000000..82d5f1ebaf4 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNode.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLNode.h" + +#include "TFLDialect.h" + +namespace locoex +{ + +const loco::Dialect *TFLNode::dialect(void) const { return TFLDialect::get(); } + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/IR/TFLNode.h b/compiler/exo/src/Dialect/IR/TFLNode.h new file mode 100644 index 00000000000..eff69b1a5e3 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNode.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODE_H__ +#define __LOCOEX_IR_TFLNODE_H__ + +#include "TFLNodeDecl.h" +#include "TFLNodeImpl.h" + +#endif // __LOCOEX_IR_TFLNODE_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodeDecl.h b/compiler/exo/src/Dialect/IR/TFLNodeDecl.h new file mode 100644 index 00000000000..d13900ab37b --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodeDecl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODEDECL_H__ +#define __LOCOEX_IR_TFLNODEDECL_H__ + +#include +#include + +#include "TFLOpcode.h" +#include "TFLNodeVisitor.forward.h" + +namespace locoex +{ + +struct TFLNode : public loco::Node +{ + virtual ~TFLNode() = default; + + const loco::Dialect *dialect(void) const final; + virtual TFLOpcode opcode(void) const = 0; + + template T accept(TFLNodeVisitorBase *) const; + template T accept(TFLNodeMutableVisitorBase *); +}; + +template struct TFLNodeImpl : public TFLNode +{ + virtual ~TFLNodeImpl() = default; + + uint32_t opnum(void) const final { return static_cast(Code); } + TFLOpcode opcode(void) const final { return Code; } +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLNODEDECL_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodeImpl.h b/compiler/exo/src/Dialect/IR/TFLNodeImpl.h new file mode 100644 index 00000000000..63388279a16 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodeImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODEIMPL_H__ +#define __LOCOEX_IR_TFLNODEIMPL_H__ + +#include "TFLNodes.h" +#include "TFLNodeVisitor.h" + +#include + +#include + +namespace locoex +{ + +template T TFLNode::accept(TFLNodeVisitorBase *v) const +{ + switch (this->opcode()) + { +#define TFL_NODE(OPCODE, CLASS) \ + \ + case TFLOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "TFLNodes.lst" +#undef TFL_NODE + + default: + break; + } + + INTERNAL_EXN("TFLNode::accept(TFLNodeVisitorBase) not handled"); +} + +template T TFLNode::accept(TFLNodeMutableVisitorBase *v) +{ + switch (this->opcode()) + { +#define TFL_NODE(OPCODE, CLASS) \ + \ + case TFLOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "TFLNodes.lst" +#undef TFL_NODE + + default: + break; + } + + INTERNAL_EXN("TFLNode::accept(TFLNodeMutableVisitorBase) not handled"); +} + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLNODEIMPL_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodeVisitor.forward.h b/compiler/exo/src/Dialect/IR/TFLNodeVisitor.forward.h new file mode 100644 index 00000000000..e98057bc3f7 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodeVisitor.forward.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODE_VISITOR_FORWARD_H__ +#define __LOCOEX_IR_TFLNODE_VISITOR_FORWARD_H__ + +namespace locoex +{ + +// NOTE These forward declarations SHOULD BE aligned with Node delcarations in +// "TFLNodeVisitor.h" +template struct TFLNodeVisitorBase; +template struct TFLNodeMutableVisitorBase; + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLNODE_VISITOR_FORWARD_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodeVisitor.h b/compiler/exo/src/Dialect/IR/TFLNodeVisitor.h new file mode 100644 index 00000000000..e1f5959c092 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodeVisitor.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODE_VISITOR_H__ +#define __LOCOEX_IR_TFLNODE_VISITOR_H__ + +#include "TFLNode.h" +#include "TFLNodes.h" + +#include + +namespace locoex +{ + +/** + * DO NOT use this class. Use TFLNodeVisitor instead. + */ +template struct TFLNodeVisitorBase +{ + virtual ~TFLNodeVisitorBase() = default; + +#define TFL_NODE(OPCODE, TFL_CLASS) virtual T visit(const TFL_CLASS *) = 0; + +#include "TFLNodes.lst" +#undef TFL_NODE +}; + +template struct TFLNodeVisitor : public TFLNodeVisitorBase +{ + virtual ~TFLNodeVisitor() = default; + +#define TFL_NODE(OPCODE, TFL_CLASS) \ + \ + virtual T visit(const TFL_CLASS *node) { return visit(static_cast(node)); } + +#include "TFLNodes.lst" +#undef TFL_NODE + + /// @brief Default fallback + virtual T visit(const TFLNode *) { INTERNAL_EXN("TFLNodeVisitor: NYI node"); } +}; + +/** + * DO NOT use this class. Use TFLNodeMutableVisitor instead. + */ +template struct TFLNodeMutableVisitorBase +{ + virtual ~TFLNodeMutableVisitorBase() = default; + +#define TFL_NODE(OPCODE, TFL_CLASS) virtual T visit(TFL_CLASS *) = 0; + +#include "TFLNodes.lst" +#undef TFL_NODE +}; + +template struct TFLNodeMutableVisitor : public TFLNodeMutableVisitorBase +{ + virtual ~TFLNodeMutableVisitor() = default; + +#define TFL_NODE(OPCODE, TFL_CLASS) \ + \ + virtual T visit(TFL_CLASS *node) { return visit(static_cast(node)); } + +#include "TFLNodes.lst" +#undef TFL_NODE + + /// @brief Default fallback + virtual T visit(TFLNode *) { INTERNAL_EXN("TFLNodeMutableVisitor: NYI node"); } +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLNODE_VISITOR_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodes.cpp b/compiler/exo/src/Dialect/IR/TFLNodes.cpp new file mode 100644 index 00000000000..f385ce0d9bc --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodes.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLNodes.h" + +#include "Check.h" + +#include + +#include + +namespace locoex +{ + +template uint32_t TFLConst::size(void) const +{ + assert(dtype() == DT); + assert(_data.size() % sizeof(typename loco::DataTypeImpl
::Type) == 0); + return _data.size() / sizeof(typename loco::DataTypeImpl
::Type); +} + +template void TFLConst::size(uint32_t l) +{ + assert(dtype() == DT); + _data.resize(l * sizeof(typename loco::DataTypeImpl
::Type)); +} + +template +const typename loco::DataTypeImpl
::Type &TFLConst::at(uint32_t n) const +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +template typename loco::DataTypeImpl
::Type &TFLConst::at(uint32_t n) +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +#define INSTANTIATE(DT) \ + template uint32_t TFLConst::size
(void) const; \ + template void TFLConst::size
(uint32_t); \ + template const typename loco::DataTypeImpl
::Type &TFLConst::at
(uint32_t) const; \ + template typename loco::DataTypeImpl
::Type &TFLConst::at
(uint32_t); + +INSTANTIATE(loco::DataType::S32); +INSTANTIATE(loco::DataType::FLOAT32); + +#undef INSTANTIATE + +void set_new_shape(locoex::TFLReshape *node, int32_t *base, uint32_t size) +{ + // Check node does not have both of new shape infos + EXO_ASSERT(node->shape() == nullptr, "node already has shape input"); + EXO_ASSERT(node->newShape()->rank() == 0, "node already has newShape attribute"); + + const loco::DataType S32 = loco::DataType::S32; + + // Set 2nd input as TFLConst + auto const_shape_node = node->graph()->nodes()->create(); + const_shape_node->rank(1); + const_shape_node->dim(0) = size; + const_shape_node->dtype(S32); + const_shape_node->size(size); + for (uint32_t axis = 0; axis < size; ++axis) + const_shape_node->at(axis) = base[axis]; + node->shape(const_shape_node); + + // Set newShape attribute + node->newShape()->rank(size); + for (uint32_t axis = 0; axis < size; ++axis) + node->newShape()->dim(axis) = base[axis]; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/IR/TFLNodes.h b/compiler/exo/src/Dialect/IR/TFLNodes.h new file mode 100644 index 00000000000..5f521a0a6dc --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodes.h @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLNODES_H__ +#define __LOCOEX_IR_TFLNODES_H__ + +#include "TFLNodeDecl.h" +#include "TFLOpcode.h" + +#include "FusedActFunc.h" +#include "NodeMixins.h" + +#include +#include +#include + +#include + +#include + +namespace locoex +{ + +enum class Padding +{ + UNDEFINED, // This is not defined by TFLite. This was added to prevent programming error. + SAME, + VALID, +}; + +class Filter final +{ +public: + Filter() : _w(1), _h(1) {} + + int32_t w() const { return _w; } + void w(int32_t w) { _w = w; } + + int32_t h() const { return _h; } + void h(int32_t h) { _h = h; } + +private: + int32_t _w; + int32_t _h; +}; + +class Stride final +{ +public: + Stride() : _w(1), _h(1) {} + + int32_t w() const { return _w; } + void w(int32_t w) { _w = w; } + + int32_t h() const { return _h; } + void h(int32_t h) { _h = h; } + +private: + int32_t _w; + int32_t _h; +}; + +/// @brief enumeration of mixin class +enum class TFLNodeTrait +{ + FusedActFunc, + Bias +}; + +template class TFLNodeMixin; + +template <> class TFLNodeMixin +{ +public: + TFLNodeMixin() = default; + +public: + FusedActFunc fusedActivationFunction() const { return _fused_act_fun; } + void fusedActivationFunction(FusedActFunc fused_act_fun) { _fused_act_fun = fused_act_fun; } + +private: + FusedActFunc _fused_act_fun = FusedActFunc::UNDEFINED; +}; + +/** + * @brief Mixin class for nodes that has a bias input + */ +template <> class TFLNodeMixin +{ +public: + TFLNodeMixin() = default; + +public: + virtual loco::Node *bias(void) const = 0; /// @brief get the input for bias. + virtual void bias(loco::Node *node) = 0; /// @brief set the input for bias. +}; + +/** + * @brief ADD in TensorFlow Lite + */ +class TFLAdd final : public FixedArityNode<2, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +/** + * @brief AVERAGE_POOL_2D in TensorFlow Lite + */ +class TFLAveragePool2D final : public FixedArityNode<1, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + TFLAveragePool2D() : _padding(Padding::UNDEFINED) { /* empty */} + +public: + loco::Node *value(void) const { return at(0)->node(); } + void value(loco::Node *node) { at(0)->node(node); } + + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Filter *filter(void) const { return &_filter; } + Filter *filter(void) { return &_filter; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; + Filter _filter; +}; + +/** + * @brief CONCATENATION in TensorFlow Lite + */ +class TFLConcatenation final : public VariadicArityNode>, + public TFLNodeMixin +{ +public: + TFLConcatenation(uint32_t arity) : VariadicArityNode>(arity) + { + // TODO Support when arity is 0 + assert(arity >= 1); + } + +public: + uint32_t numValues(void) const { return arity(); } + +public: + Node *values(uint32_t index) const + { + assert(index < numValues()); + return at(index)->node(); + } + void values(uint32_t index, Node *node) + { + assert(index < numValues()); + at(index)->node(node); + } + +public: + uint32_t axis(void) const { return _axis; } + void axis(uint32_t axis) { _axis = axis; } + +private: + uint32_t _axis; +}; + +/** + * @brief Class to build tensor data + * @note This will not be exported as a specific op + */ +class TFLConst final : public FixedArityNode<0, TFLNodeImpl>, + public loco::NodeMixin, + public loco::NodeMixin +{ +public: + TFLConst() = default; + +public: + template uint32_t size(void) const; + template void size(uint32_t size); + template const typename loco::DataTypeImpl
::Type &at(uint32_t n) const; + template typename loco::DataTypeImpl
::Type &at(uint32_t n); + +private: + std::vector _data; +}; + +/** + * @brief CONV_2D in TensorFlow Lite + */ +class TFLConv2D final : public FixedArityNode<3, TFLNodeImpl>, + public TFLNodeMixin, + public TFLNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } + +public: + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding = Padding::UNDEFINED; + Stride _stride; +}; + +/** + * @brief DEPTHWISE_CONV_2D in TensorFlow Lite + */ +class TFLDepthwiseConv2D final + : public FixedArityNode<3, TFLNodeImpl>, + public TFLNodeMixin, + public TFLNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } + +public: + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + + int32_t depthMultiplier(void) const { return _depth_multiplier; } + void depthMultiplier(int32_t arg) { _depth_multiplier = arg; } + +private: + Padding _padding = Padding::UNDEFINED; + Stride _stride; + int32_t _depth_multiplier = 0; +}; + +/** + * @brief DIV in TensorFlow Lite + */ +class TFLDiv final : public FixedArityNode<2, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + TFLDiv() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +/** + * @brief FULLY_CONNECTED in TensorFlow Lite + */ +class TFLFullyConnected final : public FixedArityNode<3, TFLNodeImpl>, + public TFLNodeMixin, + public TFLNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *weights(void) const { return at(1)->node(); } + void weights(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } +}; + +/** + * @brief MAXIMUM in TensorFlow Lite + */ +class TFLMaximum final : public FixedArityNode<2, TFLNodeImpl> +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +/** + * @brief MAX_POOL_2D in TensorFlow Lite + */ +class TFLMaxPool2D final : public FixedArityNode<1, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + TFLMaxPool2D() : _padding(Padding::UNDEFINED) { /* empty */} + +public: + loco::Node *value(void) const { return at(0)->node(); } + void value(loco::Node *node) { at(0)->node(node); } + + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Filter *filter(void) const { return &_filter; } + Filter *filter(void) { return &_filter; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; + Filter _filter; +}; + +class TFLMean final : public FixedArityNode<2, TFLNodeImpl> +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *reduction_indices(void) const { return at(1)->node(); } + void reduction_indices(loco::Node *node) { at(1)->node(node); } + +public: + bool keep_dims(void) const { return _keep_dims; } + void keep_dims(bool keep_dims) { _keep_dims = keep_dims; } + +private: + bool _keep_dims = false; +}; + +/** + * @brief MUL in TensorFlow Lite + */ +class TFLMul final : public FixedArityNode<2, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +class TFLRelu final : public FixedArityNode<1, TFLNodeImpl> +{ +public: + TFLRelu() = default; + +public: + loco::Node *features(void) const { return at(0)->node(); } + void features(loco::Node *node) { at(0)->node(node); } +}; + +class TFLRelu6 final : public FixedArityNode<1, TFLNodeImpl> +{ +public: + TFLRelu6() = default; + +public: + loco::Node *features(void) const { return at(0)->node(); } + void features(loco::Node *node) { at(0)->node(node); } +}; + +class TFLReshape final : public FixedArityNode<2, TFLNodeImpl> +{ +public: + TFLReshape() = default; + +public: + loco::Node *tensor(void) const { return at(0)->node(); } + void tensor(loco::Node *node) { at(0)->node(node); } + + // TODO Make this input optional. That is, loco system does not emit error + // with this input being null + loco::Node *shape(void) const { return at(1)->node(); } + void shape(loco::Node *node) { at(1)->node(node); } + +public: + class Shape + { + public: + uint32_t rank(void) const { return _shape.size(); } + void rank(uint32_t rank) { _shape.resize(rank); } + + int32_t dim(uint32_t n) const { return _shape.at(n); } + int32_t &dim(uint32_t n) { return _shape.at(n); } + + private: + std::vector _shape; + }; + + const Shape *newShape(void) const { return &_new_shape; } + Shape *newShape(void) { return &_new_shape; } + +private: + Shape _new_shape; +}; + +/** + * @brief Set both TFLReshape's 2nd input as TFLConst, and newShape attribute + * with same value + * @note Shape inference for TFLReshape forces them to be same + * TODO find better place for this helper + */ +void set_new_shape(locoex::TFLReshape *node, int32_t *base, uint32_t size); + +class TFLRsqrt final : public FixedArityNode<1, TFLNodeImpl> +{ +public: + TFLRsqrt() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } +}; + +// TODO TFLSoftmax + +class TFLSqrt final : public FixedArityNode<1, TFLNodeImpl> +{ +public: + TFLSqrt() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } +}; + +class TFLSquaredDifference final + : public FixedArityNode<2, TFLNodeImpl> +{ +public: + TFLSquaredDifference() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +/** + * @brief SUB in TensorFlow Lite + */ +class TFLSub final : public FixedArityNode<2, TFLNodeImpl>, + public TFLNodeMixin +{ +public: + TFLSub() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +// TODO TFLTanh + +/** + * @brief TRANSPOSE in TensorFlow Lite + */ +class TFLTranspose final : public FixedArityNode<2, TFLNodeImpl> +{ +public: + TFLTranspose() = default; + +public: + /// @brief Get the input node to transpose + loco::Node *a(void) const { return at(0)->node(); } + + /// @brief Set the input node to transpose + void a(loco::Node *node) { at(0)->node(node); } + + loco::Node *perm(void) const { return at(1)->node(); } + void perm(loco::Node *node) { at(1)->node(node); } +}; + +/** + * @brief TRANSPOSE_CONV in TensorFlow Lite + * + * @note Argument node function names are from TensorFlow. So refering 'in' and + * 'out' acutally means 'out' and 'in' of the this node. + */ +class TFLTransposeConv final : public FixedArityNode<3, TFLNodeImpl> +{ +public: + loco::Node *inputSizes(void) const { return at(0)->node(); } + void inputSizes(Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(Node *node) { at(1)->node(node); } + + loco::Node *outBackprop(void) const { return at(2)->node(); } + void outBackprop(Node *node) { at(2)->node(node); } + +public: + const Padding &padding(void) const { return _padding; } + void padding(const Padding &padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; +}; + +// TODO define more children of TFLNode + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLNODES_H__ diff --git a/compiler/exo/src/Dialect/IR/TFLNodes.lst b/compiler/exo/src/Dialect/IR/TFLNodes.lst new file mode 100644 index 00000000000..225e2be3b1c --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodes.lst @@ -0,0 +1,30 @@ +#ifndef TFL_NODE +#error "Define TFL_NODE" +#endif // TFL_NODE + +// +// PLEASE SORT NODE DECLS IN ALPHABETICAL ORDER +// +TFL_NODE(ADD, locoex::TFLAdd) +TFL_NODE(AVERAGE_POOL_2D, locoex::TFLAveragePool2D) +TFL_NODE(CONCATENATION, locoex::TFLConcatenation) +TFL_NODE(CONST, locoex::TFLConst) +TFL_NODE(CONV_2D, locoex::TFLConv2D) +TFL_NODE(DEPTHWISE_CONV_2D, locoex::TFLDepthwiseConv2D) +TFL_NODE(DIV, locoex::TFLDiv) +TFL_NODE(FULLY_CONNECTED, locoex::TFLFullyConnected) +TFL_NODE(MAXIMUM, locoex::TFLMaximum) +TFL_NODE(MAX_POOL_2D, locoex::TFLMaxPool2D) +TFL_NODE(MEAN, locoex::TFLMean) +TFL_NODE(MUL, locoex::TFLMul) +TFL_NODE(RELU, locoex::TFLRelu) +TFL_NODE(RELU6, locoex::TFLRelu6) +TFL_NODE(RESHAPE, locoex::TFLReshape) +TFL_NODE(RSQRT, locoex::TFLRsqrt) +// TODO TFLSoftmax +TFL_NODE(SQRT, locoex::TFLSqrt) +TFL_NODE(SQUARED_DIFFERENCE, locoex::TFLSquaredDifference) +TFL_NODE(SUB, locoex::TFLSub) +// TODO TFLTanh +TFL_NODE(TRANSPOSE, locoex::TFLTranspose) +TFL_NODE(TRANSPOSE_CONV, locoex::TFLTransposeConv) diff --git a/compiler/exo/src/Dialect/IR/TFLNodes.test.cpp b/compiler/exo/src/Dialect/IR/TFLNodes.test.cpp new file mode 100644 index 00000000000..09c5c83a003 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLNodes.test.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLNodes.h" + +#include "TFLDialect.h" +#include "TFLOpcode.h" + +#include + +TEST(TFLAddTest, constructor) +{ + locoex::TFLAdd add_node; + + ASSERT_EQ(add_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(add_node.opcode(), locoex::TFLOpcode::ADD); + + ASSERT_EQ(add_node.x(), nullptr); + ASSERT_EQ(add_node.y(), nullptr); +} + +// TODO TFLAveragePool2D + +TEST(TFLConcatTest, constructor) +{ + locoex::TFLConcatenation concat_node(3); + + ASSERT_EQ(concat_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(concat_node.opcode(), locoex::TFLOpcode::CONCATENATION); + + ASSERT_EQ(concat_node.numValues(), 3); + ASSERT_EQ(concat_node.values(0), nullptr); + ASSERT_EQ(concat_node.values(1), nullptr); + ASSERT_EQ(concat_node.values(2), nullptr); + ASSERT_EQ(concat_node.fusedActivationFunction(), locoex::FusedActFunc::UNDEFINED); +} + +// TODO TFLConv2D + +TEST(TFLDepthwiseConv2DTest, constructor) +{ + locoex::TFLDepthwiseConv2D dw_conv2d_node; + + ASSERT_EQ(dw_conv2d_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(dw_conv2d_node.opcode(), locoex::TFLOpcode::DEPTHWISE_CONV_2D); + + ASSERT_EQ(dw_conv2d_node.input(), nullptr); + ASSERT_EQ(dw_conv2d_node.filter(), nullptr); + ASSERT_EQ(dw_conv2d_node.bias(), nullptr); + ASSERT_EQ(dw_conv2d_node.padding(), locoex::Padding::UNDEFINED); + ASSERT_EQ(dw_conv2d_node.stride()->h(), 1); + ASSERT_EQ(dw_conv2d_node.stride()->w(), 1); + ASSERT_EQ(dw_conv2d_node.depthMultiplier(), 0); + ASSERT_EQ(dw_conv2d_node.fusedActivationFunction(), locoex::FusedActFunc::UNDEFINED); +} + +TEST(TFLDivTest, constructor) +{ + locoex::TFLDiv div_node; + + ASSERT_EQ(div_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(div_node.opcode(), locoex::TFLOpcode::DIV); + + ASSERT_EQ(div_node.x(), nullptr); + ASSERT_EQ(div_node.y(), nullptr); +} + +// TODO TFLMaxPool2D + +TEST(TFLMulTest, constructor) +{ + locoex::TFLMul mul_node; + + ASSERT_EQ(mul_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(mul_node.opcode(), locoex::TFLOpcode::MUL); + + ASSERT_EQ(mul_node.x(), nullptr); + ASSERT_EQ(mul_node.y(), nullptr); +} + +TEST(TFLReluTest, constructor) +{ + locoex::TFLRelu relu_node; + + ASSERT_EQ(relu_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(relu_node.opcode(), locoex::TFLOpcode::RELU); + + ASSERT_EQ(relu_node.features(), nullptr); +} + +// TODO TFLRelu6 + +TEST(TFLReshapeTest, constructor) +{ + locoex::TFLReshape reshape; + + ASSERT_EQ(reshape.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(reshape.opcode(), locoex::TFLOpcode::RESHAPE); + + ASSERT_EQ(reshape.tensor(), nullptr); + ASSERT_EQ(reshape.shape(), nullptr); + ASSERT_EQ(reshape.newShape()->rank(), 0); +} + +TEST(TFLReshapeTest, alloc_new_shape) +{ + locoex::TFLReshape reshape; + + reshape.newShape()->rank(2); + ASSERT_EQ(reshape.newShape()->rank(), 2); + + reshape.newShape()->dim(0) = 0; + reshape.newShape()->dim(1) = 1; + + auto &const_reshape = const_cast(reshape); + ASSERT_EQ(const_reshape.newShape()->dim(0), 0); + ASSERT_EQ(const_reshape.newShape()->dim(1), 1); +} + +// TODO TFLSoftmax + +// TODO TFLSqrt + +TEST(TFLSubTest, constructor) +{ + locoex::TFLSub sub_node; + + ASSERT_EQ(sub_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(sub_node.opcode(), locoex::TFLOpcode::SUB); + + ASSERT_EQ(sub_node.x(), nullptr); + ASSERT_EQ(sub_node.y(), nullptr); +} + +// TODO TFLTanh + +TEST(TFLTransposeTest, constructor) +{ + locoex::TFLTranspose tr_node; + + ASSERT_EQ(tr_node.dialect(), locoex::TFLDialect::get()); + ASSERT_EQ(tr_node.opcode(), locoex::TFLOpcode::TRANSPOSE); + + ASSERT_EQ(tr_node.a(), nullptr); + ASSERT_EQ(tr_node.perm(), nullptr); +} diff --git a/compiler/exo/src/Dialect/IR/TFLOpcode.h b/compiler/exo/src/Dialect/IR/TFLOpcode.h new file mode 100644 index 00000000000..0c0ab64bd96 --- /dev/null +++ b/compiler/exo/src/Dialect/IR/TFLOpcode.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_IR_TFLOPCODE_H__ +#define __LOCOEX_IR_TFLOPCODE_H__ + +namespace locoex +{ + +enum class TFLOpcode +{ +#define TFL_NODE(OPCODE, CLASS) OPCODE, +#include "TFLNodes.lst" +#undef TFL_NODE +}; + +} // namespace locoex + +#endif // __LOCOEX_IR_TFLOPCODE_H__ diff --git a/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.cpp b/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.cpp new file mode 100644 index 00000000000..2e71aa00067 --- /dev/null +++ b/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleShapeInferenceRule.h" + +#include "Dialect/IR/CircleNodes.h" +#include "Dialect/IR/CircleDialect.h" +#include "Dialect/IR/CircleNodeVisitor.h" + +#include "Check.h" + +#include + +namespace +{ + +/** + * @brief Class to infer the shape of CircleNode + * + * @note All CircleNode's inputs and outputs are always loco::Domain::Tensor + */ +class ShapeInferenceAlgorithm final : public locoex::CircleNodeVisitor +{ +public: + loco::NodeShape visit(const locoex::CircleInstanceNorm *node) final + { + auto input_shape = loco::shape_get(node->input()).as(); + + return loco::NodeShape{input_shape}; + } +}; + +} // namespace + +namespace locoex +{ + +bool CircleShapeInferenceRule::recognize(const loco::Dialect *d) const +{ + return CircleDialect::get() == d; +} + +bool CircleShapeInferenceRule::infer(const loco::Node *node, loco::NodeShape &shape) const +{ + assert(node->dialect() == CircleDialect::get()); + assert(dynamic_cast(node) != nullptr); + + ShapeInferenceAlgorithm alg; + shape = dynamic_cast(node)->accept(&alg); + + return true; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.h b/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.h new file mode 100644 index 00000000000..92f23c9dd3c --- /dev/null +++ b/compiler/exo/src/Dialect/Service/CircleShapeInferenceRule.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_CIRCLESHAPE_INFERENCE_RULE_H__ +#define __LOCOEX_SERVICE_CIRCLESHAPE_INFERENCE_RULE_H__ + +#include + +namespace locoex +{ + +struct CircleShapeInferenceRule final : public loco::ShapeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::NodeShape &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_CIRCLESHAPE_INFERENCE_RULE_H__ diff --git a/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.cpp b/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.cpp new file mode 100644 index 00000000000..6bc95a1b57a --- /dev/null +++ b/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleTypeInferenceRule.h" + +#include "Dialect/IR/CircleDialect.h" +#include "Dialect/IR/CircleNodeVisitor.h" +#include "Dialect/IR/CircleNodes.h" + +#include + +namespace +{ + +struct TypeInferenceAlgorithm final : public locoex::CircleNodeVisitor +{ + loco::DataType visit(const locoex::CircleInstanceNorm *node) final + { + return loco::dtype_get(node->input()); + } +}; + +} // namespace + +namespace locoex +{ + +bool CircleTypeInferenceRule::recognize(const loco::Dialect *d) const +{ + return CircleDialect::get() == d; +} + +bool CircleTypeInferenceRule::infer(const loco::Node *node, loco::DataType &dtype) const +{ + assert(node->dialect() == CircleDialect::get()); + + TypeInferenceAlgorithm alg; + + dtype = dynamic_cast(node)->accept(&alg); + assert(dtype != loco::DataType::Unknown); + + return true; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.h b/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.h new file mode 100644 index 00000000000..c073dfc5418 --- /dev/null +++ b/compiler/exo/src/Dialect/Service/CircleTypeInferenceRule.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_CIRCLETYPE_INFERENCE_RULE_H__ +#define __LOCOEX_SERVICE_CIRCLETYPE_INFERENCE_RULE_H__ + +#include + +namespace locoex +{ + +/** + * @brief Type Inference Rule for CircleDialect + */ +struct CircleTypeInferenceRule final : public loco::TypeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::DataType &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_CIRCLETYPE_INFERENCE_RULE_H__ diff --git a/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.cpp b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.cpp new file mode 100644 index 00000000000..f4bb1036401 --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.cpp @@ -0,0 +1,627 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLShapeInferenceRule.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include "Check.h" + +#include + +#include +#include +#include + +namespace +{ + +// Call this for TFLAvgPool2D and TFLMaxPool2D only +template loco::NodeShape infer_pool_2d_shape(const Pool2DType *node) +{ + EXO_ASSERT(loco::shape_known(node->value()), "Shape must be known"); + + auto ifm_shape = loco::shape_get(node->value()).template as(); + assert(ifm_shape.rank() == 4); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t window_height = node->filter()->h(); + uint32_t window_width = node->filter()->w(); + uint32_t dilation_height = 1; // dilation for TFLAvgPool2D and TFLMaxPool2D is 1 + uint32_t dilation_width = 1; + uint32_t effective_window_height = dilation_height * (window_height - 1) + 1; + uint32_t effective_window_width = dilation_width * (window_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == locoex::Padding::VALID) + { + output_height = (input_height + stride_height - effective_window_height) / stride_height; + output_width = (input_width + stride_width - effective_window_width) / stride_width; + } + else if (node->padding() == locoex::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + EXO_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ifm_shape.dim(3); + + return loco::NodeShape{ofm_shape}; +} + +/** + * @brief Create a higher-rank TensorShape following NumPy broadcasting semantics + * + * HOW TO USE: + * + * auto expanded_tensor_shape = expand(tensor_shape).to(N); + */ +class TensorShapeExpander +{ +public: + TensorShapeExpander(const loco::TensorShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + loco::TensorShape to(uint32_t output_rank) + { + auto const &input_shape = _shape; + uint32_t const input_rank = input_shape.rank(); + + assert(input_rank <= output_rank && "Cannot shrink rank"); + uint32_t const axis_shift = output_rank - input_rank; + + loco::TensorShape output_shape; + + output_shape.rank(output_rank); + for (uint32_t axis = 0; axis < output_rank; ++axis) + { + output_shape.dim(axis) = (axis < axis_shift) ? 1 : input_shape.dim(axis - axis_shift); + } + + return output_shape; + } + +private: + const loco::TensorShape _shape; +}; + +/** + * @breif Expand shape x and y to same rank by align right and filling with 1 + */ +void expand_rank(loco::TensorShape &x, loco::TensorShape &y) +{ + auto x_rank = x.rank(); + auto y_rank = y.rank(); + + if (x_rank == y_rank) + return; + + TensorShapeExpander x_exp(x); + TensorShapeExpander y_exp(y); + + auto xy_rank = std::max(x_rank, y_rank); + + x = x_rank > y_rank ? x : x_exp.to(xy_rank); + y = y_rank > x_rank ? y : y_exp.to(xy_rank); +} + +/** + * @breif Returns shape of expanded dimension of input x and y having same rank + */ +loco::TensorShape expand_dimension(const loco::TensorShape &x, const loco::TensorShape &y) +{ + assert(x.rank() == y.rank()); + + auto rank = x.rank(); + + loco::TensorShape output_shape; + + output_shape.rank(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + assert(x.dim(axis).known() && y.dim(axis).known()); + + auto x_dim = x.dim(axis).value(); + auto y_dim = y.dim(axis).value(); + + // each dimension of x and y should be same or one must be 1 if different + if (!((x_dim == y_dim) || (x_dim == 1 || y_dim == 1))) + INTERNAL_EXN("Cannot produce expand_dimension of two shapes"); + + output_shape.dim(axis) = std::max(x_dim, y_dim); + } + + return output_shape; +} + +loco::TensorShape broadcast_shape(const loco::TensorShape &x, const loco::TensorShape &y) +{ + auto x_match = x; + auto y_match = y; + + expand_rank(x_match, y_match); + + auto output_shape = expand_dimension(x_match, y_match); + + return output_shape; +} + +/** + * @brief Class to infer the shape of TFLNode + * + * @note All TFLNode's inputs and outputs are always loco::Domain::Tensor + */ +class ShapeInferenceAlgorithm final : public locoex::TFLNodeVisitor +{ +public: + loco::NodeShape visit(const locoex::TFLAdd *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLAveragePool2D *node) final + { + return infer_pool_2d_shape(node); + } + + loco::NodeShape visit(const locoex::TFLConcatenation *node) final + { + // TODO Support when TFLConcatenation has 0 input + assert(node->numValues() > 0); + + auto axis = node->axis(); + auto first_shape = loco::shape_get(node->values(0)).as(); + + loco::TensorShape output_shape; + + output_shape.rank(first_shape.rank()); + for (uint32_t i = 0; i < output_shape.rank(); ++i) + output_shape.dim(i) = first_shape.dim(i); + + for (uint32_t i = 1; i < node->numValues(); ++i) + { + auto input_shape = loco::shape_get(node->values(i)).as(); + + for (uint32_t j = 0; j < output_shape.rank(); ++j) + { + if (j == axis) + output_shape.dim(j) = output_shape.dim(j).value() + input_shape.dim(j).value(); + else + assert(output_shape.dim(j) == input_shape.dim(j)); + } + } + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLConst *node) final + { + loco::TensorShape shape; + + shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); axis++) + shape.dim(axis) = node->dim(axis); + + return loco::NodeShape{shape}; + } + + loco::NodeShape visit(const locoex::TFLConv2D *node) final + { + auto ifm_shape = loco::shape_get(node->input()).as(); // in NHWC + auto ker_shape = loco::shape_get(node->filter()).as(); // in OHWI + + assert(ifm_shape.rank() == 4); + assert(ker_shape.rank() == 4); + assert(ifm_shape.dim(3) == ker_shape.dim(3)); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t ker_height = ker_shape.dim(1).value(); + uint32_t ker_width = ker_shape.dim(2).value(); + uint32_t dilation_height = 1; + uint32_t dilation_width = 1; + uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1; + uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == locoex::Padding::VALID) + { + output_height = (input_height + stride_height - effective_ker_height) / stride_height; + output_width = (input_width + stride_width - effective_ker_width) / stride_width; + } + else if (node->padding() == locoex::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + EXO_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ker_shape.dim(0); + + return loco::NodeShape{ofm_shape}; + } + + loco::NodeShape visit(const locoex::TFLDepthwiseConv2D *node) final + { + auto ifm_shape = loco::shape_get(node->input()).as(); // in NHWC + auto ker_shape = loco::shape_get(node->filter()).as(); // in 1 H W CM + + assert(ifm_shape.rank() == 4); + assert(ker_shape.rank() == 4); + assert(ker_shape.dim(0).value() == 1); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t ker_height = ker_shape.dim(1).value(); + uint32_t ker_width = ker_shape.dim(2).value(); + uint32_t dilation_height = 1; + uint32_t dilation_width = 1; + uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1; + uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == locoex::Padding::VALID) + { + output_height = (input_height + stride_height - effective_ker_height) / stride_height; + output_width = (input_width + stride_width - effective_ker_width) / stride_width; + } + else if (node->padding() == locoex::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + EXO_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ker_shape.dim(3); + + return loco::NodeShape{ofm_shape}; + } + + loco::NodeShape visit(const locoex::TFLDiv *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLFullyConnected *node) final + { + auto input_shape = loco::shape_get(node->input()).as(); + auto weights_shape = loco::shape_get(node->weights()).as(); + + // Checking shape capability for multiplication + EXO_ASSERT(input_shape.rank() == 2, "NYI for input shape rank > 2"); + EXO_ASSERT(weights_shape.rank() == 2, "Incompatible weights rank for fully connected"); + EXO_ASSERT(input_shape.dim(1) == weights_shape.dim(1), + "Incompatible shapes for fully connected"); + + loco::TensorShape out_shape; + out_shape.rank(2); + + out_shape.dim(0) = input_shape.dim(0); + out_shape.dim(1) = weights_shape.dim(0); + + return loco::NodeShape{out_shape}; + } + + loco::NodeShape visit(const locoex::TFLMaximum *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLMaxPool2D *node) final + { + return infer_pool_2d_shape(node); + } + + loco::NodeShape visit(const locoex::TFLMean *node) final + { + const loco::DataType S32 = loco::DataType::S32; + + auto input_shape = loco::shape_get(node->input()).as(); + auto reduction_indices = dynamic_cast(node->reduction_indices()); + + { // Exceptions + // TODO support non-const case + EXO_ASSERT(reduction_indices, "Only support constant reduction_indices"); + // TODO support other data type + EXO_ASSERT(reduction_indices->dtype() == S32, "Only support int 32"); + } + + std::vector reduction_values; + + for (uint32_t i = 0; i < reduction_indices->size(); ++i) + { + int32_t axis = reduction_indices->at(i); + if (axis < 0) + axis += input_shape.rank(); + if (not(0 <= axis and axis < static_cast(input_shape.rank()))) + INTERNAL_EXN_V("Invalid reduction axis for MEAN", oops::to_uint32(axis)); + reduction_values.push_back(axis); + } + + loco::TensorShape output_shape; + + if (node->keep_dims()) + { + output_shape.rank(input_shape.rank()); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + output_shape.dim(i) = input_shape.dim(i); + for (uint32_t i = 0; i < reduction_values.size(); ++i) + output_shape.dim(reduction_values.at(i)) = 1; + } + else + { + std::vector check_reduce(input_shape.rank(), false); + for (uint32_t i = 0; i < reduction_values.size(); ++i) + check_reduce.at(reduction_values.at(i)) = true; + + uint32_t reduce_cnt = 0; + for (uint32_t i = 0; i < check_reduce.size(); ++i) + if (check_reduce.at(i)) + ++reduce_cnt; + + output_shape.rank(input_shape.rank() - reduce_cnt); + for (uint32_t i = 0, j = 0; i < check_reduce.size(); ++i) + if (check_reduce.at(i) == false) + output_shape.dim(j++) = i; + } + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLMul *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLRelu *node) final + { + auto input_shape = loco::shape_get(node->features()).as(); + + return loco::NodeShape{input_shape}; + } + + loco::NodeShape visit(const locoex::TFLRelu6 *node) final + { + auto input_shape = loco::shape_get(node->features()).as(); + + return loco::NodeShape{input_shape}; + } + + /** + * @note TFLReshape has new shape info in two places: 2nd input and attribute. + * This shape inference forces both to exist, and match each other. + * When this condition satisfied, it return the inferred shape + * + * TODO Change this policy when not appropriate + */ + loco::NodeShape visit(const locoex::TFLReshape *node) final + { + const loco::DataType S32 = loco::DataType::S32; + + loco::TensorShape shape_by_input; + { + EXO_ASSERT(node->shape(), "2nd input shape() should not be nullptr"); + + // Only support node's shape() is TFLConst with S32 + // TODO support other node with other types + auto const_shape_node = dynamic_cast(node->shape()); + EXO_ASSERT(const_shape_node, "Only support TFLConst for shape of TFLReshape"); + EXO_ASSERT(const_shape_node->dtype() == S32, "Only support int32 TFLConst"); + + if (const_shape_node->rank() != 1) + INTERNAL_EXN_V("Only support rank 1 TFLConst", oops::to_uint32(const_shape_node->rank())); + + shape_by_input.rank(const_shape_node->dim(0).value()); + + for (uint32_t axis = 0; axis < shape_by_input.rank(); ++axis) + { + EXO_ASSERT(const_shape_node->at(axis) > 0, "Dimension should be > 0") + shape_by_input.dim(axis) = const_shape_node->at(axis); + } + } + + loco::TensorShape shape_by_attr; + { + shape_by_attr.rank(node->newShape()->rank()); + + for (uint32_t axis = 0; axis < shape_by_attr.rank(); ++axis) + { + EXO_ASSERT(node->newShape()->dim(axis) > 0, "Dimension should be > 0") + shape_by_attr.dim(axis) = node->newShape()->dim(axis); + } + } + + EXO_ASSERT(shape_by_input == shape_by_attr, + "Warning: Two new shape information mismatched for TFLReshape"); + + return loco::NodeShape{shape_by_input}; + } + + loco::NodeShape visit(const locoex::TFLRsqrt *node) final + { + auto input_shape = loco::shape_get(node->x()).as(); + + return loco::NodeShape{input_shape}; + } + + // TODO TFLSoftmax + + loco::NodeShape visit(const locoex::TFLSqrt *node) final + { + auto input_shape = loco::shape_get(node->x()).as(); + + return loco::NodeShape{input_shape}; + } + + loco::NodeShape visit(const locoex::TFLSquaredDifference *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const locoex::TFLSub *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + // TODO TFLTanh + + /// @brief Returns output shape of transpose. Use loco::ConstGen and locoex::TFLConst for ConstT. + template + loco::TensorShape output_shape_of_transpose(loco::TensorShape input_shape, + const ConstT *perm_node) + { + loco::TensorShape output_shape; + output_shape.rank(input_shape.rank()); + + assert(perm_node->dtype() == loco::DataType::S32); + assert(input_shape.rank() == perm_node->template size()); + + for (uint32_t out_axis = 0; out_axis < output_shape.rank(); out_axis++) + { + auto new_dim = perm_node->template at(out_axis); + output_shape.dim(new_dim) = input_shape.dim(out_axis); + } + + return output_shape; + } + + loco::NodeShape visit(const locoex::TFLTranspose *node) final + { + auto input_shape = loco::shape_get(node->a()).as(); + + auto canon_perm = dynamic_cast(node->perm()); + auto tfl_perm = dynamic_cast(node->perm()); + + if (canon_perm) + { + return loco::NodeShape{output_shape_of_transpose(input_shape, canon_perm)}; + } + else if (tfl_perm) + { + return loco::NodeShape{output_shape_of_transpose(input_shape, tfl_perm)}; + } + else + INTERNAL_EXN("perm of TFLTranspose should be either ConstGen or TFLConst"); + } + + loco::NodeShape visit(const locoex::TFLTransposeConv *node) final + { + // TransposeConv's output shape is written in its 'inputSizes' argument + auto input_sizes_const = dynamic_cast(node->inputSizes()); + EXO_ASSERT(input_sizes_const, "Only support when TFLTransposeConv's inputSizes is TFLConst") + EXO_ASSERT(input_sizes_const->dtype() == loco::DataType::S32, "Only support S32 dtype") + EXO_ASSERT(input_sizes_const->rank() == 1 && input_sizes_const->dim(0).value() == 4, + "Only support rank 1 with 4 entries") + + loco::TensorShape shape; + + shape.rank(4); + for (uint32_t axis = 0; axis < 4; ++axis) + shape.dim(axis) = input_sizes_const->at(axis); + + return loco::NodeShape{shape}; + } +}; + +} // namespace + +namespace locoex +{ + +bool TFLShapeInferenceRule::recognize(const loco::Dialect *d) const +{ + return TFLDialect::get() == d; +} + +bool TFLShapeInferenceRule::infer(const loco::Node *node, loco::NodeShape &shape) const +{ + assert(node->dialect() == TFLDialect::get()); + assert(dynamic_cast(node) != nullptr); + + ShapeInferenceAlgorithm alg; + shape = dynamic_cast(node)->accept(&alg); + + return true; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.h b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.h new file mode 100644 index 00000000000..434a145cc8d --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_TFLSHAPE_INFERENCE_RULE_H__ +#define __LOCOEX_SERVICE_TFLSHAPE_INFERENCE_RULE_H__ + +#include + +namespace locoex +{ + +struct TFLShapeInferenceRule final : public loco::ShapeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::NodeShape &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_TFLSHAPE_INFERENCE_RULE_H__ diff --git a/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.test.cpp b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.test.cpp new file mode 100644 index 00000000000..35c8f0b2a29 --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLShapeInferenceRule.test.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TestGraph.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/Service/TFLShapeInferenceRule.h" + +#include +#include +#include +#include +#include + +#include + +#include + +TEST(TFLShapeInferenceRuleTest, minimal_with_TFLRelu) +{ + // Create a simple network + exo::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(tfl_node); + + // set shape + { + graph.pull->rank(2); + graph.pull->dim(0) = 3; + graph.pull->dim(1) = 4; + } + + // pre-check + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + locoex::TFLShapeInferenceRule tfl_rule; + loco::CanonicalShapeInferenceRule canonical_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(locoex::TFLDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 2); + ASSERT_EQ(shape.dim(0), 3); + ASSERT_EQ(shape.dim(1), 4); + } +} + +// based on the case shown in +// https://www.corvil.com/kb/what-is-the-difference-between-same-and-valid-padding-in-tf-nn-max-pool-of-tensorflow +TEST(TFLShapeInferenceRuleTest, avgpool2d_valid) +{ + exo::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(); + + auto pull = graph.pull; + { + pull->shape({1, 4, 3, 1}); + } + // setting TFLAveragePool2D + { + tfl_node->filter()->h(2); + tfl_node->filter()->w(2); + tfl_node->stride()->h(2); + tfl_node->stride()->w(2); + tfl_node->fusedActivationFunction(locoex::FusedActFunc::NONE); + tfl_node->padding(locoex::Padding::VALID); + } + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + locoex::TFLShapeInferenceRule tfl_rule; + loco::CanonicalShapeInferenceRule canonical_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(locoex::TFLDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0).value(), 1); + ASSERT_EQ(shape.dim(1).value(), 2); + ASSERT_EQ(shape.dim(2).value(), 1); + ASSERT_EQ(shape.dim(3).value(), 1); + } +} + +TEST(TFLShapeInferenceRuleTest, avgpool2d_same) +{ + exo::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(); + + auto pull = graph.pull; + { + pull->shape({1, 4, 3, 1}); + } + + // setting TFLAveragePool2D + { + tfl_node->filter()->h(2); + tfl_node->filter()->w(2); + tfl_node->stride()->h(2); + tfl_node->stride()->w(2); + tfl_node->fusedActivationFunction(locoex::FusedActFunc::NONE); + tfl_node->padding(locoex::Padding::SAME); + } + + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + locoex::TFLShapeInferenceRule tfl_rule; + loco::CanonicalShapeInferenceRule canonical_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(locoex::TFLDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0).value(), 1); + ASSERT_EQ(shape.dim(1).value(), 2); + ASSERT_EQ(shape.dim(2).value(), 2); + ASSERT_EQ(shape.dim(3).value(), 1); + } +} + +/** + * @note Function to test: Shape inference of two different input shapes + * + * Rank expansion to higher input side + * x(2,1,5) + y(3,5) --> x(2,1,5) + y(1,3,5) + * Do output shape inference like numpy + * x(2,1,5) + y(1,3,5) --> output(2,3,5) + * For each axis, dim value should be same OR one of them should be 1 + */ +TEST(TFLShapeInferenceRuleTest, TFAdd_shapeinf_different) +{ + auto g = loco::make_graph(); + + auto x_node = g->nodes()->create(); + { + x_node->rank(3); + x_node->dim(0) = 2; + x_node->dim(1) = 1; + x_node->dim(2) = 5; + } + auto y_node = g->nodes()->create(); + { + y_node->rank(2); + y_node->dim(0) = 3; + y_node->dim(1) = 5; + } + auto tfl_node = g->nodes()->create(); + { + tfl_node->x(x_node); + tfl_node->y(y_node); + } + auto push_node = g->nodes()->create(); + { + push_node->from(tfl_node); + } + + auto x_input = g->inputs()->create(); + { + x_input->name("x"); + loco::link(x_input, x_node); + } + auto y_input = g->inputs()->create(); + { + y_input->name("y"); + loco::link(y_input, y_node); + } + auto output = g->outputs()->create(); + { + output->name("output"); + loco::link(output, push_node); + } + + // pre-check + ASSERT_FALSE(loco::shape_known(tfl_node)); + + exo::ShapeInferencePass pass; + while (pass.run(g.get()) == true) + { + ; + } + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 3); + ASSERT_EQ(shape.dim(0), 2); + ASSERT_EQ(shape.dim(1), 3); + ASSERT_EQ(shape.dim(2), 5); + } +} + +TEST(TFLShapeInferenceRuleTest, TFLTranspose_simple) +{ + exo::test::ExampleGraph g; + + g.pull->rank(4); + g.pull->dim(0) = 10; + g.pull->dim(1) = 20; + g.pull->dim(2) = 30; + g.pull->dim(3) = 40; + + g.const_perm->dtype(loco::DataType::S32); + g.const_perm->rank(1); + g.const_perm->dim(0) = 4; + g.const_perm->size(4); + g.const_perm->at(0) = 2; + g.const_perm->at(1) = 3; + g.const_perm->at(2) = 0; + g.const_perm->at(3) = 1; + + // pre-check + ASSERT_FALSE(loco::shape_known(g.tfl_transpose)); + + exo::ShapeInferencePass pass; + while (pass.run(g.graph()) == true) + ; + + // Verify + { + ASSERT_TRUE(loco::shape_known(g.tfl_transpose)); + + auto shape = loco::shape_get(g.tfl_transpose).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0), 30); + ASSERT_EQ(shape.dim(1), 40); + ASSERT_EQ(shape.dim(2), 10); + ASSERT_EQ(shape.dim(3), 20); + } +} diff --git a/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.cpp b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.cpp new file mode 100644 index 00000000000..3f123a6db9c --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLTypeInferenceRule.h" + +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodeVisitor.h" +#include "Dialect/IR/TFLNodes.h" + +#include + +namespace +{ + +struct TypeInferenceAlgorithm final : public locoex::TFLNodeVisitor +{ + loco::DataType visit(const locoex::TFLAdd *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const locoex::TFLAveragePool2D *node) final + { + return loco::dtype_get(node->value()); + } + + loco::DataType visit(const locoex::TFLConcatenation *node) final + { + // TODO Support when TFLConcatenation has 0 input + assert(node->numValues() > 0); + + for (uint32_t i = 1; i < node->numValues(); ++i) + assert(loco::dtype_get(node->values(i - 1)) == loco::dtype_get(node->values(i))); + + return loco::dtype_get(node->values(0)); + } + + loco::DataType visit(const locoex::TFLConst *node) final { return node->dtype(); } + + loco::DataType visit(const locoex::TFLConv2D *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const locoex::TFLDepthwiseConv2D *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const locoex::TFLDiv *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const locoex::TFLFullyConnected *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const locoex::TFLMaximum *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const locoex::TFLMaxPool2D *node) final + { + return loco::dtype_get(node->value()); + } + + loco::DataType visit(const locoex::TFLMean *node) final { return loco::dtype_get(node->input()); } + + loco::DataType visit(const locoex::TFLMul *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const locoex::TFLRelu *node) final + { + return loco::dtype_get(node->features()); + } + + loco::DataType visit(const locoex::TFLRelu6 *node) final + { + return loco::dtype_get(node->features()); + } + + loco::DataType visit(const locoex::TFLReshape *node) final + { + return loco::dtype_get(node->tensor()); + } + + loco::DataType visit(const locoex::TFLRsqrt *node) final { return loco::dtype_get(node->x()); } + + // TODO TFLSoftmax + + loco::DataType visit(const locoex::TFLSqrt *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const locoex::TFLSquaredDifference *node) final + { + return loco::dtype_get(node->x()); + } + + loco::DataType visit(const locoex::TFLSub *node) final { return loco::dtype_get(node->x()); } + + // TODO TFLTanh + + loco::DataType visit(const locoex::TFLTranspose *node) final + { + return loco::dtype_get(node->a()); + } + + loco::DataType visit(const locoex::TFLTransposeConv *node) final + { + return loco::dtype_get(node->outBackprop()); + } +}; + +} // namespace + +namespace locoex +{ + +bool TFLTypeInferenceRule::recognize(const loco::Dialect *d) const +{ + return TFLDialect::get() == d; +} + +bool TFLTypeInferenceRule::infer(const loco::Node *node, loco::DataType &dtype) const +{ + assert(node->dialect() == TFLDialect::get()); + + TypeInferenceAlgorithm alg; + + dtype = dynamic_cast(node)->accept(&alg); + assert(dtype != loco::DataType::Unknown); + + return true; +} + +} // namespace locoex diff --git a/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.h b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.h new file mode 100644 index 00000000000..31765dcbacb --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_TFLTYPE_INFERENCE_RULE_H__ +#define __LOCOEX_SERVICE_TFLTYPE_INFERENCE_RULE_H__ + +#include + +namespace locoex +{ + +/** + * @brief Type Inference Rule for TFLDialect + */ +struct TFLTypeInferenceRule final : public loco::TypeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + + bool infer(const loco::Node *, loco::DataType &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_TFLTYPE_INFERENCE_RULE_H__ diff --git a/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.test.cpp b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.test.cpp new file mode 100644 index 00000000000..dd1f93c4d0c --- /dev/null +++ b/compiler/exo/src/Dialect/Service/TFLTypeInferenceRule.test.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/Service/TFLTypeInferenceRule.h" + +#include "TestGraph.h" + +#include +#include +#include + +#include + +#include + +TEST(TFLTypeInferenceRuleTest, minimal_with_TFLRelu) +{ + // Create a simple network + exo::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(tfl_node); + + graph.pull->dtype(loco::DataType::S32); + + // pre-check + ASSERT_FALSE(loco::dtype_known(tfl_node)); + + // type inference + locoex::TFLTypeInferenceRule tfl_rule; + loco::CanonicalTypeInferenceRule canon_rule; + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canon_rule); + rules.bind(locoex::TFLDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + ASSERT_TRUE(loco::dtype_known(tfl_node)); + auto type = loco::dtype_get(tfl_node); + ASSERT_EQ(type, loco::DataType::S32); +} diff --git a/compiler/exo/src/ExoFormattedGraph.cpp b/compiler/exo/src/ExoFormattedGraph.cpp new file mode 100644 index 00000000000..5d3b18be1a7 --- /dev/null +++ b/compiler/exo/src/ExoFormattedGraph.cpp @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ExoFormattedGraph.h" + +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodes.h" + +#include "Dialect/IR/CircleDialect.h" +#include "Dialect/IR/CircleNodes.h" + +#include +#include + +#include +#include + +// For TF lite +namespace +{ + +const char *to_str(locoex::FusedActFunc fused) +{ + switch (fused) + { + case locoex::FusedActFunc::NONE: + return "NONE"; + case locoex::FusedActFunc::RELU: + return "RELU"; + case locoex::FusedActFunc::RELU6: + return "RELU6"; + default: + return "Error"; + } +} + +const char *to_str(locoex::Padding padding) +{ + switch (padding) + { + case locoex::Padding::SAME: + return "SAME"; + case locoex::Padding::VALID: + return "VALID"; + default: + return "Error"; + } +} + +std::string to_str(const locoex::Stride *stride) +{ + return pepper::str(stride->h(), ",", stride->w()); +} + +std::string to_str(const locoex::Filter *filter) +{ + return pepper::str(filter->h(), ",", filter->w()); +} + +std::string tfl_opname(uint32_t opnum) +{ + static std::string prefix{"tfl."}; + + switch (static_cast(opnum)) + { +#define TFL_NODE(OPCODE, CLASS) \ + case locoex::TFLOpcode::OPCODE: \ + return prefix + #OPCODE; +#include "Dialect/IR/TFLNodes.lst" +#undef TFL_NODE + default: + break; + }; + + return prefix + "Invalid"; +} + +// TFLNodeSummaryBuilder with default implementation +class TFLNodeSummaryBuilderBase : public locop::NodeSummaryBuilder +{ +public: + TFLNodeSummaryBuilderBase(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *, locop::NodeSummary &s) const final; + +protected: +#define TFL_NODE(OPCODE, CLASS) \ + virtual bool summary(const CLASS *, locop::NodeSummary &s) const \ + { \ + s.comments().append("Emitted by Default TFLNodeSummaryBuilder"); \ + s.state(locop::NodeSummary::State::PartiallyKnown); \ + return true; \ + } +#include "Dialect/IR/TFLNodes.lst" +#undef TFL_NODE + +protected: + const locop::SymbolTable *tbl(void) const { return _tbl; } + + // Please do not use _tbl directly and use tbl(). + // This will be changed to private in near future. +protected: + const locop::SymbolTable *_tbl; +}; + +class TFLNodeSummaryBuilder final : public TFLNodeSummaryBuilderBase +{ +public: + TFLNodeSummaryBuilder(const locop::SymbolTable *tbl) : TFLNodeSummaryBuilderBase(tbl) + { + // DO NOTHING + } + +private: +#define IMPLEMENT(CLASS) bool summary(const CLASS *, locop::NodeSummary &) const final; + IMPLEMENT(locoex::TFLAdd) + IMPLEMENT(locoex::TFLAveragePool2D) + IMPLEMENT(locoex::TFLConcatenation) + IMPLEMENT(locoex::TFLConst) + IMPLEMENT(locoex::TFLConv2D) + IMPLEMENT(locoex::TFLDepthwiseConv2D) + IMPLEMENT(locoex::TFLDiv) + IMPLEMENT(locoex::TFLMaximum) + IMPLEMENT(locoex::TFLMaxPool2D) + IMPLEMENT(locoex::TFLMean) + IMPLEMENT(locoex::TFLMul) + IMPLEMENT(locoex::TFLRelu) + IMPLEMENT(locoex::TFLRelu6) + IMPLEMENT(locoex::TFLReshape) + IMPLEMENT(locoex::TFLRsqrt) + IMPLEMENT(locoex::TFLSqrt) + IMPLEMENT(locoex::TFLSquaredDifference) + IMPLEMENT(locoex::TFLSub) + IMPLEMENT(locoex::TFLTranspose) + IMPLEMENT(locoex::TFLTransposeConv) +#undef IMPLEMENT +}; + +bool TFLNodeSummaryBuilderBase::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (node->dialect() != locoex::TFLDialect::get()) + return false; + +#define TFL_NODE(OPCODE, CLASS) \ + if (dynamic_cast(node)) \ + { \ + s.opname(tfl_opname(node->opnum())); \ + return summary(dynamic_cast(node), s); \ + } +#include "Dialect/IR/TFLNodes.lst" +#undef TFL_NODE + + return false; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLAdd *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.args().append("fused_activation_function", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLAveragePool2D *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + + s.args().append("value", tbl()->lookup(node->value())); + s.args().append("filter(h,w)", to_str(node->filter())); + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLConcatenation *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + + for (uint32_t i = 0; i < node->numValues(); ++i) + s.args().append("values", tbl()->lookup(node->values(i))); + s.args().append("axis", pepper::str(node->axis())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLConst *, locop::NodeSummary &s) const +{ + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLConv2D *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + assert(node->padding() != locoex::Padding::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("bias", tbl()->lookup(node->bias())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLDepthwiseConv2D *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + assert(node->padding() != locoex::Padding::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("bias", tbl()->lookup(node->bias())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("depthMultiplier", std::to_string(node->depthMultiplier())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLDiv *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLMaximum *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLMaxPool2D *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + + s.args().append("value", tbl()->lookup(node->value())); + s.args().append("filter(h,w)", to_str(node->filter())); + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLMean *node, locop::NodeSummary &s) const +{ + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("reduction_indices", tbl()->lookup(node->reduction_indices())); + s.args().append("keep_dims", node->keep_dims() ? "true" : "false"); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLMul *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != locoex::FusedActFunc::UNDEFINED); + + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.args().append("fused_activation_function", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLRelu *node, locop::NodeSummary &s) const +{ + s.args().append("features", tbl()->lookup(node->features())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLRelu6 *node, locop::NodeSummary &s) const +{ + s.args().append("features", tbl()->lookup(node->features())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLReshape *node, locop::NodeSummary &s) const +{ + s.args().append("tensor", tbl()->lookup(node->tensor())); + s.args().append("shape", tbl()->lookup(node->shape())); + // TODO Show newShape info + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLRsqrt *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +// TODO TFLSoftmax + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLSqrt *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLSquaredDifference *node, + locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLSub *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +// TODO TFLTanh + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLTranspose *node, locop::NodeSummary &s) const +{ + s.args().append("a", tbl()->lookup(node->a())); + s.args().append("perm", tbl()->lookup(node->perm())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool TFLNodeSummaryBuilder::summary(const locoex::TFLTransposeConv *node, + locop::NodeSummary &s) const +{ + assert(node->padding() != locoex::Padding::UNDEFINED); + + s.args().append("inputSizes", tbl()->lookup(node->inputSizes())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("outBackprop", tbl()->lookup(node->outBackprop())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +} // namespace + +// For Circle +namespace +{ + +std::string circle_opname(uint32_t opnum) +{ + static std::string prefix{"circle."}; + + switch (static_cast(opnum)) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + case locoex::CircleOpcode::OPCODE: \ + return prefix + #OPCODE; +#include "Dialect/IR/CircleNodes.lst" +#undef CIRCLE_NODE + default: + break; + }; + + return prefix + "Invalid"; +} + +// CircleNodeSummaryBuilder with default implementation +class CircleNodeSummaryBuilderBase : public locop::NodeSummaryBuilder +{ +public: + CircleNodeSummaryBuilderBase(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *, locop::NodeSummary &s) const final; + +protected: +#define CIRCLE_NODE(OPCODE, CLASS) \ + virtual bool summary(const CLASS *, locop::NodeSummary &s) const \ + { \ + s.comments().append("Emitted by Default CircleNodeSummaryBuilder"); \ + s.state(locop::NodeSummary::State::PartiallyKnown); \ + return true; \ + } +#include "Dialect/IR/CircleNodes.lst" +#undef CIRCLE_NODE + +protected: + const locop::SymbolTable *tbl(void) const { return _tbl; } + + // Please do not use _tbl directly and use tbl(). + // This will be changed to private in near future. +protected: + const locop::SymbolTable *_tbl; +}; + +class CircleNodeSummaryBuilder final : public CircleNodeSummaryBuilderBase +{ +public: + CircleNodeSummaryBuilder(const locop::SymbolTable *tbl) : CircleNodeSummaryBuilderBase(tbl) + { + // DO NOTHING + } + +private: +#define IMPLEMENT(CLASS) bool summary(const CLASS *, locop::NodeSummary &) const final; + IMPLEMENT(locoex::CircleInstanceNorm) +#undef IMPLEMENT +}; + +bool CircleNodeSummaryBuilderBase::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (node->dialect() != locoex::CircleDialect::get()) + return false; + +#define CIRCLE_NODE(OPCODE, CLASS) \ + if (dynamic_cast(node)) \ + { \ + s.opname(circle_opname(node->opnum())); \ + return summary(dynamic_cast(node), s); \ + } +#include "Dialect/IR/CircleNodes.lst" +#undef CIRCLE_NODE + + return false; +} + +bool CircleNodeSummaryBuilder::summary(const locoex::CircleInstanceNorm *node, + locop::NodeSummary &s) const +{ + auto fused = node->fusedActivationFunction(); + assert(fused != locoex::FusedActFunc::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("gamma", tbl()->lookup(node->gamma())); + s.args().append("beta", tbl()->lookup(node->beta())); + s.args().append("epsilon", pepper::str(node->epsilon())); + s.args().append("fused_activation_function", to_str(fused)); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +} // namespace + +namespace exo +{ + +bool NodeSummaryBuilder::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (locop::CanonicalNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + if (TFLNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + if (CircleNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + if (locoex::COpNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + return false; +} + +} // namespace exo diff --git a/compiler/exo/src/ExoFormattedGraph.h b/compiler/exo/src/ExoFormattedGraph.h new file mode 100644 index 00000000000..714e483b5f6 --- /dev/null +++ b/compiler/exo/src/ExoFormattedGraph.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXO_FORMATTED_GRAPH_H__ +#define __EXO_FORMATTED_GRAPH_H__ + +#include + +#include + +namespace exo +{ + +class NodeSummaryBuilder final : public locop::NodeSummaryBuilder +{ +public: + NodeSummaryBuilder(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *node, locop::NodeSummary &s) const final; + +private: + const locop::SymbolTable *_tbl; +}; + +class NodeSummaryBuilderFactory final : public locop::NodeSummaryBuilderFactory +{ +public: + NodeSummaryBuilderFactory() = default; + +public: + std::unique_ptr create(const locop::SymbolTable *tlb) const final + { + return stdex::make_unique(tlb); + } +}; + +} // namespace exo + +#endif // __EXO_FORMATTED_GRAPH_H__ diff --git a/compiler/exo/src/ExoOptimize.cpp b/compiler/exo/src/ExoOptimize.cpp new file mode 100644 index 00000000000..d7278e90083 --- /dev/null +++ b/compiler/exo/src/ExoOptimize.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ExoOptimize.h" + +#include "Knob.h" +#include "Passes.h" +#include "ProgressReporter.h" + +#include + +#include + +namespace exo +{ + +void optimize(loco::Graph *g) +{ + logo::Phase phase; + { + // prepare type and shape before optimization + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + + if (get()) + { + phase.emplace_back(stdex::make_unique()); + } + + if (get()) + { + phase.emplace_back(stdex::make_unique()); + } + + if (get()) + { + phase.emplace_back(stdex::make_unique()); + } + phase.emplace_back(stdex::make_unique()); + + if (get()) + { + phase.emplace_back(stdex::make_unique()); + } + + phase.emplace_back(stdex::make_unique()); + + phase.emplace_back(stdex::make_unique()); + } + + logo::PhaseRunner phase_runner{g}; + + ProgressReporter prog(g, logo::PhaseStrategy::Restart); + phase_runner.attach(&prog); + phase_runner.run(phase); +} + +} // namespace exo diff --git a/compiler/exo/src/ExoOptimize.h b/compiler/exo/src/ExoOptimize.h new file mode 100644 index 00000000000..4769c119387 --- /dev/null +++ b/compiler/exo/src/ExoOptimize.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OPTIMIZE_H__ +#define __OPTIMIZE_H__ + +#include + +namespace exo +{ + +/** + * @brief Run passes for a graph after completion of converting canonical nodes into TFL nodes. + * + * TODO Separate optimize pass dedicated to TFL and Circle dialect when necessary + */ +void optimize(loco::Graph *); + +} // namespace exo + +#endif // __OPTIMIZE_H__ diff --git a/compiler/exo/src/ExporterUtils.cpp b/compiler/exo/src/ExporterUtils.cpp new file mode 100644 index 00000000000..41ccdcd719b --- /dev/null +++ b/compiler/exo/src/ExporterUtils.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ExporterUtils.h" + +#include + +#include + +namespace exo +{ + +ShapeDescription to_shape_description(const loco::TensorShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(shape.rank()); + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + // All the dimensions SHOULD be known + assert(shape.dim(axis).known()); + res._dims.at(axis) = shape.dim(axis).value(); + } + + return res; +} + +ShapeDescription to_shape_description(const loco::FeatureShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a feature map as a NHWC tensor + res._dims.resize(4); + res._dims.at(0) = shape.count().value(); + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::FilterShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a convolution filter as a NHWC tensor + res._dims.resize(4); + res._dims.at(0) = shape.count().value(); + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::DepthwiseFilterShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a depthwise convolution filter as a [1, H, W, C*M] tensor + res._dims.resize(4); + res._dims.at(0) = 1; + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value() * shape.multiplier().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::BiasShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(1); + res._dims.at(0) = shape.length().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::MatrixShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(2); + res._dims.at(0) = shape.height().value(); + res._dims.at(1) = shape.width().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::NodeShape &shape) +{ + switch (shape.domain()) + { + case loco::Domain::Tensor: + return to_shape_description(shape.as()); + case loco::Domain::Feature: + return to_shape_description(shape.as()); + case loco::Domain::Filter: + return to_shape_description(shape.as()); + case loco::Domain::DepthwiseFilter: + return to_shape_description(shape.as()); + case loco::Domain::Bias: + return to_shape_description(shape.as()); + case loco::Domain::Matrix: + return to_shape_description(shape.as()); + default: + break; + } + + INTERNAL_EXN_V("Unsupported loco domain", oops::to_uint32(shape.domain())); +} + +} // namespace exo diff --git a/compiler/exo/src/ExporterUtils.h b/compiler/exo/src/ExporterUtils.h new file mode 100644 index 00000000000..e1f1f66a897 --- /dev/null +++ b/compiler/exo/src/ExporterUtils.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXPORTER_UTILS_H__ +#define __EXPORTER_UTILS_H__ + +#include "loco.h" + +#include "loco/IR/PermutingCodec.h" +#include "loco/IR/NodeShape.h" + +namespace exo +{ + +struct ShapeDescription +{ + std::vector _dims; + bool _rank_known; +}; + +ShapeDescription to_shape_description(const loco::TensorShape &shape); +ShapeDescription to_shape_description(const loco::FeatureShape &shape); +ShapeDescription to_shape_description(const loco::FilterShape &shape); +ShapeDescription to_shape_description(const loco::BiasShape &shape); +ShapeDescription to_shape_description(const loco::MatrixShape &shape); +ShapeDescription to_shape_description(const loco::NodeShape &shape); + +template inline bool isNHWC(Permutation *perm); + +template <> inline bool isNHWC(loco::Permutation *perm) +{ + return perm->axis(loco::FeatureAxis::Count) == 0 && perm->axis(loco::FeatureAxis::Height) == 1 && + perm->axis(loco::FeatureAxis::Width) == 2 && perm->axis(loco::FeatureAxis::Depth) == 3; +} + +template <> inline bool isNHWC(loco::Permutation *perm) +{ + return perm->axis(loco::FilterAxis::Count) == 0 && perm->axis(loco::FilterAxis::Height) == 1 && + perm->axis(loco::FilterAxis::Width) == 2 && perm->axis(loco::FilterAxis::Depth) == 3; +} + +} // namespace exo + +#endif // __EXPORTER_UTILS_H__ diff --git a/compiler/exo/src/GraphBlock.cpp b/compiler/exo/src/GraphBlock.cpp new file mode 100644 index 00000000000..0a45ce8ad15 --- /dev/null +++ b/compiler/exo/src/GraphBlock.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "GraphBlock.h" + +#include "Check.h" + +#include +#include + +namespace +{ + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + // Make NHWC permutation for encoder and decoder + loco::Permutation NHWC; + + NHWC.axis(loco::FeatureAxis::Count) = 0; + NHWC.axis(loco::FeatureAxis::Height) = 1; + NHWC.axis(loco::FeatureAxis::Width) = 2; + NHWC.axis(loco::FeatureAxis::Depth) = 3; + + return NHWC; +} + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + loco::Permutation HWIO; // a.k.a., HWCN + + HWIO.axis(loco::FilterAxis::Height) = 0; + HWIO.axis(loco::FilterAxis::Width) = 1; + HWIO.axis(loco::FilterAxis::Depth) = 2; + HWIO.axis(loco::FilterAxis::Count) = 3; + + return HWIO; +} + +template <> loco::Permutation perm() +{ + + // Make NHWC permutation for encoder and decoder + loco::Permutation OHWI; // a.k.a., NHWC + + OHWI.axis(loco::FilterAxis::Count) = 0; + OHWI.axis(loco::FilterAxis::Height) = 1; + OHWI.axis(loco::FilterAxis::Width) = 2; + OHWI.axis(loco::FilterAxis::Depth) = 3; + + return OHWI; +} + +template loco::Permutation perm(); + +template <> +loco::Permutation perm() +{ + loco::Permutation HWCM; + + HWCM.axis(loco::DepthwiseFilterAxis::Height) = 0; + HWCM.axis(loco::DepthwiseFilterAxis::Width) = 1; + HWCM.axis(loco::DepthwiseFilterAxis::Depth) = 2; + HWCM.axis(loco::DepthwiseFilterAxis::Multiplier) = 3; + + return HWCM; +} + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + loco::Permutation HW; + + HW.axis(loco::MatrixAxis::Height) = 0; + HW.axis(loco::MatrixAxis::Width) = 1; + + return HW; +} + +template <> loco::Permutation perm() +{ + loco::Permutation WH; + + WH.axis(loco::MatrixAxis::Height) = 1; + WH.axis(loco::MatrixAxis::Width) = 0; + + return WH; +} + +} // namespace + +namespace exo +{ + +template loco::FeatureEncode *make_feature_encode(loco::Node *input_for_encode) +{ + EXO_ASSERT(input_for_encode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::FeatureDecode *make_feature_decode(loco::Node *input_for_decode) +{ + EXO_ASSERT(input_for_decode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode) +{ + EXO_ASSERT(input_for_encode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode) +{ + EXO_ASSERT(input_for_decode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template +loco::DepthwiseFilterDecode *make_dw_filter_decode(loco::Node *input_for_decode) +{ + EXO_ASSERT(input_for_decode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode) +{ + EXO_ASSERT(input_for_encode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode) +{ + EXO_ASSERT(input_for_decode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +// template instantiation +template loco::FeatureEncode * +make_feature_encode(loco::Node *input_for_encode); + +template loco::FeatureDecode * +make_feature_decode(loco::Node *input_for_encode); + +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode); +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode); + +template loco::DepthwiseFilterDecode * +make_dw_filter_decode(loco::Node *input_for_decode); + +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); + +} // namespace exo diff --git a/compiler/exo/src/GraphBlock.h b/compiler/exo/src/GraphBlock.h new file mode 100644 index 00000000000..b771c821b25 --- /dev/null +++ b/compiler/exo/src/GraphBlock.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BLOCK_H__ +#define __GRAPH_BLOCK_H__ + +#include +#include + +#include + +#include + +namespace exo +{ + +/// @brief feature layout of TFLITE file +enum class FeatureLayout +{ + NHWC, +}; + +/// @brief Creates a loco::FeatureEncode with T layout (NHWC for tflite) and add it to graph. +template loco::FeatureEncode *make_feature_encode(loco::Node *input_for_encode); + +/// @brief Creates a loco::FeatureDecode with T layout (NHWC for tflite) and add it to graph. +template loco::FeatureDecode *make_feature_decode(loco::Node *input_for_decode); + +enum class FilterLayout +{ + OHWI, // a.k.a., NHWC, Tensorflow Lite uses this layout for filter + HWIO, // a.k.a., HWCN, Tensorflow uses this layout for filter +}; + +/// @brief Create a loco::FilterEncode of given layout +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode); + +/// @brief Create a loco::FilterDecode of given layout +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode); + +enum class DepthwiseFilterLayout +{ + HWCM, +}; + +/// @brief Create a loco::DepthwiseFilterDecode of given layout +template +loco::DepthwiseFilterDecode *make_dw_filter_decode(loco::Node *input_for_decode); + +enum class MatrixLayout +{ + HW, + WH +}; + +/// @brief Create a loco::MatrixEncode of given layout +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); + +/// @brief Create a loco::MatrixDecode of given layout +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); + +} // exo + +// +// DomainConverter +// + +/** + * Some canonical nodes can have input of various loco::Domain, e.g., loco::Domain::Tensor, + * loco::Domain::Feature, etc. However, TFL node accepts only loco::Domain::Tensor. + * So, When converting such canonical node to TFL node and input(s) of a canonical node are not + * loco::Domain::Tensor, additional nodes need to be inserted. + * + * The following two classes helps this insertion. + * + * For example, in case of loco::Relu conversion, + * + * Before: + * + * A (output: feature) -- loco::ReLU --- B (input:feature) + * + * After: + * + * A -- loco::FeatureDecode -- locoex::TFLRelu -- loco::FeatureEncode --- B + * + * loco::ReLU (dead node) + */ + +namespace exo +{ + +/** + * @brief Handles input(s) while converting a canonical node to TFL node(s). + * This class informs DomainConverter how to handle inputs of a specific canonical node. + */ +template class InputHandler +{ +public: + /** + * @brief Assign origin's inputs to replacer's inputs. + * (This is called when origin belongs in Tensor domain.) + */ + virtual void handover(CanonicalT *origin, TFLT *replacer) = 0; + + /** + * @brief Returns the list of inputs that needs to have FeatureDecode as its input. + * (This is called when origin belongs in Feature domain.) + */ + virtual std::vector getInputsToConvert(CanonicalT *origin) = 0; + + /// @brief Set the inputs of replacer to new_inputs + virtual void set(TFLT *replacer, std::vector &new_inputs) = 0; + + /// @brief Set the inputs to nullptr + virtual void nullify(CanonicalT *origin) = 0; +}; + +/** + * @brief Class to handle domain conversion while converting a canonical node to TFL node(s) + */ +template class DomainConverter +{ +public: + template + TFLT *convert(CanonicalT *origin, InputHandler &input_handler); +}; + +/** + * @brief Performs domain conversion + * + * 1. if origin belong to loco::Domain::Tensor, and replace origin to a TFL node. + * 2. if origin belong to loco::Domain::Feature, insert loco::FeatureDecode for input(s) and + * insert loco::FeatureEncode for output. Then replace origin to a TFL node. + * + * @return new TFL node; nullptr if shape of origin cannot be known + */ +template +template +TFLT *DomainConverter::convert(CanonicalT *origin, + InputHandler &input_handler) +{ + static_assert(FeatureLayoutT == FeatureLayout::NHWC, "Feature layout should be NHWC"); + + if (!loco::shape_known(origin)) + { + return nullptr; + } + + auto tfl_node = origin->graph()->nodes()->template create(); + + // when the input is Tensor, just replace canonical node to TFL node. + if (loco::shape_get(origin).domain() == loco::Domain::Tensor) + { + input_handler.handover(origin, tfl_node); + + loco::replace(origin).with(tfl_node); + input_handler.nullify(origin); + + return tfl_node; + } + else if (loco::shape_get(origin).domain() == loco::Domain::Feature) + { + std::vector feature_decodes; + + for (auto input : input_handler.getInputsToConvert(origin)) + { + auto dec = make_feature_decode(input); + feature_decodes.emplace_back(dec); + } + + input_handler.set(tfl_node, feature_decodes); + + auto enc = make_feature_encode(tfl_node); + + loco::replace(origin).with(enc); + input_handler.nullify(origin); + + return tfl_node; + } + else + INTERNAL_EXN_V("Unsupported loco::Domain", oops::to_uint32(loco::shape_get(origin).domain())); +} + +} // namespace exo + +#endif //__GRAPH_BLOCK_H__ diff --git a/compiler/exo/src/Knob.cpp b/compiler/exo/src/Knob.cpp new file mode 100644 index 00000000000..50d78f4b717 --- /dev/null +++ b/compiler/exo/src/Knob.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Knob.h" + +#include + +#include +#include +#include + +// Basic Infrastructure to declare and access Knob values +namespace +{ + +using KnobName = std::string; + +/** + * @brief Load configuration (from somewhere) + */ +struct KnobLoader +{ + virtual ~KnobLoader() = default; + + virtual bool load(const KnobName &name, bool default_value) const = 0; +}; + +/** + * @brief Load configuration from environment variables + * + * Given a prefix P, EnvKnobLoader reads a configuration K from concat(P, K). + * + * For example, let us assume that P is "MY_" and K is "CONFIG". + * + * Then, EnvKnobLoader reads configuration CONFIG from environment variable MY_CONFIG. + */ +class EnvKnobLoader final : public KnobLoader +{ +public: + EnvKnobLoader() = default; + +public: + bool load(const KnobName &knob_name, bool default_value) const override + { + auto envvar = _prefix + knob_name; + auto s = std::getenv(envvar.c_str()); + + return pepper::safe_strcast(s, default_value ? 1 : 0) != 0; + } + void knob_set(const KnobName &knob_name, bool value) { _knob[knob_name] = value; } + void dialect_set(const exo::Dialect &dialect_name) { _prefix = _label[dialect_name]; } + bool knob_get(const KnobName &knob_name) { return load(knob_name, _knob[knob_name]); } + +private: + /// @brief Environment variable prefix + std::string _prefix; + std::map _knob; + std::map _label = {{exo::Dialect::TFLITE, "TFL_"}, + {exo::Dialect::CIRCLE, "CIRCLE_"}}; +}; + +} // namespace + +namespace +{ + +EnvKnobLoader &knob_loader(void) +{ + // TODO separate "EXOTFLITE_" and "EXOCIRCLE_" when necessary + static EnvKnobLoader loader; + return loader; +} + +} // namespace + +namespace exo +{ + +#define KNOB_BOOL(NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESC) \ + template <> typename KnobTrait::ValueType get(void) \ + { \ + return ::knob_loader().knob_get(#NAME); \ + } +#include "Knob.lst" +#undef KNOB_BOOL + +void set(Dialect d) +{ + ::knob_loader().dialect_set(d); + switch (d) + { + case Dialect::TFLITE: +#define KNOB_BOOL(NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESC) \ + ::knob_loader().knob_set(#NAME, TFL_DEFAULT); +#include "Knob.lst" +#undef KNOB_BOOL + break; + case Dialect::CIRCLE: +#define KNOB_BOOL(NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESC) \ + ::knob_loader().knob_set(#NAME, CIRCLE_DEFAULT); +#include "Knob.lst" +#undef KNOB_BOOL + break; + default: + std::runtime_error("UnKnown dialect"); + } +} + +} // namespace exo diff --git a/compiler/exo/src/Knob.h b/compiler/exo/src/Knob.h new file mode 100644 index 00000000000..98613120c1f --- /dev/null +++ b/compiler/exo/src/Knob.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __KNOB_H__ +#define __KNOB_H__ + +namespace exo +{ + +enum class Dialect +{ + TFLITE, + CIRCLE +}; + +enum class Knob +{ +#define KNOB_BOOL(NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESC) NAME, +#include "Knob.lst" +#undef KNOB_BOOL +}; + +template struct KnobTrait; + +#define KNOB_BOOL(NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESC) \ + template <> struct KnobTrait \ + { \ + using ValueType = bool; \ + }; +#include "Knob.lst" +#undef KNOB_BOOL + +template typename KnobTrait::ValueType get(void); +void set(Dialect); + +} // namespace exo + +#endif // __KNOB_H__ diff --git a/compiler/exo/src/Knob.lst b/compiler/exo/src/Knob.lst new file mode 100644 index 00000000000..7f59c93f347 --- /dev/null +++ b/compiler/exo/src/Knob.lst @@ -0,0 +1,11 @@ +#ifndef KNOB_BOOL +#error "KNOB_BOOL is not defined" +#endif // KNOB_BOOL + +// KNOB_BOOL(KNOB_NAME, TFL_DEFAULT, CIRCLE_DEFAULT, DESCRIPTION) + +// Optimization pass +KNOB_BOOL(UseFuseBiasAddPass, true, true, Fuse TFLAdd or TFLSub into TFLConv2D) +KNOB_BOOL(UseFuseInstanceNormPass, false, true, Fuse InstanceNorm pattern) +KNOB_BOOL(UseFuseReluPass, true, true, Fuse TFLAdd or TFLSub into TFLConv2D or so) +KNOB_BOOL(UseFuseSquaredDifferencePass, false, true, Fuse SquaredDifference pattern) diff --git a/compiler/exo/src/Log.cpp b/compiler/exo/src/Log.cpp new file mode 100644 index 00000000000..aa762968bef --- /dev/null +++ b/compiler/exo/src/Log.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Log.h" + +#include +#include + +#include +#include + +// TODO Extract these lexical conversion routines as a library +namespace +{ + +/** + * @brief Convert C-string as a value of type T + * + * safecast(s, v) returns v if s is nullptr. + */ +template T safecast(const char *, const T &); + +template <> bool safecast(const char *s, const bool &value) +{ + return (s == nullptr) ? value : (std::stoi(s) != 0); +} + +} // namespace + +namespace exo +{ + +// +// Logger +// +Logger::Logger(hermes::Context *ctx) { activate(ctx->sources(), ctx->bus()); } +Logger::~Logger() { deactivate(); } + +// +// LoggerConfig +// +LoggerConfig::LoggerConfig() +{ + // Turn on logging if EXO_LOG is set as non-zero value + _enabled = safecast(std::getenv("EXO_LOG"), false); +} + +void LoggerConfig::configure(const hermes::Source *source, hermes::Source::Setting &setting) const +{ + // Let's ignore hermes::Sources if that is not a exo logger + if (auto logger = dynamic_cast(source)) + { + configure(logger, setting); + } +} + +void LoggerConfig::configure(const Logger *, hermes::Source::Setting &setting) const +{ + if (_enabled) + { + // Enable all catagories + setting.accept_all(); + } + else + { + // Disable all catagories + setting.reject_all(); + } +} + +} // namespace exo diff --git a/compiler/exo/src/Log.h b/compiler/exo/src/Log.h new file mode 100644 index 00000000000..8ca38c3ec5b --- /dev/null +++ b/compiler/exo/src/Log.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOG_H__ +#define __LOG_H__ + +#include "exo/LoggingContext.h" + +#include + +namespace exo +{ + +/** + * @brief Logger Implementation + */ +class Logger final : public hermes::Source +{ +public: + Logger(hermes::Context *ctx); + ~Logger(); +}; + +/** + * @brief Logger Configuration + * + * Users are able to turn logging on/off via EXO_LOG environment variable. + */ +class LoggerConfig final : public hermes::Config +{ +public: + LoggerConfig(); + +public: + void configure(const hermes::Source *, hermes::Source::Setting &) const final; + void configure(const Logger *, hermes::Source::Setting &) const; + +private: + bool _enabled; +}; + +} // namespace exo + +/** + * HOW TO USE: + * + * LOGGER(l); + * + * INFO(l) << "Hello, World" << std::endl; + * + */ +#define LOGGER(name) ::exo::Logger name{::exo::LoggingContext::get()}; + +// TODO Support FATAL, ERROR, WARN, and VERBOSE +#define INFO(name) HERMES_INFO(name) + +// WARNING! +// +// THE CURRENT IMPLEMENTATION IS NOT THREAD SAFE. +// + +#endif // __LOG_H__ diff --git a/compiler/exo/src/LogHelper.cpp b/compiler/exo/src/LogHelper.cpp new file mode 100644 index 00000000000..7520b7ec845 --- /dev/null +++ b/compiler/exo/src/LogHelper.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "LogHelper.h" + +namespace loco +{ + +std::ostream &operator<<(std::ostream &os, const loco::FeatureShape &feature_shape) +{ + os << "[" << feature_shape.count().value() << "," << feature_shape.height().value() << "," + << feature_shape.width().value() << "," << feature_shape.depth().value() << "]"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const loco::FilterShape &filter_shape) +{ + os << "[" << filter_shape.height().value() << "," << filter_shape.width().value() << "," + << filter_shape.depth().value() << "," << filter_shape.count().value() << "]"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const loco::TensorShape &tensor_shape) +{ + os << "["; + for (uint32_t r = 0; r < tensor_shape.rank(); ++r) + { + if (r) + os << ","; + os << tensor_shape.dim(r).value(); + } + os << "]"; + return os; +} + +std::ostream &operator<<(std::ostream &os, const loco::Padding2D &pad) +{ + os << "[TLBR " << pad.top() << "," << pad.left() << "," << pad.bottom() << "," << pad.right() + << "]"; + + return os; +} + +} // namespace loco + +std::ostream &operator<<(std::ostream &os, const std::vector &vi64) +{ + for (auto vi : vi64) + { + os << vi << " "; + } + return os; +} + +#include "ExoFormattedGraph.h" + +namespace exo +{ + +FormattedGraph fmt(loco::Graph *g) +{ + auto node_summary_builder = stdex::make_unique(); + return std::move(locop::fmt(g).with(std::move(node_summary_builder))); +} + +} // namespace exo diff --git a/compiler/exo/src/LogHelper.h b/compiler/exo/src/LogHelper.h new file mode 100644 index 00000000000..69d81af9e4f --- /dev/null +++ b/compiler/exo/src/LogHelper.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOG_HELPER_H__ +#define __LOG_HELPER_H__ + +#include + +#include +#include +#include + +#include +#include + +namespace loco +{ + +/** + * @brief dump FeatureShape values to stream + */ +std::ostream &operator<<(std::ostream &os, const loco::FeatureShape &feature_shape); + +/** + * @brief dump FilterShape values to stream + */ +std::ostream &operator<<(std::ostream &os, const loco::FilterShape &filter_shape); + +/** + * @brief dump TensorShape values to stream + */ +std::ostream &operator<<(std::ostream &os, const loco::TensorShape &tensor_shape); + +/** + * @brief dump Padding2D values to stream + */ +std::ostream &operator<<(std::ostream &os, const loco::Padding2D &pad); + +} // namespace loco + +/** + * @brief dump std::vector values to stream + */ +std::ostream &operator<<(std::ostream &os, const std::vector &vi64); + +namespace exo +{ + +using FormattedGraph = locop::FormattedGraphImpl; + +FormattedGraph fmt(loco::Graph *g); + +static inline FormattedGraph fmt(const std::unique_ptr &g) { return fmt(g.get()); } + +} // namespace exo + +#endif // __LOG_HELPER_H__ diff --git a/compiler/exo/src/LoggingContext.cpp b/compiler/exo/src/LoggingContext.cpp new file mode 100644 index 00000000000..1c14d97b9e4 --- /dev/null +++ b/compiler/exo/src/LoggingContext.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "exo/LoggingContext.h" +#include "Log.h" // To use LoggerConfig + +#include +#include + +namespace exo +{ + +hermes::Context *LoggingContext::get(void) +{ + static hermes::Context *ctx = nullptr; + + if (ctx == nullptr) + { + ctx = new hermes::Context; + ctx->sinks()->append(stdex::make_unique()); + ctx->config(stdex::make_unique()); + } + + return ctx; +} + +} // namespac exo diff --git a/compiler/exo/src/Pass/FoldReshapeOfConstPass.cpp b/compiler/exo/src/Pass/FoldReshapeOfConstPass.cpp new file mode 100644 index 00000000000..0fdcea939e3 --- /dev/null +++ b/compiler/exo/src/Pass/FoldReshapeOfConstPass.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FoldReshapeOfConstPass.h" + +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include + +#include + +namespace +{ + +/** + * @brief Check if node is TFLReshape and its input is TFLConst + * @return Casted TFLReshape for foldable candidate, nullptr otherwise + */ +locoex::TFLReshape *as_candidate(loco::Node *node) +{ + auto reshape = dynamic_cast(node); + if (not reshape) + return nullptr; + + // Only accept Constant input of Reshape + if (not dynamic_cast(reshape->tensor())) + return nullptr; + + return reshape; +} + +uint32_t volume(loco::Node *tensor_node) +{ + auto shape = loco::shape_get(tensor_node).as(); + + uint32_t vol = 1; + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + vol *= shape.dim(axis).value(); + + return vol; +} + +void fold_reshape_of_const(locoex::TFLReshape *reshape) +{ + const loco::DataType FLOAT32 = loco::DataType::FLOAT32; + + auto const_orig = dynamic_cast(reshape->tensor()); + + // Exceptions + { + EXO_ASSERT(const_orig, "Only support for Reshape-Const pair"); + // TODO support other data types + if (const_orig->dtype() != FLOAT32) + INTERNAL_EXN_V("NYI for this data type", oops::to_uint32(const_orig->dtype())); + + if (volume(const_orig) != volume(reshape)) + INTERNAL_EXN("New shape of Reshape is not matched"); + } + + auto new_shape = loco::shape_get(reshape).as(); + + // TFLConst to replace + auto const_new = reshape->graph()->nodes()->create(); + + const_new->dtype(FLOAT32); + const_new->rank(new_shape.rank()); + const_new->size(const_orig->size()); + for (uint32_t axis = 0; axis < new_shape.rank(); ++axis) + const_new->dim(axis) = new_shape.dim(axis); + + for (uint32_t i = 0; i < const_new->size(); ++i) + { + const_new->at(i) = const_orig->at(i); + } + + // replace + loco::replace(reshape).with(const_new); +} + +} // namespace + +namespace exo +{ + +bool FoldReshapeOfConstPass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (auto reshape = as_candidate(node)) + { + fold_reshape_of_const(reshape); + changed = true; + } + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FoldReshapeOfConstPass.h b/compiler/exo/src/Pass/FoldReshapeOfConstPass.h new file mode 100644 index 00000000000..10f8004bf5c --- /dev/null +++ b/compiler/exo/src/Pass/FoldReshapeOfConstPass.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_FOLD_RESHAPE_OF_CONST_PASS_H__ +#define __PASS_FOLD_RESHAPE_OF_CONST_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse TFLReshape + TFLConst into one equivalent TFLConst + * + * + * TFLConst --- TFLReshape --- Out + * + * + * TFLConst --- TFLReshape --- + * TFLConst (new) ------------ Out + * + * TODO This pass is for temporary. Deprecate this pass. + */ +struct FoldReshapeOfConstPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FoldReshapeOfConstPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __PASS_FOLD_RESHAPE_OF_CONST_PASS_H__ diff --git a/compiler/exo/src/Pass/FoldTransposeOfConstPass.cpp b/compiler/exo/src/Pass/FoldTransposeOfConstPass.cpp new file mode 100644 index 00000000000..005c429448d --- /dev/null +++ b/compiler/exo/src/Pass/FoldTransposeOfConstPass.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FoldTransposeOfConstPass.h" + +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +// TODO remove dependency to angkor +#include +#include + +#include + +namespace +{ + +/** + * @brief Check if node is TFLTranspose and its input is TFLConst + * @return Casted TFLTranspose for foldable candidate, nullptr otherwise + */ +locoex::TFLTranspose *as_candidate(loco::Node *node) +{ + auto transpose = dynamic_cast(node); + if (not transpose) + return nullptr; + + // Only accept Constant input of Transpose + if (not dynamic_cast(transpose->a())) + return nullptr; + + // Only accept Constant permutation of Transpose + if (not dynamic_cast(transpose->perm())) + return nullptr; + + return transpose; +} + +nncc::core::ADT::tensor::Shape angkor_shape(locoex::TFLConst *node) +{ + nncc::core::ADT::tensor::Shape ret; + + ret.resize(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + ret.dim(axis) = node->dim(axis).value(); + } + + return ret; +} + +void fold_transpose_of_const(locoex::TFLTranspose *transpose) +{ + const loco::DataType FLOAT32 = loco::DataType::FLOAT32; + const loco::DataType S32 = loco::DataType::S32; + + auto const_orig = dynamic_cast(transpose->a()); + auto perm = dynamic_cast(transpose->perm()); + + // Exceptions + { + EXO_ASSERT(const_orig, "Only support for Transpose-Const pair"); + // TODO support other data types + if (const_orig->dtype() != FLOAT32) + INTERNAL_EXN_V("NYI for this data type", oops::to_uint32(const_orig->dtype())); + + EXO_ASSERT(perm, "Only support for constant permutation for Transpose"); + // TODO support other data types + if (perm->dtype() != S32) + INTERNAL_EXN_V("NYI for this data type", oops::to_uint32(perm->dtype())); + + auto okay = [&]() { + if (perm->rank() != 1) + return false; + if (perm->dim(0).value() != const_orig->rank()) + return false; + return true; + }; + if (not okay()) + INTERNAL_EXN("Input and permutation for Transpose is not congruent"); + } + + uint32_t rank = const_orig->rank(); + + // TFLConst to replace + auto const_new = transpose->graph()->nodes()->create(); + + const_new->dtype(FLOAT32); + const_new->rank(rank); + const_new->size(const_orig->size()); + for (uint32_t axis = 0; axis < rank; ++axis) + const_new->dim(axis) = const_orig->dim(perm->at(axis)).value(); + + // TODO remove dependency to angkor + auto shape_orig = angkor_shape(const_orig); + auto shape_new = angkor_shape(const_new); + + nncc::core::ADT::tensor::LexicalLayout l; + nncc::core::ADT::tensor::IndexEnumerator e{shape_new}; + + for (; e.valid(); e.advance()) + { + loco::TensorIndex index_new = e.current(); + loco::TensorIndex index_orig; + + // Set original index from matching new index + index_orig.resize(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + index_orig.at(perm->at(axis)) = index_new.at(axis); + + const_new->at(l.offset(shape_new, index_new)) = + const_orig->at(l.offset(shape_orig, index_orig)); + } + + // replace + loco::replace(transpose).with(const_new); +} + +} // namespace + +namespace exo +{ + +bool FoldTransposeOfConstPass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (auto transpose = as_candidate(node)) + { + fold_transpose_of_const(transpose); + changed = true; + } + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FoldTransposeOfConstPass.h b/compiler/exo/src/Pass/FoldTransposeOfConstPass.h new file mode 100644 index 00000000000..26656a11844 --- /dev/null +++ b/compiler/exo/src/Pass/FoldTransposeOfConstPass.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_FOLD_TRANSPOSE_OF_CONST_PASS_H__ +#define __PASS_FOLD_TRANSPOSE_OF_CONST_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse TFLTranspose + TFLConst into one equivalent TFLConst + * + * + * TFLConst --- TFLTranspose --- Out + * + * + * TFLConst --- TFLTranspose --- + * TFLConst (new) -------------- Out + * + * TODO This pass is for temporary. Deprecate this pass. + */ +struct FoldTransposeOfConstPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FoldTransposeOfConstPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __PASS_FOLD_TRANSPOSE_OF_CONST_PASS_H__ diff --git a/compiler/exo/src/Pass/FuseBiasAddPass.cpp b/compiler/exo/src/Pass/FuseBiasAddPass.cpp new file mode 100644 index 00000000000..aab82099555 --- /dev/null +++ b/compiler/exo/src/Pass/FuseBiasAddPass.cpp @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseBiasAddPass.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include +#include + +#include + +#include + +/* + Note: Terms for variables in this implementation is as follows: + + ex) subgraph handled: TFLConv2D -------- TFLAdd + (or TFLDepthwiseConv2D) (or TFLSub) + | | + \|/ \|/ + variable name : former latter + Type : FormerT LatterT + (shortened name from Mixin) (template type) +*/ +namespace +{ + +using FormerT = locoex::TFLNodeMixin; + +loco::Node *as_loco_node(FormerT *former) +{ + auto loco_node = dynamic_cast(former); + assert(loco_node != nullptr); + + return loco_node; +} + +locoex::TFLConst *get_const(loco::Node *x, loco::Node *y) +{ + if (auto const_node = dynamic_cast(x)) + return const_node; + else if (auto const_node = dynamic_cast(y)) + return const_node; + + return nullptr; +} + +FormerT *get_former(loco::Node *x, loco::Node *y) +{ + if (auto node = dynamic_cast(x)) + return node; + else if (auto node = dynamic_cast(y)) + return node; + + return nullptr; +} + +/// @brief Finds input that is TFLConst and set it to new_input +void set_const_input(locoex::TFLNode *node, locoex::TFLConst *new_input) +{ + if (auto add = dynamic_cast(node)) + { + if (dynamic_cast(add->x())) + add->x(new_input); + else if (dynamic_cast(add->y())) + add->y(new_input); + else + assert(false and "One node should be TFLConst"); + + return; + } + + if (auto sub = dynamic_cast(node)) + { + if (dynamic_cast(sub->x())) + sub->x(new_input); + else if (dynamic_cast(sub->y())) + sub->y(new_input); + else + assert(false and "One node should be TFLConst"); + + return; + } + + assert(false and "Param should be TFLAdd or TFLSub"); +} + +/** + * @brief Creates a TFLConst whose shape is [to] and values are all const_node->at(0), + * where const_node has only one element(a scalar or a tensor of shape [1]) + */ +locoex::TFLConst *create_widened(locoex::TFLConst *const_node, uint32_t to) +{ + auto const_shape = loco::shape_get(const_node).as(); + + assert(const_shape.rank() == 0 or (const_shape.rank() == 1 and const_shape.dim(0) == 1)); + + auto g = const_node->graph(); + + auto widened_const = g->nodes()->create(); + { + widened_const->dtype(loco::DataType::FLOAT32); + widened_const->rank(1); + widened_const->dim(0) = to; + widened_const->size(to); + for (uint32_t x = 0; x < to; x++) + widened_const->at(x) = const_node->at(0); + } + return widened_const; +} + +template float calc(float, float); + +template <> float calc(float x, float y) { return x + y; } +template <> float calc(float x, float y) { return x - y; } + +template class Fuser +{ +public: + Fuser(LatterT *latter) + { + static_assert(std::is_same::value || + std::is_same::value, + "wrong template type"); + + _latter = latter; + _graph = _latter->graph(); + _const_node = get_const(_latter->x(), _latter->y()); + _former = get_former(_latter->x(), _latter->y()); + + assert(_const_node && _former); + } + + void fuse(void); + +private: + loco::Graph *_graph; + LatterT *_latter; + locoex::TFLConst *_const_node; + FormerT *_former; + + locoex::TFLConst *create_fused_bias_const(); +}; + +// instantiation +template class Fuser; +template class Fuser; + +template locoex::TFLConst *Fuser::create_fused_bias_const() +{ + // we have to create a new bias const by adding/substracting bias and const node (of TFLAdd or + // TFLSub) + auto bias = dynamic_cast(_former->bias()); + assert(bias->dtype() == loco::DataType::FLOAT32 && + _const_node->dtype() == loco::DataType::FLOAT32); + + assert(bias->rank() == 1 && _const_node->rank() == 1); + assert(bias->dim(0) == _const_node->dim(0)); + + // build a new bias const + auto new_bias = _graph->nodes()->create(); + { + new_bias->dtype(loco::DataType::FLOAT32); + + new_bias->rank(1); + new_bias->dim(0) = bias->dim(0); + + new_bias->size(bias->dim(0).value()); + + for (uint32_t x = 0; x < bias->dim(0).value(); x++) + new_bias->at(x) = calc( + bias->at(x), _const_node->at(x)); + } + + return new_bias; +} + +// FuseBiasAddPass works when former->fusedActivationFunction() == NONE +bool check_act_func(FormerT *former) +{ + using FusedActFuncMixin = locoex::TFLNodeMixin; + + if (auto node = dynamic_cast(former)) + return node->fusedActivationFunction() == locoex::FusedActFunc::NONE; + else + return true; +} + +template void set_act_func(FormerT *former, LatterT *latter) +{ + using FusedActFuncMixin = locoex::TFLNodeMixin; + + if (auto node = dynamic_cast(former)) + node->fusedActivationFunction(latter->fusedActivationFunction()); +} + +// instantiation +template void set_act_func(FormerT *, locoex::TFLAdd *); +template void set_act_func(FormerT *, locoex::TFLSub *); + +/** + * @brief Fuse TFLAdd or TFLSub (latter) into TFLConv2d or TFLDepthwiseConv2D (former). + * All conditions should be checked before calling this. + * + * @note TFLAdd can have fused activation function (let's call this FAF for simplicity). + * + * Conv2D's FAF | TFLAdd's FAF => FAF after fusing TFLAdd into TFLConv2D + * ----------------|--------------- -------------------------------------- + * NONE | NONE, RELU or RELU6 => TFLAdd's FAF + * other than NONE | anything => cannot be fused + */ +template void Fuser::fuse(void) +{ + // check fused activation function + { + assert(check_act_func(_former)); + + set_act_func(_former, _latter); + } + + auto new_bias = create_fused_bias_const(); + + // replace node with new_bias + // note that loco::replace() is not used because bias could be input of other op just in case + _former->bias(new_bias); + + // remove TFLAdd or TFLSub node + loco::replace(_latter).with(as_loco_node(_former)); + _latter->x(nullptr); + _latter->y(nullptr); +} + +struct Collector final : public locoex::TFLNodeMutableVisitor +{ + template + void setCandidate(FormerT *former, LatterT *latter, locoex::TFLConst *const_node) + { + static_assert(std::is_same::value || + std::is_same::value, + "wrong template type"); + + if (!check_act_func(former)) + return; + + auto depth = + loco::shape_get(as_loco_node(former)).template as().dim(3).value(); + auto const_shape = loco::shape_get(const_node).template as(); + + if (const_shape.rank() == 1 and const_shape.dim(0) == depth) + { + candidates.insert(latter); + } + // when Const has only one value, create a new const with shape [depth] + else if (const_shape.rank() == 0 or (const_shape.rank() == 1 and const_shape.dim(0) == 1)) + { + if (!(loco::dtype_get(as_loco_node(former)) == loco::DataType::FLOAT32)) + INTERNAL_EXN_V("Unsupported data type", + oops::to_uint32(loco::dtype_get(as_loco_node(former)))); + if (!(const_node->dtype() == loco::DataType::FLOAT32)) + INTERNAL_EXN_V("Unsupported data type", oops::to_uint32(const_node->dtype())); + + auto new_bias_node = create_widened(const_node, depth); + + // Replacing TFLConst input of TFLAdd or TFLSub. + // Note that calling loco::replace(const_node).with(new_bias_node) could be dangerous + // because const_node could be the input of many nodes + set_const_input(latter, new_bias_node); + + candidates.insert(latter); + } + } + + void visit(locoex::TFLAdd *latter) final + { + auto former = get_former(latter->x(), latter->y()); + auto const_node = get_const(latter->x(), latter->y()); + + if (former && const_node) + setCandidate(former, latter, const_node); + } + + void visit(locoex::TFLSub *latter) final + { + // TFLSub, of which x() = TFLConv2D or TFLDepthwiseConv2D, y() = TFLConst, is fusing target + auto former = dynamic_cast(latter->x()); + auto const_node = dynamic_cast(latter->y()); + + if (former && const_node) + setCandidate(former, latter, const_node); + } + + void visit(locoex::TFLNode *) final { return; } + + std::set candidates; +}; + +struct Performer final : public locoex::TFLNodeMutableVisitor +{ + void visit(locoex::TFLAdd *latter) final + { + assert(get_former(latter->x(), latter->y())); + + Fuser fuser(latter); + fuser.fuse(); + } + + void visit(locoex::TFLSub *latter) final + { + assert(get_former(latter->x(), latter->y())); + + Fuser fuser(latter); + fuser.fuse(); + } + + void visit(locoex::TFLNode *) final { assert(false && "should not be called"); } +}; + +} // namespace + +namespace exo +{ + +bool FuseBiasAddPass::run(loco::Graph *g) +{ + Collector collector; + + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (node->dialect() == locoex::TFLDialect::get()) + { + auto tfl_node = dynamic_cast(node); + tfl_node->accept(&collector); + } + } + + Performer performer; + + for (auto node : collector.candidates) + { + node->accept(&performer); + } + + return collector.candidates.size() > 0; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FuseBiasAddPass.h b/compiler/exo/src/Pass/FuseBiasAddPass.h new file mode 100644 index 00000000000..68e624c6bd2 --- /dev/null +++ b/compiler/exo/src/Pass/FuseBiasAddPass.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_FUSE_BIASADD_PASS_H__ +#define __PASS_FUSE_BIASADD_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse TFLAdd or TFLSub into Bias input of the following ops: + * - TFLConv2D, TFLDepthwiseConv2D + * - TODO Consider to add FullyConnected, etc. + * + * Case 1. Conv2D and TFLAdd + * + * BEFORE: + * + * TFLConst A (a scalar or a tensor of shape [1] or [depth of TFLConv2D]) + * | + * Foo -- TFLConv2D -- TFLAdd (or TFLSub) -- Bar + * | + * TFLConst B --+ (bias) + * + * AFTER: + * Foo ----- TFLConv2D ----- Bar + * | + * TFLConst A' --+ (bias) + * + * TFLConst B (dead node) + * + * TFLAdd (or TFLSub) (dead node) + * + * @note TFLSub, of which x() == TFLConv2D and y() == TFLConst, will be fused. + * If x() == TFLConst and y() == TFLConv2D, it won't be fused. + */ +struct FuseBiasAddPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FuseBiasAddPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __PASS_FUSE_BIASADD_PASS_H__ diff --git a/compiler/exo/src/Pass/FuseBiasAddPass.test.cpp b/compiler/exo/src/Pass/FuseBiasAddPass.test.cpp new file mode 100644 index 00000000000..6ba728de013 --- /dev/null +++ b/compiler/exo/src/Pass/FuseBiasAddPass.test.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseBiasAddPass.h" + +#include "Dialect/IR/TFLNodes.h" +#include "TestGraph.h" +#include "TestHelper.h" + +#include + +#include + +namespace +{ + +void init(loco::Pull *pull) +{ + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2, 3, 3, 2}); +} + +/// @brief Initializes TFLConv2D and related filter and bias +void init(locoex::TFLConv2D *conv2d, locoex::TFLConst *filter, locoex::TFLConst *bias) +{ + // set conv2d + { + conv2d->fusedActivationFunction(locoex::FusedActFunc::NONE); + conv2d->padding(locoex::Padding::VALID); + } + + // set filter + { + filter->dtype(loco::DataType::FLOAT32); + filter->shape({2, 3, 3, 2}); + filter->size(2 * 3 * 3 * 2); + + for (uint32_t x = 0; x < 2 * 3 * 3 * 2; x++) + filter->at(x) = 0.0; + } + + // set bias + { + bias->dtype(loco::DataType::FLOAT32); + bias->shape({2}); + bias->size(2); + + for (uint32_t x = 0; x < 2; x++) + bias->at(x) = 0.0; + } +} + +template void init(T *node, locoex::FusedActFunc f) +{ + static_assert(std::is_same::value || std::is_same::value, + "wrong template type"); + + node->fusedActivationFunction(f); +} + +/// @brief Initializes one param of TFLAdd or TFLSub +void init(locoex::TFLConst *addsub_param) +{ + // set addsub_param : y() value of TFLAdd or TFLSub + addsub_param->dtype(loco::DataType::FLOAT32); + addsub_param->shape({2}); + addsub_param->size(2); + + for (uint32_t x = 0; x < 2; x++) + addsub_param->at(x) = (x + 1) * 1.5; // 1.5, 3 +} + +} // namespace + +// A case when +// - TFLConv2D has bias (0, 0) +// - TFLAdd, of which x() or y() == TFLConv2D +// - Another param of TFLAdd is TFLConst, (1.5, 3) +// +// After fusion, bias shold be (1.5, 3) +TEST(FuseBiasAddPassTest, Conv2D_Add_01_basic) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto add_y = g.append(); + auto add = g.append(conv2d, add_y); + + g.complete(add); + + init(g.pull); + init(conv2d, filter, bias); + init(add, locoex::FusedActFunc::NONE); + init(add_y); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + + auto a_bias = dynamic_cast(a_conv2d->bias()); + ASSERT_TRUE(a_bias != nullptr); + + ASSERT_TRUE(a_bias->dim(0) == 2); + ASSERT_FLOAT_EQ(a_bias->at(0), + bias->at(0) + add_y->at(0)); + ASSERT_FLOAT_EQ(a_bias->at(1), + bias->at(1) + add_y->at(1)); +} + +// A case when +// - TFLConv2D has bias (0, 0) +// - TFLAdd, of which x() or y() == TFLConv2D +// - Another param of TFLAdd is TFLConst, (1.5) <-- scalar +// +// After fusion, bias shold be (1.5, 1.5) +TEST(FuseBiasAddPassTest, Conv2D_Add_02_TFLAdd_y_is_scalar) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto add_y = g.append(); + auto add = g.append(conv2d, add_y); + + g.complete(add); + + init(g.pull); + init(conv2d, filter, bias); // channel of conv2d is 2 + + { + // Size of this TFLConst is 1. + // Note that this should be widened later to the shape of [channel of Conv2D], which is [2] + add_y->dtype(loco::DataType::FLOAT32); + add_y->shape({1}); + add_y->size(1); + add_y->at(0) = 1.5; + } + init(add, locoex::FusedActFunc::NONE); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + + auto a_bias = dynamic_cast(a_conv2d->bias()); + ASSERT_TRUE(a_bias != nullptr); + + ASSERT_TRUE(a_bias->dim(0) == 2); + ASSERT_FLOAT_EQ(a_bias->at(0), + bias->at(0) + 1.5); + ASSERT_FLOAT_EQ(a_bias->at(1), + bias->at(1) + 1.5); +} + +// A case when +// - TFLConv2D has bias (0, 0) +// - TFLSub.x() == TFLConv2D +// - TFLSub.y() == TFLConst, (1.5, 3) +// +// After fusion, bias shold be (-1.5, -3) +TEST(FuseBiasAddPassTest, Conv2D_Sub_01_basic) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto sub_y = g.append(); + auto sub = g.append(conv2d, sub_y); + + g.complete(sub); + + init(g.pull); + init(conv2d, filter, bias); + init(sub, locoex::FusedActFunc::NONE); + init(sub_y); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + + auto a_bias = dynamic_cast(a_conv2d->bias()); + ASSERT_TRUE(a_bias != nullptr); + + ASSERT_TRUE(a_bias->dim(0) == 2); + ASSERT_FLOAT_EQ(a_bias->at(0), + bias->at(0) - sub_y->at(0)); + ASSERT_FLOAT_EQ(a_bias->at(1), + bias->at(1) - sub_y->at(1)); +} + +// A case when TFLConv2D is input of TFLSub but fusion cannot be performed. +// - TFLSub.x() == TFLConst +// - TFLSub.y() == TFLConv2D +// +// Here, TFLSub cannot be fused into TFLConst. To be fused, TFLSub.x() should be TFLConv2D and +// TFLSub.y() should be TFLConst. So fusion will NOT happen. +TEST(FuseBiasAddPassTest, Conv2D_Sub_02_fusing_will_not_performed) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto sub_y = g.append(); + auto sub = g.append(sub_y, conv2d); // This WON'T be fused + + g.complete(sub); + + init(g.pull); + init(conv2d, filter, bias); + init(sub, locoex::FusedActFunc::NONE); + init(sub_y); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + + auto a_bias = dynamic_cast(a_conv2d->bias()); + ASSERT_TRUE(a_bias != nullptr); + + ASSERT_TRUE(a_bias->dim(0) == 2); + ASSERT_FLOAT_EQ(a_bias->at(0), 0); + ASSERT_FLOAT_EQ(a_bias->at(1), 0); + + auto a_sub = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_sub != nullptr); + ASSERT_TRUE(a_sub->y() == a_conv2d); // Checking 'not-fused' state +} + +// A case when +// - TFLConv2D has an activation function with Relu +// - TFLAdd, has no activation function +// +// No fusion should happen +TEST(FuseBiasAddPassTest, Regression_Conv2D_Add_fused_action_00) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto add_y = g.append(); + auto add = g.append(conv2d, add_y); + + g.complete(add); + + init(g.pull); + init(conv2d, filter, bias); + init(add, locoex::FusedActFunc::NONE); + init(add_y); + + // Updating Fused Activation for this test + conv2d->fusedActivationFunction(locoex::FusedActFunc::RELU); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + ASSERT_TRUE(a_conv2d->fusedActivationFunction() == locoex::FusedActFunc::RELU); + + auto an_add = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(an_add != nullptr); + ASSERT_TRUE(an_add->fusedActivationFunction() == locoex::FusedActFunc::NONE); + + ASSERT_TRUE(an_add->x() == a_conv2d or an_add->y() == a_conv2d); +} + +// A case when +// - TFLConv2D has NONE activation function +// - TFLAdd has Relu activation function +// +// TFLConv2D should have Relu activation function, TFLAdd is fused into bias input +TEST(FuseBiasAddPassTest, Regression_Conv2D_Add_fused_action_01) +{ + exo::test::TestGraph g; + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto add_y = g.append(); + auto add = g.append(conv2d, add_y); + + g.complete(add); + + init(g.pull); + init(conv2d, filter, bias); + init(add, locoex::FusedActFunc::RELU); + init(add_y); + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + + auto a_bias = dynamic_cast(a_conv2d->bias()); + ASSERT_TRUE(a_bias != nullptr); + + ASSERT_TRUE(a_bias->dim(0) == 2); + ASSERT_FLOAT_EQ(a_bias->at(0), + bias->at(0) + add_y->at(0)); + ASSERT_FLOAT_EQ(a_bias->at(1), + bias->at(1) + add_y->at(1)); + + ASSERT_TRUE(a_conv2d->fusedActivationFunction() == locoex::FusedActFunc::RELU); +} diff --git a/compiler/exo/src/Pass/FuseInstanceNormPass.cpp b/compiler/exo/src/Pass/FuseInstanceNormPass.cpp new file mode 100644 index 00000000000..04d4a62cd54 --- /dev/null +++ b/compiler/exo/src/Pass/FuseInstanceNormPass.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseInstanceNormPass.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/CircleNodes.h" + +#include + +#include +#include + +// Helper to find commutative node's arguments +namespace +{ + +/** + * INTRODUCTION + * Binary operation f(x,y) is 'commutative' when + * f(x,y) == f(y,x) holds for all x, y. + * For examples, ADD, MUL and SQUARED_DIFFERENCE are commutative. + * These helpers make it easy to find commutative arguemnts of commtative node. + * + * HOW TO USE + * COMM_NODE *node; + * ARG_TYPE_1 *arg1; + * ARG_TYPE_2 *arg2; + * + * bool ok = fill(&arg1, &arg2).with_commutative_args_of(node); + * + * Result + * If 'node's commutative argument types are actually {ARG_TYPE_1, ARG_TYPE_2} + * (as a set), 'arg1' and 'arg2' set as actual 'node's arguemnts with matching + * type, and return value 'ok' is true. + * Otherwise, 'arg1' and 'arg2' not changed, 'ok' is false. + */ + +template class NodeFiller final +{ +public: + NodeFiller(ARG_TYPE_1 **arg_1, ARG_TYPE_2 **arg_2) : _arg_1(arg_1), _arg_2(arg_2) + { + // DO NOTHING + } + + /** + * @return true When 'node's argument types are 'ARG_TYPE_1' and 'ARG_TYPE_2' + * In such case, it assign '_arg_1' and '_arg_2' to actual arguments + * + * @return false When 'node's argument types are NOT matched with 'ARG_TYPE_*' + * In such case, it does not amend '_arg_1' and '_arg_2' + * + * @require COMM_NODE has member x() and y() + */ + template bool with_commutative_args_of(const COMM_NODE *node); + +private: + ARG_TYPE_1 **_arg_1; + ARG_TYPE_2 **_arg_2; +}; + +template +inline NodeFiller fill(ARG_TYPE_1 **arg_1, ARG_TYPE_2 **arg_2) +{ + return NodeFiller{arg_1, arg_2}; +} + +template +template +bool NodeFiller::with_commutative_args_of(const COMM_NODE *node) +{ + // Case 1) X == ARG_TYPE_1 / Y == ARG_TYPE_2 + { + auto x = dynamic_cast(node->x()); + auto y = dynamic_cast(node->y()); + + if (x && y) + { + *_arg_1 = x; + *_arg_2 = y; + return true; + } + } + + // Case 2) X == ARG_TYPE_2 / Y == ARG_TYPE_1 + { + auto x = dynamic_cast(node->x()); + auto y = dynamic_cast(node->y()); + + if (x && y) + { + *_arg_1 = y; + *_arg_2 = x; + return true; + } + } + + return false; +} + +} // namespace + +// Helper to check detail +namespace +{ + +/// @return true When node has shape of '1 x .. x 1 x depth' +bool is_1D_with_dummy_dim(locoex::TFLConst *node, uint32_t depth) +{ + auto rank = node->rank(); + uint32_t axis; + for (axis = 0; axis < rank - 1; ++axis) + { + if (node->dim(axis).value() != 1) + return false; + } + return node->dim(axis).value() == depth; +} + +bool is_instance_mean(locoex::TFLMean *mean) +{ + // + // CHECK 1) input is rank 4 + // + auto input = mean->input(); + if (not loco::shape_known(input)) + return false; + auto input_shape = loco::shape_get(input).as(); + if (input_shape.rank() != 4) + return false; + + // + // CHECK 2) 'reduction indices' is TFLConst of value [1,2], that is HW of NHWC + // + // TODO Support equivalent case, like [-3,-2] + // TODO Support non-Const case? + // TODO What if input is NCHW format in Circle? + auto red_indices = dynamic_cast(mean->reduction_indices()); + if (not red_indices) + return false; + if (red_indices->rank() != 1) + return false; + std::set red_indices_set; + { + // TODO Currently only support S32, support other types + assert(red_indices->dtype() == loco::DataType::S32); + for (uint32_t i = 0; i < red_indices->dim(0).value(); ++i) + red_indices_set.insert(red_indices->at(i)); + } + if (red_indices_set.size() != 2) + return false; + if (red_indices_set.find(1) == red_indices_set.end()) + return false; + if (red_indices_set.find(2) == red_indices_set.end()) + return false; + + // + // CHECK 3) keep_dims == true (?) + // + // We only have case of 'keep_dims == true' so far, but it might be okay with 'keep_dims == false' + // TODO Check this fact, and if true, return true regardless of keep_dims + return mean->keep_dims(); +} + +} // namespace + +// Helper to fuse Instance Norm +namespace +{ + +/** + * SUBGRAPH PATTERN + * + * - Below diagram shows Instance Norm pattern to fuse. + * - Execution dependency order is top to the bottom. + * - Node name is matched with variable name of InstanceNormPattern class. + * - Usually, first word of node name (variable name) is node type. For e.g. + * variable 'mean_as_variance' is pointer to TFLMean. + * - (Item in parenthesis) means actually exist, but not having a name and + * not a variable of InstanceNormPattern class. + * + * TODO support other semantically same patterns for instance norm + * + * [In] + * | + * V + * +----------- ifm -----+ (reduction indicies) + * | | | | + * | | V V + * | | mean_of_ifm ----------------+ + * | V | | + * | sqdiff <--+ (reduction indicies) | + * | | | | + * | V | | + * | mean_as_variance <---+ const_as_epsilon | + * | | | | + * | V | | + * | add_as_variance <--------+ | + * | | | + * | V | + * | rsqrt const_as_gamma | + * | | | | + * | V | | + * | mul_gamma <--+ | + * | | | | + * V V V | + * mul_as_scaled_ifm mul_as_scaled_mean <-------------+ + * | | + * | const_as_beta | + * | | V + * | +------> sub + * V | + * add_as_terminal <----------+ + * | + * V + * [Out] + */ +class InstanceNormPattern final +{ +public: + InstanceNormPattern(locoex::TFLAdd *candidate) + { + assert(candidate); + add_as_terminal = candidate; + } + +public: + bool matched(); + bool matched() const { return _matched; } + +public: + // Context + loco::Node *ifm = nullptr; + locoex::TFLMean *mean_of_ifm = nullptr; + locoex::TFLSquaredDifference *sqdiff = nullptr; + locoex::TFLMean *mean_as_variance = nullptr; + locoex::TFLConst *const_as_epsilon = nullptr; + locoex::TFLAdd *add_as_variance = nullptr; + locoex::TFLRsqrt *rsqrt = nullptr; + locoex::TFLConst *const_as_gamma = nullptr; + locoex::TFLMul *mul_gamma = nullptr; + locoex::TFLMul *mul_as_scaled_ifm = nullptr; + locoex::TFLMul *mul_as_scaled_mean = nullptr; + locoex::TFLConst *const_as_beta = nullptr; + locoex::TFLSub *sub = nullptr; + locoex::TFLAdd *add_as_terminal = nullptr; + +private: + bool _matched = false; +}; + +bool InstanceNormPattern::matched() +{ + if (_matched) + return true; + +#define CHECK_OR_FALSE(condition) \ + if (not(condition)) \ + return false; + + // Check order is DFS + + CHECK_OR_FALSE(fill(&mul_as_scaled_ifm, &sub).with_commutative_args_of(add_as_terminal)); + CHECK_OR_FALSE(fill(&ifm, &mul_gamma).with_commutative_args_of(mul_as_scaled_ifm)); + + CHECK_OR_FALSE(loco::shape_known(ifm)); + auto ifm_shape = loco::shape_get(ifm); + CHECK_OR_FALSE(ifm_shape.domain() == loco::Domain::Tensor); + auto ifm_tensor_shape = ifm_shape.as(); + CHECK_OR_FALSE(ifm_tensor_shape.rank() == 4); + uint32_t ifm_channel_depth = ifm_tensor_shape.dim(3).value(); + + CHECK_OR_FALSE(fill(&rsqrt, &const_as_gamma).with_commutative_args_of(mul_gamma)); + CHECK_OR_FALSE(is_1D_with_dummy_dim(const_as_gamma, ifm_channel_depth)); + + add_as_variance = dynamic_cast(rsqrt->x()); + CHECK_OR_FALSE(add_as_variance); + + CHECK_OR_FALSE( + fill(&mean_as_variance, &const_as_epsilon).with_commutative_args_of(add_as_variance)); + + CHECK_OR_FALSE(const_as_epsilon->dtype() == loco::DataType::FLOAT32); + // TODO Support regarding broadcast + CHECK_OR_FALSE(const_as_epsilon->size() == 1); + + CHECK_OR_FALSE(is_instance_mean(mean_as_variance)); + sqdiff = dynamic_cast(mean_as_variance->input()); + CHECK_OR_FALSE(sqdiff); + + loco::Node *ifm_should_be = nullptr; + CHECK_OR_FALSE(fill(&ifm_should_be, &mean_of_ifm).with_commutative_args_of(sqdiff)); + CHECK_OR_FALSE(ifm == ifm_should_be); + CHECK_OR_FALSE(is_instance_mean(mean_of_ifm)); + CHECK_OR_FALSE(ifm == mean_of_ifm->input()); + + const_as_beta = dynamic_cast(sub->x()); + CHECK_OR_FALSE(const_as_beta); + CHECK_OR_FALSE(is_1D_with_dummy_dim(const_as_beta, ifm_channel_depth)); + + mul_as_scaled_mean = dynamic_cast(sub->y()); + CHECK_OR_FALSE(mul_as_scaled_mean); + + locoex::TFLMul *mul_gamma_should_be = nullptr; + locoex::TFLMean *mean_of_ifm_should_be = nullptr; + CHECK_OR_FALSE(fill(&mul_gamma_should_be, &mean_of_ifm_should_be) + .with_commutative_args_of(mul_as_scaled_mean)); + CHECK_OR_FALSE(mul_gamma == mul_gamma_should_be); + CHECK_OR_FALSE(mean_of_ifm == mean_of_ifm_should_be); +#undef CHECK_OR_FALSE + _matched = true; + return true; +} + +/** + * Instance norm pattern would be fused like following diagram: + * + * [In] --------------------------- CircleInstanceNorm --- [Out] + * / / + * const_as_gamma --- TFLReshape --- / + * / + * const_as_beta ---- TFLReshape --- + * + * Note + * - 'const_as_gamma' and 'const_as_beta' are from original graph + * - Value of 'const_as_epsilon' would be copied to CircleInstanceNorm's attribute + * - TFLReshape is added as CircleInstanceNorm only accept 1D tensor + * - 'TFLConst --- TFLReshape' is expected to be fused in constant folding for Reshape + */ +void fuse_instance_norm(const InstanceNormPattern &p) +{ + assert(p.matched()); + + auto graph = p.add_as_terminal->graph(); + + // Make reshape for gamma & beta + auto reshape_gamma = graph->nodes()->create(); + auto reshape_beta = graph->nodes()->create(); + { + auto ifm_shape = loco::shape_get(p.ifm).as(); + uint32_t ifm_channel_depth = ifm_shape.dim(3).value(); + + int32_t new_shape[1] = {static_cast(ifm_channel_depth)}; + + reshape_gamma->tensor(p.const_as_gamma); + reshape_beta->tensor(p.const_as_beta); + + locoex::set_new_shape(reshape_gamma, new_shape, 1); + locoex::set_new_shape(reshape_beta, new_shape, 1); + } + + // Make Instance Norm to replace + auto instance_norm = graph->nodes()->create(); + instance_norm->input(p.ifm); + instance_norm->gamma(reshape_gamma); + instance_norm->beta(reshape_beta); + float epsilon = p.const_as_epsilon->at(0); + instance_norm->epsilon(epsilon); + instance_norm->fusedActivationFunction(p.add_as_terminal->fusedActivationFunction()); + + replace(p.add_as_terminal).with(instance_norm); +} + +} // namespace + +namespace exo +{ + +bool FuseInstanceNormPass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + auto add = dynamic_cast(node); + if (not add) + continue; + + InstanceNormPattern pattern(add); + if (not pattern.matched()) + continue; + + fuse_instance_norm(pattern); + changed = true; + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FuseInstanceNormPass.h b/compiler/exo/src/Pass/FuseInstanceNormPass.h new file mode 100644 index 00000000000..e6361021ca8 --- /dev/null +++ b/compiler/exo/src/Pass/FuseInstanceNormPass.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FUSE_INSTANCE_NORM_PASS_H__ +#define __FUSE_INSTANCE_NORM_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse certain pattern of subgraph into CircleInstanceNorm + * with auxiliary nodes + * + * For detailed subgraph pattern to be fused, please check its implementation. + */ +struct FuseInstanceNormPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FuseInstanceNormPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __FUSE_INSTANCE_NORM_PASS_H__ diff --git a/compiler/exo/src/Pass/FuseReluPass.cpp b/compiler/exo/src/Pass/FuseReluPass.cpp new file mode 100644 index 00000000000..d7af0c506ed --- /dev/null +++ b/compiler/exo/src/Pass/FuseReluPass.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseReluPass.h" + +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include + +namespace +{ + +bool is_pred_fusable(loco::Node *node) +{ + using namespace locoex; + + auto fusable_node = dynamic_cast *>(node); + + return (fusable_node and fusable_node->fusedActivationFunction() == FusedActFunc::NONE); +}; + +struct Collector final : public locoex::TFLNodeMutableVisitor +{ + void visit(locoex::TFLRelu *node) final + { + if (is_pred_fusable(node->features())) + candidates.insert(node); + } + + void visit(locoex::TFLRelu6 *node) final + { + if (is_pred_fusable(node->features())) + candidates.insert(node); + } + + void visit(locoex::TFLNode *) final { return; } + + std::set candidates; +}; + +void set_activation_fusion(loco::Node *node, locoex::FusedActFunc f) +{ + using namespace locoex; + + if (auto fusable_node = dynamic_cast *>(node)) + fusable_node->fusedActivationFunction(f); + else + assert(false); +} + +struct Performer final : public locoex::TFLNodeMutableVisitor +{ + void visit(locoex::TFLRelu *the_relu) final + { + set_activation_fusion(the_relu->features(), locoex::FusedActFunc::RELU); + + loco::replace(the_relu).with(the_relu->features()); + the_relu->features(nullptr); + } + + void visit(locoex::TFLRelu6 *the_relu6) final + { + set_activation_fusion(the_relu6->features(), locoex::FusedActFunc::RELU6); + + loco::replace(the_relu6).with(the_relu6->features()); + the_relu6->features(nullptr); + } + + void visit(locoex::TFLNode *) final { assert(false && "should not be called"); } +}; + +} // namespace + +namespace exo +{ + +bool FuseReluPass::run(loco::Graph *g) +{ + Collector collector; + + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (node->dialect() == locoex::TFLDialect::get()) + { + auto tfl_node = dynamic_cast(node); + tfl_node->accept(&collector); + } + } + + Performer performer; + + for (auto node : collector.candidates) + { + node->accept(&performer); + } + + return collector.candidates.size() > 0; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FuseReluPass.h b/compiler/exo/src/Pass/FuseReluPass.h new file mode 100644 index 00000000000..1cd276b2973 --- /dev/null +++ b/compiler/exo/src/Pass/FuseReluPass.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_FUSE_RELU_PASS_H__ +#define __PASS_FUSE_RELU_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse TFLRelu or TFLRelu6 into the TensorFlow Lite ops below: + * + * ADD, AVERAGE_POOL_2D, CONCATENATION, CONV_2D, DEPTHWISE_CONV_2D, + * FULLY_CONNECTED, L2_NORMALIZATION, L2_POOL_2D, MAX_POOL_2D, MUL + */ +struct FuseReluPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FuseReluPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __PASS_FUSE_RELU_PASS_H__ diff --git a/compiler/exo/src/Pass/FuseReluPass.test.cpp b/compiler/exo/src/Pass/FuseReluPass.test.cpp new file mode 100644 index 00000000000..6f83d4dd008 --- /dev/null +++ b/compiler/exo/src/Pass/FuseReluPass.test.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseReluPass.h" + +#include "Dialect/IR/TFLNodes.h" +#include "TestGraph.h" + +#include +#include + +#include + +#include // for std::is_same + +namespace +{ + +void init(loco::Pull *pull) +{ + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2, 3, 3, 2}); +} + +/// @brief Initializes TFLConv2D and related filter and bias +void init(locoex::TFLConv2D *conv2d, locoex::TFLConst *filter, locoex::TFLConst *bias) +{ + // set conv2d + { + conv2d->fusedActivationFunction(locoex::FusedActFunc::NONE); + conv2d->padding(locoex::Padding::VALID); + } + + // set filter + { + filter->dtype(loco::DataType::FLOAT32); + filter->shape({2, 3, 3, 2}); + filter->size(2 * 3 * 3 * 2); + + for (uint32_t x = 0; x < 2 * 3 * 3 * 2; x++) + filter->at(x) = 0.0; + } + + // set bias + { + bias->dtype(loco::DataType::FLOAT32); + bias->shape({2}); + bias->size(2); + + for (uint32_t x = 0; x < 2; x++) + bias->at(x) = 0.0; + } +} + +} // namespace + +/// Test code called by TEST(..) +/// This tests whether Conv2D - FusedTFLType is fused. +template void test() +{ + static_assert((std::is_same::value && + FusedActFunc == locoex::FusedActFunc::RELU) || + (std::is_same::value && + FusedActFunc == locoex::FusedActFunc::RELU6), + "wrong template type"); + + exo::test::TestGraph g; + { + auto filter = g.append(); + auto bias = g.append(); + auto conv2d = g.append(g.pull, filter, bias); + + auto fusable_node = g.append(conv2d); + + g.complete(fusable_node); + + init(g.pull); + init(conv2d, filter, bias); + } + + // let's run fusion + { + exo::test::TypeShapeReadyPhase test_phase; + + test_phase.add_pass(); + test_phase.add_pass(); // to remove TFLRelu + test_phase.run(g.graph()); + } + + auto a_conv2d = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(a_conv2d != nullptr); + ASSERT_TRUE(a_conv2d->fusedActivationFunction() == FusedActFunc); + + auto removed_fusable_node = exo::test::find_first_node_bytype(g.graph()); + ASSERT_TRUE(removed_fusable_node == nullptr); +} + +// A case with Conv2D-Relu +TEST(FuseReluTest, Conv2D_Relu_basic) { test(); } + +// A case with Conv2D-Relu6 +TEST(FuseReluTest, Conv2D_Relu6_basic) { test(); } diff --git a/compiler/exo/src/Pass/FuseRsqrtPass.cpp b/compiler/exo/src/Pass/FuseRsqrtPass.cpp new file mode 100644 index 00000000000..08d70413928 --- /dev/null +++ b/compiler/exo/src/Pass/FuseRsqrtPass.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseRsqrtPass.h" + +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +namespace +{ + +/** + * @return Casted TFLDiv for fusable candidate, nullptr otherwise + * + * This helper checkes fusability with following conditions: + * - TFLDiv has no activation + * - TFLDiv's first argument is TFLConst with all value 1 + * - TFLDiv's second argument is TFLSqrt + */ +locoex::TFLDiv *as_candidate(loco::Node *node) +{ + auto div = dynamic_cast(node); + if (not div) + return nullptr; + + // Cannot fuse Div with activation function + if (div->fusedActivationFunction() != locoex::FusedActFunc::NONE) + return nullptr; + + auto const_one = dynamic_cast(div->x()); + if (not const_one) + return nullptr; + + const loco::DataType FLOAT32 = loco::DataType::FLOAT32; + // TODO Support other dtype + EXO_ASSERT(const_one->dtype() == FLOAT32, "Only support FLOAT32 now"); + for (uint32_t i = 0; i < const_one->size(); ++i) + if (const_one->at(i) != 1.0f) + return nullptr; + + auto sqrt = dynamic_cast(div->y()); + if (not sqrt) + return nullptr; + + return div; +} + +void fuse_rsqrt(locoex::TFLDiv *div) +{ + auto sqrt = dynamic_cast(div->y()); + EXO_ASSERT(sqrt, "sqrt should be valid at this point"); + + // TFLRsqrt to replace + auto rsqrt = div->graph()->nodes()->create(); + rsqrt->x(sqrt->x()); + + // replace + loco::replace(div).with(rsqrt); +} + +} // namespace + +namespace exo +{ + +bool FuseRsqrtPass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (auto div = as_candidate(node)) + { + fuse_rsqrt(div); + changed = true; + } + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FuseRsqrtPass.h b/compiler/exo/src/Pass/FuseRsqrtPass.h new file mode 100644 index 00000000000..1e60e4a49c8 --- /dev/null +++ b/compiler/exo/src/Pass/FuseRsqrtPass.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FUSE_RSQRT_PASS_H__ +#define __FUSE_RSQRT_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse TFLSqrt that is divided(TFLDiv) by 1, into TFLRsqrt + * + * + * + * TFLConst(1) ------ + * \ + * A --- TFLSqrt --- TFLDiv --- B + * + * + * + * A --- TFLRsqrt --- B + */ +struct FuseRsqrtPass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FuseRsqrtPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __FUSE_RSQRT_PASS_H__ diff --git a/compiler/exo/src/Pass/FuseSquaredDifferencePass.cpp b/compiler/exo/src/Pass/FuseSquaredDifferencePass.cpp new file mode 100644 index 00000000000..3f985a505b0 --- /dev/null +++ b/compiler/exo/src/Pass/FuseSquaredDifferencePass.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FuseSquaredDifferencePass.h" + +#include "Check.h" + +#include "Dialect/IR/TFLNodes.h" + +namespace +{ + +/** + * @return Casted TFLMul for fusable candidate, nullptr otherwise + * + * This helper checkes fusability with following conditions: + * - TFLMul has no activation + * - TFLMul's first and second arguments are equal and TFLSub + */ +locoex::TFLMul *as_candidate(loco::Node *node) +{ + auto mul = dynamic_cast(node); + if (not mul) + return nullptr; + + // Cannot fuse mul with activation function + if (mul->fusedActivationFunction() != locoex::FusedActFunc::NONE) + return nullptr; + + if (mul->x() != mul->y()) + return nullptr; + + if (not dynamic_cast(mul->x())) + return nullptr; + + return mul; +} + +void fuse_squared_difference(locoex::TFLMul *mul) +{ + auto sub = dynamic_cast(mul->x()); + EXO_ASSERT(sub, "sub should be valid at this point"); + + // TFLSquaredDifference to replace + auto sq_diff = mul->graph()->nodes()->create(); + sq_diff->x(sub->x()); + sq_diff->y(sub->y()); + + // replace + loco::replace(mul).with(sq_diff); +} + +} // namespace + +namespace exo +{ + +bool FuseSquaredDifferencePass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (auto mul = as_candidate(node)) + { + fuse_squared_difference(mul); + changed = true; + } + } + + return changed; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/FuseSquaredDifferencePass.h b/compiler/exo/src/Pass/FuseSquaredDifferencePass.h new file mode 100644 index 00000000000..dbc15149fda --- /dev/null +++ b/compiler/exo/src/Pass/FuseSquaredDifferencePass.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FUSE_SQUARED_DIFFERENCE_PASS_H__ +#define __FUSE_SQUARED_DIFFERENCE_PASS_H__ + +#include + +namespace exo +{ + +/** + * @brief Class to fuse SquaredDifference pattern + * + * + * + * A --- TFLSub --- TFLMul --- C + * / \ / + * B ---- ----- + * + * + * + * A --- TFLSquaredDifference --- C + * / + * B ---- + */ +struct FuseSquaredDifferencePass final : public logo::Pass +{ + const char *name(void) const final { return "exo::FuseSquaredDifferencePass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace exo + +#endif // __FUSE_SQUARED_DIFFERENCE_PASS_H__ diff --git a/compiler/exo/src/Pass/MergeConcatNodesPass.cpp b/compiler/exo/src/Pass/MergeConcatNodesPass.cpp new file mode 100644 index 00000000000..8945fcfceaa --- /dev/null +++ b/compiler/exo/src/Pass/MergeConcatNodesPass.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MergeConcatNodesPass.h" +#include "Dialect/IR/TFLNodes.h" + +#include + +#include + +namespace +{ + +bool canMerge(locoex::TFLConcatenation *node1, locoex::TFLConcatenation *node2) +{ + if (node1->fusedActivationFunction() != node2->fusedActivationFunction()) + return false; + + if (node1->axis() != node2->axis()) + return false; + + switch (node1->fusedActivationFunction()) + { + case locoex::FusedActFunc::NONE: + case locoex::FusedActFunc::RELU: + case locoex::FusedActFunc::RELU6: + return true; + + // case locoex::FusedActFunc::TANH: + // return false; + + default: + INTERNAL_EXN_V("Unknown FusedActFunc", oops::to_uint32(node1->fusedActivationFunction())); + } +} + +/** + * @brief Collect all the inputs of newly created TFLConcatenation nodes + * + * in:0 -------------------------------\ + * in:1 ---- TFLConcatenation:0 -------- TFLConcatenation:3 --- C + * (axis = 0, NONE) (axis = 0, NONE) + * in:2 ---/ / + * in:3 ---- TFLConcatenation:1 ------/ + * (axis = 1, NONE) / + * in:4 ---/ / + * in:5 ---- TFLConcatenation:2 ---/ + * (axis = 0, RELU) + * in:6 ---/ + * + * For exmaple, if graph is like above, dfs(TFLConcatenation:3) will + * return [in:0, in:1, in:2, TFLConcatenation:1, TFLConcatenation:2] + * + * TFLConcatenation:0 can be merged to TFLConcatenation:3, + * because axis and fusedActivationFunction are same. + * It means that [in:1, in:2] will be linked as inputs of new TFLConcatenation. + * + * However, TFLConcatenation:1 and TFLConcatenation:2 cannot be merged to + * TFLConcatenation:3 because axis and fusedActivationFunction of each are different. + * So [in:3, in:4, in:5, in:6] will not be linked as inputs of new TFLConcatenation + * and [TFLConcatenation:1, TFLConcatenation:2] will be linked instead. + * + * Therefore, inputs of newly created TFLConcatenation node for merging + * TFLConcatenation:3 will be [in:0, in:1, in:2, TFLConcatenation:1, TFLConcatenation:2] + * and dfs(TFLConcatenation:3) will return it. + * + * + * @note The input nodes should be traversed by LRV, + * which is from left to right (input:0 --> input:N) + */ +std::vector dfs(locoex::TFLConcatenation *root) +{ + std::vector res; + + for (uint32_t i = 0; i < root->numValues(); ++i) + { + auto input = dynamic_cast(root->values(i)); + if (input != nullptr && canMerge(input, root)) + { + auto children = dfs(input); + for (auto child : children) + res.push_back(child); + } + else + { + res.push_back(root->values(i)); + } + } + + return res; +} + +} // namespace + +namespace exo +{ + +/** + * @brief Merge TFLConcatenate nodes whose axis and fusedActivationFunction are same + * + * [Before] + * in:0 -------------------------------\ + * in:1 ---- TFLConcatenation:0 -------- TFLConcatenation:3 --- C + * (axis = 0, NONE) (axis = 0, NONE) + * in:2 ---/ / + * in:3 ---- TFLConcatenation:1 ------/ + * (axis = 1, NONE) / + * in:4 ---/ / + * in:5 ---- TFLConcatenation:2 ---/ + * (axis = 0, RELU) + * in:6 ---/ + * + * [After] + * in:0 -------------------------------\ + * in:1 -------------------------------- TFLConcatenation:4 --- C + * (axis = 0, NONE) + * in:2 -------------------------------/ + * in:3 ---- TFLConcatenation:1 ------/ + * (axis = 1, NONE) / + * in:4 ---/ / + * in:5 ---- TFLConcatenation:2 ---/ + * (axis = 0, RELU) + * in:6 ---/ + * + * + * in:1 ---- TFLConcatenation:0 ---- + * (axis = 0, NONE) + * in:2 ---/ + * + * + * ---- TFLConcatenation:3 ---- + * (axis = 0, NONE) + */ +bool MergeConcatNodesPass::run(loco::Graph *graph) +{ + // Let's enumerate nodes required to compute output nodes + auto active_nodes = loco::active_nodes(loco::output_nodes(graph)); + + // Find TFLConcatenation nodes which have another TFLConcatenation nodes + // as inputs, with same axis and same fusedActivationFunction + std::vector candidates; + for (auto node : active_nodes) + { + if (auto concat = dynamic_cast(node)) + { + for (uint32_t i = 0; i < concat->numValues(); ++i) + { + auto input = dynamic_cast(concat->values(i)); + if (input != nullptr && canMerge(input, concat)) + { + candidates.push_back(concat); + break; + } + } + } + } + + // Merge multiple TFLConcatenation nodes as one TFLConcatenation node + for (auto node : candidates) + { + auto inputs = dfs(node); + + auto new_concat = graph->nodes()->create(inputs.size()); + new_concat->axis(node->axis()); + new_concat->fusedActivationFunction(node->fusedActivationFunction()); + + for (uint32_t i = 0; i < inputs.size(); ++i) + new_concat->values(i, inputs.at(i)); + + loco::replace(node).with(new_concat); + for (uint32_t i = 0; i < node->numValues(); ++i) + node->values(i, nullptr); + } + + return candidates.size() > 0; +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/MergeConcatNodesPass.h b/compiler/exo/src/Pass/MergeConcatNodesPass.h new file mode 100644 index 00000000000..823214f4387 --- /dev/null +++ b/compiler/exo/src/Pass/MergeConcatNodesPass.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_MERGE_CONCAT_NODES_H__ +#define __PASS_MERGE_CONCAT_NODES_H__ + +#include +#include + +namespace exo +{ + +/** + * @brief Merge concat nodes whose axis and fusedActivationFunction are same + * + */ +class MergeConcatNodesPass : public logo::Pass +{ +public: + virtual const char *name(void) const { return "exo::MergeConcatNodesPass"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace exo + +#endif // __PASS_MERGE_CONCAT_NODES_H__ diff --git a/compiler/exo/src/Pass/ShapeInferencePass.cpp b/compiler/exo/src/Pass/ShapeInferencePass.cpp new file mode 100644 index 00000000000..bc60f91c428 --- /dev/null +++ b/compiler/exo/src/Pass/ShapeInferencePass.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ShapeInferencePass.h" + +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/Service/TFLShapeInferenceRule.h" + +#include "Dialect/IR/CircleDialect.h" +#include "Dialect/Service/CircleShapeInferenceRule.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace exo +{ + +/** + * @note Currently, TFL and Circle backend share this inference. However, TFL + * backend does not require rule for Circle dialect. + * TODO Make dedicated inference pass for Circle Dialect. + */ +bool ShapeInferencePass::run(loco::Graph *g) +{ + loco::CanonicalShapeInferenceRule canonical_rule; + locoex::TFLShapeInferenceRule tfl_rule; + locoex::CircleShapeInferenceRule circle_rule; + locoex::COpShapeInferenceRule cop_rule; + + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(locoex::TFLDialect::get(), &tfl_rule) + .bind(locoex::CircleDialect::get(), &circle_rule) + .bind(locoex::COpDialect::get(), &cop_rule); + + return loco::apply(&rules).to(g); +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/ShapeInferencePass.h b/compiler/exo/src/Pass/ShapeInferencePass.h new file mode 100644 index 00000000000..518c87403a7 --- /dev/null +++ b/compiler/exo/src/Pass/ShapeInferencePass.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_SHAPE_INFERENCE_PASS_H__ +#define __PASS_SHAPE_INFERENCE_PASS_H__ + +#include +#include + +namespace exo +{ + +/** + * @brief Pass to infer shape of nodes + */ +class ShapeInferencePass : public logo::Pass +{ +public: + virtual const char *name(void) const { return "exo::ShapeInferencePass"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace exo + +#endif //__PASS_SHAPE_INFERENCE_PASS_H__ diff --git a/compiler/exo/src/Pass/TypeInferencePass.cpp b/compiler/exo/src/Pass/TypeInferencePass.cpp new file mode 100644 index 00000000000..31d4f13b62a --- /dev/null +++ b/compiler/exo/src/Pass/TypeInferencePass.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TypeInferencePass.h" + +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/Service/TFLTypeInferenceRule.h" + +#include "Dialect/IR/CircleDialect.h" +#include "Dialect/Service/CircleTypeInferenceRule.h" + +#include +#include +#include + +#include +#include + +namespace exo +{ + +/** + * @note Currently, TFL and Circle backend share this inference. However, TFL + * backend does not require rule for Circle dialect. + * TODO Make dedicated inference pass for Circle Dialect. + */ +bool TypeInferencePass::run(loco::Graph *g) +{ + loco::CanonicalTypeInferenceRule canonical_rule; + locoex::TFLTypeInferenceRule tfl_rule; + locoex::CircleTypeInferenceRule circle_rule; + locoex::COpTypeInferenceRule cop_rule; + + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(locoex::TFLDialect::get(), &tfl_rule) + .bind(locoex::CircleDialect::get(), &circle_rule) + .bind(locoex::COpDialect::get(), &cop_rule); + + return loco::apply(&rules).to(g); +} + +} // namespace exo diff --git a/compiler/exo/src/Pass/TypeInferencePass.h b/compiler/exo/src/Pass/TypeInferencePass.h new file mode 100644 index 00000000000..3ede587a0ee --- /dev/null +++ b/compiler/exo/src/Pass/TypeInferencePass.h @@ -0,0 +1,42 @@ + +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASS_TYPE_INFERENCE_PASS_H__ +#define __PASS_TYPE_INFERENCE_PASS_H__ + +#include + +#include + +namespace exo +{ + +/** + * @brief Pass to infer type of nodes + */ +class TypeInferencePass : public logo::Pass +{ +public: + virtual const char *name(void) const { return "exo::TypeInferencePass"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace exo + +#endif //__PASS_TYPE_INFERENCE_PASS_H__ diff --git a/compiler/exo/src/Passes.cpp b/compiler/exo/src/Passes.cpp new file mode 100644 index 00000000000..99d229c9c18 --- /dev/null +++ b/compiler/exo/src/Passes.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Passes.h" + +// This file is to make sure that Passes.h be compiled diff --git a/compiler/exo/src/Passes.h b/compiler/exo/src/Passes.h new file mode 100644 index 00000000000..2a702d01d83 --- /dev/null +++ b/compiler/exo/src/Passes.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PASSES_H__ +#define __PASSES_H__ + +// Please add in alphabetical order +// Please append 'Pass' suffix to Pass class and file names + +#include "Pass/FoldReshapeOfConstPass.h" +#include "Pass/FoldTransposeOfConstPass.h" +#include "Pass/FuseBiasAddPass.h" +#include "Pass/FuseInstanceNormPass.h" +#include "Pass/FuseReluPass.h" +#include "Pass/FuseRsqrtPass.h" +#include "Pass/FuseSquaredDifferencePass.h" +#include "Pass/MergeConcatNodesPass.h" +#include "Pass/ShapeInferencePass.h" +#include "Pass/TypeInferencePass.h" + +#include +#include +#include + +#endif // __PASSES_H__ diff --git a/compiler/exo/src/ProgressReporter.cpp b/compiler/exo/src/ProgressReporter.cpp new file mode 100644 index 00000000000..ff919dae8d1 --- /dev/null +++ b/compiler/exo/src/ProgressReporter.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ProgressReporter.h" + +#include "Log.h" +#include "LogHelper.h" + +#include +#include + +#include + +namespace +{ + +char to_char(bool b) { return b ? 'Y' : 'N'; } + +const char *to_str(logo::PhaseStrategy s) +{ + switch (s) + { + case logo::PhaseStrategy::Saturate: + return "Saturate"; + case logo::PhaseStrategy::Restart: + return "Restart"; + } + assert(false); + return ""; +} + +} // namespace + +namespace exo +{ + +void ProgressReporter::notify(const logo::PhaseEventInfo *) +{ + LOGGER(prime); + + INFO(prime) << "=============================================================="; + INFO(prime) << "exo::PhaseRunner<" << to_str(strategy()) << ">"; + INFO(prime) << "Initial graph"; + INFO(prime) << fmt(graph()); +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *) +{ + LOGGER(prime); + + INFO(prime) << "exo::PhaseRunner<" << to_str(strategy()) << "> - done"; +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *info) +{ + LOGGER(prime); + + INFO(prime) << "--------------------------------------------------------------"; + INFO(prime) << "Before " << logo::pass_name(info->pass()); +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *info) +{ + LOGGER(prime); + + INFO(prime) << "After " << logo::pass_name(info->pass()) + << " (changed: " << to_char(info->changed()) << ")"; + INFO(prime) << fmt(graph()); +} + +} // namespace exo diff --git a/compiler/exo/src/ProgressReporter.h b/compiler/exo/src/ProgressReporter.h new file mode 100644 index 00000000000..b0f420df94b --- /dev/null +++ b/compiler/exo/src/ProgressReporter.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PROGRESSREPORTER_H__ +#define __PROGRESSREPORTER_H__ + +#include + +#include + +namespace exo +{ + +class ProgressReporter : public logo::PhaseEventListener +{ +public: + ProgressReporter(loco::Graph *graph, logo::PhaseStrategy strategy) + : _graph{graph}, _strategy{strategy} + { + // DO NOTHING + } + +public: + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + +public: + loco::Graph *graph(void) const { return _graph; } + logo::PhaseStrategy strategy(void) const { return _strategy; } + +private: + loco::Graph *_graph; + logo::PhaseStrategy _strategy; +}; + +} // namespace exo + +#endif // __PROGRESSREPORTER_H__ diff --git a/compiler/exo/src/ShapeInference.cpp b/compiler/exo/src/ShapeInference.cpp new file mode 100644 index 00000000000..bceb1495f22 --- /dev/null +++ b/compiler/exo/src/ShapeInference.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ShapeInference.h" +#include "Dialect/IR/TFLDialect.h" +#include "Dialect/Service/TFLShapeInferenceRule.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace exo +{ + +ShapeDescription ShapeInference::get(loco::Node *node) +{ + // TODO Adjust indentation level + { + assert(loco::shape_known(node)); + return to_shape_description(loco::shape_get(node)); + } +} + +} // namespace exo diff --git a/compiler/exo/src/ShapeInference.h b/compiler/exo/src/ShapeInference.h new file mode 100644 index 00000000000..ec141ccfc0c --- /dev/null +++ b/compiler/exo/src/ShapeInference.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __SHAPE_INFERENCE_H__ +#define __SHAPE_INFERENCE_H__ + +#include "ExporterUtils.h" + +#include + +namespace exo +{ + +/** + * @brief Get the shape of each node as a node annotation + * + * HOW TO USE + * + * ShapeInference::get(g->nodes()->at(..)); + */ +struct ShapeInference +{ + static ShapeDescription get(loco::Node *node); +}; + +} // namespace exo + +#endif // __SHAPE_INFERENCE_H__ diff --git a/compiler/exo/src/TFLite/TFLExporter.cpp b/compiler/exo/src/TFLite/TFLExporter.cpp new file mode 100644 index 00000000000..cf002b3e1a4 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporter.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "exo/TFLExporter.h" + +#include "TFLExporterImpl.h" + +#include + +#include + +#include + +namespace exo +{ + +TFLExporter::TFLExporter(loco::Graph *graph) : _impl(stdex::make_unique(graph)) +{ + // NOTHING TO DO +} + +TFLExporter::~TFLExporter() = default; + +void TFLExporter::dumpToFile(const char *path) const +{ + const char *ptr = _impl->getBufferPointer(); + const size_t size = _impl->getBufferSize(); + + if (!ptr) + INTERNAL_EXN("Graph was not serialized by FlatBuffer for some reason"); + + std::ofstream file(path, std::ofstream::binary); + file.write(ptr, size); +} + +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLExporterImpl.cpp b/compiler/exo/src/TFLite/TFLExporterImpl.cpp new file mode 100644 index 00000000000..07adbfb9d01 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterImpl.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLExporterImpl.h" + +#include "Convert.h" +#include "ExoOptimize.h" + +#include "TFLTensorExporter.h" +#include "TFLOperationExporter.h" +#include "TFLExporterUtils.h" + +#include "Log.h" +#include "Knob.h" + +#include + +#include +#include +#include +#include + +namespace +{ + +using namespace exo; +using namespace exo::tflite_detail; + +void registerGraphInputTensors(loco::Graph *graph, SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->inputs()->size(); ++n) + { + auto node = loco::pull_node(graph, n); + assert(node != nullptr); + ctx._inputs.push_back(get_tensor_index(node)); + } +} + +void registerGraphOutputTensors(loco::Graph *graph, SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->outputs()->size(); ++n) + { + auto push = loco::push_node(graph, n); + assert(push != nullptr); + auto node = push->from(); + assert(node != nullptr); + ctx._outputs.push_back(get_tensor_index(node)); + } +} + +} // namespace + +namespace +{ +using namespace tflite; +using namespace flatbuffers; + +Offset>> +encodeOperatorCodes(FlatBufferBuilder &builder, std::unordered_map &opcodes, + std::unordered_map &custom_opcodes) +{ + std::vector> operator_codes_vec(opcodes.size()); + for (auto it : opcodes) + { + uint32_t idx = it.second; + if (it.first.opcode != BuiltinOperator_CUSTOM) + { + operator_codes_vec[idx] = CreateOperatorCode(builder, it.first.opcode); + } + else // custom op + { + auto opCode = it.first; + auto custom_code = custom_opcodes.find(opCode); + if (custom_code == custom_opcodes.end()) + INTERNAL_EXN("Cannot find code for custom op"); + + operator_codes_vec[idx] = + CreateOperatorCode(builder, it.first.opcode, builder.CreateString(custom_code->second)); + } + } + return builder.CreateVector(operator_codes_vec); +} + +} // namespace + +namespace exo +{ + +using namespace exo::tflite_detail; +using namespace tflite; +using namespace flatbuffers; + +TFLExporter::Impl::Impl(loco::Graph *graph) { exportGraph(graph); } + +::flatbuffers::Offset<::tflite::SubGraph> TFLExporter::Impl::exportSubgraph(SerializedModelData &gd) +{ + auto tensors = _builder.CreateVector(gd._tensors); + auto inputs = _builder.CreateVector(gd._inputs); + auto outputs = _builder.CreateVector(gd._outputs); + auto operators = _builder.CreateVector(gd._operators); + auto subgraph = CreateSubGraph(_builder, tensors, inputs, outputs, operators); + return subgraph; +} + +void TFLExporter::Impl::exportGraph(loco::Graph *graph) +{ + LOGGER(l); + + // IR-level conversion and optimization + { + convert_to_TFLNodes(graph); + set(Dialect::TFLITE); + optimize(graph); + } + + _builder.Clear(); + + SerializedModelData gd; + + // This version is taken from comment in fbs + constexpr uint32_t version = 3; + + registerGraphIOName(graph, gd); + + // parse graph into SerializedModelData structure + exportOpDefinedTensors(graph, _builder, gd); + + // NOTE Invoke these register functions only after each node is annotated with its tensor_index + registerGraphInputTensors(graph, gd); + registerGraphOutputTensors(graph, gd); + + exportNodes(graph, _builder, gd); + + // encode operator codes + auto operator_codes = + encodeOperatorCodes(_builder, gd._operator_codes, gd._custom_operator_codes); + + // Subgraphs + Offset subgraph = exportSubgraph(gd); + auto subgraphs = _builder.CreateVector(std::vector>{subgraph}); + + // Description + std::string description_str = "nnpackage"; + auto description = _builder.CreateString(description_str); + + // create array of buffers + auto buffers = _builder.CreateVector(gd._buffers); + + // empty metadata + std::vector metadata_buffer_vec; + auto metadata_buffer = _builder.CreateVector(metadata_buffer_vec); + + // Model + auto model_offset = CreateModel(_builder, version, operator_codes, subgraphs, description, + buffers, metadata_buffer); + FinishModelBuffer(_builder, model_offset); +} + +const char *TFLExporter::Impl::getBufferPointer() const +{ + return reinterpret_cast(_builder.GetBufferPointer()); +} + +size_t TFLExporter::Impl::getBufferSize() const { return _builder.GetSize(); } + +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLExporterImpl.h b/compiler/exo/src/TFLite/TFLExporterImpl.h new file mode 100644 index 00000000000..01c549a43ae --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterImpl.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TFL_EXPORTER_IMPL_H__ +#define __TFL_EXPORTER_IMPL_H__ + +#include "exo/TFLExporter.h" +#include "schema_generated.h" + +#include + +namespace exo +{ + +namespace tflite_detail +{ + +struct SerializedModelData; + +} // namespace tflite_detail + +using namespace tflite_detail; + +/** + * internal implementation of interface exporter class + */ +class TFLExporter::Impl +{ +public: + Impl() = delete; + ~Impl() = default; + + explicit Impl(loco::Graph *graph); + + /** + * @return pointer to buffer with serialized graph + */ + const char *getBufferPointer() const; + + /** + * @return size of buffer with serialized graph + */ + size_t getBufferSize() const; + +private: + /** + * @brief create Subgraph using data stored in SerializedModelData + * @param gd information about serializer parts of model + * @return offset in buffer corresponding to serialized subgraph + */ + flatbuffers::Offset exportSubgraph(SerializedModelData &gd); + + /** + * @brief root function that writes graph into internal buffer + * @param graph + */ + void exportGraph(loco::Graph *graph); + +private: + flatbuffers::FlatBufferBuilder _builder; +}; + +} // namespace exo + +#endif // __TFL_EXPORTER_IMPL_H__ diff --git a/compiler/exo/src/TFLite/TFLExporterImpl.test.cpp b/compiler/exo/src/TFLite/TFLExporterImpl.test.cpp new file mode 100644 index 00000000000..7d74223c5e5 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterImpl.test.cpp @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLExporterImpl.h" + +#include "schema_generated.h" + +#include "TestGraph.h" +#include "GraphBlock.h" +#include "Knob.h" + +#include +#include + +#include + +namespace +{ + +class TFLExporterImplTests : public ::testing::Test +{ +public: + TFLExporterImplTests() { _graph = loco::make_graph(); } + +public: + virtual ~TFLExporterImplTests() = default; + +protected: + loco::Graph *graph(void) { return _graph.get(); } + + template NodeT *make_node(void); + +private: + std::unique_ptr _graph; +}; + +template NodeT *TFLExporterImplTests::make_node(void) +{ + return graph()->nodes()->create(); +} + +template <> loco::FeatureEncode *TFLExporterImplTests::make_node(void) +{ + loco::FeatureEncode *encode_layer = graph()->nodes()->create(); + + auto encoder = stdex::make_unique>(); + (*encoder->perm())[loco::FeatureAxis::Count] = 0; + (*encoder->perm())[loco::FeatureAxis::Depth] = 1; + (*encoder->perm())[loco::FeatureAxis::Height] = 2; + (*encoder->perm())[loco::FeatureAxis::Width] = 3; + encode_layer->encoder(std::move(encoder)); + + return encode_layer; +} + +template <> loco::FeatureDecode *TFLExporterImplTests::make_node(void) +{ + loco::FeatureDecode *decode_layer = graph()->nodes()->create(); + + auto decoder = stdex::make_unique>(); + (*decoder->perm())[loco::FeatureAxis::Count] = 0; + (*decoder->perm())[loco::FeatureAxis::Depth] = 1; + (*decoder->perm())[loco::FeatureAxis::Height] = 2; + (*decoder->perm())[loco::FeatureAxis::Width] = 3; + decode_layer->decoder(std::move(decoder)); + + return decode_layer; +} + +} // namespace + +// TODO TFLAdd + +// TODO TFLAveragePool2D + +TEST_F(TFLExporterImplTests, Concatenate) +{ + auto pull1 = make_node(); + { + pull1->dtype(loco::DataType::FLOAT32); + pull1->shape({1, 2, 3, 4}); + } + auto pull2 = make_node(); + { + pull2->dtype(loco::DataType::FLOAT32); + pull2->shape({1, 2, 3, 4}); + } + auto concat = make_node(); + { + concat->lhs(pull1); + concat->rhs(pull2); + } + auto push = make_node(); + { + push->from(concat); + } + + auto input1 = graph()->inputs()->create(); + { + input1->name("input1"); + loco::link(input1, pull1); + } + auto input2 = graph()->inputs()->create(); + { + input2->name("input2"); + loco::link(input2, pull2); + } + auto output = graph()->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + exo::TFLExporter::Impl exporter{graph()}; + + // TODO Add more checks + SUCCEED(); +} + +// TODO TFLConv2D + +// TODO TFLDepthwiseConv2D + +// TODO TFLDiv + +// TODO TFLMaxPool2D + +// TODO TFLMul + +TEST_F(TFLExporterImplTests, Relu6) +{ + auto pull = make_node(); + { + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1, 8, 8, 3}); + } + auto relu6 = make_node(); + { + relu6->input(pull); + } + auto push = make_node(); + { + push->from(relu6); + } + + auto input = graph()->inputs()->create(); + { + input->name("input"); + loco::link(input, pull); + } + auto output = graph()->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + exo::TFLExporter::Impl exporter{graph()}; + + // TODO Add more checks + SUCCEED(); +} + +// TODO TFLRelu6 + +// TODO TFLReshape + +// TODO TFLSoftmax + +// TODO TFLSqrt + +// TODO TFLSub + +// TODO TFLTanh + +TEST(TFLExporterImplTest, Transpose_simple) +{ + exo::test::ExampleGraph g; + + // pull attribute + { + g.pull->dtype(loco::DataType::FLOAT32); + g.pull->shape({1, 2, 2, 3}); + } + + // transpose attribute + { + g.transpose->perm()->size(4); + g.transpose->perm()->axis(0) = 1; + g.transpose->perm()->axis(1) = 2; + g.transpose->perm()->axis(2) = 3; + g.transpose->perm()->axis(3) = 0; + } + + exo::TFLExporter::Impl exporter{g.graph()}; + { + auto model = tflite::GetModel(exporter.getBufferPointer()); + auto operators = model->subgraphs()->Get(0)->operators(); + + assert(operators->Length() == 1); + + int n = 0; // op index of Transpose in tflite file + + auto opcode_index = operators->Get(n)->opcode_index(); + + ASSERT_EQ(model->operator_codes()->Get(opcode_index)->builtin_code(), + tflite::BuiltinOperator_TRANSPOSE); + + auto perm = operators->Get(n)->inputs()->Get(1); + + auto perm_tensor = model->subgraphs()->Get(0)->tensors()->Get(perm); + ASSERT_EQ(perm_tensor->type(), tflite::TensorType::TensorType_INT32); + ASSERT_EQ(perm_tensor->shape()->size(), 1); + ASSERT_EQ(perm_tensor->shape()->Get(0), 4); + + auto bufs = (model->buffers()); + auto *perm_buf = + reinterpret_cast(bufs->Get(perm_tensor->buffer())->data()->data()); + + ASSERT_EQ(perm_buf[0], 1); + ASSERT_EQ(perm_buf[1], 2); + ASSERT_EQ(perm_buf[2], 3); + ASSERT_EQ(perm_buf[3], 0); + } +} + +/* + test case: + Pull ----- FeatureEncode ---- FeatureDecode --- Push + 0 -----------> H ---------+ O 0 + 1 W +----> H -----------> 1 + 2 I(depth) W 2 + 3 O(coutn) I 3 + + axis 0 ----------> H --------------> H -----------> 1 + axis 1 ----------> W --------------> W -----------> 2 + axis 2 ----------> I --------------> I -----------> 3 + axis 3 ----------> O --------------> O -----------> 0 + + So, perm vector of Tranpose = [3, 0, 1, 2]. + Please refer to loco::TensorTranspose about the definition of perm vector. +*/ +TEST(TFLExporterImplTest, Transpose_from_FilterEncode_FilterDecode) +{ + exo::test::ExampleGraph g; + + // pull attribute + { + g.pull->dtype(loco::DataType::FLOAT32); + g.pull->shape({1, 2, 3, 4}); // whatever value of rank 4 + } + + exo::TFLExporter::Impl exporter{g.graph()}; + { + auto model = tflite::GetModel(exporter.getBufferPointer()); + auto operators = model->subgraphs()->Get(0)->operators(); + + assert(operators->Length() == 1); + + int n = 0; // op index of Transpose in tflite file + + auto opcode_index = operators->Get(n)->opcode_index(); + + ASSERT_EQ(model->operator_codes()->Get(opcode_index)->builtin_code(), + tflite::BuiltinOperator_TRANSPOSE); + + auto perm = operators->Get(n)->inputs()->Get(1); + + auto perm_tensor = model->subgraphs()->Get(0)->tensors()->Get(perm); + ASSERT_EQ(perm_tensor->type(), tflite::TensorType::TensorType_INT32); + ASSERT_EQ(perm_tensor->shape()->size(), 1); + ASSERT_EQ(perm_tensor->shape()->Get(0), 4); + + auto bufs = (model->buffers()); + auto *perm_buf = + reinterpret_cast(bufs->Get(perm_tensor->buffer())->data()->data()); + ASSERT_EQ(perm_buf[0], 3); + ASSERT_EQ(perm_buf[1], 0); + ASSERT_EQ(perm_buf[2], 1); + ASSERT_EQ(perm_buf[3], 2); + } +} + +/** + * What happens when there is a mismatch between generation and execution order!? + */ +TEST_F(TFLExporterImplTests, Regression_0000) +{ + // This test was written without considering fusion. + // For this reason, this check is needed. + // TODO Rewrite this test + if (exo::get()) + return; + + // Execution Order: MaxPool2D -> ReLU + // Generation Order: ReLU -> MaxPool2D + auto pull = make_node(); + { + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1, 8, 8, 3}); + } + auto relu = make_node(); + auto encode = exo::make_feature_encode(pull); + auto maxpool = make_node(); + auto decode = exo::make_feature_decode(relu); + auto push = make_node(); + + ASSERT_EQ(maxpool->window()->vertical(), 1); + ASSERT_EQ(maxpool->window()->horizontal(), 1); + + maxpool->ifm(encode); + relu->input(maxpool); + push->from(decode); + + auto input = graph()->inputs()->create(); + { + input->name("input"); + loco::link(input, pull); + } + auto output = graph()->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + exo::TFLExporter::Impl exporter{graph()}; + { + int64_t maxpool_execution_index = -1; + int64_t relu_exeuction_index = -1; + + auto model = tflite::GetModel(exporter.getBufferPointer()); + auto operators = model->subgraphs()->Get(0)->operators(); + + for (uint32_t n = 0; n < operators->Length(); ++n) + { + auto opcode_index = operators->Get(n)->opcode_index(); + + switch (model->operator_codes()->Get(opcode_index)->builtin_code()) + { + case tflite::BuiltinOperator_RELU: + ASSERT_EQ(relu_exeuction_index, -1); + relu_exeuction_index = static_cast(n); + break; + case tflite::BuiltinOperator_MAX_POOL_2D: + ASSERT_EQ(maxpool_execution_index, -1); + maxpool_execution_index = static_cast(n); + break; + default: + break; + } + } + + ASSERT_NE(maxpool_execution_index, -1); + ASSERT_NE(relu_exeuction_index, -1); + // maxpool SHOULD precede ReLU + ASSERT_LT(maxpool_execution_index, relu_exeuction_index); + } +} + +/** + * @brief Test exporter buffer generation + */ +TEST_F(TFLExporterImplTests, Regression_0001) +{ + auto cgen = make_node(); + cgen->rank(1); + cgen->dim(0) = 2; + cgen->dtype(loco::DataType::FLOAT32); + cgen->size(2); + cgen->at(0) = 3.3f; + cgen->at(1) = 1.1f; + + auto push = make_node(); + push->from(cgen); + + auto output = graph()->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + exo::TFLExporter::Impl exporter{graph()}; + { + auto model = tflite::GetModel(exporter.getBufferPointer()); + auto buffers = model->buffers(); + + // 0'th empty buffer + ConstGen data + ConstGen node output + ASSERT_EQ(buffers->Length(), 3); + + // 0'th should be empty buffer + auto buffer_0 = (*buffers)[0]; + auto array_0 = buffer_0->data(); + ASSERT_EQ(array_0, nullptr); + + // 1'st should be ConstGen data which is two float + auto buffer_1 = (*buffers)[1]; + auto array_1 = buffer_1->data(); + size_t size_1 = array_1->size(); + ASSERT_EQ(size_1, 2 * sizeof(float)); + } +} diff --git a/compiler/exo/src/TFLite/TFLExporterUtils.cpp b/compiler/exo/src/TFLite/TFLExporterUtils.cpp new file mode 100644 index 00000000000..d35afc9aacd --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterUtils.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLExporterUtils.h" + +#include + +namespace exo +{ + +tflite::ActivationFunctionType to_tflite_actfunc(locoex::FusedActFunc func) +{ + switch (func) + { + case locoex::FusedActFunc::NONE: + return tflite::ActivationFunctionType_NONE; + case locoex::FusedActFunc::RELU: + return tflite::ActivationFunctionType_RELU; + case locoex::FusedActFunc::RELU6: + return tflite::ActivationFunctionType_RELU6; + default: + INTERNAL_EXN_V("Unsupported locoex FusedActFunc Type", oops::to_uint32(func)); + } +} + +} // namespace exo + +namespace exo +{ +namespace tflite_detail +{ + +uint32_t SerializedModelData::registerBuiltinOpcode(tflite::BuiltinOperator builtin_code) +{ + auto it = _operator_codes.find(OpCode{builtin_code}); + if (it != _operator_codes.end()) + { + return it->second; + } + auto idx = static_cast(_operator_codes.size()); + _operator_codes.emplace(OpCode{builtin_code}, idx); + return idx; +} + +uint32_t SerializedModelData::registerCustomOpcode(const std::string &custom_op) +{ + tflite::BuiltinOperator custom_code = tflite::BuiltinOperator_CUSTOM; + auto idx = registerBuiltinOpcode(custom_code); + _custom_operator_codes.emplace(OpCode{custom_code}, custom_op); + return idx; +} + +tflite::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm) +{ + // VALID padding + if (pad->top() == 0 && pad->bottom() == 0 && pad->left() == 0 && pad->right() == 0) + return tflite::Padding_VALID; + + // SAME padding + // + // For same padding, by definition, following equation should hold: + // O = floor((I - 1) / S) + 1 + // where input size I, output size O, stride S + // + // NOTE input and output 'feature' map are shape of NHWC + bool same_padding_criterion_1 = + (static_cast(ofm._dims[1]) == (ifm._dims[1] - 1) / stride->vertical() + 1) && + (static_cast(ofm._dims[2]) == (ifm._dims[2] - 1) / stride->horizontal() + 1); + + // For same padding, rear padding is same or bigger than front padding by at most 1 + bool same_padding_criterion_2 = + (pad->top() <= pad->bottom()) && (pad->bottom() <= pad->top() + 1) && + (pad->left() <= pad->right()) && (pad->right() <= pad->left() + 1); + + if (same_padding_criterion_1 && same_padding_criterion_2) + return tflite::Padding_SAME; + + INTERNAL_EXN("NYI for custom PAD"); +} + +tflite::Padding getOpPadding(const locoex::Padding pad) +{ + if (pad == locoex::Padding::VALID) + return tflite::Padding_VALID; + if (pad == locoex::Padding::SAME) + return tflite::Padding_SAME; + + INTERNAL_EXN_V("Unknown padding", oops::to_uint32(pad)); +} + +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd) +{ + for (uint32_t in = 0; in < graph->inputs()->size(); ++in) + { + auto pull = loco::pull_node(graph, in); + auto name = graph->inputs()->at(in)->name(); + + gd._pull_to_name[pull] = name; + } + for (uint32_t out = 0; out < graph->outputs()->size(); ++out) + { + auto push = loco::push_node(graph, out); + auto name = graph->outputs()->at(out)->name(); + + gd._push_to_name[push] = name; + } +} + +#include + +#include + +namespace +{ + +class TFLTensorIndexAnnotation final : public loco::NodeAnnotation +{ +public: + TFLTensorIndexAnnotation(const TFLTensorIndex &index) : _index{index} + { + // DO NOTHING + } + +public: + const TFLTensorIndex &index(void) const { return _index; } + +private: + TFLTensorIndex _index; +}; + +} // namespace + +void set_tensor_index(loco::Node *node, const TFLTensorIndex &tensor_id) +{ + assert(node->annot() == nullptr); + node->annot(stdex::make_unique(tensor_id)); +} + +TFLTensorIndex get_tensor_index(loco::Node *node) +{ + assert(node->annot() != nullptr); + return node->annot()->index(); +} + +} // namespace tflite_detail +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLExporterUtils.h b/compiler/exo/src/TFLite/TFLExporterUtils.h new file mode 100644 index 00000000000..dbd7a52fb8c --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterUtils.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TFL_EXPORTER_UTILS_H__ +#define __TFL_EXPORTER_UTILS_H__ + +#include "ExporterUtils.h" + +#include "schema_generated.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +#include + +namespace exo +{ +namespace tflite_detail +{ + +struct OpCode +{ + tflite::BuiltinOperator opcode; + + bool operator==(const OpCode &rhs) const { return opcode == rhs.opcode; } +}; + +} // namespace tflite_detail +} // namespace exo + +namespace exo +{ + +tflite::ActivationFunctionType to_tflite_actfunc(locoex::FusedActFunc func); + +} // namespace exo + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const exo::tflite_detail::OpCode &x) const { return hash()(x.opcode); } +}; + +} // namespace std + +namespace exo +{ +namespace tflite_detail +{ + +/** + * @breif Record the information of T/F Lite SubGraph and its mapping to loco + */ +struct SubGraphContext +{ + /// @brief SubGraph input tensor id + std::vector _inputs; + /// @brief SubGraph output tensor id + std::vector _outputs; +}; + +// Prerequisites for tflite::Model object creation +struct SerializedModelData final : public SubGraphContext +{ + SerializedModelData() = default; + SerializedModelData(const SerializedModelData &) = delete; + + std::unordered_map _operator_codes; + std::unordered_map _custom_operator_codes; + std::vector> _operators; + std::vector> _tensors; + std::vector> _buffers; + + // Graph input and output names + std::unordered_map _pull_to_name; + std::unordered_map _push_to_name; + + /** + * @brief if opcode is not registered in table of opcodes add it + * @param builtin_code + * @return idx of opcode in table of opcodes (see schema) + */ + uint32_t registerBuiltinOpcode(tflite::BuiltinOperator builtin_code); + uint32_t registerCustomOpcode(const std::string &custom_op); +}; + +tflite::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm); +tflite::Padding getOpPadding(const locoex::Padding pad); + +/// @brief Register graph input and output names to SerializedModelData +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd); + +using TFLTensorIndex = int32_t; + +void set_tensor_index(loco::Node *node, const TFLTensorIndex &tensor_id); +TFLTensorIndex get_tensor_index(loco::Node *node); + +} // namespace tflite_detail +} // namespace exo + +#endif // __TFL_EXPORTER_UTILS_H__ diff --git a/compiler/exo/src/TFLite/TFLExporterUtils.test.cpp b/compiler/exo/src/TFLite/TFLExporterUtils.test.cpp new file mode 100644 index 00000000000..d19f87d25a5 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLExporterUtils.test.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLExporterUtils.h" + +#include + +using namespace exo::tflite_detail; + +TEST(ExporterUtilsTests, getOpPadding) +{ + loco::Padding2D pad; + loco::Stride<2> stride; + exo::ShapeDescription ifm; + exo::ShapeDescription ofm; + + ifm._dims.resize(4); + ofm._dims.resize(4); + + // VALID padding + { + pad.top(0); + pad.bottom(0); + pad.left(0); + pad.right(0); + + stride.vertical(2); + stride.horizontal(2); + + ifm._dims[1] = 5; + ifm._dims[2] = 5; + + ofm._dims[1] = 2; + ofm._dims[2] = 2; + + ASSERT_EQ(getOpPadding(&pad, &stride, ifm, ofm), tflite::Padding_VALID); + } + + // SAME padding + { + pad.top(1); + pad.bottom(1); + pad.left(1); + pad.right(1); + + stride.vertical(2); + stride.horizontal(2); + + ifm._dims[1] = 5; + ifm._dims[2] = 5; + + ofm._dims[1] = 3; + ofm._dims[2] = 3; + + ASSERT_EQ(getOpPadding(&pad, &stride, ifm, ofm), tflite::Padding_SAME); + } + + // Custom padding 1 - Not supported by tflite + { + pad.top(2); + pad.bottom(0); + pad.left(1); + pad.right(1); + + stride.vertical(2); + stride.horizontal(2); + + ifm._dims[1] = 5; + ifm._dims[2] = 5; + + ofm._dims[1] = 3; + ofm._dims[2] = 3; + + ASSERT_ANY_THROW(getOpPadding(&pad, &stride, ifm, ofm)); + } + + // Custom padding 2 - Not supported by tflite + { + pad.top(2); + pad.bottom(2); + pad.left(2); + pad.right(2); + + stride.vertical(2); + stride.horizontal(2); + + ifm._dims[1] = 5; + ifm._dims[2] = 5; + + ofm._dims[1] = 4; + ofm._dims[2] = 4; + + ASSERT_ANY_THROW(getOpPadding(&pad, &stride, ifm, ofm)); + } +} diff --git a/compiler/exo/src/TFLite/TFLOperationExporter.cpp b/compiler/exo/src/TFLite/TFLOperationExporter.cpp new file mode 100644 index 00000000000..79b5b628780 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLOperationExporter.cpp @@ -0,0 +1,1199 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLOperationExporter.h" +#include "TFLExporterUtils.h" +#include "ShapeInference.h" + +#include "Dialect/IR/TFLNode.h" +#include "Dialect/IR/TFLNodes.h" +#include "Dialect/IR/TFLNodeVisitor.h" + +#include "Check.h" + +#include +#include +#include +#include + +#include + +#include + +using namespace flatbuffers; +using namespace tflite; + +namespace +{ + +using namespace exo; +using namespace exo::tflite_detail; + +class OperationExporter final : public locoex::TFLNodeMutableVisitor, + public loco::CanonicalNodeMutableVisitor +{ +public: + OperationExporter(FlatBufferBuilder &fbb, SerializedModelData &ctx) : builder{fbb}, gd{ctx} + { + // DO NOTHING + } + +public: + // FOR TFLNodes + void visit(locoex::TFLAdd *) final; + void visit(locoex::TFLAveragePool2D *) final; + void visit(locoex::TFLConcatenation *) final; + void visit(locoex::TFLConst *) final{/* skip, everything is done in exportOpDefinedTensors */}; + void visit(locoex::TFLConv2D *) final; + void visit(locoex::TFLDepthwiseConv2D *) final; + void visit(locoex::TFLDiv *) final; + void visit(locoex::TFLFullyConnected *) final; + void visit(locoex::TFLMaximum *) final; + void visit(locoex::TFLMaxPool2D *) final; + void visit(locoex::TFLMean *) final; + void visit(locoex::TFLMul *) final; + void visit(locoex::TFLRelu *) final; + void visit(locoex::TFLRelu6 *) final; + // TODO TFLReshape + void visit(locoex::TFLRsqrt *) final; + // TODO TFLSoftmax + void visit(locoex::TFLSqrt *) final; + void visit(locoex::TFLSquaredDifference *) final; + void visit(locoex::TFLSub *) final; + // TODO TFLTanh + void visit(locoex::TFLTranspose *) final; + void visit(locoex::TFLTransposeConv *) final; + + // FOR canonical nodes. These will be removed later + void visit(loco::ReLU *) final; + void visit(loco::ReLU6 *) final; + void visit(loco::Tanh *) final; + void visit(loco::Push *) final { /* DO NOTHING */} + void visit(loco::Pull *) final { /* DO NOTHING */} + void visit(loco::FeatureEncode *) final; + void visit(loco::FeatureDecode *) final; + void visit(loco::FilterEncode *) final; + void visit(loco::DepthwiseFilterEncode *) final; + void visit(loco::ConstGen *) final { /* skip, everything is done in exportOpDefinedTensors */} + void visit(loco::MaxPool2D *) final; + void visit(loco::AvgPool2D *) final; + void visit(loco::Conv2D *) final; + void visit(loco::TransposedConv2D *) final; + void visit(loco::DepthwiseConv2D *) final; + void visit(loco::TensorConcat *) final; + void visit(loco::TensorReduce *) final; + void visit(loco::TensorSoftmax *) final; + void visit(loco::BiasEncode *) final; + void visit(loco::TensorBiasAdd *) final; + void visit(loco::FeatureBiasAdd *) final; + void visit(loco::EltwiseAdd *) final; + void visit(loco::EltwiseMax *) final; + void visit(loco::EltwiseMul *) final; + void visit(loco::EltwiseSub *) final; + void visit(loco::EltwiseDiv *) final; + void visit(loco::EltwiseSqrt *) final; + void visit(loco::FixedReshape *) final; + void visit(loco::TensorBroadcast *) final; + void visit(loco::TensorConstantPad *) final; + + void visit(locoex::COpCall *); + +private: + /** + * @brief Exports TFLMaxPool2D or TFLAveragePool2D + * + * @note TFLPool2D should be one of TFLMaxPool2D or TFLAveragePool2D + */ + template + void export_pool_2d(TFLPool2D *node, tflite::BuiltinOperator builtin_op); + +private: + FlatBufferBuilder &builder; + SerializedModelData &gd; +}; + +void OperationExporter::visit(locoex::TFLAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder, to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLAveragePool2D *node) +{ + export_pool_2d(node, tflite::BuiltinOperator_AVERAGE_POOL_2D); +} + +void OperationExporter::visit(locoex::TFLConcatenation *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_CONCATENATION); + std::vector inputs_vec; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + + for (uint32_t i = 0; i < node->numValues(); ++i) + inputs_vec.push_back(get_tensor_index(node->values(i))); + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder, node->axis(), + to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ConcatenationOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding(node->padding()); + auto options = CreateConv2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + to_tflite_actfunc(node->fusedActivationFunction())); + + // Make CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Conv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLDepthwiseConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_DEPTHWISE_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding(node->padding()); + auto options = CreateDepthwiseConv2DOptions(builder, padding, node->stride()->w(), + node->stride()->h(), node->depthMultiplier(), + to_tflite_actfunc(node->fusedActivationFunction())); + + // Make DEPTHWISE_CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_DepthwiseConv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLDiv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_DIV); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateDivOptions(builder, to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_DivOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLFullyConnected *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_FULLY_CONNECTED); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->weights()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = + CreateFullyConnectedOptions(builder, to_tflite_actfunc(node->fusedActivationFunction())); + + // Make FULLY_CONNECTED operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_FullyConnectedOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMaximum *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MAXIMUM); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMaximumMinimumOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_MaximumMinimumOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMaxPool2D *node) +{ + export_pool_2d(node, tflite::BuiltinOperator_MAX_POOL_2D); +} + +void OperationExporter::visit(locoex::TFLMean *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MEAN); + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->reduction_indices())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateReducerOptions(builder, node->keep_dims()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ReducerOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLMul *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MUL); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMulOptions(builder, to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_MulOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLRelu *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RELU); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLRelu6 *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RELU6); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO TFLReshape + +void OperationExporter::visit(locoex::TFLRsqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RSQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO TFLSoftmax + +void OperationExporter::visit(locoex::TFLSqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLSquaredDifference *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SQUARED_DIFFERENCE); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSquaredDifferenceOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_SquaredDifferenceOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLSub *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SUB); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSubOptions(builder, to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_SubOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +// TODO TFLTanh + +void OperationExporter::visit(locoex::TFLTranspose *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TRANSPOSE); + std::vector inputs_vec{get_tensor_index(node->arg(0)), get_tensor_index(node->arg(1))}; + std::vector outputs_vec{get_tensor_index(node)}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateTransposeOptions(builder); + + auto op_offset = + CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions::BuiltinOptions_TransposeOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(locoex::TFLTransposeConv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TRANSPOSE_CONV); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->inputSizes()), + get_tensor_index(node->filter()), + get_tensor_index(node->outBackprop())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding(node->padding()); + auto options = + CreateTransposeConvOptions(builder, padding, node->stride()->w(), node->stride()->h()); + + // Make TRANSPOSE_CONV operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_TransposeConvOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +template +void OperationExporter::export_pool_2d(TFLPool2D *node, tflite::BuiltinOperator builtin_op) +{ + EXO_ASSERT(builtin_op == tflite::BuiltinOperator_MAX_POOL_2D || + builtin_op == tflite::BuiltinOperator_AVERAGE_POOL_2D, + "should be maxpool or avgpool"); + EXO_ASSERT(node->padding() != locoex::Padding::UNDEFINED, "Padding is not set"); + + uint32_t op_idx = gd.registerBuiltinOpcode(builtin_op); + std::vector inputs_vec{get_tensor_index(node->value())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + tflite::Padding padding = getOpPadding(node->padding()); + + auto options = CreatePool2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + node->filter()->w(), node->filter()->h(), + to_tflite_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::ReLU *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RELU); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::ReLU6 *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RELU6); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::Tanh *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TANH); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::MaxPool2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MAX_POOL_2D); + std::vector inputs_vec{get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreatePool2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), node->window()->horizontal(), + node->window()->vertical()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::AvgPool2D *node) +{ + // TFlite only support Valid convention of average pooling + assert(node->convention() == loco::AvgPool2D::Convention::Valid); + + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_AVERAGE_POOL_2D); + std::vector inputs_vec{get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreatePool2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), node->window()->horizontal(), + node->window()->vertical()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::Conv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_CONV_2D); + + // Third input of CONV_2D of tflite should be bias. We will make (and register to gd) dummy zero + // bias. Bias would be rank 1, have size of output kernel count, and have all zero values, i.e. + // zero bias. + auto *ker = dynamic_cast(node->ker()); + assert(ker); + int32_t bias_vec_size = ShapeInference::get(ker)._dims[0]; // output kernel count + + auto bias_vec_shape_offset = builder.CreateVector(std::vector{bias_vec_size}); + size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t); + + std::vector bias_vec_data(bias_vec_size); // initialized as zero vector + + auto bias_vec_offset = + builder.CreateVector(reinterpret_cast(bias_vec_data.data()), raw_bias_vec_size); + + auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset); + + const auto bias_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(bias_buffer_offset); + + auto bias_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id)); + + auto bias_tensor_offset = + CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset); + gd._tensors.push_back(bias_tensor_offset); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()), + bias_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + auto options = CreateConv2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical()); + + // Make CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_Conv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TransposedConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TRANSPOSE_CONV); + + // TRANSPOSE_CONV's first input is output shape array. + const int32_t outshape_vec_size = 4; + auto outshape_vec_shape_offset = builder.CreateVector(std::vector{outshape_vec_size}); + size_t raw_outshape_vec_size = outshape_vec_size * sizeof(int32_t); + + std::vector outshape_vec_data(outshape_vec_size); + { + // Copy inferred output shape of node + auto out_feature_shape = loco::shape_get(node).as(); + + // Feature tensor in TFlite is NHWC + outshape_vec_data.at(0) = out_feature_shape.count().value(); + outshape_vec_data.at(1) = out_feature_shape.height().value(); + outshape_vec_data.at(2) = out_feature_shape.width().value(); + outshape_vec_data.at(3) = out_feature_shape.depth().value(); + } + + auto outshape_vec_offset = builder.CreateVector( + reinterpret_cast(outshape_vec_data.data()), raw_outshape_vec_size); + + auto outshape_buffer_offset = CreateBuffer(builder, outshape_vec_offset); + + const auto outshape_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(outshape_buffer_offset); + + auto outshape_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(outshape_tensor_id)); + + auto outshape_tensor_offset = CreateTensor(builder, outshape_vec_shape_offset, TensorType_INT32, + outshape_buffer_id, name_offset); + gd._tensors.push_back(outshape_tensor_offset); + + // Make input, output and options for operator + std::vector inputs_vec{outshape_tensor_id, get_tensor_index(node->ker()), + get_tensor_index(node->ifm())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + // NOTE input and output is inversed to use this function + tflite::Padding padding = getOpPadding(node->pad(), node->stride(), ShapeInference::get(node), + ShapeInference::get(node->ifm())); + auto options = CreateTransposeConvOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical()); + + // Make TRANSPOSE_CONV operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_TransposeConvOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::DepthwiseConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_DEPTHWISE_CONV_2D); + + // Third input of DEPTHWISE_CONV2D of tflite should be bias. We will make (and register to gd) + // dummy zero bias. Bias would be rank 1, have size of output kernel count, and have all zero + // values, i.e. zero bias. + auto *ker = dynamic_cast(node->ker()); + assert(ker); + + int32_t bias_vec_size = ShapeInference::get(ker)._dims[3]; // output_size(C*M) + auto bias_vec_shape_offset = builder.CreateVector(std::vector{bias_vec_size}); + + size_t raw_bias_vec_size = bias_vec_size * sizeof(int32_t); + std::vector bias_vec_data(bias_vec_size); + auto bias_vec_offset = + builder.CreateVector(reinterpret_cast(bias_vec_data.data()), raw_bias_vec_size); + + auto bias_buffer_offset = CreateBuffer(builder, bias_vec_offset); + + const auto bias_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(bias_buffer_offset); + + auto bias_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(bias_tensor_id)); + + auto bias_tensor_offset = + CreateTensor(builder, bias_vec_shape_offset, TensorType_FLOAT32, bias_buffer_id, name_offset); + gd._tensors.push_back(bias_tensor_offset); + + std::vector inputs_vec{get_tensor_index(node->ifm()), get_tensor_index(node->ker()), + bias_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + tflite::Padding padding = getOpPadding( + node->pad(), node->stride(), ShapeInference::get(node->ifm()), ShapeInference::get(node)); + + int32_t ifm_channel_size = ShapeInference::get(node->ifm())._dims[3]; + // multiplier = bias_vec_size(output_size)/ifm_channel_size + auto options = + CreateDepthwiseConv2DOptions(builder, padding, node->stride()->horizontal(), + node->stride()->vertical(), bias_vec_size / ifm_channel_size); + + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_DepthwiseConv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TensorReduce *node) +{ + uint32_t op_idx; + + switch (node->func()) + { + case loco::ReduceFunc::Mean: + op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MEAN); + break; + + // TODO Support more reduce type operation + default: + INTERNAL_EXN_V("Not supported reduce type", oops::to_uint32(node->func())); + } + + // Create a vector for axes data + std::vector axes_vec; + auto rank = ShapeInference::get(node->input())._dims.size(); + for (uint32_t i = 0; i < rank; ++i) + if (node->axes()->defined(i)) + axes_vec.push_back(i); + + int32_t axes_vec_size = axes_vec.size(); + auto axes_vec_shape_offset = builder.CreateVector(std::vector{axes_vec_size}); + + size_t raw_axes_vec_size = axes_vec_size * sizeof(int32_t); + auto axes_vec_offset = + builder.CreateVector(reinterpret_cast(axes_vec.data()), raw_axes_vec_size); + + auto axes_buffer_offset = CreateBuffer(builder, axes_vec_offset); + + const auto axes_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(axes_buffer_offset); + + auto axes_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(axes_tensor_id)); + + auto axes_tensor_offset = + CreateTensor(builder, axes_vec_shape_offset, TensorType_INT32, axes_buffer_id, name_offset); + gd._tensors.push_back(axes_tensor_offset); + + std::vector inputs_vec{get_tensor_index(node->input()), axes_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateReducerOptions(builder, true); // true is for keep_dims option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ReducerOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::TensorSoftmax *node) +{ + // TODO Support when the input rank of TensorSoftmax is not 2 + assert(ShapeInference::get(node->input())._dims.size() == 2); + + // NOTE TFLite only accepts axis when the value is last dimension + assert(node->axis() == ShapeInference::get(node->input())._dims.size() - 1); + + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SOFTMAX); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSoftmaxOptions(builder, 1.0f); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_SoftmaxOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +/// @brief Export given node into identity, i.e. CONCATENATION with one input +template +void exportIdentity(NodeT *node, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_CONCATENATION); + std::vector inputs_vec{get_tensor_index(node->arg(0))}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder); // use dummy 0 axis and NONE activation + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ConcatenationOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +/// @brief Export loco nodes as TRANSPOSE +void exportAsTranspose(loco::Node *node, FlatBufferBuilder &builder, + std::vector &perm_vec_data, SerializedModelData &gd) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_TRANSPOSE); + + auto options = CreateTransposeOptions(builder); + + // Create constant tensor with perm vector + constexpr int perm_vec_size = 4; + assert(perm_vec_data.size() == perm_vec_size); + auto perm_vec_shape_offset = builder.CreateVector(std::vector{perm_vec_size}); + constexpr size_t raw_perm_vec_size = perm_vec_size * sizeof(int32_t); + + auto perm_vec_offset = + builder.CreateVector(reinterpret_cast(perm_vec_data.data()), raw_perm_vec_size); + + auto perm_buffer_offset = CreateBuffer(builder, perm_vec_offset); + + const auto perm_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(perm_buffer_offset); + + auto perm_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(perm_tensor_id)); + + auto perm_tensor_offset = + CreateTensor(builder, perm_vec_shape_offset, TensorType_INT32, perm_buffer_id, name_offset); + gd._tensors.push_back(perm_tensor_offset); + + // Create permutation node + + std::vector inputs_vec{get_tensor_index(node->arg(0)), perm_tensor_id}; + std::vector outputs_vec{get_tensor_index(node)}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + constexpr auto options_type = tflite::BuiltinOptions::BuiltinOptions_TransposeOptions; + + auto transpose_offset = + CreateOperator(builder, op_idx, inputs, outputs, options_type, options.Union()); + gd._operators.push_back(transpose_offset); +} + +void OperationExporter::visit(loco::FeatureEncode *node) +{ + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + + if (isNHWC(perm)) + { + // Note that tflite represents feature as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + perm_vec_data[0] = perm->axis(loco::FeatureAxis::Count); + perm_vec_data[1] = perm->axis(loco::FeatureAxis::Height); + perm_vec_data[2] = perm->axis(loco::FeatureAxis::Width); + perm_vec_data[3] = perm->axis(loco::FeatureAxis::Depth); + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void OperationExporter::visit(loco::FeatureDecode *node) +{ + auto decoder = dynamic_cast *>(node->decoder()); + auto perm = decoder->perm(); + + if (isNHWC(perm)) + { + // Note that tflite represents feature as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + perm_vec_data[perm->axis(loco::FeatureAxis::Count)] = 0; + perm_vec_data[perm->axis(loco::FeatureAxis::Height)] = 1; + perm_vec_data[perm->axis(loco::FeatureAxis::Width)] = 2; + perm_vec_data[perm->axis(loco::FeatureAxis::Depth)] = 3; + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void OperationExporter::visit(loco::FilterEncode *node) +{ + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + + if (isNHWC(perm)) + { + // Note that tflite represents filter as NHWC + exportIdentity(node, builder, gd); + } + else + { + std::vector perm_vec_data(4); + // NOTE In tflite, all tensors means NHWC, so 0 = N, 1 = H, 2 = W, 3 = C + perm_vec_data[0] = perm->axis(loco::FilterAxis::Count); + perm_vec_data[1] = perm->axis(loco::FilterAxis::Height); + perm_vec_data[2] = perm->axis(loco::FilterAxis::Width); + perm_vec_data[3] = perm->axis(loco::FilterAxis::Depth); + + exportAsTranspose(node, builder, perm_vec_data, gd); + } +} + +void exportAsReshape(loco::Node *node, FlatBufferBuilder &builder, + std::vector &new_shape_vec, SerializedModelData &gd) +{ + // NOTE TFLite has two ways to get new shape paramter, + // one is by attribute 'new_shape' and the other is by input 'shape'. + // Therefore TFLite interpreter calculates Reshape operation correctly + // if one of them is valid. + // However, since NN runtime usually get new shape parameter by input 'shape', + // passing new shape only by attribute can cause some problems. + // Of course, the opposite situation can be occurred in the future. + // To prevent those problems, we pass new shape parameter not only by attribute + // but also by input. + + auto input_shape_shape_vec_offset = + builder.CreateVector(std::vector{(int32_t)new_shape_vec.size()}); + + size_t input_shape_vec_size = new_shape_vec.size() * sizeof(int32_t); + auto input_shape_input_vec_offset = + builder.CreateVector(reinterpret_cast(new_shape_vec.data()), input_shape_vec_size); + auto input_shape_buffer_offset = CreateBuffer(builder, input_shape_input_vec_offset); + + const auto input_shape_buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(input_shape_buffer_offset); + + auto input_shape_tensor_id = static_cast(gd._tensors.size()); + auto name_offset = builder.CreateString("t_" + std::to_string(input_shape_tensor_id)); + auto input_shape_tensor_offset = CreateTensor( + builder, input_shape_shape_vec_offset, TensorType_INT32, input_shape_buffer_id, name_offset); + gd._tensors.push_back(input_shape_tensor_offset); + + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_RESHAPE); + + std::vector inputs_vec{get_tensor_index(node->arg(0)), input_shape_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + auto new_shape_vec_offset = builder.CreateVector(new_shape_vec); + auto options = CreateReshapeOptions(builder, new_shape_vec_offset); + + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ReshapeOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::DepthwiseFilterEncode *node) +{ + auto ker = node->input(); // [H, W, C, M] + + // tflite represents filter as [1, H, W, C*M] where M is multiplier. + std::vector new_shape_vec(4); + new_shape_vec[0] = 1; + new_shape_vec[1] = ShapeInference::get(ker)._dims[0]; + new_shape_vec[2] = ShapeInference::get(ker)._dims[1]; + new_shape_vec[3] = ShapeInference::get(ker)._dims[2] * ShapeInference::get(ker)._dims[3]; + + exportAsReshape(node, builder, new_shape_vec, gd); +} + +void OperationExporter::visit(loco::BiasAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::FeatureBiasAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->value()), get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +/// @brief Export CONCATENATION of **TWO** tensors only +void OperationExporter::visit(loco::TensorConcat *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_CONCATENATION); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder, node->axis()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_ConcatenationOptions, options.Union()); + + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::BiasEncode *encode) { exportIdentity(encode, builder, gd); } + +void OperationExporter::visit(loco::EltwiseAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseMax *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MAXIMUM); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMaximumMinimumOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_MaximumMinimumOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseMul *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_MUL); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMulOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_MulOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseSub *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SUB); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSubOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_SubOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseDiv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_DIV); + std::vector inputs_vec{get_tensor_index(node->lhs()), get_tensor_index(node->rhs())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateDivOptions(builder); // dummy option + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_DivOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::EltwiseSqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_SQRT); + std::vector inputs_vec{get_tensor_index(node->input())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(loco::FixedReshape *node) +{ + std::vector new_shape_vec; + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + assert(node->dim(axis).known()); + new_shape_vec.push_back(node->dim(axis).value()); + } + + exportAsReshape(node, builder, new_shape_vec, gd); +} + +void OperationExporter::visit(loco::TensorBroadcast *) +{ + INTERNAL_EXN("TensorBroadcast should not exist in the graph"); +} + +void OperationExporter::visit(loco::TensorConstantPad *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(tflite::BuiltinOperator_PAD); + + // make padding attribute an input + auto padding = node->padding(); + // get padding vector size + int32_t padding_vec_size = padding->rank(); + // get byte size of vector + size_t padding_vec_byte_size = padding_vec_size * sizeof(int32_t) * 2; // [rank, 2] + // create vector for data + std::vector padding_vec_data(padding_vec_size * 2); + // set data + for (int32_t i = 0; i < padding_vec_size; i++) + { + padding_vec_data.at(i * 2) = padding->front(i); + padding_vec_data.at(i * 2 + 1) = padding->back(i); + } + // create FlatBuffer vector + auto padding_vec_ptr = builder.CreateVector(reinterpret_cast(padding_vec_data.data()), + padding_vec_byte_size); + + // create buffer + auto padding_buffer_ptr = CreateBuffer(builder, padding_vec_ptr); + // get buffer id + const auto padding_buffer_id = static_cast(gd._buffers.size()); + + gd._buffers.push_back(padding_buffer_ptr); + + // create padding shape vector + auto padding_shape_vec_ptr = builder.CreateVector(std::vector{padding_vec_size, 2}); + // create tensor + auto padding_tensor_ptr = + CreateTensor(builder, padding_shape_vec_ptr, TensorType_INT32, padding_buffer_id); + // get tensor id + const auto padding_tensor_id = static_cast(gd._tensors.size()); + + gd._tensors.push_back(padding_tensor_ptr); + + std::vector inputs_vec{get_tensor_index(node->input()), padding_tensor_id}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +inline flatbuffers::Offset> +CreateCOpCallOptions(flatbuffers::FlatBufferBuilder &fbb, locoex::COpCall *copCall) +{ + // read attrs in FlexBuffer format and pass them to FlatBuffer builder + flexbuffers::Builder flexbuf; + { + size_t map_start = flexbuf.StartMap(); + + // Note: among attrs of COpCall, 'op' and 'name' won't be included into tflite file + auto names = copCall->attr_names(); + for (auto name : names) + { + if (auto int_val = copCall->attr(name)) + flexbuf.Int(name.c_str(), int_val->val()); + else if (auto float_val = copCall->attr(name)) + flexbuf.Float(name.c_str(), float_val->val()); + else + // TODO Support more attribute types + INTERNAL_EXN("Not supported type while writing flexbuffer"); + } + + flexbuf.EndMap(map_start); + flexbuf.Finish(); + } + + auto offset = fbb.CreateVector(flexbuf.GetBuffer()); + + return offset; +} + +void OperationExporter::visit(locoex::COpCall *call) +{ + // Registering this custom op name into tflite Operator Codes table + uint32_t op_idx = gd.registerCustomOpcode(call->op()); + + std::vector inputs_vec; + { + inputs_vec.resize(call->arity()); + for (uint32_t i = 0; i < call->arity(); i++) + inputs_vec[i] = get_tensor_index(call->arg(i)); + } + + std::vector outputs_vec{get_tensor_index(static_cast(call))}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + auto custom_options = CreateCOpCallOptions(builder, call); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + tflite::BuiltinOptions_NONE, // builtin_options_type + 0, // built-in option + custom_options, // custom options + tflite::CustomOptionsFormat_FLEXBUFFERS); + + gd._operators.push_back(op_offset); +} + +void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &data) +{ + // TODO Use explicit tagging to prevent possible mistake + auto isNoOp = [](loco::Node *node) { + if (node->arity() == 1) + { + assert(node->arg(0) != nullptr); + return get_tensor_index(node) == get_tensor_index(node->arg(0)); + } + return false; + }; + + if (isNoOp(node)) + { + // Skip if a given node is marked as NoOp (op with no effect) before + return; + } + + if (auto canonical_node = dynamic_cast(node)) + { // TODO Consider removing this later + OperationExporter exporter{builder, data}; + canonical_node->accept(&exporter); + } + else if (auto tfl_node = dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + tfl_node->accept(&exporter); + } + else if (dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + exporter.visit(dynamic_cast(node)); + } + else + { + assert(false && "unsupported node found"); + } +} + +} // namespace + +namespace exo +{ +namespace tflite_detail +{ + +void exportNodes(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + exportNode(node, builder, gd); + } +} + +} // namespace tflite_detail +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLOperationExporter.h b/compiler/exo/src/TFLite/TFLOperationExporter.h new file mode 100644 index 00000000000..60f2b5eb2a2 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLOperationExporter.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TFL_OPERATION_EXPORTER_H__ +#define __TFL_OPERATION_EXPORTER_H__ + +#include "TFLExporterUtils.h" + +#include + +namespace exo +{ +namespace tflite_detail +{ + +/** + * @brief create Operators corresponding to model nodes + * @param nodes container with nodes + * @param gd information about serializer parts of model + */ +void exportNodes(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, SerializedModelData &gd); + +} // namespace tflite_detail +} // namespace exo + +#endif // __TFL_OPERATION_EXPORTER_H__ diff --git a/compiler/exo/src/TFLite/TFLTensorExporter.cpp b/compiler/exo/src/TFLite/TFLTensorExporter.cpp new file mode 100644 index 00000000000..66854ef8723 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLTensorExporter.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLTensorExporter.h" +#include "TFLTypeInference.h" +#include "ShapeInference.h" + +// TODO Fix include style +#include "loco/IR/Algorithm.h" +#include "loco/IR/CanonicalNode.h" +#include "loco/IR/CanonicalNodeVisitor.h" +#include "loco/IR/DataTypeTraits.h" + +#include "Dialect/IR/TFLNodes.h" + +#include + +using namespace tflite; +using namespace flatbuffers; + +namespace +{ + +using namespace exo; +using namespace exo::tflite_detail; + +class TFLTensorInfo +{ +public: + TFLTensorInfo() = default; + +public: + void name(const std::string &name) { _name = name; } + const std::string &name(void) const { return _name; } + +public: + const tflite::TensorType &dtype(void) const { return _dtype; } + void dtype(const tflite::TensorType &dtype) { _dtype = dtype; } + + const ShapeDescription &shape(void) const { return _shape; } + void shape(const ShapeDescription &shape) { _shape = shape; } + +public: + locoex::TFLConst *tfl_content(void) const { return _tfl_content; } + void tfl_content(locoex::TFLConst *c) { _tfl_content = c; } + +private: + std::string _name; + + tflite::TensorType _dtype; + ShapeDescription _shape; + + // TODO Find a better design + loco::ConstGen *_content = nullptr; // TODO deprecate + locoex::TFLConst *_tfl_content = nullptr; +}; + +using TFLTensorContext = std::vector; + +struct NoOpDetector final : public loco::CanonicalNodeMutableVisitor +{ + bool visit(loco::BiasEncode *) final + { + // BiasEncode is always noop + return true; + } + + bool visit(loco::FilterEncode *node) final + { + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + + return isNHWC(perm); + } + + bool visit(loco::FeatureEncode *node) final + { + auto encoder = dynamic_cast *>(node->encoder()); + auto perm = encoder->perm(); + return isNHWC(perm); + } + + bool visit(loco::FeatureDecode *node) final + { + auto decoder = dynamic_cast *>(node->decoder()); + auto perm = decoder->perm(); + return isNHWC(perm); + } + + // Return false by default + bool visit(loco::Node *) final { return false; } +}; + +bool isNoOp(loco::Node *node) +{ + if (auto canonical_node = dynamic_cast(node)) + { + NoOpDetector d; + return canonical_node->accept(&d); + } + return false; +} + +void allocateTFLiteTensor(loco::Node *node, TFLTensorContext &ctx) +{ + if (isNoOp(node)) + { + assert(node->arity() == 1 && node->arg(0) != nullptr); + set_tensor_index(node, get_tensor_index(node->arg(0))); + return; + } + + auto tensor_index = static_cast(ctx.size()); + // TODO Use Graph-level metadata for Input & Output + auto tensor_name = "t_" + std::to_string(tensor_index); + + TFLTensorInfo tensor_info; + + tensor_info.name(tensor_name); + tensor_info.dtype(TypeInference::get(node)); + tensor_info.shape(ShapeInference::get(node)); + + tensor_info.tfl_content(dynamic_cast(node)); + + set_tensor_index(node, tensor_index); + + ctx.emplace_back(tensor_info); +} + +} // namespace + +namespace +{ + +flatbuffers::Offset> encodeShape(FlatBufferBuilder &builder, + const ShapeDescription &shape) +{ + assert(shape._rank_known && "unknown number of dimensions is not supported"); + return builder.CreateVector(shape._dims); +} + +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, NodeT *) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBufferByDType(FlatBufferBuilder &builder, + locoex::TFLConst *c) +{ + using NativeType = typename loco::DataTypeImpl
::Type; + + std::vector raw_data; + const uint32_t size = c->size
(); + raw_data.reserve(size); + for (uint32_t i = 0; i < size; ++i) + { + raw_data.push_back(c->at
(i)); + } + const size_t raw_size = size * sizeof(NativeType); + auto array_offset = builder.CreateVector(reinterpret_cast(raw_data.data()), raw_size); + return CreateBuffer(builder, array_offset); +} + +template <> +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, locoex::TFLConst *c) +{ + if (c->dtype() == loco::DataType::FLOAT32) + { + return encodeOpBufferByDType(builder, c); + } + else if (c->dtype() == loco::DataType::S32) + { + return encodeOpBufferByDType(builder, c); + } + + INTERNAL_EXN_V("Unsupported datatype", oops::to_uint32(c->dtype())); +} + +} // namespace + +namespace exo +{ +namespace tflite_detail +{ + +void exportOpDefinedTensor(const TFLTensorInfo &info, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + // Create and register output tensor shape + auto shape_offset = encodeShape(builder, info.shape()); + + // encode and register output tensor buffer + auto buffer = info.tfl_content() == nullptr ? encodeOpBuffer(builder) + : encodeOpBuffer(builder, info.tfl_content()); + + auto buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(buffer); + + auto name_offset = builder.CreateString(info.name()); + auto tensor_offset = CreateTensor(builder, shape_offset, info.dtype(), buffer_id, name_offset, + /*quantization*/ 0, /*is_variable*/ false); + gd._tensors.push_back(tensor_offset); +} + +void exportOpDefinedTensors(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + TFLTensorContext tensor_ctx; + + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + allocateTFLiteTensor(node, tensor_ctx); + } + + // add one empty buffer + // note: there's a comment in tflite fbs file + // - Note the 0th entry of this array must be an empty buffer (sentinel). + // - This is a convention so that tensors without a buffer can provide 0 as + // - their buffer. + auto buffer = encodeOpBuffer(builder); + gd._buffers.push_back(buffer); + + for (const auto &tensor_info : tensor_ctx) + { + exportOpDefinedTensor(tensor_info, builder, gd); + } +} + +} // namespace tflite_detail +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLTensorExporter.h b/compiler/exo/src/TFLite/TFLTensorExporter.h new file mode 100644 index 00000000000..97e702665b2 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLTensorExporter.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TFL_TENSOR_EXPORTER_H__ +#define __TFL_TENSOR_EXPORTER_H__ + +#include "TFLExporterUtils.h" + +#include + +#include + +namespace exo +{ +namespace tflite_detail +{ + +/** + * @brief create Tensors corresponding to results of all nodes in graph + * @param computational graph + * @param gd information about serialized parts of model + */ +void exportOpDefinedTensors(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &gd); + +} // namespace tflite_detail +} // namespace exo + +#endif // __TFL_TENSOR_EXPORTER_H__ diff --git a/compiler/exo/src/TFLite/TFLTypeInference.cpp b/compiler/exo/src/TFLite/TFLTypeInference.cpp new file mode 100644 index 00000000000..8d6bb8d8ca9 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLTypeInference.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLTypeInference.h" + +#include "schema_generated.h" + +#include "Dialect/Service/TFLTypeInferenceRule.h" +#include "Dialect/IR/TFLDialect.h" + +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace +{ + +tflite::TensorType translateLocoTypeToTFLite(loco::DataType dtype) +{ + switch (dtype) + { + case loco::DataType::U8: + return tflite::TensorType_UINT8; + // case loco::DataType::U16: unsupported + // case loco::DataType::U32: unsupported + // case loco::DataType::U64: unsupported + case loco::DataType::S8: + return tflite::TensorType_INT8; + case loco::DataType::S16: + return tflite::TensorType_INT16; + case loco::DataType::S32: + return tflite::TensorType_INT32; + case loco::DataType::S64: + return tflite::TensorType_INT64; + case loco::DataType::FLOAT16: + return tflite::TensorType_FLOAT16; + case loco::DataType::FLOAT32: + return tflite::TensorType_FLOAT32; + // case loco::DataType::FLOAT64: unsupported + default: + break; + } + + INTERNAL_EXN_V("Trying to converte unsupported loco dtype", oops::to_uint32(dtype)); +} + +} // namespace + +namespace exo +{ + +tflite::TensorType TypeInference::get(loco::Node *node) +{ + assert(loco::dtype_known(node)); + return translateLocoTypeToTFLite(loco::dtype_get(node)); +} + +} // namespace exo diff --git a/compiler/exo/src/TFLite/TFLTypeInference.h b/compiler/exo/src/TFLite/TFLTypeInference.h new file mode 100644 index 00000000000..3d3a2e4809e --- /dev/null +++ b/compiler/exo/src/TFLite/TFLTypeInference.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TFL_TYPE_INFERENCE_H__ +#define __TFL_TYPE_INFERENCE_H__ + +#include "TFLExporterUtils.h" + +#include + +namespace exo +{ + +/** + * @brief Get the type of each node as NodeAnnotation + * + * HOW TO USE + * + * TypeInference::get(g->nodes()->at(0)); + * TypeInference::get(g->nodes()->at(...)); + */ +struct TypeInference +{ + static tflite::TensorType get(loco::Node *node); +}; + +} // namespace exo + +#endif // __TFL_TYPE_INFERENCE_H__ diff --git a/compiler/exo/src/TFLite/TFLTypeInference.test.cpp b/compiler/exo/src/TFLite/TFLTypeInference.test.cpp new file mode 100644 index 00000000000..0712f0a25f9 --- /dev/null +++ b/compiler/exo/src/TFLite/TFLTypeInference.test.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TFLTypeInference.h" +#include "Pass/TypeInferencePass.h" + +#include +#include + +#include + +using stdex::make_unique; + +namespace +{ + +class Sequential +{ +public: + loco::Pull *addPullLayer(const loco::DataType &dtype = loco::DataType::FLOAT32) + { + loco::Pull *pull = _graph.nodes()->create(); + + auto graph_input = _graph.inputs()->create(); + graph_input->name("graph_input"); + loco::link(graph_input, pull); + + pull->dtype(dtype); + setSampleShape(pull); + + return last(pull); + } + + loco::ReLU *addReLULayer(void) + { + loco::ReLU *relu = _graph.nodes()->create(); + + relu->input(_last); + + return last(relu); + } + + loco::Push *addPushLayer(void) + { + loco::Push *push = _graph.nodes()->create(); + + auto graph_output = _graph.outputs()->create(); + graph_output->name("graph_output"); + loco::link(graph_output, push); + + push->from(_last); + + return last(push); + } + + loco::Graph *graph() { return &_graph; } + +private: + template uint32_t setSampleShape(T *op) + { + const uint32_t n = 1; + const uint32_t h = 100; + const uint32_t w = 100; + const uint32_t c = 3; + op->rank(4); + op->dim(0).set(n); + op->dim(1).set(c); + op->dim(2).set(h); + op->dim(3).set(w); + return n * h * w * c; + } + + template T *last(T *node) + { + _last = node; + return node; + } + +private: + loco::Graph _graph; + loco::Node *_last; +}; + +struct TypeInferenceTest : public Sequential, public ::testing::Test +{ + virtual ~TypeInferenceTest() = default; +}; + +} // namespace + +// TypeInference SHOULD PROPAGATE type information properly +TEST_F(TypeInferenceTest, Regression_0000) +{ + auto pull = addPullLayer(loco::DataType::S8); + auto relu = addReLULayer(); + auto push = addPushLayer(); + + using namespace exo; + + TypeInferencePass type_inf_pass; + type_inf_pass.run(graph()); + + ASSERT_EQ(TypeInference::get(relu), tflite::TensorType_INT8); + ASSERT_EQ(TypeInference::get(push), tflite::TensorType_INT8); +} diff --git a/compiler/exo/src/TestGraph.h b/compiler/exo/src/TestGraph.h new file mode 100644 index 00000000000..f919cc9ae6d --- /dev/null +++ b/compiler/exo/src/TestGraph.h @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TEST_GRAPH_H__ +#define __TEST_GRAPH_H__ + +#include "Dialect/IR/TFLNodes.h" +#include "GraphBlock.h" +#include "TestHelper.h" + +#include + +#include + +#include + +namespace exo +{ +namespace test +{ + +class TestGraph +{ +public: + std::unique_ptr g; + loco::Pull *pull; + loco::Push *push; + + TestGraph() // creates Pull and Push + { + g = loco::make_graph(); + + pull = g->nodes()->create(); + + push = g->nodes()->create(); + + auto input = g->inputs()->create(); + { + input->name("input"); + loco::link(input, pull); + } + auto output = g->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + _next_input = pull; + } + + loco::Graph *graph() { return g.get(); } + + /// @brief Creates node with NO arg and appends it to graph + template T *append() + { + auto node = g->nodes()->create(); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=1) with arg1 as an input and appends it to graph + template T *append(loco::Node *arg1) + { + auto node = g->nodes()->create(); + setInput(node, arg1); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=2) with arg1, arg2 as inputs and appends it to graph + template T *append(loco::Node *arg1, loco::Node *arg2) + { + auto node = g->nodes()->create(); + setInput(node, arg1, arg2); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=3) with arg1, arg2, arg3 as inputs and appends it to graph + template T *append(loco::Node *arg1, loco::Node *arg2, loco::Node *arg3) + { + auto node = g->nodes()->create(); + setInput(node, arg1, arg2, arg3); + _next_input = node; + + return node; + } + + // push will get the last appended node + void complete() { push->from(_next_input); } + + void complete(loco::Node *last_node) { push->from(last_node); } + +private: + // arity 1 + void setInput(loco::Node *node, loco::Node *) { assert(false && "NYI"); }; + + void setInput(loco::AvgPool2D *node, loco::Node *input) { node->ifm(input); } + void setInput(loco::BiasDecode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::BiasEncode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::FeatureDecode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::FeatureEncode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::MaxPool2D *node, loco::Node *input) { node->ifm(input); } + void setInput(loco::Push *node, loco::Node *input) { node->from(input); }; + void setInput(loco::ReLU *node, loco::Node *input) { node->input(input); }; + void setInput(loco::ReLU6 *node, loco::Node *input) { node->input(input); }; + void setInput(loco::Tanh *node, loco::Node *input) { node->input(input); }; + void setInput(loco::TensorTranspose *node, loco::Node *input) { node->input(input); }; + + void setInput(locoex::TFLAveragePool2D *node, loco::Node *input) { node->value(input); }; + void setInput(locoex::TFLMaxPool2D *node, loco::Node *input) { node->value(input); }; + void setInput(locoex::TFLRelu *node, loco::Node *input) { node->features(input); }; + void setInput(locoex::TFLRelu6 *node, loco::Node *input) { node->features(input); }; + + // arity 2 + void setInput(loco::Node *node, loco::Node *, loco::Node *) { assert(false && "NYI"); }; + + void setInput(loco::Conv2D *node, loco::Node *input, loco::Node *filter) + { + node->ifm(input); + node->ker(filter); + } + + void setInput(loco::EltwiseAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->lhs(arg1); + node->rhs(arg2); + }; + + void setInput(loco::FeatureBiasAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->value(arg1); + node->bias(arg2); + }; + + void setInput(locoex::TFLAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(locoex::TFLMul *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(locoex::TFLSub *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(locoex::TFLTranspose *node, loco::Node *arg1, loco::Node *arg2) + { + node->a(arg1); + node->perm(arg2); + }; + + // arity 3 + void setInput(loco::Node *node, loco::Node *, loco::Node *, loco::Node *) + { + assert(false && "NYI"); + }; + + void setInput(locoex::TFLConv2D *node, loco::Node *input, loco::Node *filter, loco::Node *bias) + { + node->input(input); + node->filter(filter); + node->bias(bias); + } + +private: + loco::Node *_next_input; +}; + +enum class ExampleGraphType +{ + FeatureBiasAdd, + ConstGen_ReLU, + FilterEncode_FilterDecode, + Transpose, + + TFLTranspose, +}; + +template class ExampleGraph; + +/** + * @brief Class to create the following: + * + * Pull - FeatureEncoder - FeatureBiasAdd - FeatureDecode - Push + * | + * ConstGen - BiasEncode --+ + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::FeatureEncode *fea_enc = nullptr; + loco::ConstGen *constgen = nullptr; + loco::BiasEncode *bias_enc = nullptr; + loco::FeatureBiasAdd *fea_bias_add = nullptr; + loco::FeatureDecode *fea_dec = nullptr; + +public: + ExampleGraph() + { + fea_enc = exo::make_feature_encode(pull); + constgen = append(); + bias_enc = append(constgen); + fea_bias_add = append(fea_enc, bias_enc); + fea_dec = exo::make_feature_decode(fea_bias_add); + complete(fea_dec); + } +}; + +/** + * @brief Class to creates the following: + * + * ConstGen -- ReLU -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::ConstGen *constgen = nullptr; + loco::ReLU *relu = nullptr; + +public: + ExampleGraph() + { + constgen = append(); + relu = append(constgen); + complete(relu); + } +}; + +/** + * @brief Class to creates the following: + * + * Pull -- Transpose -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::TensorTranspose *transpose = nullptr; + +public: + ExampleGraph() + { + transpose = append(pull); + complete(transpose); + } +}; + +/** + * @brief Class to creates the following: + * + * Pull -- FilterEncode -- FilterDecode -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::FilterEncode *filterEncode = nullptr; + loco::FilterDecode *filterDecode = nullptr; + +public: + ExampleGraph() + { + filterEncode = exo::make_filter_encode(pull); // from Tensorflow + filterDecode = + exo::make_filter_decode(filterEncode); // to Tensorflow Lite + complete(filterDecode); + } +}; + +/** + * @brief Class to create the following: + * + * Pull -- TFLTranspose -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::ConstGen *const_perm = nullptr; + locoex::TFLTranspose *tfl_transpose = nullptr; + +public: + ExampleGraph() + { + const_perm = append(); + tfl_transpose = append(pull, const_perm); + complete(tfl_transpose); + } +}; + +} // namespace test +} // namespace exo + +#endif // __TEST_GRAPH_H__ diff --git a/compiler/exo/src/TestHelper.h b/compiler/exo/src/TestHelper.h new file mode 100644 index 00000000000..1a3de50f5eb --- /dev/null +++ b/compiler/exo/src/TestHelper.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TEST_HELPER_H__ +#define __TEST_HELPER_H__ + +#include "Check.h" +#include "ProgressReporter.h" +#include "Passes.h" + +#include +#include + +#include + +#include + +#include + +/** + * @brief Check the number of nodes in a graph starting from OUTPUTS + */ +#define EXO_TEST_ASSERT_NODE_COUNT(OUTPUTS, COUNT) \ + { \ + auto v = loco::postorder_traversal(OUTPUTS); \ + ASSERT_EQ(v.size(), (COUNT)); \ + } + +namespace exo +{ +namespace test +{ + +/** + * @brief Phase for test, that is used to test pass. This phase initially adds TypeInferencePass + * and ShapeInferencePass + */ +class TypeShapeReadyPhase +{ +public: + TypeShapeReadyPhase() + { + // Type and Shape inference is prerequisite for run other test + _phase.emplace_back(stdex::make_unique<::exo::TypeInferencePass>()); + _phase.emplace_back(stdex::make_unique<::exo::ShapeInferencePass>()); + } + + template void add_pass() { _phase.emplace_back(stdex::make_unique()); } + + void run(loco::Graph *g) + { + const auto restart = logo::PhaseStrategy::Restart; + logo::PhaseRunner phase_runner{g}; + + ::exo::ProgressReporter prog(g, restart); + phase_runner.attach(&prog); + phase_runner.run(_phase); + } + +private: + logo::Phase _phase; +}; + +/** + * @brief Get the only succ object of type LocoNodeT. (The name `only succ` comes from English word + * `only child`.) + * parent must have 1 succ only. + * When there is no succ of type LocoNodeT, nullptr will be returned. + */ +template inline LocoNodeT *get_only_succ(loco::Node *parent) +{ + auto succs = loco::succs(parent); + EXO_ASSERT(succs.size() == 1, "parent has more than 1 succs."); + + return dynamic_cast(*succs.begin()); +} + +template inline T *find_first_node_bytype(loco::Graph *g) +{ + T *first_node = nullptr; + loco::Graph::NodeContext *nodes = g->nodes(); + uint32_t count = nodes->size(); + + for (uint32_t i = 0; i < count; ++i) + { + first_node = dynamic_cast(nodes->at(i)); + if (first_node != nullptr) + break; + } + + return first_node; +} + +} // namespace test +} // namespace exo + +#endif // __TEST_HELPER_H__ diff --git a/compiler/fipe/CMakeLists.txt b/compiler/fipe/CMakeLists.txt new file mode 100644 index 00000000000..2cabf6279d5 --- /dev/null +++ b/compiler/fipe/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(fipe INTERFACE) +target_include_directories(fipe INTERFACE include) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(fipe_test fipe.test.cpp) +target_link_libraries(fipe_test fipe) diff --git a/compiler/fipe/fipe.test.cpp b/compiler/fipe/fipe.test.cpp new file mode 100644 index 00000000000..347f26f9b3b --- /dev/null +++ b/compiler/fipe/fipe.test.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "fipe.h" + +#include + +#include + +namespace +{ + +int dec(int n) { return n - 1; } + +} // namespace + +TEST(FunctionPipeTests, top_level_function) +{ + // GCC rejects this code if dec is not wrapped by "fipe::wrap" + // TODO Find a better way + ASSERT_EQ(4 | fipe::wrap(dec), 3); +} + +TEST(FunctionPipeTests, static_method) +{ + struct Sample + { + static int dbl(int n) { return n * 2; } + }; + + ASSERT_EQ(4 | fipe::wrap(Sample::dbl), 8); +} + +TEST(FunctionPipeTests, normal_method) +{ + struct Sample + { + public: + int shift(int n) { return n + shiftamt; } + + private: + int shiftamt = 6; + }; + + using namespace std::placeholders; + + Sample s; + + auto value = 4 | std::bind(&Sample::shift, &s, _1); + + ASSERT_EQ(value, 10); +} + +TEST(FunctionPipeTests, lambda) +{ + auto inc = [](int n) { return n + 1; }; + ASSERT_EQ(4 | inc, 5); +} + +TEST(FunctionPipeTests, functor) { ASSERT_EQ(4 | std::negate(), -4); } diff --git a/compiler/fipe/include/fipe.h b/compiler/fipe/include/fipe.h new file mode 100644 index 00000000000..0a661aa04dd --- /dev/null +++ b/compiler/fipe/include/fipe.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __FIPE_H__ +#define __FIPE_H__ + +#include +#include + +namespace fipe +{ + +/** + * @brief Convert a function pointer as a callable std::function + * + * NOTE "fipe" works only for unary functions. + */ +template std::function wrap(Ret (*p)(Arg)) { return p; } + +} // namespace fipe + +template auto operator|(T &&v, Callable &&f) -> decltype(f(v)) +{ + return std::forward(f)(v); +} + +#endif // __FIPE_H__ diff --git a/compiler/gen-core/CMakeLists.txt b/compiler/gen-core/CMakeLists.txt new file mode 100644 index 00000000000..3732f493ba4 --- /dev/null +++ b/compiler/gen-core/CMakeLists.txt @@ -0,0 +1,17 @@ +find_package(HDF5 COMPONENTS CXX QUIET) + +if(NOT HDF5_FOUND) + return() +endif(NOT HDF5_FOUND) + +nnas_find_package(TensorFlow QUIET) + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_library(gen_core STATIC ${SOURCES}) +set_target_properties(gen_core PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(gen_core PUBLIC include) +target_include_directories(gen_core PRIVATE ${HDF5_INCLUDE_DIRS}) +target_link_libraries(gen_core ${HDF5_CXX_LIBRARIES}) +target_link_libraries(gen_core tfinfo_v2) +target_link_libraries(gen_core angkor) diff --git a/compiler/gen-core/README.md b/compiler/gen-core/README.md new file mode 100644 index 00000000000..cc98ef00b75 --- /dev/null +++ b/compiler/gen-core/README.md @@ -0,0 +1,3 @@ +# gen-core + +_gen-core_ is a common library used by _gen-tf-input_, _gen-tf-output_, and _gen-tflite-output_. diff --git a/compiler/gen-core/include/gencore/HDF5Common.h b/compiler/gen-core/include/gencore/HDF5Common.h new file mode 100644 index 00000000000..87367c99c64 --- /dev/null +++ b/compiler/gen-core/include/gencore/HDF5Common.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HDF5COMMON_H__ +#define __HDF5COMMON_H__ + +#include + +namespace gencore +{ + +/** + * @brief Construct HDF5-compatible dataset name from a given string + * + * When someone attempts to access 'A/B/C' dataset, HDF5 tries to open + * dataset C in group B in top-level group A, which means that dataset + * names SHOULD NOT contain '/' in it. + * + * This mangle function replaces all the occurence of '/' in a given + * string with '_' to construct HDF5-compatible dataset name. + */ +std::string mangle(const std::string &); + +#if 0 +Let us assume that a tensor context includes N + 1 tensors. + +Then, HDF5 export will generate a HDF5 file whose structure is given as follows: +[value group]/ + [file 0] <- A dataset that contains the value of 1st (=0) tensor + [file 1] + ... + [file N] +[name group]/ + [file 0] <- An attribute that contains the name of 1st (=0) tensor + [file 1] + ... + [file N] +#endif + +/// @brief Return the name of "value group" +std::string value_grpname(void); +/// @brief Return the name of n-th tensor dataset +std::string value_filename(uint32_t n); + +/// @brief Return the name of "name group" +std::string name_grpname(void); +/// @brief Return the name of n-th tensor attribute +std::string name_filename(uint32_t n); + +} // namespace gencore + +#endif // __HDF5COMMON_H__ diff --git a/compiler/gen-core/include/gencore/HDF5Exporter.h b/compiler/gen-core/include/gencore/HDF5Exporter.h new file mode 100644 index 00000000000..10cc1c6131d --- /dev/null +++ b/compiler/gen-core/include/gencore/HDF5Exporter.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GENCORE_HDF5EXPORTER_H__ +#define __GENCORE_HDF5EXPORTER_H__ + +#include "HDF5Common.h" + +#include +#include + +#include + +namespace gencore +{ + +class H5Exporter +{ +public: + H5Exporter(const std::string &path) : _file{path.c_str(), H5F_ACC_TRUNC} + { + _value_grp = _file.createGroup(value_grpname()); + _name_grp = _file.createGroup(name_grpname()); + } + +public: + template + void write(uint32_t nth, const std::string &name, const angkor::TensorShape &shape, + const nncc::core::ADT::tensor::Reader
&buf_reader); + +private: + H5::H5File _file; + H5::Group _value_grp; + H5::Group _name_grp; +}; + +} // namespace gencore + +#endif // __GENCORE_HDF5EXPORTER_H__ diff --git a/compiler/gen-core/include/gencore/HDF5Importer.h b/compiler/gen-core/include/gencore/HDF5Importer.h new file mode 100644 index 00000000000..85374419911 --- /dev/null +++ b/compiler/gen-core/include/gencore/HDF5Importer.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GENCORE_HDF5IMPORTER_H__ +#define __GENCORE_HDF5IMPORTER_H__ + +#include "HDF5Common.h" + +#include + +#include +#include + +#include + +namespace gencore +{ + +class HDF5Importer +{ +public: + HDF5Importer(const std::string &path) : _file{path, H5F_ACC_RDONLY} + { + _value_grp = _file.openGroup(value_grpname()); + } + +public: + /** + * @brief Reads tensor data from file and store it into buf_accessor + */ + template + void read(uint32_t nth, const std::string &name, const angkor::TensorShape &shape, + nncc::core::ADT::tensor::Accessor
*buf_accessor); + +private: + H5::H5File _file; + H5::Group _value_grp; +}; + +} // namespace gencore + +#endif // __GENCORE_HDF5IMPORTER_H__ diff --git a/compiler/gen-core/requires.cmake b/compiler/gen-core/requires.cmake new file mode 100644 index 00000000000..a424f1f4a1e --- /dev/null +++ b/compiler/gen-core/requires.cmake @@ -0,0 +1,2 @@ +require("tfinfo-v2") +require("angkor") diff --git a/compiler/gen-core/src/HDF5Common.cpp b/compiler/gen-core/src/HDF5Common.cpp new file mode 100644 index 00000000000..c254d9e1e8d --- /dev/null +++ b/compiler/gen-core/src/HDF5Common.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "gencore/HDF5Common.h" + +namespace gencore +{ + +std::string mangle(const std::string &name) +{ + std::string res = name; + + for (uint32_t n = 0; n < res.size(); ++n) + { + if (res.at(n) == '/') + { + res.at(n) = '_'; + } + } + + return res; +} + +std::string value_grpname(void) { return "value"; } +std::string value_filename(uint32_t n) { return std::to_string(n); } + +std::string name_grpname(void) { return "name"; } +std::string name_filename(uint32_t n) { return std::to_string(n); } + +} // namespace gencore diff --git a/compiler/gen-core/src/HDF5Exporter.cpp b/compiler/gen-core/src/HDF5Exporter.cpp new file mode 100644 index 00000000000..6b77710c409 --- /dev/null +++ b/compiler/gen-core/src/HDF5Exporter.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "gencore/HDF5Exporter.h" + +#include +#include +#include +#include + +#include + +namespace +{ + +template H5::PredType get_h5_datatype(); + +template <> H5::PredType get_h5_datatype() { return H5::PredType::NATIVE_FLOAT; } + +template H5::PredType get_h5_store_format(); + +template <> H5::PredType get_h5_store_format() { return H5::PredType::IEEE_F32BE; } + +} // namespace + +namespace gencore +{ + +template +void H5Exporter::write(uint32_t nth, const std::string &name, const angkor::TensorShape &shape, + const nncc::core::ADT::tensor::Reader
&buf_reader) +{ + // Record tensor values + { + const auto rank = shape.rank(); + + hsize_t dims[rank]; + + for (uint32_t axis = 0; axis < rank; ++axis) + { + dims[axis] = shape.dim(axis); + } + + H5::DataSpace dataspace(rank, dims); + + auto dataset = + _value_grp.createDataSet(value_filename(nth), get_h5_store_format
(), dataspace); + + DT *h5_data = new DT[nncc::core::ADT::tensor::num_elements(shape)]; + { + using nncc::core::ADT::tensor::IndexEnumerator; + using nncc::core::ADT::tensor::LexicalLayout; + + LexicalLayout layout{}; + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + auto i = e.current(); + h5_data[layout.offset(shape, i)] = buf_reader.at(i); + } + } + + dataset.write(h5_data, get_h5_datatype
()); + + delete[] h5_data; + } + + // Record name + { + H5::DataSpace name_dataspace(H5S_SCALAR); + H5::StrType name_datatype(H5::PredType::C_S1, name.size()); + + auto name_attr = _name_grp.createAttribute(value_filename(nth), name_datatype, name_dataspace); + + name_attr.write(name_datatype, name); + } +} + +// template instantiation +template void H5Exporter::write(uint32_t, const std::string &, const angkor::TensorShape &, + const nncc::core::ADT::tensor::Reader &); + +} // namespace gencore diff --git a/compiler/gen-core/src/HDF5Importer.cpp b/compiler/gen-core/src/HDF5Importer.cpp new file mode 100644 index 00000000000..83691b20b46 --- /dev/null +++ b/compiler/gen-core/src/HDF5Importer.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "gencore/HDF5Importer.h" +#include "gencore/HDF5Common.h" + +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + +template H5::PredType get_h5_datatype(); + +template <> H5::PredType get_h5_datatype() { return H5::PredType::NATIVE_FLOAT; } + +template H5::PredType get_h5_store_format(); + +template <> H5::PredType get_h5_store_format() { return H5::PredType::IEEE_F32BE; } + +} // namespace + +namespace gencore +{ + +template +void HDF5Importer::read(uint32_t nth, const std::string &name, const angkor::TensorShape &shape, + nncc::core::ADT::tensor::Accessor
*buf_accessor) +{ + assert(buf_accessor != nullptr); + + try + { + auto dataset = _value_grp.openDataSet(value_filename(nth)); + + assert(dataset.getDataType() == get_h5_store_format
()); + + std::vector
file_buf; + { + file_buf.resize(nncc::core::ADT::tensor::num_elements(shape)); + dataset.read(file_buf.data(), get_h5_datatype
()); + } + + using nncc::core::ADT::tensor::IndexEnumerator; + using nncc::core::ADT::tensor::LexicalLayout; + + LexicalLayout layout{}; + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + auto i = e.current(); + buf_accessor->at(i) = file_buf[layout.offset(shape, i)]; + } + } + catch (const H5::FileIException &) + { + // Skip if data is not present in HDF5 file + } +} + +// template instantiation +template void HDF5Importer::read(uint32_t, const std::string &, const angkor::TensorShape &, + nncc::core::ADT::tensor::Accessor *); + +} // namespace gencore diff --git a/compiler/gen-tf-input/CMakeLists.txt b/compiler/gen-tf-input/CMakeLists.txt new file mode 100644 index 00000000000..12b78b5b343 --- /dev/null +++ b/compiler/gen-tf-input/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") + +# making gen-tf-input +add_executable(gen-tf-input ${SOURCES}) diff --git a/compiler/gen-tf-input/README.md b/compiler/gen-tf-input/README.md new file mode 100644 index 00000000000..2ea6f71b429 --- /dev/null +++ b/compiler/gen-tf-input/README.md @@ -0,0 +1,11 @@ +# gen-tf-input + +_gen-tf-input_ generates random input data for testing in HDF5 format. + +# How to use + +Use the following to generate a file that contains random values of input tensors: + +``` +$ gen-tf-input +``` diff --git a/compiler/gen-tf-input/src/Driver.cpp b/compiler/gen-tf-input/src/Driver.cpp new file mode 100644 index 00000000000..f2ce20f16ce --- /dev/null +++ b/compiler/gen-tf-input/src/Driver.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include + +namespace +{ + +void print_help() +{ + std::cerr << "This generates a file that contains random values of input tensors" << std::endl + << "Usage:" << std::endl + << " gen-tf-input " << std::endl; +} + +} // namespace + +namespace +{ + +void gen_input(const std::string info_v2_path, const std::string pb_path, + const std::string input_path) +{ + // TODO write code + assert("Not yet written" && nullptr); +} + +} // namespace + +int main(int argc, char **argv) +{ + // TODO We need better args parsing in future + if (argc != 4) + { + print_help(); + return 255; + } + + gen_input(argv[1], argv[2], argv[3]); + + return 0; +} diff --git a/compiler/gen-tf-output/CMakeLists.txt b/compiler/gen-tf-output/CMakeLists.txt new file mode 100644 index 00000000000..c2b91a9cdb8 --- /dev/null +++ b/compiler/gen-tf-output/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(gen-tf-output ${SOURCES}) diff --git a/compiler/gen-tf-output/README.md b/compiler/gen-tf-output/README.md new file mode 100644 index 00000000000..ca54c75d53c --- /dev/null +++ b/compiler/gen-tf-output/README.md @@ -0,0 +1,13 @@ +# gen-tf-output + +_gen-tf-output_ generates a file containing the result of running TensorFlow in HDF5 format. + +# How to use + +Use the following: + +``` +$ gen-tf-output +``` + +Use _gen_tf_input_ to generate `` file. diff --git a/compiler/gen-tf-output/src/Driver.cpp b/compiler/gen-tf-output/src/Driver.cpp new file mode 100644 index 00000000000..20965198799 --- /dev/null +++ b/compiler/gen-tf-output/src/Driver.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include +#include + +namespace +{ + +void print_help() +{ + std::cerr << "This generates a file that contains result of running TensorFlow" << std::endl + << "Usage:" << std::endl + << "\t" + << "gen-tf-output " + "" + << std::endl; +} + +void gen_tf_output(const std::string info_v2_path, const std::string pb_path, + const std::string input_path, const std::string output_path) +{ + throw std::runtime_error("Not Yet Implemented"); +} + +} // namespace + +int main(int argc, char **argv) +{ + // TODO We need better args parsing in future + if (argc != 5) + { + print_help(); + return 255; + } + + gen_tf_output(argv[1], argv[2], argv[3], argv[4]); + + return 0; +} diff --git a/compiler/gen-tflite-output/CMakeLists.txt b/compiler/gen-tflite-output/CMakeLists.txt new file mode 100644 index 00000000000..1c9d2601d28 --- /dev/null +++ b/compiler/gen-tflite-output/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(gen-tflite-output ${SOURCES}) diff --git a/compiler/gen-tflite-output/README.md b/compiler/gen-tflite-output/README.md new file mode 100644 index 00000000000..a9c985006fe --- /dev/null +++ b/compiler/gen-tflite-output/README.md @@ -0,0 +1,14 @@ +# gen-tflite-output + +_gen-tflite-output_ generates a file containing the result of running TensorFlow Lite interpreter +in HDF5 format. + +# How to use + +Use the following: + +``` +$ gen-tflite-output +``` + +Use _gen_tf_input_ to generate `` file. diff --git a/compiler/gen-tflite-output/src/Driver.cpp b/compiler/gen-tflite-output/src/Driver.cpp new file mode 100644 index 00000000000..90559ec2f2c --- /dev/null +++ b/compiler/gen-tflite-output/src/Driver.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include +#include + +namespace +{ + +void print_help() +{ + std::cerr << "This generates a file that contains result of running TensorFlow Lite interpreter" + << std::endl + << "Usage:" << std::endl + << "\t" + << "$ gen-tflite-output " + << std::endl; +} + +void gen_tflite_output(const std::string tflite_path, const std::string input_path, + const std::string output_path) +{ + throw std::runtime_error("Not Yet Implemented"); +} + +} // namespace + +int main(int argc, char **argv) +{ + // TODO We need better args parsing in future + if (argc != 4) + { + print_help(); + return 255; + } + + gen_tflite_output(argv[1], argv[2], argv[3]); + + return 0; +} diff --git a/compiler/hermes-std/CMakeLists.txt b/compiler/hermes-std/CMakeLists.txt new file mode 100644 index 00000000000..c7b02e14c62 --- /dev/null +++ b/compiler/hermes-std/CMakeLists.txt @@ -0,0 +1,27 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(hermes_std STATIC ${SOURCES}) +set_target_properties(hermes_std PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(hermes_std PUBLIC include) +target_link_libraries(hermes_std PUBLIC hermes) +target_link_libraries(hermes_std PRIVATE stdex) +target_link_libraries(hermes_std PRIVATE pepper_strcast) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(hermes_std PRIVATE nncc_common) +target_link_libraries(hermes_std PUBLIC nncc_coverage) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(hermes_std_test ${TESTS}) +target_link_libraries(hermes_std_test stdex) +target_link_libraries(hermes_std_test hermes_std) diff --git a/compiler/hermes-std/README.md b/compiler/hermes-std/README.md new file mode 100644 index 00000000000..f5f4b860fe5 --- /dev/null +++ b/compiler/hermes-std/README.md @@ -0,0 +1,3 @@ +# hermes-std + +_hermes-std_ is a collection of **primitive** _hermes_ extensions. diff --git a/compiler/hermes-std/include/hermes/ConsoleReporter.h b/compiler/hermes-std/include/hermes/ConsoleReporter.h new file mode 100644 index 00000000000..e09dd5785f4 --- /dev/null +++ b/compiler/hermes-std/include/hermes/ConsoleReporter.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_STD_CONSOLE_REPORTER_H__ +#define __HERMES_STD_CONSOLE_REPORTER_H__ + +#include + +namespace hermes +{ + +/** + * @brief Print messages into standard console + */ +struct ConsoleReporter final : public hermes::Sink +{ + void notify(const Message *m) final; +}; + +} // namespace hermes + +#endif // __HERMES_STD_CONSOLE_REPORTER_H__ diff --git a/compiler/hermes-std/include/hermes/EnvConfig.h b/compiler/hermes-std/include/hermes/EnvConfig.h new file mode 100644 index 00000000000..e4c392fd6de --- /dev/null +++ b/compiler/hermes-std/include/hermes/EnvConfig.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_STD_ENV_CONFIG_H__ +#define __HERMES_STD_ENV_CONFIG_H__ + +#include + +#include + +namespace hermes +{ + +using EnvName = std::string; + +enum class EnvFormat +{ + // Non-zero -> Enable + // Zero -> Diable + BooleanNumber, +}; + +template class EnvConfig; + +template <> class EnvConfig : public Config +{ +public: + EnvConfig(const EnvName &name); + +public: + virtual ~EnvConfig() = default; + +public: + void configure(const Source *, SourceSetting &) const final; + +private: + bool _enabled = false; +}; + +} // namespace hermes + +#endif // __HERMES_STD_ENV_CONFIG_H__ diff --git a/compiler/hermes-std/requires.cmake b/compiler/hermes-std/requires.cmake new file mode 100644 index 00000000000..4aa6b1528a5 --- /dev/null +++ b/compiler/hermes-std/requires.cmake @@ -0,0 +1 @@ +require("pepper-strcast") diff --git a/compiler/hermes-std/src/ConsoleReporter.cpp b/compiler/hermes-std/src/ConsoleReporter.cpp new file mode 100644 index 00000000000..3cc9f09ed76 --- /dev/null +++ b/compiler/hermes-std/src/ConsoleReporter.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/ConsoleReporter.h" + +#include + +namespace hermes +{ + +void ConsoleReporter::notify(const hermes::Message *m) +{ + for (uint32_t n = 0; n < m->text()->lines(); ++n) + { + std::cout << m->text()->line(n) << std::endl; + } +} + +} // namespace hermes diff --git a/compiler/hermes-std/src/ConsoleReporter.test.cpp b/compiler/hermes-std/src/ConsoleReporter.test.cpp new file mode 100644 index 00000000000..c2e1f1c8566 --- /dev/null +++ b/compiler/hermes-std/src/ConsoleReporter.test.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/ConsoleReporter.h" + +#include + +#include + +#include + +TEST(ConsoleReporterTest, constructor) +{ + hermes::ConsoleReporter r; + + SUCCEED(); +} + +TEST(ConsoleReporterTest, notify) +{ + hermes::Message m; + { + std::stringstream ss; + + ss << "Hello" << std::endl; + + m.text(stdex::make_unique(ss)); + } + + hermes::ConsoleReporter r; + + ASSERT_NO_THROW(r.notify(&m)); +} diff --git a/compiler/hermes-std/src/EnvConfig.cpp b/compiler/hermes-std/src/EnvConfig.cpp new file mode 100644 index 00000000000..e8f7fcda454 --- /dev/null +++ b/compiler/hermes-std/src/EnvConfig.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/EnvConfig.h" + +#include + +namespace hermes +{ + +EnvConfig::EnvConfig(const EnvName &name) +{ + auto s = std::getenv(name.c_str()); + _enabled = (pepper::safe_strcast(s, 0 /* DISABLE BY DEFAULT */) != 0); +} + +void EnvConfig::configure(const Source *, SourceSetting &setting) const +{ + if (_enabled) + { + // Enable all the sources + setting.accept_all(); + } + else + { + // Disable all the sources + setting.reject_all(); + } +} + +} // namespace hermes diff --git a/compiler/hermes/CMakeLists.txt b/compiler/hermes/CMakeLists.txt new file mode 100644 index 00000000000..5debfbca050 --- /dev/null +++ b/compiler/hermes/CMakeLists.txt @@ -0,0 +1,28 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(hermes STATIC ${SOURCES}) +set_target_properties(hermes PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(hermes PUBLIC include) +target_link_libraries(hermes PRIVATE stdex) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(hermes PRIVATE nncc_common) +target_link_libraries(hermes PUBLIC nncc_coverage) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +add_executable(hermes_test ${TESTS}) +target_link_libraries(hermes_test gtest_main) +target_link_libraries(hermes_test stdex) +target_link_libraries(hermes_test hermes) + +add_test(hermes_test hermes_test) diff --git a/compiler/hermes/README.md b/compiler/hermes/README.md new file mode 100644 index 00000000000..c896abf6c08 --- /dev/null +++ b/compiler/hermes/README.md @@ -0,0 +1,3 @@ +# hermes + +An **extensible** logging framework diff --git a/compiler/hermes/include/hermes.h b/compiler/hermes/include/hermes.h new file mode 100644 index 00000000000..13202e62154 --- /dev/null +++ b/compiler/hermes/include/hermes.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_H__ +#define __HERMES_H__ + +#include "hermes/core/Severity.h" +#include "hermes/core/Message.h" +#include "hermes/core/Context.h" +// TO BE FILLED + +#endif // __HERMES_H__ diff --git a/compiler/hermes/include/hermes/core/Config.h b/compiler/hermes/include/hermes/core/Config.h new file mode 100644 index 00000000000..d937a36b8c1 --- /dev/null +++ b/compiler/hermes/include/hermes/core/Config.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_CONFIG_H__ +#define __HERMES_CONFIG_H__ + +#include "hermes/core/Severity.h" // TODO Put this into SourceSetting.h +#include "hermes/core/SourceSetting.h" + +namespace hermes +{ + +// TODO Introduce Source.forward.h +class Source; + +/** + * @brief Top-level configuration interface + * + * All Hermes configurations SHOULD inherit this interface. + */ +struct Config +{ + virtual ~Config() = default; + + virtual void configure(const Source *, SourceSetting &) const = 0; +}; + +} // namespace hermes + +#endif // __HERMES_CONFIG_H__ diff --git a/compiler/hermes/include/hermes/core/Context.h b/compiler/hermes/include/hermes/core/Context.h new file mode 100644 index 00000000000..4054587a48c --- /dev/null +++ b/compiler/hermes/include/hermes/core/Context.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_CONTEXT_H__ +#define __HERMES_CONTEXT_H__ + +#include "hermes/core/Config.h" +#include "hermes/core/Source.h" +#include "hermes/core/Sink.h" +#include "hermes/core/MessageBus.h" + +#include +#include + +namespace hermes +{ + +/** + * @brief Logging controller + * + * This "Context" serves as a controller for associated logging source/sink. + * + * WARNING This "Context" is not yet thread-safe. + * TODO Support multi-threaded application logging + */ +class Context final : private MessageBus, private Source::Registry, private Sink::Registry +{ +public: + /// @brief Get the global configuration + const Config *config(void) const; + /// @brief Update the global configuration + void config(std::unique_ptr &&); + +public: + MessageBus *bus(void) { return this; } + +private: + /// This implements "post" method that MessageBus interface requires. + void post(std::unique_ptr &&msg) override; + +public: + Source::Registry *sources(void) { return this; } + +private: + /// This implements "attach" method that "Source::Registry" interface requires. + void attach(Source *source) override; + /// This implements "detach" method that "Source::Registry" interface requires. + void detach(Source *source) override; + +public: + Sink::Registry *sinks(void) { return this; } + +private: + /// This implements "append" method that "Sink::Registry" interface requires. + void append(std::unique_ptr &&sink) override; + +private: + std::unique_ptr _config; + std::set _sources; + std::set> _sinks; +}; + +} // namespace hermes + +#endif // __HERMES_CONTEXT_H__ diff --git a/compiler/hermes/include/hermes/core/Message.h b/compiler/hermes/include/hermes/core/Message.h new file mode 100644 index 00000000000..28cfd7942b6 --- /dev/null +++ b/compiler/hermes/include/hermes/core/Message.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_MESSAGE_H__ +#define __HERMES_MESSAGE_H__ + +#include +#include +#include +#include + +namespace hermes +{ + +/** + * @brief Mutie-line text message + */ +class MessageText +{ +public: + /// WARNING! Be careful. This constructor updates "ss". + MessageText(std::stringstream &ss); + +public: + /// @brief The number of lines + uint32_t lines(void) const { return _lines.size(); } + /// @breif The content of a specific line + const std::string &line(uint32_t n) const { return _lines.at(n); } + +private: + std::vector _lines; +}; + +/** + * @brief Message with metadata + * + * TODO Add "Timestamp" field + * TODO Add "Severity" field + * TODO Support extensible "attribute" annotation + */ +class Message final +{ +public: + Message() = default; + +public: + void text(std::unique_ptr &&text) { _text = std::move(text); } + const MessageText *text(void) const { return _text.get(); } + +private: + std::unique_ptr _text; +}; + +} // namespace hermes + +#endif // __HERMES_MESSAGE_H__ diff --git a/compiler/hermes/include/hermes/core/MessageBuffer.h b/compiler/hermes/include/hermes/core/MessageBuffer.h new file mode 100644 index 00000000000..a2f1de74d0c --- /dev/null +++ b/compiler/hermes/include/hermes/core/MessageBuffer.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_MESSAGE_BUFFER_H__ +#define __HERMES_MESSAGE_BUFFER_H__ + +#include "hermes/core/MessageBus.h" + +#include +#include + +namespace hermes +{ + +/** + * @brief A buffer for a message under construction + * + * MessageBuffer will post the buffered message on destruction. + */ +class MessageBuffer final +{ +public: + MessageBuffer(MessageBus *); + ~MessageBuffer(); + +public: + std::ostream &os(void) { return _ss; } + +private: + MessageBus *_bus; + + /// @brief Content buffer + std::stringstream _ss; +}; + +} // namespace hermes + +#endif // __HERMES_MESSAGE_BUFFER_H__ diff --git a/compiler/hermes/include/hermes/core/MessageBus.h b/compiler/hermes/include/hermes/core/MessageBus.h new file mode 100644 index 00000000000..4ec272352da --- /dev/null +++ b/compiler/hermes/include/hermes/core/MessageBus.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_MESSAGE_BUS_H__ +#define __HERMES_MESSAGE_BUS_H__ + +#include "hermes/core/Message.h" + +#include + +namespace hermes +{ + +/** + * @brief A bridge between Source and Sink + */ +struct MessageBus +{ + virtual ~MessageBus() = default; + + // "post" takes the ownership of posted messages. + virtual void post(std::unique_ptr &&msg) = 0; +}; + +} // namespace hermes + +#endif // __HERMES_MESSAGE_BUS_H__ diff --git a/compiler/hermes/include/hermes/core/Severity.h b/compiler/hermes/include/hermes/core/Severity.h new file mode 100644 index 00000000000..25de35d8055 --- /dev/null +++ b/compiler/hermes/include/hermes/core/Severity.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_SEVERITY_H__ +#define __HERMES_SEVERITY_H__ + +#include + +namespace hermes +{ + +/** + * FATAL > ERROR > WARN > INFO > VERBOSE + * + * Hermes deliberately declares SeverityCategory as "enum" (instead of "enum class") + * in order to reduce namespace nesting. + */ +enum SeverityCategory : uint16_t +{ + FATAL = 0, + ERROR = 1, + WARN = 2, + INFO = 3, + VERBOSE = 4, +}; + +class Severity final +{ +public: + friend Severity fatal(void); + friend Severity error(void); + friend Severity warn(void); + friend Severity info(void); + friend Severity verbose(uint16_t level); + +private: + /** + * Use below "factory" helpers. + */ + Severity(SeverityCategory cat, uint16_t lvl) : _cat{cat}, _lvl{lvl} + { + // DO NOTHING + } + +public: + const SeverityCategory &category(void) const { return _cat; } + + /** + * @brief Verbose level + * + * "level" is fixed as 0 for all the categories except VERBOSE. + * + * 0 (most significant) <--- level ---> 65535 (least significant) + */ + const uint16_t &level(void) const { return _lvl; } + +private: + SeverityCategory _cat; + uint16_t _lvl; +}; + +inline Severity fatal(void) { return Severity{FATAL, 0}; } +inline Severity error(void) { return Severity{ERROR, 0}; } +inline Severity warn(void) { return Severity{WARN, 0}; } +inline Severity info(void) { return Severity{INFO, 0}; } +inline Severity verbose(uint16_t level) { return Severity{VERBOSE, level}; } + +} // namespace hermes + +#endif // __HERMES_SEVERITY_H__ diff --git a/compiler/hermes/include/hermes/core/Sink.h b/compiler/hermes/include/hermes/core/Sink.h new file mode 100644 index 00000000000..f53aff9fc59 --- /dev/null +++ b/compiler/hermes/include/hermes/core/Sink.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_SINK_H__ +#define __HERMES_SINK_H__ + +#include "hermes/core/Message.h" + +#include + +namespace hermes +{ + +/** + * @brief Message consumer interface + * + * All message consumers should inherit this interface. + */ +struct Sink +{ + struct Registry + { + virtual ~Registry() = default; + + // NOTE SinkRegistry takes the ownership of all the appended Sink objects + virtual void append(std::unique_ptr &&) = 0; + }; + + virtual ~Sink() = default; + + virtual void notify(const Message *) = 0; +}; + +} // namespace hermes + +#endif // __HERMES_SINK_H__ diff --git a/compiler/hermes/include/hermes/core/Source.h b/compiler/hermes/include/hermes/core/Source.h new file mode 100644 index 00000000000..b28532b2d98 --- /dev/null +++ b/compiler/hermes/include/hermes/core/Source.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_SOURCE_H__ +#define __HERMES_SOURCE_H__ + +#include "hermes/core/Config.h" +#include "hermes/core/Severity.h" +#include "hermes/core/MessageBus.h" +#include "hermes/core/MessageBuffer.h" +#include "hermes/core/SourceSetting.h" + +namespace hermes +{ + +/** + * @brief Message Source + * + * "Source" is the actual interface for users. "Source" accepts log messages from client. + */ +class Source +{ +public: + struct Registry + { + virtual ~Registry() = default; + + // NOTE Each "Source" SHOULD outlive "Registry" + virtual void attach(Source *) = 0; + virtual void detach(Source *) = 0; + }; + + // NOTE This using statement is introduced for backward compatibility + // TODO Remove this using declaration after migration + using Setting = SourceSetting; + +protected: + Source(); + virtual ~Source(); + +protected: + // Each "Source" implementation SHOULD invoke activate/deactivate appropriately + void activate(Registry *, MessageBus *); + void deactivate(void); + +protected: + Setting &setting(void) { return _setting; } + +public: + /** + * @brief Check whether a message with a given severity is acceptable or not + * + * + * NOTE This routine is performance critical as app always invokes this routine + * (even when logging is disabled). + */ + inline bool check(const Severity &s) const + { + return static_cast(s.level()) < _setting.limit(s.category()).level(); + } + +public: + /** + * @brief Update Source with a given configuration + * + * WARNING Do NOT invoke this manually. + * + * TODO Remove virtual after migration + */ + virtual void reload(const Config *); + +public: + std::unique_ptr buffer(const Severity &) const; + +private: + Setting _setting; + +private: + Registry *_reg = nullptr; + MessageBus *_bus = nullptr; +}; + +} // namespace hermes + +#define HERMES_FATAL(s) \ + if ((s).check(::hermes::fatal())) \ + (s).buffer(::hermes::fatal())->os() + +#define HERMES_ERROR(s) \ + if ((s).check(::hermes::error())) \ + (s).buffer(::hermes::error())->os() + +#define HERMES_WARN(s) \ + if ((s).check(::hermes::warn())) \ + (s).buffer(::hermes::warn())->os() + +#define HERMES_INFO(s) \ + if ((s).check(::hermes::info())) \ + (s).buffer(::hermes::info())->os() + +#define HERMES_VERBOSE(s, lv) \ + if ((s).check(::hermes::verbose((lv)))) \ + (s).buffer(::hermes::verbose((lv)))->os() + +#endif // __HERMES_SOURCE_H__ diff --git a/compiler/hermes/include/hermes/core/SourceSetting.h b/compiler/hermes/include/hermes/core/SourceSetting.h new file mode 100644 index 00000000000..3beaaa1965a --- /dev/null +++ b/compiler/hermes/include/hermes/core/SourceSetting.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __HERMES_SOURCE_SETTING_H__ +#define __HERMES_SOURCE_SETTING_H__ + +#include +#include + +namespace hermes +{ + +class Filter final +{ +public: + Filter(int32_t *ptr) : _ptr{ptr} + { + // DO NOTHING + } + +public: + inline void reject_all(void) { *_ptr = -1; } + inline void accept_upto(uint16_t lv) { *_ptr = static_cast(lv); } + inline void accept_all(void) { *_ptr = 65536; } + +private: + int32_t *_ptr; +}; + +class Limit final +{ +public: + Limit(const int32_t *ptr) : _ptr{ptr} + { + // DO NOTHING + } + +public: + inline int32_t level(void) const { return *_ptr; } + +private: + const int32_t *_ptr; +}; + +class SourceSetting final +{ +public: + SourceSetting() + { + // Reject all the messages by default + reject_all(); + } + +public: + void reject_all(void) + { + filter(FATAL).reject_all(); + filter(ERROR).reject_all(); + filter(WARN).reject_all(); + filter(INFO).reject_all(); + filter(VERBOSE).reject_all(); + } + + void accept_all(void) + { + filter(FATAL).accept_all(); + filter(ERROR).accept_all(); + filter(WARN).accept_all(); + filter(INFO).accept_all(); + filter(VERBOSE).accept_all(); + } + + inline Filter filter(const SeverityCategory &cat) + { + return _ulimits.data() + static_cast(cat); + } + + inline Limit limit(const SeverityCategory &cat) const + { + return _ulimits.data() + static_cast(cat); + } + +private: + /** + * @brief Allowed message level for each category + * + * This source will accept all the messages whose level belongs to [0, ulimit) + * where ulimit corresdpons to "limit(cat).value()" + */ + std::array _ulimits; +}; + +} // namespace hermes + +#endif // __HERMES_SOURCE_SETTING_H__ diff --git a/compiler/hermes/requires.cmake b/compiler/hermes/requires.cmake new file mode 100644 index 00000000000..a4855289c53 --- /dev/null +++ b/compiler/hermes/requires.cmake @@ -0,0 +1 @@ +require("stdex") diff --git a/compiler/hermes/src/core/Context.cpp b/compiler/hermes/src/core/Context.cpp new file mode 100644 index 00000000000..a6970f09338 --- /dev/null +++ b/compiler/hermes/src/core/Context.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Context.h" + +#include + +namespace hermes +{ + +const Config *Context::config(void) const +{ + // Return the current configuration + return _config.get(); +} + +void Context::config(std::unique_ptr &&config) +{ + _config = std::move(config); + + // Apply updated configurations + for (auto source : _sources) + { + source->reload(_config.get()); + } +} + +void Context::post(std::unique_ptr &&msg) +{ + // Validate message + assert((msg != nullptr) && "invalid message"); + assert((msg->text() != nullptr) && "missing text"); + + // Take the ownership of a given message + auto m = std::move(msg); + + // Notify appended sinks + for (const auto &sink : _sinks) + { + sink->notify(m.get()); + } + + // TODO Stop the process if "FATAL" message is posted +} + +void Context::attach(Source *source) +{ + // Configure source first + source->reload(config()); + // Insert source + _sources.insert(source); +} + +void Context::detach(Source *source) +{ + // Remove source + _sources.erase(source); +} + +void Context::append(std::unique_ptr &&sink) +{ + // Append sink + _sinks.insert(std::move(sink)); +} + +} // namespace hermes diff --git a/compiler/hermes/src/core/Context.test.cpp b/compiler/hermes/src/core/Context.test.cpp new file mode 100644 index 00000000000..0c8defd2a65 --- /dev/null +++ b/compiler/hermes/src/core/Context.test.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Context.h" + +#include + +TEST(ContextTest, constructor) +{ + hermes::Context ctx; + + ASSERT_NE(ctx.bus(), nullptr); + ASSERT_NE(ctx.sources(), nullptr); + ASSERT_NE(ctx.sinks(), nullptr); +} diff --git a/compiler/hermes/src/core/Message.cpp b/compiler/hermes/src/core/Message.cpp new file mode 100644 index 00000000000..63fe12b5fa8 --- /dev/null +++ b/compiler/hermes/src/core/Message.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Message.h" + +#include + +namespace hermes +{ + +MessageText::MessageText(std::stringstream &ss) +{ + while (!ss.eof()) + { + assert(ss.good()); + + std::string line; + std::getline(ss, line); + + // Trim the last empty line (by std::endl) + if (ss.eof() && line.empty()) + { + break; + } + + _lines.emplace_back(line); + } +} + +} // namespace hermes diff --git a/compiler/hermes/src/core/Message.test.cpp b/compiler/hermes/src/core/Message.test.cpp new file mode 100644 index 00000000000..1db88c71135 --- /dev/null +++ b/compiler/hermes/src/core/Message.test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Message.h" + +#include + +TEST(MessageTextTest, multiline) +{ + std::stringstream ss; + + ss << "Hello, World" << std::endl; + ss << "Nice to meet you" << std::endl; + + hermes::MessageText text{ss}; + + ASSERT_EQ(text.lines(), 2); + ASSERT_EQ(text.line(0), "Hello, World"); + ASSERT_EQ(text.line(1), "Nice to meet you"); +} + +TEST(MessageTest, ctor) +{ + hermes::Message msg; + + // Text is empty at the beginning + ASSERT_EQ(msg.text(), nullptr); +} diff --git a/compiler/hermes/src/core/MessageBuffer.cpp b/compiler/hermes/src/core/MessageBuffer.cpp new file mode 100644 index 00000000000..175a45d3f28 --- /dev/null +++ b/compiler/hermes/src/core/MessageBuffer.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/MessageBuffer.h" + +#include + +namespace hermes +{ + +MessageBuffer::MessageBuffer(MessageBus *bus) : _bus{bus} +{ + // DO NOTHING +} + +MessageBuffer::~MessageBuffer() +{ + // NOTE The current implementation is unsafe as it may throw an excpetion. + // TODO Find a better safe implementation. + auto msg = stdex::make_unique(); + + msg->text(stdex::make_unique(_ss)); + + _bus->post(std::move(msg)); +} + +} // namespace hermes diff --git a/compiler/hermes/src/core/MessageBuffer.test.cpp b/compiler/hermes/src/core/MessageBuffer.test.cpp new file mode 100644 index 00000000000..ff08eaa98d2 --- /dev/null +++ b/compiler/hermes/src/core/MessageBuffer.test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/MessageBuffer.h" + +#include + +#include + +namespace +{ + +class MockMessageBus final : public hermes::MessageBus +{ +public: + MockMessageBus() = default; + +public: + void post(std::unique_ptr &&msg) override + { + _count += 1; + _message = std::move(msg); + } + +public: + uint32_t count(void) const { return _count; } + const hermes::Message *message(void) const { return _message.get(); } + +private: + unsigned _count = 0; + std::unique_ptr _message = nullptr; +}; + +} // namespace + +TEST(MessageBufferTest, pass_constructed_message_on_descturction) +{ + MockMessageBus bus; + + { + hermes::MessageBuffer buf{&bus}; + + buf.os() << "Hello" << std::endl; + buf.os() << "Nice to meet you" << std::endl; + } + + ASSERT_EQ(bus.count(), 1); + ASSERT_NE(bus.message(), nullptr); + ASSERT_NE(bus.message()->text(), nullptr); + ASSERT_EQ(bus.message()->text()->lines(), 2); + ASSERT_EQ(bus.message()->text()->line(0), "Hello"); + ASSERT_EQ(bus.message()->text()->line(1), "Nice to meet you"); +} diff --git a/compiler/hermes/src/core/MessageBus.cpp b/compiler/hermes/src/core/MessageBus.cpp new file mode 100644 index 00000000000..05101089e8a --- /dev/null +++ b/compiler/hermes/src/core/MessageBus.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/MessageBus.h" + +// NOTE This empty file validates "MessageBus.h" diff --git a/compiler/hermes/src/core/Severity.test.cpp b/compiler/hermes/src/core/Severity.test.cpp new file mode 100644 index 00000000000..44fb800cb9d --- /dev/null +++ b/compiler/hermes/src/core/Severity.test.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Severity.h" + +#include + +TEST(SeverityTest, fatal) +{ + auto severity = hermes::fatal(); + + ASSERT_EQ(severity.category(), hermes::FATAL); + ASSERT_EQ(severity.level(), 0); +} + +TEST(SeverityTest, error) +{ + auto severity = hermes::error(); + + ASSERT_EQ(severity.category(), hermes::ERROR); + ASSERT_EQ(severity.level(), 0); +} + +TEST(SeverityTest, warn) +{ + auto severity = hermes::warn(); + + ASSERT_EQ(severity.category(), hermes::WARN); + ASSERT_EQ(severity.level(), 0); +} + +TEST(SeverityTest, info) +{ + auto severity = hermes::info(); + + ASSERT_EQ(severity.category(), hermes::INFO); + ASSERT_EQ(severity.level(), 0); +} + +TEST(SeverityTest, verbose) +{ + auto severity = hermes::verbose(100); + + ASSERT_EQ(severity.category(), hermes::VERBOSE); + ASSERT_EQ(severity.level(), 100); +} diff --git a/compiler/hermes/src/core/Sink.cpp b/compiler/hermes/src/core/Sink.cpp new file mode 100644 index 00000000000..1677073b130 --- /dev/null +++ b/compiler/hermes/src/core/Sink.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Sink.h" + +// NOTE This empty file validates "Sink.h" diff --git a/compiler/hermes/src/core/Source.cpp b/compiler/hermes/src/core/Source.cpp new file mode 100644 index 00000000000..33f8b0570b2 --- /dev/null +++ b/compiler/hermes/src/core/Source.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Source.h" + +#include + +#include + +namespace hermes +{ + +Source::Source() +{ + assert(_reg == nullptr); + assert(_bus == nullptr); +} + +Source::~Source() +{ + assert(_bus == nullptr); + assert(_reg == nullptr); +} + +void Source::activate(Registry *reg, MessageBus *bus) +{ + assert((_reg == nullptr) && (_bus == nullptr)); + + _reg = reg; + _bus = bus; + + _reg->attach(this); + + assert((_bus != nullptr) && (_reg != nullptr)); +} + +void Source::deactivate(void) +{ + assert((_bus != nullptr) && (_reg != nullptr)); + + _reg->detach(this); + + _bus = nullptr; + _reg = nullptr; + + assert((_reg == nullptr) && (_bus == nullptr)); +} + +void Source::reload(const Config *c) { c->configure(this, _setting); } + +std::unique_ptr Source::buffer(const Severity &) const +{ + // TODO Pass Severity + return stdex::make_unique(_bus); +} + +} // namespace hermes diff --git a/compiler/hermes/src/core/Source.test.cpp b/compiler/hermes/src/core/Source.test.cpp new file mode 100644 index 00000000000..f98a645095e --- /dev/null +++ b/compiler/hermes/src/core/Source.test.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes/core/Source.h" + +#include + +namespace +{ + +struct MockSourceRegistry final : public hermes::Source::Registry +{ + void attach(hermes::Source *) override { return; } + void detach(hermes::Source *) override { return; } +}; + +struct MockMessageBus final : public hermes::MessageBus +{ + void post(std::unique_ptr &&msg) override + { + msg.reset(); + ++cnt; + } + + uint32_t cnt = 0; +}; + +struct MockSource final : public hermes::Source +{ + MockSource(hermes::Source::Registry *r, hermes::MessageBus *b) { activate(r, b); } + ~MockSource() { deactivate(); } + + void reload(const hermes::Config *) override { return; } + + void enable(void) { setting().accept_all(); } +}; + +} // namespace + +TEST(SourceTest, construct) +{ + MockSourceRegistry registry; + MockMessageBus bus; + + MockSource source{®istry, &bus}; + + // Source are off at the beginning + ASSERT_FALSE(source.check(::hermes::fatal())); + ASSERT_FALSE(source.check(::hermes::error())); + ASSERT_FALSE(source.check(::hermes::warn())); + ASSERT_FALSE(source.check(::hermes::info())); + ASSERT_FALSE(source.check(::hermes::verbose(100))); +} + +TEST(SourceTest, macro) +{ + MockSourceRegistry registry; + + MockMessageBus bus; + + MockSource source{®istry, &bus}; + + source.enable(); + + uint32_t expected_count = 0; + + // No message at the beginning + ASSERT_EQ(bus.cnt, 0); + + HERMES_ERROR(source) << "A"; + ASSERT_EQ(bus.cnt, ++expected_count); + + HERMES_WARN(source) << "A"; + ASSERT_EQ(bus.cnt, ++expected_count); + + HERMES_INFO(source) << "A"; + ASSERT_EQ(bus.cnt, ++expected_count); + + HERMES_VERBOSE(source, 100) << "A"; + ASSERT_EQ(bus.cnt, ++expected_count); + +// FATAL message should terminate the execution. Let's check how to check this! +// TODO Enable FATAL feature and enable this test +#if 0 + HERMES_FATAL(source) << "A"; + ASSERT_EQ(bus.cnt, 1); +#endif +} diff --git a/compiler/hermes/src/hermes.cpp b/compiler/hermes/src/hermes.cpp new file mode 100644 index 00000000000..048521a3281 --- /dev/null +++ b/compiler/hermes/src/hermes.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes.h" + +// NOTE This empty file validates "hermes.h" diff --git a/compiler/hermes/src/hermes.test.cpp b/compiler/hermes/src/hermes.test.cpp new file mode 100644 index 00000000000..2cbc0939db0 --- /dev/null +++ b/compiler/hermes/src/hermes.test.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "hermes.h" + +#include + +TEST(HermesTest, simple_usecase) +{ + // TO BE FILLED +} diff --git a/compiler/i5diff/CMakeLists.txt b/compiler/i5diff/CMakeLists.txt new file mode 100644 index 00000000000..321ae49a0a2 --- /dev/null +++ b/compiler/i5diff/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(HDF5 COMPONENTS CXX QUIET) + +if(NOT HDF5_FOUND) + return() +endif(NOT HDF5_FOUND) + +message(STATUS "Enable i5diff: TRUE") + +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(i5diff ${SOURCES}) +target_include_directories(i5diff PRIVATE ${HDF5_INCLUDE_DIRS}) +target_link_libraries(i5diff PRIVATE ${HDF5_CXX_LIBRARIES}) +target_link_libraries(i5diff PRIVATE angkor) +target_link_libraries(i5diff PRIVATE safemain) diff --git a/compiler/i5diff/README.md b/compiler/i5diff/README.md new file mode 100644 index 00000000000..35d81884a74 --- /dev/null +++ b/compiler/i5diff/README.md @@ -0,0 +1,20 @@ +# i5diff + +_i5diff_ compares two HDF5 files that _nnkit_ HDF5 export action generates. + +**DISCLAIMER** _i5diff_ is not designed as a general diff tool. +It works only for HDF5 files that _nnkit_ HDF5 export action generates. + +## Yet Another Diff? + +_i5diff_ is able to detect _shape mismatch_ that _h5diff_ cannot detect. + +To be precise, _h5diff_ is also able to detect _shape mismatch_. +Unfortunately, however, _h5diff_ ends with 0 exitcode in the presence of _shape mismatch_, and thus +it is impossible to use _h5diff_ for continuous integration. + +## How to use + +``` +$ /path/to/i5diff -d 0.001 /path/to/fst.h5 /path/to/snd.h5 +``` diff --git a/compiler/i5diff/requires.cmake b/compiler/i5diff/requires.cmake new file mode 100644 index 00000000000..a6222db7602 --- /dev/null +++ b/compiler/i5diff/requires.cmake @@ -0,0 +1,2 @@ +require("angkor") +require("safemain") diff --git a/compiler/i5diff/src/entry.cpp b/compiler/i5diff/src/entry.cpp new file mode 100644 index 00000000000..456467f542f --- /dev/null +++ b/compiler/i5diff/src/entry.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +enum class ErrorCode +{ + CountMismatch, + TypeMismatch, + ShapeMismatch, + ValueMismatch, +}; + +template class ErrorDetail; + +// TODO Record the details +template <> class ErrorDetail +{ +public: + ErrorDetail() = default; +}; + +// TODO Record the details +template <> class ErrorDetail +{ +public: + ErrorDetail() = default; +}; + +// TODO Record the details +template <> class ErrorDetail +{ +public: + ErrorDetail() = default; +}; + +// TODO Record the details +template <> class ErrorDetail +{ +public: + ErrorDetail() = default; +}; + +struct Observer +{ + virtual ~Observer() = default; + + virtual void notify(const ErrorDetail &) = 0; + virtual void notify(const ErrorDetail &) = 0; + virtual void notify(const ErrorDetail &) = 0; + virtual void notify(const ErrorDetail &) = 0; +}; + +class Mux final : public Observer +{ +public: + Mux() = default; + +public: + void attach(Observer *o) { _observers.insert(o); } + +private: + template void notify_all(const ErrorDetail &e) + { + for (auto o : _observers) + { + o->notify(e); + } + } + +public: + void notify(const ErrorDetail &e) final { notify_all(e); } + void notify(const ErrorDetail &e) final { notify_all(e); } + void notify(const ErrorDetail &e) final { notify_all(e); } + void notify(const ErrorDetail &e) final { notify_all(e); } + +public: + std::set _observers; +}; + +class ExitcodeTracker final : public Observer +{ +public: + const int &exitcode(void) const { return _exitcode; } + +public: + void notify(const ErrorDetail &) { _exitcode = 1; } + void notify(const ErrorDetail &) { _exitcode = 1; } + void notify(const ErrorDetail &) { _exitcode = 1; } + void notify(const ErrorDetail &) { _exitcode = 1; } + +public: + int _exitcode = 0; +}; + +} // namespace + +// +// HDF5 helpers +// +namespace +{ + +enum class DataType +{ + UNKNOWN, + FLOAT32, + /* TO BE ADDED */ +}; + +DataType to_internal_dtype(const H5::DataType &dtype) +{ + if (dtype == H5::PredType::IEEE_F32BE) + { + return DataType::FLOAT32; + } + return DataType::UNKNOWN; +} + +using TensorShape = nncc::core::ADT::tensor::Shape; + +TensorShape to_internal_shape(const H5::DataSpace &dataspace) +{ + int rank = dataspace.getSimpleExtentNdims(); + + std::vector dims; + + dims.resize(rank, 0); + + dataspace.getSimpleExtentDims(dims.data()); + + TensorShape res; + + res.resize(rank); + for (int axis = 0; axis < rank; ++axis) + { + res.dim(axis) = dims[axis]; + } + + return res; +} + +uint32_t element_count(const H5::DataSpace &dataspace) +{ + return nncc::core::ADT::tensor::num_elements(to_internal_shape(dataspace)); +} + +std::vector as_float_vector(const H5::DataSet &dataset) +{ + std::vector buffer; + + buffer.resize(element_count(dataset.getSpace())); + dataset.read(buffer.data(), H5::PredType::NATIVE_FLOAT); + + return buffer; +} + +using LexicalLayout = nncc::core::ADT::tensor::LexicalLayout; +using TensorIndexEnumerator = nncc::core::ADT::tensor::IndexEnumerator; + +} // namespace + +// TODO Report the details +int entry(int argc, char **argv) +{ + // The current implementation works only for command-line of the following form: + // + // i5diff -d 0.001 /path/to/left.h5 /path/to/right.h5 + // + // TODO Support more options + assert(argc == 5); + assert(std::string(argv[1]) == "-d"); + assert(std::string(argv[2]) == "0.001"); + + H5::H5File lhs{argv[3], H5F_ACC_RDONLY}; + H5::H5File rhs{argv[4], H5F_ACC_RDONLY}; + + ExitcodeTracker exitcode_tracker; + + Mux mux; + mux.attach(&exitcode_tracker); + + // Compare values + do + { + // NOTE The name of value group SHOULD BE aligned with nnkit HDF5 actions + const std::string value_grpname{"value"}; + + H5::Group lhs_value_grp = lhs.openGroup(value_grpname); + H5::Group rhs_value_grp = rhs.openGroup(value_grpname); + + // Compare value count + int64_t value_count = -1; + { + uint32_t lhs_value_count = static_cast(lhs_value_grp.getNumObjs()); + uint32_t rhs_value_count = static_cast(rhs_value_grp.getNumObjs()); + + if (lhs_value_count != rhs_value_count) + { + ErrorDetail error{}; + mux.notify(error); + break; + } + + value_count = std::max(lhs_value_count, rhs_value_count); + } + assert(value_count >= 0); + + // Compare each dataset + for (int64_t n = 0; n < value_count; ++n) + { + // NOTE The name of dataset SHOULD BE aligned with nnkit HDF5 actions + const std::string dataset_name = std::to_string(n); + + auto lhs_dataset = lhs_value_grp.openDataSet(dataset_name); + auto rhs_dataset = rhs_value_grp.openDataSet(dataset_name); + + auto lhs_dtype = to_internal_dtype(lhs_dataset.getDataType()); + auto rhs_dtype = to_internal_dtype(rhs_dataset.getDataType()); + + // TODO Support other data types + assert(rhs_dtype == DataType::FLOAT32); + assert(lhs_dtype == DataType::FLOAT32); + + if (lhs_dtype != rhs_dtype) + { + ErrorDetail error{}; + mux.notify(error); + continue; + } + + auto lhs_shape = to_internal_shape(lhs_dataset.getSpace()); + auto rhs_shape = to_internal_shape(rhs_dataset.getSpace()); + + if (!(lhs_shape == rhs_shape)) + { + ErrorDetail error{}; + mux.notify(error); + continue; + } + + assert(lhs_shape == rhs_shape); + assert(lhs_dtype == rhs_dtype); + const auto &shape = lhs_shape; + const auto &dtype = lhs_dtype; + + switch (dtype) + { + case DataType::FLOAT32: + { + auto lhs_vector = as_float_vector(lhs_dataset); + auto rhs_vector = as_float_vector(rhs_dataset); + + assert(lhs_vector.size() == rhs_vector.size()); + + LexicalLayout layout; + + for (TensorIndexEnumerator e{shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + auto lhs_value = lhs_vector.at(layout.offset(shape, ind)); + auto rhs_value = rhs_vector.at(layout.offset(shape, ind)); + + // TODO Abstract equality criterion + if (std::abs(lhs_value - rhs_value) >= 0.001f) + { + ErrorDetail error{}; + mux.notify(error); + continue; + } + } + + break; + } + default: + throw std::runtime_error{"Not supported, yet"}; + }; + } + } while (false); + + // TODO Compare names (if requested) + + return exitcode_tracker.exitcode(); +} diff --git a/compiler/kuma/CMakeLists.txt b/compiler/kuma/CMakeLists.txt new file mode 100644 index 00000000000..e705bfedb55 --- /dev/null +++ b/compiler/kuma/CMakeLists.txt @@ -0,0 +1,19 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(kuma STATIC ${SOURCES}) +set_target_properties(kuma PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(kuma PUBLIC include) +target_link_libraries(kuma PRIVATE nncc_common) +target_link_libraries(kuma PUBLIC nncc_coverage) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for test +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(kuma_test ${TESTS}) +target_link_libraries(kuma_test kuma) diff --git a/compiler/kuma/README.md b/compiler/kuma/README.md new file mode 100644 index 00000000000..7e5123968b2 --- /dev/null +++ b/compiler/kuma/README.md @@ -0,0 +1,7 @@ +# kuma + +_kuma_ is a collection of offline memory allocators. + +## What does "kuma" mean? + +_kuma_ originates from _cooma_ which is an abbreviation of **C**ollection **O**f **O**ffline **M**emory **A**lloators. diff --git a/compiler/kuma/include/kuma.h b/compiler/kuma/include/kuma.h new file mode 100644 index 00000000000..a3d9a2e916a --- /dev/null +++ b/compiler/kuma/include/kuma.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __KUMA_H__ +#define __KUMA_H__ + +#include +#include + +namespace kuma +{ + +// Supported algorithms +enum Algorithm +{ + // No reuse + Greedy, + LinearScanFirstFit, +}; + +/** + * Each algorithm defines its own context. The context describes its in and out. + */ +template class Context; + +using ItemID = uint32_t; +using ItemSize = uint32_t; + +using MemoryOffset = uint32_t; +using MemorySize = uint32_t; + +// +// Greedy Algorithm +// +template <> class Context +{ +public: + virtual ~Context() = default; + +public: // Inputs + // count() returns the number of items to be allocated + virtual uint32_t item_count(void) const = 0; + + // size(N) returns the size of the N-th item + virtual ItemSize item_size(const ItemID &) const = 0; + +public: // Outputs + virtual void mem_offset(const ItemID &, const MemoryOffset &) = 0; + virtual void mem_total(const MemorySize &) = 0; +}; + +void solve(Context *); + +// +// Linear Scan First-Fit Algorithm +// +template <> class Context +{ +public: + virtual ~Context() = default; + +public: // Inputs + // count() returns the number of items to be allocated + virtual uint32_t item_count(void) const = 0; + + // size(N) returns the size of the N-th item + virtual ItemSize item_size(const ItemID &) const = 0; + + // conflict_with(N) returns all the items that are in conflict with item N + // - An item N is said to be in conflict with item M if item M and N cannot have overlap + // + // NOTE + // - conflict_with(N) SHOULD NOT include N itself + virtual std::set conflict_with(const ItemID &) const = 0; + +public: // Outputs + virtual void mem_offset(const ItemID &, const MemoryOffset &) = 0; + virtual void mem_total(const MemorySize &) = 0; +}; + +void solve(Context *); + +} // namespace kuma + +#endif // __KUMA_H__ diff --git a/compiler/kuma/src/IntervalSet.cpp b/compiler/kuma/src/IntervalSet.cpp new file mode 100644 index 00000000000..f6790c65421 --- /dev/null +++ b/compiler/kuma/src/IntervalSet.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IntervalSet.h" + +#include +#include + +namespace kuma +{ +namespace details +{ + +IntervalSet::IntervalSet(uint32_t len) +{ + // Update _content + _content[len] = len; +} + +void IntervalSet::insert(const IntervalMask &m) +{ + auto s = m.s; + auto e = m.e; + + assert(s <= e); + + if (s == e) + { + // Empty region, nothing to do + return; + } + + // lower_bound() returns an iterator to the first element not less than the given key + auto lb = _content.lower_bound(s); + + // NOTE 1. "lower_bound" ensures "prev_s < s <= curr_e" + // NOTE 2. "e" points to somewhere after "s" + auto curr_s = lb->first - lb->second; + auto curr_e = lb->first; + + if (curr_s < s) + { + // Split the current interval + _content[s] = s - curr_s; + // NOTE The invariant over "_content" is temporarily broken here. + } + + if (e < curr_e) + { + // Adjust the current interval + _content[curr_e] = curr_e - e; + } + else + { + // Remove the current interval + _content.erase(curr_e); + // Check the next interval (e > curr_e) + // + // TODO Remove this recursive call (to prevent stack overflow issue) + insert(mask(curr_e, e)); + } +} + +uint32_t IntervalSet::firstfit(uint32_t len) const +{ + for (auto it = _content.begin(); it != _content.end(); ++it) + { + if (it->second >= len) + { + // Got it! This interval is larger than "len". + return it->first - it->second; + } + } + + throw std::runtime_error{"infeasible"}; +} + +} // namespace details +} // namespace kuma diff --git a/compiler/kuma/src/IntervalSet.h b/compiler/kuma/src/IntervalSet.h new file mode 100644 index 00000000000..3b6c5f66655 --- /dev/null +++ b/compiler/kuma/src/IntervalSet.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __KUMA_DETAILS_LIVE_INTERVAL_SET_H__ +#define __KUMA_DETAILS_LIVE_INTERVAL_SET_H__ + +#include + +namespace kuma +{ +namespace details +{ + +struct IntervalMask +{ + uint32_t s; + uint32_t e; +}; + +inline IntervalMask mask(uint32_t s, uint32_t e) +{ + IntervalMask mask; + + mask.s = s; + mask.e = e; + + return mask; +} + +class IntervalSet +{ +public: + // [0, len) is live at the beginning + IntervalSet(uint32_t len = 0xffffffff); + +public: + void insert(const IntervalMask &); + + /** + * "firstfit(l)" returns the offset of an interval whose length is larger than "l". + * + * When multiple intervals meet this condition, "firstfit(l)" chooses the interval + * with the smallest offset as its name suggests. + * + * NOTE This method throws std::runtime_error if fails to find a proper region + */ + uint32_t firstfit(uint32_t len) const; + +private: + using End = uint32_t; + using Len = uint32_t; + + // If [e -> l] is in _content, it means that [e - l, e) is a valid interval. + // + // INVARIANT + // + // If key m and n (m <= n) are consecutive in _content, "m <= n - _content.at(n)" holds. + // + std::map _content; +}; + +} // namespace details +} // namespace kuma + +#endif // __KUMA_DETAILS_LIVE_INTERVAL_SET_H__ diff --git a/compiler/kuma/src/IntervalSet.test.cpp b/compiler/kuma/src/IntervalSet.test.cpp new file mode 100644 index 00000000000..848ddee03b5 --- /dev/null +++ b/compiler/kuma/src/IntervalSet.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "IntervalSet.h" + +#include + +using namespace kuma::details; + +TEST(IntervalSetTests, mask_and_firstfit) +{ + IntervalSet intervals; + + // Exclude [0, 16) from available region + intervals.insert(mask(0, 16)); + + ASSERT_EQ(intervals.firstfit(4), 16); +} diff --git a/compiler/kuma/src/kuma.cpp b/compiler/kuma/src/kuma.cpp new file mode 100644 index 00000000000..6fad96b26f2 --- /dev/null +++ b/compiler/kuma/src/kuma.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "kuma.h" + +// +// Greedy Allocation Algorithm +// +namespace kuma +{ + +void solve(Context *ctx) +{ + uint32_t next = 0; + + for (uint32_t n = 0; n < ctx->item_count(); ++n) + { + ctx->mem_offset(n, next); + next += ctx->item_size(n); + } + + ctx->mem_total(next); +}; + +} // namespace kuma + +// +// Linear Scan First Fit Algorithm +// +#include "IntervalSet.h" + +namespace kuma +{ + +void solve(Context *ctx) +{ + using namespace kuma::details; + + uint32_t upper_bound = 0; + std::map> committed_items; + + // Allocate items in linear order (from item 0, item 1, ...) + // + // The implementor of Context is responsible for item ordering. + for (uint32_t n = 0; n < ctx->item_count(); ++n) + { + IntervalSet intervals; + + for (auto item_in_conflict : ctx->conflict_with(n)) + { + auto it = committed_items.find(item_in_conflict); + + // Skip if item_in_conflict is not committed yet + if (it == committed_items.end()) + { + continue; + } + + auto const alloc_s = it->second.first; + auto const alloc_e = it->second.second; + intervals.insert(mask(alloc_s, alloc_e)); + } + + uint32_t const item_size = ctx->item_size(n); + uint32_t const item_alloc_s = intervals.firstfit(item_size); + uint32_t const item_alloc_e = item_alloc_s + item_size; + + // Notify "mem_offset" + ctx->mem_offset(n, item_alloc_s); + + // Update "upper bound" and commit allocation + upper_bound = std::max(upper_bound, item_alloc_e); + committed_items[n] = std::make_pair(item_alloc_s, item_alloc_e); + } + + // Notify "mem_total" + ctx->mem_total(upper_bound); +} + +} // namespace kuma diff --git a/compiler/kuma/src/kuma.test.cpp b/compiler/kuma/src/kuma.test.cpp new file mode 100644 index 00000000000..5d947ea6bf2 --- /dev/null +++ b/compiler/kuma/src/kuma.test.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "kuma.h" + +#include + +using namespace kuma; + +TEST(GreedyAlgorithmTests, empty) +{ + struct ContextImpl : public Context + { + uint32_t item_count(void) const final { return 0; } + ItemSize item_size(const ItemID &) const final { throw std::runtime_error{"error"}; } + + void mem_offset(const ItemID &, const MemoryOffset &) { throw std::runtime_error{"error"}; }; + void mem_total(const MemorySize &total) final { _total = total; } + + uint32_t _total = 0xffffffff; + }; + + ContextImpl ctx; + + solve(&ctx); + + ASSERT_EQ(ctx._total, 0); +} + +TEST(LinearScanFirstFitTests, reuse) +{ + struct ContextImpl : public Context + { + uint32_t item_count(void) const final { return 3; } + ItemSize item_size(const ItemID &) const final { return 4; } + + std::set conflict_with(const ItemID &id) const + { + // 0 <-> 1 <-> 2 + switch (id) + { + case 0: + return std::set({1}); + case 1: + return std::set({0, 2}); + case 2: + return std::set({1}); + default: + break; + }; + + throw std::runtime_error{"Invalid"}; + } + + void mem_offset(const ItemID &id, const MemoryOffset &offset) { _offsets[id] = offset; }; + void mem_total(const MemorySize &total) final { _total = total; } + + uint32_t _offsets[3]; + uint32_t _total = 0xffffffff; + }; + + ContextImpl ctx; + + solve(&ctx); + + // EXPECTED MEMORY LAYOUT: + // ------------------ 0 + // | ITEM 0, ITEM 2 | + // ------------------ 4 + // | ITEM 1 | + // ------------------ 8 + ASSERT_EQ(ctx._total, 8); + ASSERT_EQ(ctx._offsets[0], 0); + ASSERT_EQ(ctx._offsets[1], 4); + ASSERT_EQ(ctx._offsets[2], 0); +} diff --git a/compiler/loco/CMakeLists.txt b/compiler/loco/CMakeLists.txt new file mode 100644 index 00000000000..f9405284032 --- /dev/null +++ b/compiler/loco/CMakeLists.txt @@ -0,0 +1,28 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(loco SHARED ${SOURCES}) +target_include_directories(loco PUBLIC include) +# TODO Remove dependencies on angkor library +target_link_libraries(loco PUBLIC angkor) +target_link_libraries(loco PRIVATE stdex) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(loco PRIVATE nncc_common) +target_link_libraries(loco PUBLIC nncc_coverage) +# Q. HOW TO MAKE DEV PACKAGE(?) +install(TARGETS loco DESTINATION lib) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(loco_test ${TESTS}) +target_link_libraries(loco_test stdex) +target_link_libraries(loco_test loco) diff --git a/compiler/loco/README.md b/compiler/loco/README.md new file mode 100644 index 00000000000..a388d8e48c1 --- /dev/null +++ b/compiler/loco/README.md @@ -0,0 +1,3 @@ +# loco + +_loco_ is a graph-based intermediate representation (IR) for neural network compilers. diff --git a/compiler/loco/doc/LEP_000_Dialect_Service.md b/compiler/loco/doc/LEP_000_Dialect_Service.md new file mode 100644 index 00000000000..f6f6dc80922 --- /dev/null +++ b/compiler/loco/doc/LEP_000_Dialect_Service.md @@ -0,0 +1,116 @@ +# Dialect Service + +This loco enhancement proposal (_LEP_) discusses how to permit a _loco_ graph without canonical dialect. + +## Revision + +| Date | Status | +| --- | --- | +| 2019/09/03 | Proposed | + +## Motivation + +One of key design principles behind _loco_ is to allow users (= NN compiler writers) to easily define their own intermediate representation (IR) on top of shared infrastructure. + +Unfortunately, however, there is a gap between dream and reality. +It is currently impossible to create a _loco_ graph only with non-canonical dialects; +there is no way to express the interaction between graph-level output without _canonical.Push_ node. + +This proposal aims to remove this restriction in order to bridge the gap between dream and reality. + +## Design + +Each dialect is now allowed to expose its internal to its client (such as transformations and core algorithms) through a so-called "Service" interface. + +Although this proposal focuses on ``output_nodes`` helper in _loco.core_, its coverage is not limited to this helper. +Any pass and algorithm can take an advantage of this generic infrastructure. + +Let us dive into some details. + +### What is "service"? + +A service declares a collection of APIs that each **client** (not dialect) needs. + +Let us consider ``output_nodes``. ``output_nodes`` needs to check whether a node is associated with any graph-level output. + +Here is one possible service design that satisfies this need. +```cxx +virtual bool associated(const Node *node) const = 0; +virtual GraphOutputIndex index(const Node *node) const = 0; +``` + +### How to declare a service + +All of these service interfaces should inherit ``loco::DialectService`` interface that _loco.core_ defines. +```cxx +struct DialectService +{ + virtual ~DialectService() = default; +}; +``` + +For example, it is possible to declare the service that ``output_nodes`` needs as follows: +```cxx +struct GraphOutputIndexQueryService : public DialectService +{ + virtual ~GraphOutputIndexQueryService() = default; + + virtual bool associated(const Node *node) const = 0; + virtual GraphOutputIndex index(const Node *node) const = 0; +}; +``` + +### How to access a service + +This proposal extends ``Dialect`` class with ``service`` method. + +Each dialect SHOULD return a valid pointer on ``service`` method call if it implements that service. Otherwise, it SHOULD return a null pointer otherwise. + +**WARNING** It is impossible to use ``get``. ``get`` is currently reserved for singleton accessor. + +Given a ``GraphOutputIndexQueryService``, it is possible to revise ``output_nodes`` as follows: +```cxx +std::vector output_nodes(loco::Graph *g) +{ + std::map table; + + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + auto node = g->nodes()->at(n); + + if (auto service = node->dialect()->service()) + { + if (service->associated(node)) + { + auto output_index = service->index(node); + assert(table.find(output_index) == table.end()); + table[output_index] = node; + } + } + } + + std::vector res; + + for (uint32_t n = 0; n < g->outputs()->size(); ++n) + { + auto it = table.find(n); + // NOTE This behavior originates from the current implementation of output_nodes + res.emplace_back(it == table.end() ? nullptr : it->second); + } + + return res; +} +``` + +**PLEASE NOTE THAT** ``output_nodes`` now works with all the dialects that implement ``GraphOutputIndexQueryService``. + +### How to register a service + +Each dialect should invoke protected ``service`` method during its construction. +```cxx +AwesomeDialect::AwesomeDialect() +{ + std::unique_ptr impl = ...; + service(std::move(impl)); +} +``` diff --git a/compiler/loco/include/loco.h b/compiler/loco/include/loco.h new file mode 100644 index 00000000000..5cc4487ea1c --- /dev/null +++ b/compiler/loco/include/loco.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_H__ +#define __LOCO_H__ + +#include "loco/IR/Graph.h" +#include "loco/IR/Algorithm.h" +#include "loco/IR/Verifier.h" + +#include "loco/IR/PermutingCodec.h" + +#endif // __LOCO_H__ diff --git a/compiler/loco/include/loco/ADT/AnnotatedItem.h b/compiler/loco/include/loco/ADT/AnnotatedItem.h new file mode 100644 index 00000000000..be0d9ac1d67 --- /dev/null +++ b/compiler/loco/include/loco/ADT/AnnotatedItem.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_ADT_ANNOTATED_ITEM_H__ +#define __LOCO_ADT_ANNOTATED_ITEM_H__ + +#include +#include +#include + +namespace loco +{ + +template class AnnotatedItem +{ +public: + AnnotatedItem() = default; + +public: + virtual ~AnnotatedItem() = default; + +public: + /** + * @brief Retrieve a stored annotation of type T + * + * @note This method returns nullptr if annotation does not exist + */ + template const T *annot(void) const + { + // TODO Insert static_assert(T derives Annotation); + + auto it = _attrs.find(typeid(T)); + + if (it == _attrs.end()) + { + return nullptr; + } + + // TODO Insert null check + return dynamic_cast(it->second.get()); + } + + /** + * @brief Attach or remove a new annotation of type T + * + * @note annot(nullptr) removes an attached annotation if it exists + */ + template void annot(std::unique_ptr &&p) + { + // TODO: Insert static_assert(T derives Annotation); + + if (p == nullptr) + { + _attrs.erase(typeid(T)); + } + else + { + // TODO: assert(_attribs.find(typeid(T)) == _attribs.end()); + _attrs[typeid(T)] = std::move(p); + } + } + +private: + std::map> _attrs; +}; + +} // namespace loco + +#endif // __LOCO_ADT_ANNOTATED_ITEM_H__ diff --git a/compiler/loco/include/loco/ADT/ObjectPool.h b/compiler/loco/include/loco/ADT/ObjectPool.h new file mode 100644 index 00000000000..3f3a25c1689 --- /dev/null +++ b/compiler/loco/include/loco/ADT/ObjectPool.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_ADT_OBJECT_POOL_H__ +#define __LOCO_ADT_OBJECT_POOL_H__ + +#include +#include +#include + +namespace loco +{ + +/** + * @brief Object Pool + * @note ObjectPool owns registered objects. + */ +template class ObjectPool +{ +public: + virtual ~ObjectPool() = default; + +public: + /// @brief Return the number of objects + uint32_t size(void) const { return _pool.size(); } + + /// @brief Access N-th object + T *at(uint32_t n) const { return _pool.at(n).get(); } + +protected: + /// @brief Take the ownership of a given object and returns its raw pointer + template U *take(std::unique_ptr &&o) + { + auto res = o.get(); + _pool.emplace_back(std::move(o)); + return res; + } + + /** + * @brief Erase an object from the pool + * + * erase(p) returns false if p does not belong to this object pool. + */ + bool erase(T *ptr) + { + auto pred = [ptr](const std::unique_ptr &o) { return o.get() == ptr; }; + auto it = std::find_if(_pool.begin(), _pool.end(), pred); + + if (it == _pool.end()) + { + return false; + } + + _pool.erase(it); + return true; + } + +private: + std::vector> _pool; +}; + +} // namespace loco + +#endif // __LOCO_ADT_OBJECT_POOL_H__ diff --git a/compiler/loco/include/loco/IR/Algorithm.h b/compiler/loco/include/loco/IR/Algorithm.h new file mode 100644 index 00000000000..f7812e85d6d --- /dev/null +++ b/compiler/loco/include/loco/IR/Algorithm.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_ALGORITHM_H__ +#define __LOCO_IR_ALGORITHM_H__ + +#include "loco/IR/Node.h" + +#include +#include + +namespace loco +{ + +/** + * @brief Generate postorder traversal sequence starting from "roots" + * + * HOW TO USE + * + * for (auto node : postorder_traversal(...)) + * { + * ... node->do_something() ... + * } + * + */ +std::vector postorder_traversal(const std::vector &roots); + +/** + * @brief Enumerate all the nodes required to compute "roots" + */ +std::set active_nodes(const std::vector &roots); + +} // namespace loco + +#endif // __LOCO_IR_ALGORITHM_H__ diff --git a/compiler/loco/include/loco/IR/BiasShape.h b/compiler/loco/include/loco/IR/BiasShape.h new file mode 100644 index 00000000000..037b0873e17 --- /dev/null +++ b/compiler/loco/include/loco/IR/BiasShape.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_BIAS_SHAPE_H__ +#define __LOCO_IR_BIAS_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +/** + * \brief Bias Shape + */ +class BiasShape final +{ +public: + BiasShape() = default; + +public: + const Dimension &length(void) const { return _length; } + Dimension &length(void) { return _length; } + +private: + Dimension _length; +}; + +} // namespace loco + +#endif // __LOCO_IR_BIAS_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalDialect.h b/compiler/loco/include/loco/IR/CanonicalDialect.h new file mode 100644 index 00000000000..940d29a597d --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalDialect.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_DIALECT_H__ +#define __LOCO_IR_CANONICAL_DIALECT_H__ + +#include "loco/IR/Dialect.h" + +namespace loco +{ + +/** + * @brief A singleton for Canonical Dialect + * + * CanonicalDialect serves as an in-memory unique identifier. + */ +class CanonicalDialect final : public Dialect +{ +private: + CanonicalDialect(); + +public: + CanonicalDialect(const CanonicalDialect &) = delete; + CanonicalDialect(CanonicalDialect &&) = delete; + +public: + static Dialect *get(void); +}; + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_DIALECT_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNode.h b/compiler/loco/include/loco/IR/CanonicalNode.h new file mode 100644 index 00000000000..2dcc02e5da7 --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNode.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_NODE_H__ +#define __LOCO_IR_CANONICAL_NODE_H__ + +#include "loco/IR/CanonicalNodeDecl.h" +#include "loco/IR/CanonicalNodeImpl.h" + +#endif // __LOCO_IR_CANONICAL_NODE_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNodeDecl.h b/compiler/loco/include/loco/IR/CanonicalNodeDecl.h new file mode 100644 index 00000000000..872edbb3e99 --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNodeDecl.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_NODE_DECL_H__ +#define __LOCO_IR_CANONICAL_NODE_DECL_H__ + +#include "loco/IR/Node.h" +#include "loco/IR/Dialect.h" +#include "loco/IR/CanonicalOpcode.h" +#include "loco/IR/CanonicalNodeVisitor.forward.h" + +namespace loco +{ + +struct CanonicalNode : public Node +{ + virtual ~CanonicalNode() = default; + + const Dialect *dialect(void) const final; + virtual CanonicalOpcode opcode(void) const = 0; + + template T accept(CanonicalNodeVisitorBase *) const; + template T accept(CanonicalNodeMutableVisitorBase *); +}; + +template class... Mixins> +struct CanonicalNodeDef : public virtual CanonicalNode, public Mixins... +{ + virtual ~CanonicalNodeDef() = default; + + uint32_t opnum(void) const final { return static_cast(Code); } + CanonicalOpcode opcode(void) const final { return Code; } +}; + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_NODE_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNodeImpl.h b/compiler/loco/include/loco/IR/CanonicalNodeImpl.h new file mode 100644 index 00000000000..73aa4caa55d --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNodeImpl.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_NODE_IMPL_H__ +#define __LOCO_IR_CANONICAL_NODE_IMPL_H__ + +#include "loco/IR/Nodes.h" +#include "loco/IR/CanonicalNodeVisitor.h" + +#include + +namespace loco +{ + +template T CanonicalNode::accept(CanonicalNodeVisitorBase *v) const +{ + switch (this->opcode()) + { +#define CANONICAL_NODE(OPCODE, CLASS) \ + case CanonicalOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE + default: + break; + } + + throw std::runtime_error{"NYI"}; +} + +template T CanonicalNode::accept(CanonicalNodeMutableVisitorBase *v) +{ + switch (this->opcode()) + { +#define CANONICAL_NODE(OPCODE, CLASS) \ + case CanonicalOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE + default: + break; + } + + throw std::runtime_error{"NYI"}; +} + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_NODE_IMPL_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNodeVisitor.forward.h b/compiler/loco/include/loco/IR/CanonicalNodeVisitor.forward.h new file mode 100644 index 00000000000..425d7799740 --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNodeVisitor.forward.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_NODE_VISITOR_FORWARD_H__ +#define __LOCO_IR_CANONICAL_NODE_VISITOR_FORWARD_H__ + +namespace loco +{ + +// NOTE These forward declarations SHOULD BE aligned with "CanonicalNodeVisitor.h" +template struct CanonicalNodeVisitorBase; +template struct CanonicalNodeMutableVisitorBase; + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_NODE_VISITOR_FORWARD_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNodeVisitor.h b/compiler/loco/include/loco/IR/CanonicalNodeVisitor.h new file mode 100644 index 00000000000..b9ffd547267 --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNodeVisitor.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_NODE_VISITOR_H__ +#define __LOCO_IR_CANONICAL_NODE_VISITOR_H__ + +#include "loco/IR/Nodes.h" + +#include + +namespace loco +{ + +/** + * DO NOT use this class. Use CanonicalNodeVisitor instead. + */ +template struct CanonicalNodeVisitorBase +{ + virtual ~CanonicalNodeVisitorBase() = default; + +#define CANONICAL_NODE(OPCODE, CLASS) virtual T visit(const CLASS *) = 0; +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE +}; + +template struct CanonicalNodeVisitor : public CanonicalNodeVisitorBase +{ + virtual ~CanonicalNodeVisitor() = default; + +#define CANONICAL_NODE(OPCODE, CLASS) \ + virtual T visit(const CLASS *node) { return visit(static_cast(node)); } +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE + + /// @brief Default fallback + virtual T visit(const Node *) { throw std::runtime_error{"Not implemented, yet"}; } +}; + +/** + * DO NOT use this class. Use CanonicalNodeMutableVisitor instead. + */ +template struct CanonicalNodeMutableVisitorBase +{ + virtual ~CanonicalNodeMutableVisitorBase() = default; + +#define CANONICAL_NODE(OPCODE, CLASS) virtual T visit(CLASS *) = 0; +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE +}; + +template struct CanonicalNodeMutableVisitor : public CanonicalNodeMutableVisitorBase +{ + virtual ~CanonicalNodeMutableVisitor() = default; + +#define CANONICAL_NODE(OPCODE, CLASS) \ + virtual T visit(CLASS *node) { return visit(static_cast(node)); } +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE + + /// @brief Default fallback + virtual T visit(Node *) { throw std::runtime_error{"Not implemented, yet"}; } +}; + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_NODE_VISITOR_H__ diff --git a/compiler/loco/include/loco/IR/CanonicalNodes.lst b/compiler/loco/include/loco/IR/CanonicalNodes.lst new file mode 100644 index 00000000000..527856fbe79 --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalNodes.lst @@ -0,0 +1,49 @@ +#ifndef CANONICAL_NODE +#error "Define CANONICAL_NODE" +#endif // CANONICAL_NODE + +// +// PLEASE SORT NODE DECLS IN ALPHABETICAL ORDER +// + +// CANONICAL_NODE(OPCODE, CLASS) +CANONICAL_NODE(AvgPool2D, AvgPool2D) +CANONICAL_NODE(BiasDecode, BiasDecode) +CANONICAL_NODE(BiasEncode, BiasEncode) +CANONICAL_NODE(ConstGen, ConstGen) +CANONICAL_NODE(Conv2D, Conv2D) +CANONICAL_NODE(DepthwiseConv2D, DepthwiseConv2D) +CANONICAL_NODE(DepthwiseFilterDecode, DepthwiseFilterDecode) +CANONICAL_NODE(DepthwiseFilterEncode, DepthwiseFilterEncode) +CANONICAL_NODE(EltwiseAdd, EltwiseAdd) +CANONICAL_NODE(EltwiseDiv, EltwiseDiv) +CANONICAL_NODE(EltwiseMax, EltwiseMax) +CANONICAL_NODE(EltwiseMul, EltwiseMul) +CANONICAL_NODE(EltwiseSqrt, EltwiseSqrt) +CANONICAL_NODE(EltwiseSub, EltwiseSub) +CANONICAL_NODE(FeatureBiasAdd, BiasAdd) +CANONICAL_NODE(FeatureDecode, FeatureDecode) +CANONICAL_NODE(FeatureEncode, FeatureEncode) +CANONICAL_NODE(FilterDecode, FilterDecode) +CANONICAL_NODE(FilterEncode, FilterEncode) +CANONICAL_NODE(FixedReshape, Reshape) +CANONICAL_NODE(Forward, Forward) +CANONICAL_NODE(MaxPool2D, MaxPool2D) +// WARN Push may be excluded from canoncial dialect in the future +CANONICAL_NODE(Push, Push) +// WARN Pull may be excluded from canoncial dialect in the future +CANONICAL_NODE(Pull, Pull) +CANONICAL_NODE(ReLU, ReLU) +CANONICAL_NODE(ReLU6, ReLU6) +CANONICAL_NODE(Tanh, Tanh) +CANONICAL_NODE(TensorConcat, TensorConcat) +CANONICAL_NODE(TensorConstantPad, TensorConstantPad) +CANONICAL_NODE(TensorBiasAdd, BiasAdd) +CANONICAL_NODE(TensorBroadcast, TensorBroadcast) +CANONICAL_NODE(TensorReduce, TensorReduce) +CANONICAL_NODE(TensorTranspose, TensorTranspose) +CANONICAL_NODE(TensorSoftmax, Softmax) +CANONICAL_NODE(TransposedConv2D, TransposedConv2D) +CANONICAL_NODE(MatrixEncode, MatrixEncode) +CANONICAL_NODE(MatrixDecode, MatrixDecode) +CANONICAL_NODE(MatMul, MatMul) diff --git a/compiler/loco/include/loco/IR/CanonicalOpcode.h b/compiler/loco/include/loco/IR/CanonicalOpcode.h new file mode 100644 index 00000000000..58aa7de6ddf --- /dev/null +++ b/compiler/loco/include/loco/IR/CanonicalOpcode.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_CANONICAL_OPCODE_H__ +#define __LOCO_IR_CANONICAL_OPCODE_H__ + +namespace loco +{ + +/** + * @brief Canonical Node Opcode + * + * WARNING The order is subject to change. DO NOT serialize this value. + */ +enum class CanonicalOpcode +{ +#define CANONICAL_NODE(OPCODE, CLASS) OPCODE, +#include "CanonicalNodes.lst" +#undef CANONICAL_NODE +}; + +} // namespace loco + +#endif // __LOCO_IR_CANONICAL_OPCODE_H__ diff --git a/compiler/loco/include/loco/IR/DataType.h b/compiler/loco/include/loco/IR/DataType.h new file mode 100644 index 00000000000..1c3abd1514b --- /dev/null +++ b/compiler/loco/include/loco/IR/DataType.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DATA_TYPE_H__ +#define __LOCO_IR_DATA_TYPE_H__ + +namespace loco +{ + +/** + * @brief "scalar" value type + */ +enum class DataType +{ + Unknown, // Unknown type (serves as a default value) + + U8, // 8-bit unsigned integer + U16, // 16-bit unsigned integer + U32, // 32-bit unsigned integer + U64, // 64-bit unsigned integer + + S8, // 8-bit signed integer + S16, // 16-bit signed integer + S32, // 32-bit signed integer + S64, // 64-bit signed integer + + FLOAT16, // IEEE 16-bit floating-point + FLOAT32, // IEEE 32-bit floating-point + FLOAT64, // IEEE 64-bit floating-point +}; + +} // namespace loco + +#endif // __LOCO_IR_DATA_TYPE_H__ diff --git a/compiler/loco/include/loco/IR/DataTypeTraits.h b/compiler/loco/include/loco/IR/DataTypeTraits.h new file mode 100644 index 00000000000..e8270b93522 --- /dev/null +++ b/compiler/loco/include/loco/IR/DataTypeTraits.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DATA_TYPE_TRAITS_H__ +#define __LOCO_IR_DATA_TYPE_TRAITS_H__ + +#include "loco/IR/DataType.h" + +#include + +namespace loco +{ + +/** + * @brief C++ scalar type corresponding to each DataType + */ +template struct DataTypeImpl +{ + // using Type = ... +}; + +// TODO Support other enum values +template <> struct DataTypeImpl +{ + // Use C++ int8_t type for 8bit integer + using Type = int8_t; +}; + +template <> struct DataTypeImpl +{ + // Use C++ uint8_t type for unsigned 8bit integer + using Type = uint8_t; +}; + +template <> struct DataTypeImpl +{ + // Use C++ int32_t type for 32bit integer + using Type = int32_t; +}; + +template <> struct DataTypeImpl +{ + // Use C++ float type for IEEE 32-bit floating-point numbers + using Type = float; +}; + +} // namespace loco + +#endif // __LOCO_IR_DATA_TYPE_TRAITS_H__ diff --git a/compiler/loco/include/loco/IR/DepthwiseFilterAxis.h b/compiler/loco/include/loco/IR/DepthwiseFilterAxis.h new file mode 100644 index 00000000000..eb4650ec957 --- /dev/null +++ b/compiler/loco/include/loco/IR/DepthwiseFilterAxis.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DEPTHWISE_FILTER_AXIS_H__ +#define __LOCO_IR_DEPTHWISE_FILTER_AXIS_H__ + +namespace loco +{ + +enum class DepthwiseFilterAxis +{ + Depth, + Multiplier, + Height, + Width +}; + +} // namespace loco + +#endif // __LOCO_IR_DEPTHWISE_FILTER_AXIS_H__ diff --git a/compiler/loco/include/loco/IR/DepthwiseFilterCodec.h b/compiler/loco/include/loco/IR/DepthwiseFilterCodec.h new file mode 100644 index 00000000000..0d9286b462a --- /dev/null +++ b/compiler/loco/include/loco/IR/DepthwiseFilterCodec.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DEPTHWISE_FILTER_CODEC_H__ +#define __LOCO_IR_DEPTHWISE_FILTER_CODEC_H__ + +#include "loco/IR/DepthwiseFilterShape.h" +#include "loco/IR/DepthwiseFilterIndex.h" + +#include "loco/IR/TensorShape.h" +#include "loco/IR/TensorIndex.h" + +namespace loco +{ + +/** + * @brief Describe how to build a depthwise convolution filter from a tensor + * + * Let us assume that "enc" is a depthwise filter encoder. + * + * Given a tensor "inp" and its shape "inp.shape", "enc" builds a depthwise filter + * "out" as follows: + * + * for each valid filter_index for enc.shape(inp.shape) + * out.at(filter_index) = inp.at(enc.value(filter_index)) + */ +struct DepthwiseFilterEncoder +{ + virtual ~DepthwiseFilterEncoder() = default; + + virtual DepthwiseFilterShape shape(const TensorShape &shape) const = 0; + virtual TensorIndex value(const DepthwiseFilterIndex &index) const = 0; +}; + +/** + * @brief Describe how to build a tensor from a depthwise convolution filter + * + * Let us assume that "dec" is a depthwise filter decoder. + * + * Given a depthwise filter "inp" and its shape "inp.shape", "dec" builds a tensor + * "out" as follows: + * + * for each valid tensor_index for dec.shape(inp.shape) + * out.at(tensor_index) = inp.at(dec.value(tensor_index)) + */ +struct DepthwiseFilterDecoder +{ + virtual ~DepthwiseFilterDecoder() = default; + + virtual TensorShape shape(const DepthwiseFilterShape &shape) const = 0; + virtual DepthwiseFilterIndex value(const TensorIndex &index) const = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_DEPTHWISE_FILTER_CODEC_H__ diff --git a/compiler/loco/include/loco/IR/DepthwiseFilterIndex.h b/compiler/loco/include/loco/IR/DepthwiseFilterIndex.h new file mode 100644 index 00000000000..884e50a5687 --- /dev/null +++ b/compiler/loco/include/loco/IR/DepthwiseFilterIndex.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DEPTHWISE_FILTER_INDEX_H__ +#define __LOCO_IR_DEPTHWISE_FILTER_INDEX_H__ + +#include + +namespace loco +{ + +/** + * @brief DepthwiseFilter Index + * + * DepthwiseFilter Index indicates an "element" in a given Depthwise convolution filter. + * + * Assume there is a filter K where KS denotes its shape (of DepthwiseFilterShape type). + * + * Then, any valid filter index I satisfies the following invariants: + * - 0 <= I.channel() < KS.depth() + * - 0 <= I.nth() < KS.multiplier() + * - 0 <= I.row() < KS.height() + * - 0 <= I.column() < KS.width() + */ +class DepthwiseFilterIndex final +{ +public: + DepthwiseFilterIndex() = default; + +public: + const uint32_t &channel(void) const { return _channel; } + uint32_t &channel(void) { return _channel; } + + const uint32_t &nth(void) const { return _nth; } + uint32_t &nth(void) { return _nth; } + + const uint32_t &row(void) const { return _row; } + uint32_t &row(void) { return _row; } + + const uint32_t &column(void) const { return _column; } + uint32_t &column(void) { return _column; } + +private: + uint32_t _channel = 0; + uint32_t _nth = 0; + uint32_t _row = 0; + uint32_t _column = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_DEPTHWISE_FILTER_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/DepthwiseFilterShape.h b/compiler/loco/include/loco/IR/DepthwiseFilterShape.h new file mode 100644 index 00000000000..eb1a1e33510 --- /dev/null +++ b/compiler/loco/include/loco/IR/DepthwiseFilterShape.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DEPTHWISE_FILTER_SHAPE_H__ +#define __LOCO_IR_DEPTHWISE_FILTER_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +/** + * @brief DepthwiseFilter Shape + * + * This class describes the shape of depthwise filter, which is an input of depthwise 2D + * convolutional operation. + * + * depth() refers to expected channel depth of matching input + * multiplier() refers to number of traverse for one input + * height() refers to the height of 2D weights + * width() refers to the width of 2D weights + */ +class DepthwiseFilterShape final +{ +public: + DepthwiseFilterShape() = default; + +public: + const Dimension &depth(void) const { return _depth; } + Dimension &depth(void) { return _depth; } + + const Dimension &multiplier(void) const { return _multiplier; } + Dimension &multiplier(void) { return _multiplier; } + + const Dimension &height(void) const { return _height; } + Dimension &height(void) { return _height; } + + const Dimension &width(void) const { return _width; } + Dimension &width(void) { return _width; } + +private: + Dimension _depth; + Dimension _multiplier; + Dimension _height; + Dimension _width; +}; + +} // namespace loco + +#endif // __LOCO_IR_DEPTHWISE_FILTER_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/Dialect.h b/compiler/loco/include/loco/IR/Dialect.h new file mode 100644 index 00000000000..b8942bfb4b1 --- /dev/null +++ b/compiler/loco/include/loco/IR/Dialect.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DIALECT_H__ +#define __LOCO_IR_DIALECT_H__ + +#include "loco/IR/DialectService.h" + +#include +#include +#include +#include + +namespace loco +{ + +/** + * @brief Dialect interface + * + * Each dialect implementation is expected to have static "get" method + * which returns "const Dialect *" value. + */ +class Dialect +{ +public: + virtual ~Dialect() = default; + +protected: + template void service(std::unique_ptr &&s) + { + _services[typeid(ConcreteService)] = std::move(s); + } + +public: + template ConcreteService *service(void) const + { + auto it = _services.find(typeid(ConcreteService)); + + if (it == _services.end()) + { + return nullptr; + } + + return dynamic_cast(it->second.get()); + } + +private: + std::map> _services; +}; + +} // namespace loco + +#endif // __LOCO_IR_DIALECT_H__ diff --git a/compiler/loco/include/loco/IR/DialectService.h b/compiler/loco/include/loco/IR/DialectService.h new file mode 100644 index 00000000000..54a3fac747b --- /dev/null +++ b/compiler/loco/include/loco/IR/DialectService.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DIALECT_SERVICE_H__ +#define __LOCO_IR_DIALECT_SERVICE_H__ + +namespace loco +{ + +/** + * @brief Dialect Service interface + * + * Every service that each dialect exposes should inherit this interface. + */ +struct DialectService +{ + virtual ~DialectService() = default; +}; + +} // namespace loco + +#endif // __LOCO_IR_DIALECT_SERVICE_H__ diff --git a/compiler/loco/include/loco/IR/Dimension.h b/compiler/loco/include/loco/IR/Dimension.h new file mode 100644 index 00000000000..7b5d5943fed --- /dev/null +++ b/compiler/loco/include/loco/IR/Dimension.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DIMENSION_H__ +#define __LOCO_IR_DIMENSION_H__ + +#include + +namespace loco +{ + +/** + * @brief The value of one dimension in a tensor shape + * @note The value may be unknown + */ +class Dimension final +{ +private: + enum class Kind + { + Known, + Unknown + }; + +public: + // @brief Construct an "unknown" dimension + Dimension() = default; + + // @brief Construct a "known" dimension + Dimension(uint32_t value) { set(value); } + +public: + // @brief Return whether the value is known (or not) + bool known(void) const { return _kind == Kind::Known; } + + // @brief Return the value + // @note This value is meaningful only for known dimension + uint32_t value(void) const { return _value; } + + void set(uint32_t value) + { + _kind = Kind::Known; + _value = value; + } + + void unset(void) + { + _kind = Kind::Unknown; + _value = 0; + } + +private: + Kind _kind{Kind::Unknown}; + uint32_t _value{0}; +}; + +/** + * @brief Equality operator between two Dimensions + * + * @note Refer to the definition of equality of dimemsion at + * https://www.tensorflow.org/api_docs/python/tf/Dimension#__eq__ + */ +bool operator==(const Dimension &, const Dimension &); +bool operator==(const Dimension &, uint32_t); +bool operator==(uint32_t, const Dimension &); + +// @brief Make an "unknown" dimension +Dimension make_dimension(void); + +} // namespace loco + +#endif // __LOCO_IR_DIMENSION_H__ diff --git a/compiler/loco/include/loco/IR/Domain.h b/compiler/loco/include/loco/IR/Domain.h new file mode 100644 index 00000000000..823bc1833fc --- /dev/null +++ b/compiler/loco/include/loco/IR/Domain.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_DOMAIN_H__ +#define __LOCO_IR_DOMAIN_H__ + +namespace loco +{ + +/** + * @brief Describe the kind of (N-dimensional) loco values + * + * loco is an intermediate representation for neural network compiler, which mainly focuses on + * N-dimensional values (usually referred to as Tensor). + * + * There are several special cases for N-dimensional values according to its usage. For example, + * vision community often refers to 4D array as "FeatureMap". + * + * It is definitely possible to represent all of these special cases using Tensor, but that scheme + * may introduces some confusion (e.g. NCHW vs NHWC issue). + * + * loco distinguishes these special cases from Tensor in order to reduce such confusion. + * + * This "Domain" enum class enumerates all of these special cases that loco supports. + */ +enum class Domain +{ + Unknown, + Tensor, + Feature, + Filter, /* 2D Convolution Filter */ + DepthwiseFilter, /* Depthwise 2D Convolution Filter */ + Bias, + Matrix, + /* ... */ +}; + +} // namespace loco + +#endif // __LOCO_IR_DOMAIN_H__ diff --git a/compiler/loco/include/loco/IR/FeatureAxis.h b/compiler/loco/include/loco/IR/FeatureAxis.h new file mode 100644 index 00000000000..cf020edd2d5 --- /dev/null +++ b/compiler/loco/include/loco/IR/FeatureAxis.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FEATURE_AXIS_H__ +#define __LOCO_IR_FEATURE_AXIS_H__ + +namespace loco +{ + +enum class FeatureAxis +{ + Count, + Depth, + Height, + Width +}; + +} // namespace loco + +#endif // __LOCO_IR_FEATURE_AXIS_H__ diff --git a/compiler/loco/include/loco/IR/FeatureCodec.h b/compiler/loco/include/loco/IR/FeatureCodec.h new file mode 100644 index 00000000000..93094e13ad1 --- /dev/null +++ b/compiler/loco/include/loco/IR/FeatureCodec.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FEATURE_CODEC_H__ +#define __LOCO_IR_FEATURE_CODEC_H__ + +#include "loco/IR/FeatureShape.h" +#include "loco/IR/FeatureIndex.h" + +#include "loco/IR/TensorShape.h" +#include "loco/IR/TensorIndex.h" + +#include + +namespace loco +{ + +/** + * @brief Decribe how to build a (convolution) feature map from a tensor + * + * Let us assume that "enc" is a feature encoder. + * + * Given a tensor "inp" and its shape "inp.shape", "enc" builds a feature map + * "out" as follows: + * + * for each valid feature index (referred to as feature_idx below) for enc.shape(inp.shape) + * out.at(feature_index) = inp.at(enc.value(feature_index)) + */ +struct FeatureEncoder +{ + virtual ~FeatureEncoder() = default; + + virtual FeatureShape shape(const TensorShape &shape) const = 0; + virtual TensorIndex value(const FeatureIndex &index) const = 0; + + virtual std::unique_ptr clone(void) const = 0; +}; + +/** + * @brief Describe how to build a tensor from a (convolution) feature map + * + * Let us assume that "dec" is a feature decoder. + * + * Given a feature map "inp" and its shape "inp.shape", "dec" builds a tensor + * "out" as follows: + * + * for each valid tensor index (referred to as tensor_index below) for dec.shape(inp.shape) + * out.at(tensor_index) = inp.at(dec.value(tensor_index)) + * + * NOTE "inp" is a feature value and "out" is a tensor value in this example. + */ +struct FeatureDecoder +{ + virtual ~FeatureDecoder() = default; + + virtual TensorShape shape(const FeatureShape &) const = 0; + virtual FeatureIndex value(const TensorIndex &) const = 0; + + virtual std::unique_ptr clone(void) const = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_FEATURE_CODEC_H__ diff --git a/compiler/loco/include/loco/IR/FeatureIndex.h b/compiler/loco/include/loco/IR/FeatureIndex.h new file mode 100644 index 00000000000..007f944911b --- /dev/null +++ b/compiler/loco/include/loco/IR/FeatureIndex.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FEATURE_INDEX_H__ +#define __LOCO_IR_FEATURE_INDEX_H__ + +#include + +namespace loco +{ + +/** + * \brief Feature Index + * + * Feature Index indicates an "element" in a given feature map. + * + * Let us assume that there is a feature map F and S denotes its shape (of FeatureShape type). + * + * Then, any valid feature index I satisfies the following invariants: + * - 0 <= I.batch() < S.count() + * - 0 <= I.channel() < S.depth() + * - 0 <= I.row() < S.height() + * - 0 <= I.column() < S.width() + */ +class FeatureIndex final +{ +public: + FeatureIndex() = default; + +public: + const uint32_t &batch(void) const { return _batch; } + uint32_t &batch(void) { return _batch; } + + const uint32_t &channel(void) const { return _channel; } + uint32_t &channel(void) { return _channel; } + + const uint32_t &row(void) const { return _row; } + uint32_t &row(void) { return _row; } + + const uint32_t &column(void) const { return _column; } + uint32_t &column(void) { return _column; } + +private: + uint32_t _batch = 0; + uint32_t _channel = 0; + uint32_t _row = 0; + uint32_t _column = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_FEATURE_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/FeatureShape.h b/compiler/loco/include/loco/IR/FeatureShape.h new file mode 100644 index 00000000000..d09a2b2b867 --- /dev/null +++ b/compiler/loco/include/loco/IR/FeatureShape.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FEATURE_SHAPE_H__ +#define __LOCO_IR_FEATURE_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +/** + * \brief Feature Map Shape + * + * This class describes the shape of feature maps, which serves as the input/output of 2D + * convolutional operations (e.g. Convolution). + * + * Each feature map is a collection of 3D features conceptually. + * Each feature has depth, height, width. + * + * count() refers to the number of features in a feature map + * depth() refers to the depth of features in a given feature map + * height() refers to the height of features in a given feature map + * width() refers to the width of features in a given feature map + */ +class FeatureShape final +{ +public: + FeatureShape() = default; + +public: + const Dimension &count(void) const { return _count; } + Dimension &count(void) { return _count; } + + const Dimension &depth(void) const { return _depth; } + Dimension &depth(void) { return _depth; } + + const Dimension &height(void) const { return _height; } + Dimension &height(void) { return _height; } + + const Dimension &width(void) const { return _width; } + Dimension &width(void) { return _width; } + +private: + Dimension _count; + Dimension _depth; + Dimension _height; + Dimension _width; +}; + +} // namespace loco + +#endif // __LOCO_IR_FEATURE_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/FilterAxis.h b/compiler/loco/include/loco/IR/FilterAxis.h new file mode 100644 index 00000000000..269e2aeccad --- /dev/null +++ b/compiler/loco/include/loco/IR/FilterAxis.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FILTER_AXIS_H__ +#define __LOCO_IR_FILTER_AXIS_H__ + +namespace loco +{ + +enum class FilterAxis +{ + Count, + Depth, + Height, + Width +}; + +} // namespace loco + +#endif // __LOCO_IR_FILTER_AXIS_H__ diff --git a/compiler/loco/include/loco/IR/FilterCodec.h b/compiler/loco/include/loco/IR/FilterCodec.h new file mode 100644 index 00000000000..3ff548d6d66 --- /dev/null +++ b/compiler/loco/include/loco/IR/FilterCodec.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FILTER_CODEC_H__ +#define __LOCO_IR_FILTER_CODEC_H__ + +#include "loco/IR/FilterShape.h" +#include "loco/IR/FilterIndex.h" + +#include "loco/IR/TensorShape.h" +#include "loco/IR/TensorIndex.h" + +namespace loco +{ + +/** + * @brief Decribe how to build a (convolution) filter from a tensor + * + * Let us assume that "enc" is a filter encoder. + * + * Given a tensor "inp" and its shape "inp.shape", "enc" builds a filter + * "out" as follows: + * + * for each valid filter index (referred to as filter_index below) for enc.shape(inp.shape) + * out.at(filter_index) = inp.at(enc.value(filter_index)) + */ +struct FilterEncoder +{ + virtual ~FilterEncoder() = default; + + virtual FilterShape shape(const TensorShape &shape) const = 0; + virtual TensorIndex value(const FilterIndex &index) const = 0; +}; + +/** + * @brief Decribe how to build a a tensor from a filter + */ +struct FilterDecoder +{ + virtual ~FilterDecoder() = default; + + virtual TensorShape shape(const FilterShape &shape) const = 0; + virtual FilterIndex value(const TensorIndex &index) const = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_FILTER_CODEC_H__ diff --git a/compiler/loco/include/loco/IR/FilterIndex.h b/compiler/loco/include/loco/IR/FilterIndex.h new file mode 100644 index 00000000000..5765ea7645e --- /dev/null +++ b/compiler/loco/include/loco/IR/FilterIndex.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FILTER_INDEX_H__ +#define __LOCO_IR_FILTER_INDEX_H__ + +#include + +namespace loco +{ + +/** + * \brief Filter Index + * + * Filter Index indicates an "element" in a given (convolutional) filter. + * + * Let us assume that there is a filter K where KS denotes its shape (of FilterShape type). + * + * Then, any valid filter index I satisfies the following invariants: + * - 0 <= I.nth() < KS.count() + * - 0 <= I.channel() < KS.depth() + * - 0 <= I.row() < KS.height() + * - 0 <= I.column() < KS.width() + */ +class FilterIndex final +{ +public: + FilterIndex() = default; + +public: + const uint32_t &nth(void) const { return _nth; } + uint32_t &nth(void) { return _nth; } + + const uint32_t &channel(void) const { return _channel; } + uint32_t &channel(void) { return _channel; } + + const uint32_t &row(void) const { return _row; } + uint32_t &row(void) { return _row; } + + const uint32_t &column(void) const { return _column; } + uint32_t &column(void) { return _column; } + +private: + uint32_t _nth = 0; + uint32_t _channel = 0; + uint32_t _row = 0; + uint32_t _column = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_FILTER_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/FilterShape.h b/compiler/loco/include/loco/IR/FilterShape.h new file mode 100644 index 00000000000..00e44892a62 --- /dev/null +++ b/compiler/loco/include/loco/IR/FilterShape.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_FILTER_SHAPE_H__ +#define __LOCO_IR_FILTER_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +/** + * \brief Filter Shape + * + * This class describes the shape of filter, which is an input of 2D + * convolutional operations (e.g. Convolution). + * + * count() refers to the number of 3D weight in a filter + * depth() refers to the depth of 3D weights + * height() refers to the height of 3D weights + * width() refers to the width of 3D weights + * + * NOTE + * + * The definition of FilterShape is almost same as that of FeatureShape, but loco + * distinguishes FeatureShape and FilterShape in class-level in order to prevent + * potential errors by type check. + */ +class FilterShape final +{ +public: + FilterShape() = default; + +public: + const Dimension &count(void) const { return _count; } + Dimension &count(void) { return _count; } + + const Dimension &depth(void) const { return _depth; } + Dimension &depth(void) { return _depth; } + + const Dimension &height(void) const { return _height; } + Dimension &height(void) { return _height; } + + const Dimension &width(void) const { return _width; } + Dimension &width(void) { return _width; } + +private: + Dimension _count; + Dimension _depth; + Dimension _height; + Dimension _width; +}; + +} // namespace loco + +#endif // __LOCO_IR_FILTER_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/Graph.forward.h b/compiler/loco/include/loco/IR/Graph.forward.h new file mode 100644 index 00000000000..2a43be93aaf --- /dev/null +++ b/compiler/loco/include/loco/IR/Graph.forward.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_GRAPH_FORWARD_H__ +#define __LOCO_IR_GRAPH_FORWARD_H__ + +namespace loco +{ + +// This forward declaration SHOULD BE aligned with the actual declaration in "Graph.h". +class Graph; + +} // namespace loco + +#endif // __LOCO_IR_GRAPH_FORWARD_H__ diff --git a/compiler/loco/include/loco/IR/Graph.h b/compiler/loco/include/loco/IR/Graph.h new file mode 100644 index 00000000000..46b08e5f571 --- /dev/null +++ b/compiler/loco/include/loco/IR/Graph.h @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_GRAPH_H__ +#define __LOCO_IR_GRAPH_H__ + +#include "loco/IR/DataType.h" +// TODO Include "Node.h" instead +#include "loco/IR/Nodes.h" +#include "loco/IR/NodePool.h" +#include "loco/IR/GraphInputIndex.h" +#include "loco/IR/GraphOutputIndex.h" + +#include "loco/ADT/ObjectPool.h" + +#include +#include +#include +#include +#include + +namespace loco +{ + +// TODO Introduce Named trait +enum class Trait +{ + // Any "DataTyped" class has the following methods + // - DataType dtype(void) const; + // - void dtype(const DataType &value); + DataTyped, + // Any "TensorShaped" class has the following methods + // - const TensorShape *shape(void) const; + // - void shape(std::unique_ptr &&); + // - void shape(std::initializer_list &&); + // + // TODO Rename NodeMixin::TensorShape as NodeMixin::NDShape + TensorShaped, +}; + +template class Mixin; + +// TODO Re-implement NodeMixin using this mixin +template <> class Mixin +{ +public: + Mixin() = default; + +public: + const DataType &dtype(void) const { return _dtype; } + void dtype(const DataType &value) { _dtype = value; } + +private: + DataType _dtype = DataType::Unknown; +}; + +template <> class Mixin +{ +public: + Mixin() = default; + +public: + const TensorShape *shape(void) const { return _shape.get(); } + void shape(std::unique_ptr &&shape) { _shape = std::move(shape); } + void shape(std::initializer_list dims); + +private: + std::unique_ptr _shape = nullptr; +}; + +/** + * @brief Trait for elements with name + */ +class NamedEntity +{ +public: + const std::string &name(void) const { return _name; } + void name(const std::string &name) { _name = name; } + +/// If new interface methods are added to this class they also will need to +/// be added in `using` of this macro to get them visible from inherited classes +#define LOCO_NAMED_ENTITY_EXPOSE using NamedEntity::name + +private: + std::string _name; +}; + +/** + * @brief Graph-level Input Metadata + */ +class GraphInput final : private NamedEntity, + public Mixin, + public Mixin +{ +public: + LOCO_NAMED_ENTITY_EXPOSE; + + // TODO Use GraphInputIndex (instead of uint32_t) + GraphInput(uint32_t index) : _index{index} + { + // DO NOTHING + } + + GraphInput(const GraphInput &) = delete; + GraphInput(GraphInput &&) = delete; + + ~GraphInput() = default; + +public: + GraphInputIndex index(void) const { return _index; } + +private: + uint32_t _index; +}; + +/** + * @brief Graph-level Output Metadata + */ +class GraphOutput final : private NamedEntity, + public Mixin, + public Mixin +{ +public: + LOCO_NAMED_ENTITY_EXPOSE; + + // TODO Use GraphOutputIndex (instead of uint32_t) + GraphOutput(uint32_t index) : _index{index} + { + // DO NOTHING + } + + GraphOutput(const GraphOutput &) = delete; + GraphOutput(GraphOutput &&) = delete; + + ~GraphOutput() = default; + +public: + GraphOutputIndex index(void) const { return _index; } + +private: + uint32_t _index; +}; + +/** + * @brief A neural network graph + */ +class Graph final +{ +public: + /** + * @brief Node Pool + * + * This alias confines the impact of changes to loco internals. + * + * TODO Remove this alias + */ + using NodeContext = NodePool; + + /** + * @brief Object Pool with Simple Factory Method + * + * TODO Remove this unused class + */ + template struct SimpleFactoryObjectPool : public ObjectPool + { + virtual ~SimpleFactoryObjectPool() = default; + + T *create(void) + { + std::unique_ptr ptr{new T}; + return ObjectPool::take(std::move(ptr)); + } + }; + + /** + * @brief GraphInput Pool + */ + struct InputContext final : public ObjectPool + { + GraphInput *create(void); + }; + + /** + * @brief GraphOutput Pool + */ + struct OutputContext final : public ObjectPool + { + GraphOutput *create(void); + }; + +public: + Graph() + { + // Associate "NodeContext" and the current "Graph" + _node_ctx.graph(this); + } + + // Copy/Move is not allowed for Graph + Graph(const Graph &) = delete; + Graph(Graph &&) = delete; + + ~Graph() = default; + +public: + NodeContext *nodes(void) { return &_node_ctx; } + const NodeContext *nodes(void) const { return &_node_ctx; } + InputContext *inputs(void) { return &_input_ctx; } + const InputContext *inputs(void) const { return &_input_ctx; } + OutputContext *outputs(void) { return &_output_ctx; } + const OutputContext *outputs(void) const { return &_output_ctx; } + +private: + NodeContext _node_ctx; + InputContext _input_ctx; + OutputContext _output_ctx; +}; + +struct GraphInputIndexQueryService : public DialectService +{ + virtual ~GraphInputIndexQueryService() = default; + + /** + * @brief Check whether a given node is associated with any Graph-level input + */ + virtual bool associated(const Node *node) const = 0; + + /** + * WARNING! CALLER SHOULD GUARANTEE that associated(node) is true before invoking this API. + */ + virtual GraphInputIndex index(const Node *node) const = 0; +}; + +std::vector input_nodes(const Graph *); + +struct GraphOutputIndexQueryService : public DialectService +{ + virtual ~GraphOutputIndexQueryService() = default; + + /** + * @brief Check whether a given node is associated with any Graph-level output + */ + virtual bool associated(const Node *node) const = 0; + + /** + * WARNING! CALLER SHOULD GUARANTEE that associated(node) is true before invoking this API. + */ + virtual GraphOutputIndex index(const Node *node) const = 0; +}; + +// TODO Use "const Graph *" +std::vector output_nodes(Graph *); + +/** + * @brief Enumerate all the nodes in a given graph + * + * NOTE This method returns std::set unlike input_nodes and output_nodes. + * + * Please use traverse algorithms that "Algorithm.h" provides (such as postorder_traversal) + * if order is relevant for implementation. + */ +std::set all_nodes(Graph *); + +std::unique_ptr make_graph(void); + +} // namespace loco + +#endif // __LOCO_IR_GRAPH_H__ diff --git a/compiler/loco/include/loco/IR/GraphInputIndex.h b/compiler/loco/include/loco/IR/GraphInputIndex.h new file mode 100644 index 00000000000..3c7ae98ef5f --- /dev/null +++ b/compiler/loco/include/loco/IR/GraphInputIndex.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_GRAPH_INPUT_INDEX_H__ +#define __LOCO_IR_GRAPH_INPUT_INDEX_H__ + +#include + +namespace loco +{ + +using GraphInputIndex = uint32_t; + +} // namespace loco + +#endif // __LOCO_IR_GRAPH_INPUT_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/GraphOutputIndex.h b/compiler/loco/include/loco/IR/GraphOutputIndex.h new file mode 100644 index 00000000000..3231cbd951c --- /dev/null +++ b/compiler/loco/include/loco/IR/GraphOutputIndex.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_GRAPH_OUTPUT_INDEX_H__ +#define __LOCO_IR_GRAPH_OUTPUT_INDEX_H__ + +#include + +namespace loco +{ + +using GraphOutputIndex = uint32_t; + +} // namespace loco + +#endif // __LOCO_IR_GRAPH_OUTPUT_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/MatrixAxis.h b/compiler/loco/include/loco/IR/MatrixAxis.h new file mode 100644 index 00000000000..8a1689bb3ed --- /dev/null +++ b/compiler/loco/include/loco/IR/MatrixAxis.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_MATRIX_AXIS_H__ +#define __LOCO_IR_MATRIX_AXIS_H__ + +namespace loco +{ + +enum class MatrixAxis +{ + Height, + Width +}; + +} // namespace loco + +#endif // __LOCO_IR_MATRIX_AXIS_H__ diff --git a/compiler/loco/include/loco/IR/MatrixCodec.h b/compiler/loco/include/loco/IR/MatrixCodec.h new file mode 100644 index 00000000000..40312641a0c --- /dev/null +++ b/compiler/loco/include/loco/IR/MatrixCodec.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_MATRIX_CODEC_H__ +#define __LOCO_IR_MATRIX_CODEC_H__ + +#include "loco/IR/MatrixShape.h" +#include "loco/IR/MatrixIndex.h" + +#include "loco/IR/TensorShape.h" +#include "loco/IR/TensorIndex.h" + +#include + +namespace loco +{ + +/** + * @brief Decribe how to build a matrix from a tensor + * + * Let us assume that "enc" is a matrix encoder. + * + * Given a tensor "inp" and its shape "inp.shape", "enc" builds a matrix + * "out" as follows: + * + * for each valid matrix index (referred to as matrix_idx below) for enc.shape(inp.shape) + * out.at(matrix_index) = inp.at(enc.value(matrix_index)) + */ +struct MatrixEncoder +{ + virtual ~MatrixEncoder() = default; + + virtual MatrixShape shape(const TensorShape &shape) const = 0; + virtual TensorIndex value(const MatrixIndex &index) const = 0; +}; + +/** + * @brief Describe how to build a tensor from a matrix + * + * Let us assume that "dec" is a matrix decoder. + * + * Given a matrix "inp" and its shape "inp.shape", "dec" builds a tensor + * "out" as follows: + * + * for each valid tensor index (referred to as tensor_index below) for dec.shape(inp.shape) + * out.at(tensor_index) = inp.at(dec.value(tensor_index)) + * + * NOTE "inp" is a matrix value and "out" is a tensor value in this example. + */ +struct MatrixDecoder +{ + virtual ~MatrixDecoder() = default; + + virtual TensorShape shape(const MatrixShape &) const = 0; + virtual MatrixIndex value(const TensorIndex &) const = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_MATRIX_CODEC_H__ diff --git a/compiler/loco/include/loco/IR/MatrixIndex.h b/compiler/loco/include/loco/IR/MatrixIndex.h new file mode 100644 index 00000000000..eb6d655803c --- /dev/null +++ b/compiler/loco/include/loco/IR/MatrixIndex.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_MATRIX_INDEX_H__ +#define __LOCO_IR_MATRIX_INDEX_H__ + +#include + +namespace loco +{ + +/** + * @brief Matrix Index + * + * Matrix Index indicates an "element" in a given Matrix + * + * Let us assume that there is a Matrix F and S denotes its shape (of MatrixShape type). + * + * Then, any valid Matrix index I satisfies the following invariants: + * - 0 <= I.row() < S.height() + * - 0 <= I.column() < S.width() + */ +class MatrixIndex final +{ +public: + MatrixIndex() = default; + +public: + const uint32_t &row(void) const { return _row; } + uint32_t &row(void) { return _row; } + + const uint32_t &column(void) const { return _column; } + uint32_t &column(void) { return _column; } + +private: + uint32_t _row = 0; + uint32_t _column = 0; +}; + +} // namespace loco + +#endif // __LOCO_IR_MATRIX_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/MatrixShape.h b/compiler/loco/include/loco/IR/MatrixShape.h new file mode 100644 index 00000000000..512691beb47 --- /dev/null +++ b/compiler/loco/include/loco/IR/MatrixShape.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_MATRIX_SHAPE_H__ +#define __LOCO_IR_MATRIX_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +/** + * @brief Matrix Shape + * + * This class describes the shape of matrix, which serves as the input/output of + * matrix operations (e.g. Matrix Multiplication). + * + * Each matrix is a collection of 2D features conceptually. + * Each matrix has height, width. + * + * height() refers to the height of matrix in a given matrix + * width() refers to the width of matrix in a given matrix + */ +class MatrixShape final +{ +public: + MatrixShape() = default; + +public: + const Dimension &height(void) const { return _height; } + Dimension &height(void) { return _height; } + + const Dimension &width(void) const { return _width; } + Dimension &width(void) { return _width; } + +private: + Dimension _height; + Dimension _width; +}; + +} // namespace loco + +#endif // __LOCO_IR_MATRIX_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/Node.forward.h b/compiler/loco/include/loco/IR/Node.forward.h new file mode 100644 index 00000000000..425b28aff74 --- /dev/null +++ b/compiler/loco/include/loco/IR/Node.forward.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_FORWARD_H__ +#define __LOCO_IR_NODE_FORWARD_H__ + +namespace loco +{ + +// NOTE This forward declaration SHOULD BE aligned with Node delcaration in "Node.h" +class Node; + +} // namespace loco + +#endif // __LOCO_IR_NODE_FORWARD_H__ diff --git a/compiler/loco/include/loco/IR/Node.h b/compiler/loco/include/loco/IR/Node.h new file mode 100644 index 00000000000..ef0bf238dbb --- /dev/null +++ b/compiler/loco/include/loco/IR/Node.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_H__ +#define __LOCO_IR_NODE_H__ + +#include "loco/ADT/AnnotatedItem.h" + +#include "loco/IR/Use.h" +#include "loco/IR/Dialect.h" +#include "loco/IR/NodePool.forward.h" +#include "loco/IR/Graph.forward.h" + +#include +#include +#include + +namespace loco +{ + +/** + * @brief Extensible Node Metadata + */ +struct NodeAnnotation +{ + virtual ~NodeAnnotation() = default; +}; + +enum class SubstQualifier +{ + Default, // Replace all the occurrences as "Use" (by default) +}; + +template class Subst; + +/** + * @brief Logical unit of computation + */ +class Node : public AnnotatedItem +{ +public: + friend class Use; + friend class Subst; + friend class NodePool; + friend std::set succs(const Node *node); + +public: + Node() = default; + + Node(const Node &) = delete; + Node(Node &&) = delete; + + virtual ~Node(); + +public: + Graph *graph(void) { return _graph; } + const Graph *graph(void) const { return _graph; } + +private: + /** + * @brief Set associated "Graph" + * + * @note Only "NodePool" class is permitted to invoke this private method. + */ + void graph(Graph *g) { _graph = g; } + +public: + /** + * @brief Return "Dialect" identifier that this node belongs to + * + * dialect() SHOULD return a valid pointer. + */ + virtual const Dialect *dialect(void) const = 0; + + virtual uint32_t opnum(void) const = 0; + +public: + /// @brief Return the number of arguments + virtual uint32_t arity(void) const = 0; + + /// @brief Access N-th argument node + virtual Node *arg(uint32_t N) const = 0; + + /** + * @brief Drop all the reference of arguments + * + * arg(n) SHOULD return nullptr for every valid n after drop() call. + */ + virtual void drop(void) = 0; + +private: + /** + * @brief Associated Graph + * + * May be nullptr if no associated Graph exists. + */ + Graph *_graph = nullptr; + + /** + * @brief The edges to a node that uses this node as its argument + * + * @note "succs" function below accesses this private field. + */ + std::set _uses; +}; + +/// @brief Enumerate all the predecessors of a given node +std::set preds(const Node *node); +/// @brief Enumerate all the successors of a given node +std::set succs(const Node *node); + +/** + * @brief A helper for below "replace" helper + */ +template <> class Subst +{ +public: + friend Subst replace(Node *node); + +private: + explicit Subst(Node *from); + +public: + void with(Node *into) const; + +private: + Node *_from; +}; + +Subst replace(Node *node); + +} // namespace loco + +#endif // __LOCO_IR_NODE_H__ diff --git a/compiler/loco/include/loco/IR/NodeMixins.h b/compiler/loco/include/loco/IR/NodeMixins.h new file mode 100644 index 00000000000..f0e34b0bad4 --- /dev/null +++ b/compiler/loco/include/loco/IR/NodeMixins.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_MIXINS_H__ +#define __LOCO_IR_NODE_MIXINS_H__ + +#include "loco/IR/Node.h" +#include "loco/IR/DataType.h" +#include "loco/IR/Dimension.h" + +#include +#include + +namespace loco +{ + +enum class NodeTrait +{ + DataType, + // Nodes with TensorShape trait will provide the following methods: + // - rank() + // - rank(value) + // - dim() + // - dim(value) + // - shape({...}) + TensorShape, +}; + +template class NodeMixin; + +template <> class NodeMixin +{ +public: + NodeMixin() = default; + +public: + const DataType &dtype(void) const { return _dtype; } + void dtype(const DataType &dtype) { _dtype = dtype; } + +private: + /// @brief Data type + DataType _dtype{DataType::Unknown}; +}; + +template <> class NodeMixin +{ +public: + NodeMixin() = default; + +public: + uint32_t rank(void) const { return _dims.size(); } + void rank(uint32_t value) { _dims.resize(value); } + + const Dimension &dim(uint32_t axis) const { return _dims.at(axis); } + Dimension &dim(uint32_t axis) { return _dims.at(axis); } + + void shape(std::initializer_list dims) + { + rank(dims.size()); + + uint32_t axis = 0; + for (auto d : dims) + { + dim(axis++) = d; + } + } + +private: + /// @brief Data shape (as tensor) + std::vector _dims; +}; + +template struct FixedArity +{ + template class Mixin : public virtual Base + { + public: + Mixin() + { + for (uint32_t n = 0; n < N; ++n) + { + _args[n] = std::unique_ptr{new Use{this}}; + } + } + + virtual ~Mixin() = default; + + public: + unsigned arity(void) const final { return N; } + + Node *arg(uint32_t n) const final { return _args.at(n)->node(); } + + void drop(void) final + { + for (uint32_t n = 0; n < N; ++n) + { + _args.at(n)->node(nullptr); + } + } + + protected: + // This API allows inherited classes to access "_args" field. + Use *at(unsigned n) const { return _args.at(n).get(); } + + private: + std::array, N> _args{}; + }; +}; + +template struct With +{ + template struct Mixin : public virtual Base, public NodeMixin + { + // DO NOTHING + }; +}; + +} // namespace loco + +#endif // __LOCO_IR_NODE_MIXINS_H__ diff --git a/compiler/loco/include/loco/IR/NodePool.forward.h b/compiler/loco/include/loco/IR/NodePool.forward.h new file mode 100644 index 00000000000..87bf01311ad --- /dev/null +++ b/compiler/loco/include/loco/IR/NodePool.forward.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_POOL_FORWARD_H__ +#define __LOCO_IR_NODE_POOL_FORWARD_H__ + +namespace loco +{ + +// This forward declaration SHOULD BE aligned with the actual declaration in "NodePool.h". +class NodePool; + +} // namespace loco + +#endif // __LOCO_IR_NODE_POOL_FORWARD_H__ diff --git a/compiler/loco/include/loco/IR/NodePool.h b/compiler/loco/include/loco/IR/NodePool.h new file mode 100644 index 00000000000..4db4caae376 --- /dev/null +++ b/compiler/loco/include/loco/IR/NodePool.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_POOL_H__ +#define __LOCO_IR_NODE_POOL_H__ + +#include "loco/IR/Node.h" +#include "loco/IR/Graph.forward.h" + +#include "loco/ADT/ObjectPool.h" + +namespace loco +{ + +class NodePool final : public ObjectPool +{ +public: + friend class Graph; + +public: + ~NodePool(); + +public: + template Derived *create(Args &&... args) + { + std::unique_ptr ptr{new Derived(std::forward(args)...)}; + ptr->graph(_graph); + return ObjectPool::take(std::move(ptr)); + } + + void destroy(Node *node) + { + if (!ObjectPool::erase(node)) + { + throw std::invalid_argument{"node"}; + } + } + +private: + /// Only "Graph" is permitted to invoke this private method. + void graph(Graph *g) { _graph = g; } + +private: + Graph *_graph = nullptr; +}; + +} // namespace loco + +#endif // __LOCO_IR_NODE_POOL_H__ diff --git a/compiler/loco/include/loco/IR/NodeShape.h b/compiler/loco/include/loco/IR/NodeShape.h new file mode 100644 index 00000000000..5eefd3c1958 --- /dev/null +++ b/compiler/loco/include/loco/IR/NodeShape.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODE_SHAPE_H__ +#define __LOCO_IR_NODE_SHAPE_H__ + +#include "loco/IR/Domain.h" + +#include "loco/IR/BiasShape.h" +#include "loco/IR/DepthwiseFilterShape.h" +#include "loco/IR/FeatureShape.h" +#include "loco/IR/FilterShape.h" +#include "loco/IR/MatrixShape.h" +#include "loco/IR/TensorShape.h" + +#include + +namespace loco +{ + +class NodeShape final +{ +public: + NodeShape() = default; + +public: + NodeShape(const BiasShape &shape) { set(shape); } + NodeShape(const DepthwiseFilterShape &shape) { set(shape); } + NodeShape(const FeatureShape &shape) { set(shape); } + NodeShape(const FilterShape &shape) { set(shape); } + NodeShape(const MatrixShape &shape) { set(shape); } + NodeShape(const TensorShape &shape) { set(shape); } + +public: + const Domain &domain(void) const { return _domain; } + +public: + void set(const BiasShape &); + void set(const DepthwiseFilterShape &); + void set(const FeatureShape &); + void set(const FilterShape &); + void set(const MatrixShape &); + void set(const TensorShape &); + +public: + template ShapeType as(void) const; + +private: + Domain _domain = Domain::Unknown; + std::vector _dims; +}; + +bool operator==(const NodeShape &lhs, const NodeShape &rhs); + +} // namespace loco + +#endif // __LOCO_IR_NODE_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/Nodes.h b/compiler/loco/include/loco/IR/Nodes.h new file mode 100644 index 00000000000..9aac48b6edb --- /dev/null +++ b/compiler/loco/include/loco/IR/Nodes.h @@ -0,0 +1,1123 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_NODES_H__ +#define __LOCO_IR_NODES_H__ + +#include "loco/IR/Node.h" +#include "loco/IR/Use.h" +#include "loco/IR/Domain.h" +#include "loco/IR/DataType.h" +#include "loco/IR/DataTypeTraits.h" +#include "loco/IR/Dimension.h" +#include "loco/IR/Window.h" +#include "loco/IR/Stride.h" +#include "loco/IR/Padding2D.h" +#include "loco/IR/PaddingND.h" +#include "loco/IR/TensorAxis.h" +#include "loco/IR/TensorAxisSet.h" +#include "loco/IR/FeatureCodec.h" +#include "loco/IR/FilterCodec.h" +#include "loco/IR/DepthwiseFilterCodec.h" +#include "loco/IR/MatrixCodec.h" +#include "loco/IR/NodeMixins.h" +#include "loco/IR/CanonicalNodeDecl.h" +#include "loco/IR/GraphInputIndex.h" +#include "loco/IR/GraphOutputIndex.h" + +namespace loco +{ + +class Graph; +class GraphInput; +class GraphOutput; + +/** + * @brief Make a value visible to user + */ +class Push /* to user */ final + : public CanonicalNodeDef::Mixin> +{ +public: + Push() = default; + +public: + Node *from(void) const { return at(0)->node(); } + void from(Node *node) { at(0)->node(node); } + +public: + void index(const GraphOutputIndex &index); + + /** + * @brief Get associated output index + * + * The behavior of this method is undefined when "index" is not set before. + * + * NOTE This method intentionally returns "GraphOutputIndex" instead of "const GraphOutputIndex &" + * not to expose the internal implementation details. + */ + GraphOutputIndex index(void) const; + + /** + * @brief Check whether index is initialized + * + * NOTE "indexed" method does not validate whether index is in a valid range + */ + bool indexed(void) const { return _index != -1; } + +private: + int64_t _index = -1; // Uninitialized +}; + +void link(GraphOutput *, Push *push); + +/// @brief Find a Push node with a given output index +Push *push_node(Graph *g, const GraphOutputIndex &index); + +/** + * @brief Create a value from user data + */ +class Pull /* from user */ final + : public CanonicalNodeDef::Mixin, + With::Mixin> +{ +public: + Pull() = default; + +public: + void index(const GraphInputIndex &index); + + /** + * @brief Get associated input index + * + * The behavior of this method is undefined when "index" is not set before. + * + * NOTE This method intentionally returns "GraphInputIndex" instead of "const GraphInputIndex &" + * not to expose the internal implementation details. + */ + GraphInputIndex index(void) const; + + /** + * @brief Check whether index is initialized + * + * NOTE "indexed" method does not validate whether index is in a valid range + */ + bool indexed(void) const { return _index != -1; } + +public: + void dtype(const DataType &d); + DataType dtype(void) const; + +private: + int64_t _index = -1; // Uninitialized + + /** + * @brief Locally cached data type attribute + * + * TODO Remove this cache once all the clients are updated + */ + DataType _dtype = DataType::Unknown; +}; + +void link(GraphInput *, Pull *pull); + +/// @brief Find a Pull node with a given input index +Pull *pull_node(Graph *g, const GraphInputIndex &index); + +/** + * @brief Create a new value identical to its input + * + * This node may encode memory transfer (such as CPU -> GPU or GPU -> CPU) + */ +class Forward final : public CanonicalNodeDef::Mixin> +{ +public: + Forward() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Create a new value that rectifies its input + */ +class ReLU final : public CanonicalNodeDef::Mixin> +{ +public: + ReLU() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Create a new value that rectifies its input capping the units at 6. + */ +class ReLU6 final : public CanonicalNodeDef::Mixin> +{ +public: + ReLU6() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Create a new value that rectifies its input by tanh + */ +class Tanh final : public CanonicalNodeDef::Mixin> +{ +public: + Tanh() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Create a value from constant byte array + * + * @note ConstGen assumes "lexical memory layout". + * + * Let us assume that a 'ConstGen' generates a constant tensor of shape "S". + * for each valid index I, the corresponding value comes from offset(S, I) + * where the implementation of "offset" is given as follows: + * + * uint32_t stride(TensorShape shape, uint32_t axis) { + * uint32_t res = 1; + * for (uint32_t n = rank(shape) - 1; n > axis; --n) { res *= shape.dim(n); } + * return res; + * } + * + * uint32_t offset(TensorShape shape, TensorIndex index) { + * uint32_t res = 0; + * for (uint32_t n = 0; n < rank(shape); ++n) { res += index.at(n) * stride(shape, n); } + * return res; + * } + */ +class ConstGen final + : public CanonicalNodeDef::Mixin, + With::Mixin, With::Mixin> +{ +public: + ConstGen() = default; + +public: + /** + * @brief Return the number of reserved elements + * @note This method returns the number of ELEMENT (not BYTE). + */ + template uint32_t size(void) const; + + /** + * @brief Adjust the number of reserved elements + */ + template void size(uint32_t size); + + /** + * @brief Get the element at a given position + * @require at(n) is valid only when n < size() + */ + template const typename DataTypeImpl
::Type &at(uint32_t n) const; + + /** + * @brief Update the element at a given position + * @require at(n) is valid only when n < size() + */ + template typename DataTypeImpl
::Type &at(uint32_t n); + +private: + /// @brief Data + std::vector _data; +}; + +/** + * @brief 2D Max Pooling + * + * MaxPool2D takes as input a feature map, and produces another feature map + * + * --- + * Any valid MaxPool2D nodes SHOULD satisfy the following conditions. + * + * Let us define several helper functions that takes a MaxPool2D nodes first: + * - IFM_DOMAIN returns the domain of its input + * - IFM_H returns the height of its input. + * - IFM_W returns the width of its input. + * - PAD_T returns the top padding required over its input + * - PAD_B returns the bottom padding required over its input + * - PAD_L returns the left padding required over its input + * - PAD_R returns the right padding required over its input + * - WIN_H returns the height of its receptive field. + * - WIN_W returns the width of its receptive field. + * - STRIDE_H returns the vertical(= on height) stride. + * - STRIDE_W returns the horizontal(= on width) stride. + * + * Condition 1 + * Statement + * + * A valid MaxPool2D node M SHOULD satisfy the following condition: + * - IFM_DOMAIN(M) == Feature + * + * Motivation + * + * There are many possible ways to encode a feature map as a tensor. + * - e.g. NCHW/NHWC/... + * + * In order to give some freedom on memory layout to backend, loco requires a feature map + * value to be explicitly encoded via FeatureEncode. + * + * Condition 2: + * Statement + * + * A valid MaxPool2D node M SHOULD satisfy the following conditions: + * - (IFM_H(M) + PAD_T(M) + PAD_B(M) - WIN_H(M)) % STRIDE_H(M) == 0 + * - (IFM_W(M) + PAD_L(M) + PAD_R(M) - WIN_W(M)) % STRIDE_W(M) == 0 + * + * Motivation + * + * The output shape may differ for each NN framework when these conditions do not hold. + * + * In order to mitigate such a difference among NN frameworks, loco requires these conditions + * for MaxPool2D nodes. + * + * This means that each frontend implementation SHOULD insert appropriate padding/trimming node + * before/after MaxPool2D node according to the semantics of the corresponding NN framework. + * --- + */ +class MaxPool2D final : public CanonicalNodeDef::Mixin> +{ +public: + Node *ifm(void) const { return at(0)->node(); } + void ifm(Node *node) { at(0)->node(node); } + +public: + const Padding2D *pad(void) const { return &_pad; } + Padding2D *pad(void) { return &_pad; } + +public: + const Window<2> *window(void) const { return &_window; } + Window<2> *window(void) { return &_window; } + +public: + const Stride<2> *stride(void) const { return &_stride; } + Stride<2> *stride(void) { return &_stride; } + +private: + // Pad + Padding2D _pad; + // Window + Window<2> _window; + // Stride + Stride<2> _stride; +}; + +/** + * @brief 2D Average Pooling + * + * @note Follows MaxPool2D (TODO: describe difference) + */ +class AvgPool2D final : public CanonicalNodeDef::Mixin> +{ +public: + enum class Convention + { + Unknown, + // Use the number of elements in each receptive field as a divisor + Full, + // Use the number of valid (non-padding) elements in each receptive field as a divisor + Valid + }; + +public: + Node *ifm(void) const { return at(0)->node(); } + void ifm(Node *node) { at(0)->node(node); } + +public: + Convention convention(void) const { return _convention; } + void convention(const Convention &convention) { _convention = convention; } + +public: + const Padding2D *pad(void) const { return &_pad; } + Padding2D *pad(void) { return &_pad; } + +public: + const Window<2> *window(void) const { return &_window; } + Window<2> *window(void) { return &_window; } + +public: + const Stride<2> *stride(void) const { return &_stride; } + Stride<2> *stride(void) { return &_stride; } + +private: + Convention _convention = Convention::Unknown; + Padding2D _pad; + Window<2> _window; + Stride<2> _stride; +}; + +/** + * @brief Create a feature map from a tensor + */ +class FeatureEncode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + FeatureEncoder *encoder(void) const { return _enc.get(); } + void encoder(std::unique_ptr &&enc) { _enc = std::move(enc); } + +private: + /// @note "encoder" is mandatory + std::unique_ptr _enc{nullptr}; +}; + +/** + * @brief Create a tensor from a feature map + */ +class FeatureDecode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + FeatureDecoder *decoder(void) const { return _dec.get(); } + void decoder(std::unique_ptr &&dec) { _dec = std::move(dec); } + +private: + /// @NOTE "decoder" is mandatory + std::unique_ptr _dec{nullptr}; +}; + +/** + * @brief Create a filter from a tensor + */ +class FilterEncode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + FilterEncoder *encoder(void) const { return _enc.get(); } + void encoder(std::unique_ptr &&enc) { _enc = std::move(enc); } + +private: + /// @note "encoder" is mandatory + std::unique_ptr _enc{nullptr}; +}; + +/** + * @brief Create a tensor from a filter + */ +class FilterDecode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + FilterDecoder *decoder(void) const { return _dec.get(); } + void decoder(std::unique_ptr &&dec) { _dec = std::move(dec); } + +private: + /// @note "decoder" is mandatory + std::unique_ptr _dec{nullptr}; +}; + +/** + * @brief Create a depthwise filter from a tensor + */ +class DepthwiseFilterEncode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + DepthwiseFilterEncoder *encoder(void) const { return _enc.get(); } + void encoder(std::unique_ptr &&enc) { _enc = std::move(enc); } + +private: + /// @note "encoder" is mandatory + std::unique_ptr _enc{nullptr}; +}; + +/** + * @brief Create a tensor from a depthwise filter + */ +class DepthwiseFilterDecode final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + DepthwiseFilterDecoder *decoder(void) const { return _dec.get(); } + void decoder(std::unique_ptr &&dec) { _dec = std::move(dec); } + +private: + /// @note "decoder" is mandatory + std::unique_ptr _dec{nullptr}; +}; + +enum class ReshapeType +{ + Fixed, // shape is known at compile time + // Add another type for a case when shape is not known at compile time +}; + +template class Reshape; + +/** + * @brief Reshape a tensor to another tensor whose shape is known at compile time + * + * @note This class reshapes the shape of an input tensor to _shape. + * Each dimension of _shape should be known at compile time. + * Any dimension of _shape should be greater than 0. + * + * Interpreter or runtime should lexicographically copy an input tensor into an output tensor. + * For example, values of an input tesor of shape [2, 2, 2, 2] will be copied into an output + * tensor of new shape [4, 4] like the following: + * input[0, 0, 0, 0] => output [0, 0] + * input[0, 0, 0, 1] => output [0, 1] + * input[0, 0, 1, 0] => output [0, 2] + * ... + * input[1, 1, 1, 1] => output [3, 3] + */ +template <> +class Reshape final + : public CanonicalNodeDef::Mixin, + With::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +using FixedReshape = Reshape; + +/** + * @brief Concatenate two tensors + * + * Given an axis, TensorConcat takes as input two tensors and produces a tensor + * concatenated along the given axis. + */ +class TensorConcat final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { at(1)->node(node); } + +public: + uint32_t axis(void) const { return _axis; } + void axis(uint32_t val) { _axis = val; } + +private: + // Axis + uint32_t _axis{0}; +}; + +/** + * @brief 2D Spatial Convolution + */ +class Conv2D final : public CanonicalNodeDef::Mixin> +{ +public: + Node *ifm(void) const { return at(0)->node(); } + void ifm(Node *node) { at(0)->node(node); } + + Node *ker(void) const { return at(1)->node(); } + void ker(Node *node) { at(1)->node(node); } + +public: + const Padding2D *pad(void) const { return &_pad; } + Padding2D *pad(void) { return &_pad; } + +public: + const Stride<2> *stride(void) const { return &_stride; } + Stride<2> *stride(void) { return &_stride; } + +private: + Padding2D _pad; + Stride<2> _stride; + + // TODO Support "Dilation" +}; + +/** + * @brief Depthwise 2D Convolution + */ +class DepthwiseConv2D final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *ifm(void) const { return at(0)->node(); } + void ifm(Node *node) { at(0)->node(node); } + + Node *ker(void) const { return at(1)->node(); } + void ker(Node *node) { at(1)->node(node); } + +public: + const Padding2D *pad(void) const { return &_pad; } + Padding2D *pad(void) { return &_pad; } + +public: + const Stride<2> *stride(void) const { return &_stride; } + Stride<2> *stride(void) { return &_stride; } + +private: + Padding2D _pad; + Stride<2> _stride; + + // TODO Support "Dilation" +}; + +/** + * @brief Reduce type functions + */ +enum class ReduceFunc +{ + Mean, // ReduceMean + // TODO Support other reduce operations +}; + +/** + * @brief Computes ReduceFunc operations for Tensor domain + * @note All the reduce functions always keep dimensions + */ +class TensorReduce final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + const TensorAxisSet *axes(void) const { return &_axes; } + TensorAxisSet *axes(void) { return &_axes; } + +public: + ReduceFunc func(void) const { return _func; } + void func(ReduceFunc func) { _func = func; } + +private: + TensorAxisSet _axes; + ReduceFunc _func; +}; + +/** + * @brief 2D Transposed Convolution + * + * @note TransposedConv2D have a few important conventions that IR users should + * understand and follow, so please check below notice carefully. + * + * + * 1. What is 'input' and 'output' + * + * For loco canonical TransposedConv2D, 'input' and 'output' mean actual input + * and output node of TransposedConv2D node. Be careful that some other + * frameworks may use opposite sense, especially TensorFlow which is inspired by + * backpropagation of convolution. + * For example, loco::TransposedConv2D::ifm() means actual input feature map + * node that is sourced into TransposedConv2D. + * + * 2. How to read kernel representation + * + * TransposedConv2D::ker() should be a node of Filter domain. Following is what + * each FilterAxis means as a kernel of TransposedConv2D: + * - FilterAxis::Height : kernel's height + * - FilterAxis::Width : kernel's width + * - FilterAxis::Depth : IFM's channel depth + * - FilterAxis::Count : OFM's channel depth + * TODO We may refactor FilterAxis as follow to reduce ambiguity: + * - FilterAxis::Height -> FilterAxis::H + * - FilterAxis::Width -> FilterAxis::W + * - FilterAxis::Depth -> FilterAxis::I + * - FilterAxis::Count -> FilterAxis::O + * + * + * 3. Tight fit rule + * + * TransposedConv2D have no information about its output shape. Instead, it + * always satisfy following 'tight fit' rule for horizontal and vertical + * dimension: + * + * O = S * ( I - 1 ) + F - P + * + * where + * O: output size + * S: stride + * I: input size + * F: effective kernal(filter) size + * P: whole pad size (= front + rear pad) + * + * With this, output shape is uniquely determined by all inputs and attributes. + */ +class TransposedConv2D final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *ifm(void) const { return at(0)->node(); } + void ifm(Node *node) { at(0)->node(node); } + + Node *ker(void) const { return at(1)->node(); } + void ker(Node *node) { at(1)->node(node); } + +public: + const Padding2D *pad(void) const { return &_pad; } + Padding2D *pad(void) { return &_pad; } + +public: + const Stride<2> *stride(void) const { return &_stride; } + Stride<2> *stride(void) { return &_stride; } + +private: + Padding2D _pad; + Stride<2> _stride; + + // TODO Support "Dilation" +}; + +/** + * @brief Computes softmax activations + */ +template class Softmax; + +/** +* @brief Computes softmax activations for Tensor domain +*/ +template <> +class Softmax final + : public CanonicalNodeDef::Mixin> +{ +public: + Softmax() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { return at(0)->node(node); } + + uint32_t axis(void) const { return _axis; } + void axis(uint32_t axis) { _axis = axis; } + +private: + uint32_t _axis = 0; +}; + +using TensorSoftmax = Softmax; + +/** + * @brief Create a "Tensor" from a "Bias" + */ +class BiasDecode final : public CanonicalNodeDef::Mixin> +{ +public: + BiasDecode() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Create a "Bias" from a "Tensor" + * + * BiasEncode currently requires a rank-1 tensor as its input. + */ +class BiasEncode final : public CanonicalNodeDef::Mixin> +{ +public: + BiasEncode() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Produce a value of domain D from an input value (of domain D) and a bias + */ +template class BiasAdd; + +/** + * @brief Add Tensor and Bias + * + * for each valid tensor index I + * out(I) = value(I) + bias(I.at(axis)) + */ +template <> +class BiasAdd final + : public CanonicalNodeDef::Mixin> +{ +public: + BiasAdd() = default; + +public: + Node *value(void) const { return at(0)->node(); } + void value(Node *node) { return at(0)->node(node); } + + Node *bias(void) const { return at(1)->node(); } + void bias(Node *node) { return at(1)->node(node); } + + uint32_t axis(void) const { return _axis; } + void axis(uint32_t axis) { _axis = axis; } + +private: + uint32_t _axis = 0; +}; + +// +// Alias for external users +// +// loco::TensorBiasAdd +// vs. +// loco::BiasAdd +// +using TensorBiasAdd = BiasAdd; + +/** + * @brief Add Feature and Bias along "depth" axis + * + * for each valid feature index (b, ch, row, col) + * out(b, ch, row, col) = value(b, ch, row, col) + bias(ch) + */ +template <> +class BiasAdd final + : public CanonicalNodeDef::Mixin> +{ +public: + BiasAdd() = default; + +public: + Node *value(void) const { return at(0)->node(); } + void value(Node *node) { return at(0)->node(node); } + + Node *bias(void) const { return at(1)->node(); } + void bias(Node *node) { return at(1)->node(node); } +}; + +using FeatureBiasAdd = BiasAdd; + +/** + * @brief Pads a tensor with constant value + * + * Pads a input tensor according to the padding with constant value. + * + * The dimension of each axis n of the output is + * output.dim(n) = padding.front(n) + input.dim(n) + padding.back(n) + * + * For example, input tensor of shape [1, 2] with + * + * padding.front(0) = 1; + * padding.back(0) = 2; + * + * padding.front(1) = 3; + * padding.back(1) = 4; + * + * will be a output tensor of shape + * [padding.front(0) + 1 + padding.back(0), padding.front(1) + 2 + padding.back(1)] = [4,9]. + */ +class TensorConstantPad final + : public CanonicalNodeDef::Mixin> +{ +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + + Node *constant(void) const { return at(1)->node(); } + void constant(Node *node) { at(1)->node(node); } + +public: + const PaddingND *padding(void) const { return &_padding; } + PaddingND *padding(void) { return &_padding; } + +private: + PaddingND _padding; +}; + +/** + * @brief Elementwise Add lhs and rhs + */ +class EltwiseAdd final : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseAdd() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Elementwise Maximum of lhs and rhs + * + * o = (l > r) ? l : r (element-wise) + */ +class EltwiseMax final : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseMax() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Elementwise Mul lhs and rhs + */ +class EltwiseMul final : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseMul() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Elementwise Sub lhs and rhs + */ +class EltwiseSub final : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseSub() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Elementwise Div lhs and rhs + */ +class EltwiseDiv final : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseDiv() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Elementwise Sqrt of input + */ +class EltwiseSqrt final + : public CanonicalNodeDef::Mixin> +{ +public: + EltwiseSqrt() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } +}; + +/** + * @brief Duplicate elements along specified axes + * + * TensorBroadcast takes a tensor and produces another tensor with the same rank but HIGHER + * dimensionality. + * + * To create such a tensor. TensorBroadcast duplicates the element along the specified axes. + * + * It is possible to control the degree of duplication with a partial map from TensorAxis to + * Dimension. + * + * TODO Explain the constraints (The dimension of inputs for specified axes SHOULD BE 1). + * TODO Explain the operation semantics + */ +class TensorBroadcast final + : public CanonicalNodeDef::Mixin> +{ +public: + TensorBroadcast() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + class Mapping final + { + public: + Mapping() = default; + + public: + bool defined(const TensorAxis &axis) const; + + const Dimension &dim(const TensorAxis &axis) const; + Dimension &dim(const TensorAxis &axis); + + private: + std::map _content; + }; + + Mapping *mapping(void) { return &_mapping; } + const Mapping *mapping(void) const { return &_mapping; } + +private: + Mapping _mapping; +}; + +/** + * @brief Create Matrix from Tensor + * + * MatrixEncode currently requires a rank-2 Tensor as its input. + */ +class MatrixEncode final + : public CanonicalNodeDef::Mixin> +{ +public: + MatrixEncode() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + MatrixEncoder *encoder(void) const { return _enc.get(); } + void encoder(std::unique_ptr &&enc) { _enc = std::move(enc); } + +private: + /// @note "encoder" is mandatory + std::unique_ptr _enc{nullptr}; +}; + +/** + * @brief Create Tensor from Matrix + * + * MatrixDecode currently requires a Matrix as its input. + */ +class MatrixDecode final + : public CanonicalNodeDef::Mixin> +{ +public: + MatrixDecode() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { at(0)->node(node); } + +public: + MatrixDecoder *decoder(void) const { return _dec.get(); } + void decoder(std::unique_ptr &&dec) { _dec = std::move(dec); } + +private: + /// @note "decoder" is mandatory + std::unique_ptr _dec{nullptr}; +}; + +/** + * @brief Matrix Multiplication lhs and rhs + * + * LHS and RHS must be on Matrix domain + */ +class MatMul final : public CanonicalNodeDef::Mixin> +{ +public: + MatMul() = default; + +public: + Node *lhs(void) const { return at(0)->node(); } + void lhs(Node *node) { return at(0)->node(node); } + + Node *rhs(void) const { return at(1)->node(); } + void rhs(Node *node) { return at(1)->node(node); } +}; + +/** + * @brief Permute an input + * + * In the following case, + * + * output = loco::TensorTranspose(input) + * + * perm()->axis(output's axis) = input's axis + * + * Input and output belong to tensor domain. + */ +class TensorTranspose final + : public CanonicalNodeDef::Mixin> +{ +public: + TensorTranspose() = default; + +public: + Node *input(void) const { return at(0)->node(); } + void input(Node *node) { return at(0)->node(node); } + + class Perm final + { + public: + Perm() = default; + + public: + uint32_t size() const { return _vals.size(); } + void size(uint32_t size) { _vals.resize(size); } + + const TensorAxis &axis(TensorAxis n) const { return _vals[n]; } + TensorAxis &axis(TensorAxis n) { return _vals[n]; } + + private: + std::vector _vals; + }; + + Perm *perm(void) { return &_perm; } + const Perm *perm(void) const { return &_perm; } + +private: + Perm _perm; +}; + +} // namespace loco + +#endif // __LOCO_IR_NODES_H__ diff --git a/compiler/loco/include/loco/IR/Padding2D.h b/compiler/loco/include/loco/IR/Padding2D.h new file mode 100644 index 00000000000..30557a8917c --- /dev/null +++ b/compiler/loco/include/loco/IR/Padding2D.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_PADDING2D_H__ +#define __LOCO_IR_PADDING2D_H__ + +#include + +namespace loco +{ + +class Padding2D final +{ +public: + Padding2D() : _top{0}, _bottom{0}, _left{0}, _right{0} + { + // DO NOTHING + } + +public: + Padding2D(uint32_t top, uint32_t bottom, uint32_t left, uint32_t right) + : _top{top}, _bottom{bottom}, _left{left}, _right{right} + { + // DO NOTHING + } + +public: + uint32_t top(void) const { return _top; } + void top(uint32_t value) { _top = value; } + +public: + uint32_t bottom(void) const { return _bottom; } + void bottom(uint32_t value) { _bottom = value; } + +public: + uint32_t left(void) const { return _left; } + void left(uint32_t value) { _left = value; } + +public: + uint32_t right(void) const { return _right; } + void right(uint32_t value) { _right = value; } + +private: + uint32_t _top; + uint32_t _bottom; + uint32_t _left; + uint32_t _right; +}; + +} // namespace loco + +#endif // __LOCO_IR_PADDING2D_H__ diff --git a/compiler/loco/include/loco/IR/PaddingND.h b/compiler/loco/include/loco/IR/PaddingND.h new file mode 100644 index 00000000000..59be73943f1 --- /dev/null +++ b/compiler/loco/include/loco/IR/PaddingND.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_PADDINGND_H__ +#define __LOCO_IR_PADDINGND_H__ + +#include +#include + +namespace loco +{ + +/** + * This class indicates how many pads to add before(front) and after(back) the contents of + * tensor in that dimension. + */ +class PaddingND final +{ + +public: + const uint32_t &front(uint32_t dim) const { return _front.at(dim); } + uint32_t &front(uint32_t dim) { return _front.at(dim); } + +public: + const uint32_t &back(uint32_t dim) const { return _back.at(dim); } + uint32_t &back(uint32_t dim) { return _back.at(dim); } + +public: + uint32_t rank(void) const { return _front.size(); } + void rank(uint32_t s) + { + _front.resize(s); + _back.resize(s); + } + +private: + std::vector _front; + std::vector _back; +}; + +} // namespace loco + +#endif // __LOCO_IR_PADDINGND_H__ diff --git a/compiler/loco/include/loco/IR/PermutingCodec.h b/compiler/loco/include/loco/IR/PermutingCodec.h new file mode 100644 index 00000000000..60b05dcbbc1 --- /dev/null +++ b/compiler/loco/include/loco/IR/PermutingCodec.h @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_PERMUTING_CODEC_H__ +#define __LOCO_IR_PERMUTING_CODEC_H__ + +#include "loco/IR/Domain.h" + +#include "loco/IR/FeatureAxis.h" +#include "loco/IR/FeatureCodec.h" +#include "loco/IR/FilterAxis.h" +#include "loco/IR/FilterCodec.h" +#include "loco/IR/DepthwiseFilterAxis.h" +#include "loco/IR/DepthwiseFilterCodec.h" +#include "loco/IR/MatrixAxis.h" +#include "loco/IR/MatrixCodec.h" +#include "loco/IR/TensorAxis.h" + +#include + +namespace loco +{ + +template class Permutation; +template class PermutingEncoder; +template class PermutingDecoder; + +/** + * @brief Mapping between Feature/Tensor Axis + */ +template <> class Permutation +{ +public: + Permutation() = default; + +public: + /** + * @brief Return whether a tensor axis is specified for a given feature axis + * + * This method does not validate the corresponding value. + */ + bool mapped(const FeatureAxis &axis_f) const; + + /** + * @brief Get the tensor axis corresponding to a given feature axis + * + * This method works correclty only when feature axis is mapped before. + */ + TensorAxis axis(const FeatureAxis &axis_f) const; + + /** + * @brief Set the tensor axis corresponding to a given feature axis + */ + TensorAxis &axis(const FeatureAxis &axis_f); + + TensorAxis operator[](const FeatureAxis &axis_f) const { return axis(axis_f); } + TensorAxis &operator[](const FeatureAxis &axis_f) { return axis(axis_f); } + +private: + std::map _map; +}; + +template <> class PermutingEncoder final : public FeatureEncoder +{ +public: + PermutingEncoder() = default; + +public: + PermutingEncoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + FeatureShape shape(const TensorShape &tensor_shape) const override; + TensorIndex value(const FeatureIndex &index) const override; + + std::unique_ptr clone(void) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +template <> class PermutingDecoder final : public FeatureDecoder +{ +public: + PermutingDecoder() = default; + +public: + PermutingDecoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + TensorShape shape(const FeatureShape &tensor_shape) const override; + FeatureIndex value(const TensorIndex &index) const override; + + std::unique_ptr clone(void) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Mapping between Filter/Tensor Axis + */ +template <> class Permutation +{ +public: + Permutation() = default; + +public: + /** + * @brief Return whether a given filter axis has a corresponding tensor axis + * + * This method does not validate the corresponding value. + */ + bool mapped(const FilterAxis &axis_f) const; + + /** + * @brief Get the tensor axis corresponding to a given filter axis + * + * This method works correctly only for mapped filter axes. + */ + const TensorAxis &axis(const FilterAxis &axis_f) const; + + /** + * @brief Set the tensor axis corresponding to a given filter axis + */ + TensorAxis &axis(const FilterAxis &axis_f); + + TensorAxis operator[](const FilterAxis &axis_f) const { return axis(axis_f); } + TensorAxis &operator[](const FilterAxis &axis_f) { return axis(axis_f); } + +private: + std::map _map; +}; + +/** + * @brief Permutation-based Tensor-to-Filter converter + */ +template <> class PermutingEncoder final : public FilterEncoder +{ +public: + PermutingEncoder() = default; + +public: + explicit PermutingEncoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + FilterShape shape(const TensorShape &tensor_shape) const override; + TensorIndex value(const FilterIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Permutation-based Filter-to-Tensor converter + */ +template <> class PermutingDecoder final : public FilterDecoder +{ +public: + PermutingDecoder() = default; + +public: + explicit PermutingDecoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + TensorShape shape(const FilterShape &tensor_shape) const override; + FilterIndex value(const TensorIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Mapping between DepthwiseFilter/Tensor Axis + */ +template <> class Permutation +{ +public: + Permutation() = default; + +public: + /** + * @brief Return whether a given depthwise filter axis has a corresponding tensor axis + * + * This method does not validate the corresponding value. + */ + bool mapped(const DepthwiseFilterAxis &axis_f) const; + + /** + * @brief Get the tensor axis corresponding to a given depthwise filter axis + * + * This method works correctly only for mapped depthwise filter axes. + */ + const TensorAxis &axis(const DepthwiseFilterAxis &axis_f) const; + + /** + * @brief Set the tensor axis corresponding to a given depthwise filter axis + */ + TensorAxis &axis(const DepthwiseFilterAxis &axis_f); + + TensorAxis operator[](const DepthwiseFilterAxis &axis_f) const { return axis(axis_f); } + TensorAxis &operator[](const DepthwiseFilterAxis &axis_f) { return axis(axis_f); } + +private: + std::map _map; +}; + +/** + * @brief Permutation-based Tensor-to-DepthwiseFilter converter + */ +template <> class PermutingEncoder final : public DepthwiseFilterEncoder +{ +public: + PermutingEncoder() = default; + +public: + PermutingEncoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + DepthwiseFilterShape shape(const TensorShape &tensor_shape) const override; + TensorIndex value(const DepthwiseFilterIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Permutation-based DepthwiseFilter-to-Tensor converter + */ +template <> class PermutingDecoder final : public DepthwiseFilterDecoder +{ +public: + PermutingDecoder() = default; + +public: + PermutingDecoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + TensorShape shape(const DepthwiseFilterShape &shape) const override; + DepthwiseFilterIndex value(const TensorIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Mapping between Matrix/Tensor Axis + */ +template <> class Permutation +{ +public: + Permutation() = default; + +public: + /** + * @brief Return whether a given matrix axis has a corresponding tensor axis + * + * This method does not validate the corresponding value. + */ + bool mapped(const MatrixAxis &axis_f) const; + + /** + * @brief Get the tensor axis corresponding to a given matrix axis + * + * This method works correctly only for mapped matrix axes. + */ + TensorAxis axis(const MatrixAxis &axis_f) const; + + /** + * @brief Set the tensor axis corresponding to a given matrix axis + */ + TensorAxis &axis(const MatrixAxis &axis_f); + + TensorAxis operator[](const MatrixAxis &axis_f) const { return axis(axis_f); } + TensorAxis &operator[](const MatrixAxis &axis_f) { return axis(axis_f); } + +private: + std::map _map; +}; + +/** + * @brief Permutation-based Tensor-to-Matrix converter + */ +template <> class PermutingEncoder final : public MatrixEncoder +{ +public: + PermutingEncoder() = default; + +public: + PermutingEncoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + MatrixShape shape(const TensorShape &tensor_shape) const override; + TensorIndex value(const MatrixIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +/** + * @brief Permutation-based Matrix-to-Tensor converter + */ +template <> class PermutingDecoder final : public MatrixDecoder +{ +public: + PermutingDecoder() = default; + +public: + PermutingDecoder(const Permutation &perm) : _perm{perm} + { + // DO NOTHING + } + +public: + bool valid(void) const; + +public: + TensorShape shape(const MatrixShape &tensor_shape) const override; + MatrixIndex value(const TensorIndex &index) const override; + +public: + const Permutation *perm(void) const { return &_perm; } + Permutation *perm(void) { return &_perm; } + void perm(const Permutation &p) { _perm = p; } + +private: + Permutation _perm; +}; + +} // namespace loco + +#endif // __LOCO_IR_PERMUTING_CODEC_H__ diff --git a/compiler/loco/include/loco/IR/Stride.h b/compiler/loco/include/loco/IR/Stride.h new file mode 100644 index 00000000000..eb9d4711559 --- /dev/null +++ b/compiler/loco/include/loco/IR/Stride.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_STRIDE_H__ +#define __LOCO_IR_STRIDE_H__ + +#include + +namespace loco +{ + +/** + * @brief Stride configuration for N-dimensional spatial operations + */ +template class Stride; + +/** + * @brief Stride configuration for 2D spatial operations + */ +template <> class Stride<2> final +{ +public: + uint32_t vertical(void) const { return _vertical; } + void vertical(uint32_t value) { _vertical = value; } + +public: + uint32_t horizontal(void) const { return _horizontal; } + void horizontal(uint32_t value) { _horizontal = value; } + +private: + uint32_t _vertical = 1; + uint32_t _horizontal = 1; +}; + +} // namespace loco + +#endif // __LOCO_IR_STRIDE_H__ diff --git a/compiler/loco/include/loco/IR/TensorAxis.h b/compiler/loco/include/loco/IR/TensorAxis.h new file mode 100644 index 00000000000..c41da512ece --- /dev/null +++ b/compiler/loco/include/loco/IR/TensorAxis.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_TENSOR_AXIS_H__ +#define __LOCO_IR_TENSOR_AXIS_H__ + +#include + +namespace loco +{ + +using TensorAxis = uint32_t; + +} // namespace loco + +#endif // __LOCO_IR_TENSOR_AXIS_H__ diff --git a/compiler/loco/include/loco/IR/TensorAxisSet.h b/compiler/loco/include/loco/IR/TensorAxisSet.h new file mode 100644 index 00000000000..240dcc5565f --- /dev/null +++ b/compiler/loco/include/loco/IR/TensorAxisSet.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_TENSOR_AXIS_SET_H__ +#define __LOCO_IR_TENSOR_AXIS_SET_H__ + +#include "loco/IR/TensorAxis.h" + +#include + +namespace loco +{ + +class TensorAxisSet final +{ +public: + TensorAxisSet() = default; + +public: + bool defined(const TensorAxis &axis) const { return _axes.find(axis) != _axes.end(); } + void insert(const TensorAxis &axis) { _axes.insert(axis); } + +private: + std::set _axes; +}; + +} // namespace loco + +#endif // __LOCO_IR_TENSOR_AXIS_SET_H__ diff --git a/compiler/loco/include/loco/IR/TensorIndex.h b/compiler/loco/include/loco/IR/TensorIndex.h new file mode 100644 index 00000000000..8f2385104db --- /dev/null +++ b/compiler/loco/include/loco/IR/TensorIndex.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_TENSOR_INDEX_H__ +#define __LOCO_IR_TENSOR_INDEX_H__ + +#include + +namespace loco +{ + +// TODO Remove dependencies on angkor +using TensorIndex = nncc::core::ADT::tensor::Index; + +} // namespace loco + +#endif // __LOCO_IR_TENSOR_INDEX_H__ diff --git a/compiler/loco/include/loco/IR/TensorShape.h b/compiler/loco/include/loco/IR/TensorShape.h new file mode 100644 index 00000000000..76f803b2b83 --- /dev/null +++ b/compiler/loco/include/loco/IR/TensorShape.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_TENSOR_SHAPE_H__ +#define __LOCO_IR_TENSOR_SHAPE_H__ + +#include "loco/IR/Dimension.h" + +#include + +namespace loco +{ + +class TensorShape +{ +public: + TensorShape() = default; + +public: + uint32_t rank(void) const { return _dims.size(); } + void rank(uint32_t r) { _dims.resize(r); } + + const Dimension &dim(uint32_t axis) const { return _dims.at(axis); } + Dimension &dim(uint32_t axis) { return _dims.at(axis); } + +private: + std::vector _dims; +}; + +/** + * @brief Return the number of elements in a tensor of given shape + * + * NOTE 1. + * + * "volume" returns 1 if the rank is 0. + * + * NOTE 2. + * + * "caller" SHOULD pass a valid shape that has no unknown dimension. + * - The behavior of "volume" on invalid is undefined. + * + */ +uint32_t element_count(const loco::TensorShape *tensor_shape); + +} // namespace loco + +#endif // __LOCO_IR_TENSOR_SHAPE_H__ diff --git a/compiler/loco/include/loco/IR/Use.h b/compiler/loco/include/loco/IR/Use.h new file mode 100644 index 00000000000..a4db924e498 --- /dev/null +++ b/compiler/loco/include/loco/IR/Use.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_USE_H__ +#define __LOCO_IR_USE_H__ + +#include "loco/IR/Node.forward.h" + +namespace loco +{ + +/** + * @brief The edge between a node definition and its user. + * + * Note that this "Use" denotes **one** edge between a node and its users, + * and thus there are unique node and user for each Use. + * + * There will be multiple "Use" edges for the same node if there are multiple + * users. + * + * This class design is heavily inspired from "Use" class in LLVM. + */ +class Use final +{ +public: + /** + * @brief Construct Use with its user + * @note user SHOULD BE set on construction. + */ + Use(Node *user) : _user{user} + { + // DO NOTHING + } + + Use(const Use &) = delete; + Use(Use &&) = delete; + + ~Use() + { + // Unlink itself from the node + node(nullptr); + } + +public: + Node *node(void) const { return _node; } + void node(Node *node); + +public: + Node *user(void) const { return _user; } + +private: + Node *_node{nullptr}; + Node *_user{nullptr}; +}; + +} // namespace loco + +#endif // __LOCO_IR_USE_H__ diff --git a/compiler/loco/include/loco/IR/Verifier.h b/compiler/loco/include/loco/IR/Verifier.h new file mode 100644 index 00000000000..8ff85e16f03 --- /dev/null +++ b/compiler/loco/include/loco/IR/Verifier.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_VERIFIER_H__ +#define __LOCO_IR_VERIFIER_H__ + +#include "loco/IR/Graph.h" + +#include + +namespace loco +{ + +/** + * @brief Possible error categories + * + * This enum class enumerates all the possible validation failure reasons. + * + * WARN DO NOT serialize this code. The tag value is subject to change. + */ +enum class ErrorCategory +{ + MissingArgument, + /* TO BE ADDED */ +}; + +/** + * @brief The details of each error + */ +template class ErrorDetail; + +/** + * @brief The details of MissingArgument error + */ +template <> class ErrorDetail +{ +public: + ErrorDetail(loco::Node *node, uint32_t index) : _node{node}, _index{index} + { + // DO NOTHING + } + +public: + /// @brief The node with missing arguments + loco::Node *node(void) const { return _node; } + /// @brief The missing argument index + uint32_t index(void) const { return _index; } + +private: + loco::Node *_node; + uint32_t _index; +}; + +/** + * @brief Error listener interface + * + * DOo NOT inherit this interface. Use DefaultErrorListener instead. + */ +struct IErrorListener +{ + virtual ~IErrorListener() = default; + + virtual void notify(const ErrorDetail &) = 0; +}; + +/** + * @brief Error listener (with default implementation) + */ +struct ErrorListener : public IErrorListener +{ + virtual ~ErrorListener() = default; + + void notify(const ErrorDetail &) override { return; } +}; + +/** + * @brief Validate a loco graph + * + * "valid" returns true if a given graph has no error. + * + * NOTE Given a valid(non-null) listener, "valid" notifies error details to the listener. + */ +bool valid(Graph *g, std::unique_ptr &&l = nullptr); + +} // namespace loco + +#endif // __LOCO_IR_VERIFIER_H__ diff --git a/compiler/loco/include/loco/IR/Window.h b/compiler/loco/include/loco/IR/Window.h new file mode 100644 index 00000000000..604fea868ee --- /dev/null +++ b/compiler/loco/include/loco/IR/Window.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_WINDOW_H__ +#define __LOCO_IR_WINDOW_H__ + +#include + +namespace loco +{ + +/** + * @brief ND Receptive Field Shape + * + * Window describes the shape of N-dimensional receptive field. + */ +template class Window; + +/** + * @brief 2D Receptive Field Shape + */ +template <> class Window<2> final +{ +public: + uint32_t vertical(void) const { return _vertical; } + void vertical(uint32_t value) { _vertical = value; } + +public: + uint32_t horizontal(void) const { return _horizontal; } + void horizontal(uint32_t value) { _horizontal = value; } + +private: + uint32_t _vertical = 1; + uint32_t _horizontal = 1; +}; + +} // namespace loco + +#endif // __LOCO_IR_WINDOW_H__ diff --git a/compiler/loco/include/loco/Service/CanonicalShapeInferenceRule.h b/compiler/loco/include/loco/Service/CanonicalShapeInferenceRule.h new file mode 100644 index 00000000000..cd3bed405ea --- /dev/null +++ b/compiler/loco/include/loco/Service/CanonicalShapeInferenceRule.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_SERVICE_CANONICAL_SHAPE_INFERENCE_RULE_H__ +#define __LOCO_SERVICE_CANONICAL_SHAPE_INFERENCE_RULE_H__ + +#include "loco/Service/ShapeInferenceRule.h" + +namespace loco +{ + +/** + * @brief Shape inference rule for canonical dialect + */ +struct CanonicalShapeInferenceRule final : public ShapeInferenceRule +{ + bool support(const API &ver) const final; + bool recognize(const Dialect *) const final; + bool infer(const Node *, NodeShape &) const final; + void infer(const Context *, const Node *, Sink *) const final; +}; + +} // namespace loco + +#endif // __LOCO_SERVICE_CANONICAL_SHAPE_INFERENCE_RULE_H__ diff --git a/compiler/loco/include/loco/Service/MultiDialectShapeInferenceRule.h b/compiler/loco/include/loco/Service/MultiDialectShapeInferenceRule.h new file mode 100644 index 00000000000..1a6c85b4296 --- /dev/null +++ b/compiler/loco/include/loco/Service/MultiDialectShapeInferenceRule.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_SERVICE_MULTI_DIALECT_SHAPE_INFERENCE_RULE_H__ +#define __LOCO_SERVICE_MULTI_DIALECT_SHAPE_INFERENCE_RULE_H__ + +#include "loco/Service/ShapeInferenceRule.h" + +#include + +namespace loco +{ + +/** + * @brief Shape inference rule for multiple dialects + */ +class MultiDialectShapeInferenceRule final : public ShapeInferenceRule +{ +public: + bool recognize(const Dialect *) const final; + bool infer(const Node *, NodeShape &) const final; + + /// @brief Bind a specific rule to a Dialect + MultiDialectShapeInferenceRule &bind(const Dialect *d, const ShapeInferenceRule *rule); + +private: + std::map _rules; +}; + +} // namespace loco + +#endif // __LOCO_SERVICE_MULTI_DIALECT_SHAPE_INFERENCE_RULE_H__ diff --git a/compiler/loco/include/loco/Service/ShapeInference.h b/compiler/loco/include/loco/Service/ShapeInference.h new file mode 100644 index 00000000000..f7bc5d4d62c --- /dev/null +++ b/compiler/loco/include/loco/Service/ShapeInference.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_SERVICE_SHAPE_INFERENCE_H__ +#define __LOCO_SERVICE_SHAPE_INFERENCE_H__ + +#include "loco/Service/ShapeInferenceRule.h" +#include "loco/IR/Graph.h" + +/** + * @file This file implements dialect-agnostic shape inference framework + * + * HOW TO USE: + * + * loco::Graph *g = ...; + * loco::ShapeInferenceRule *rule = ...; + * loco::apply(rule).to(g); + * + */ +namespace loco +{ + +class ShapeInferenceSession +{ +public: + ShapeInferenceSession(const ShapeInferenceRule *rule) : _rule{rule} + { + // DO NOTHING + } + +public: + bool to(Graph *g) const; + +private: + const ShapeInferenceRule *_rule; +}; + +inline ShapeInferenceSession apply(ShapeInferenceRule *r) { return ShapeInferenceSession{r}; } + +struct ShapeInference +{ + static bool known(const Node *); + static NodeShape get(const Node *); + static void erase(Node *); +}; + +inline bool shape_known(const Node *node) { return ShapeInference::known(node); } +inline NodeShape shape_get(const Node *node) { return ShapeInference::get(node); } +inline void shape_erase(Node *node) { ShapeInference::erase(node); } + +} // namespace loco + +#endif // __LOCO_SERVICE_SHAPE_INFERENCE_H__ diff --git a/compiler/loco/include/loco/Service/ShapeInferenceRule.h b/compiler/loco/include/loco/Service/ShapeInferenceRule.h new file mode 100644 index 00000000000..889f0b6b24b --- /dev/null +++ b/compiler/loco/include/loco/Service/ShapeInferenceRule.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_SERVICE_SHAPE_INFERENCE_RULE_H__ +#define __LOCO_SERVICE_SHAPE_INFERENCE_RULE_H__ + +#include "loco/IR/Domain.h" +#include "loco/IR/Dialect.h" +#include "loco/IR/Node.h" +#include "loco/IR/NodeShape.h" + +namespace loco +{ + +struct ShapeInferenceRule +{ + virtual ~ShapeInferenceRule() = default; + + enum class API + { + /** + * API v1 + * + * This API uses "shape_get" method to query the shape of other nodes. + */ + V1, + + /** + * API v2 + * + * This API uses a given context (defined below) to query the shape of other nodes. + */ + V2, + }; + + /// @brief Check whether a given API is available or not + virtual bool support(const API &api) const + { + // To be backward compatible + return api == API::V1; + } + + /// @brief Return true if this rule recognizes a given dialect + virtual bool recognize(const Dialect *) const = 0; + + /** + * @brief Infer node's shape + * + * WARNING!! + * + * Implementation SHOULD return true only when it succeeds in inference! + * + */ + virtual bool infer(const Node *, NodeShape &) const = 0; + + // + // API v2 + // + struct Context + { + virtual ~Context() = default; + + virtual bool known(const Node *node) const = 0; + virtual NodeShape get(const Node *node) const = 0; + }; + + struct Sink + { + virtual ~Sink() = default; + + // TODO Add methods for error reporting + + // Each ShapeInferenceRule SHOULD invoke one of okay and fail before it returns + virtual void okay(const NodeShape &) = 0; + virtual void fail(void) = 0; + }; + + // WARNING! Invoke this method only when API v2 is supported + virtual void infer(const Context *, const Node *, Sink *) const; +}; + +} // namespace loco + +#endif // __LOCO_SERVICE_SHAPE_INFERENCE_RULE_H__ diff --git a/compiler/loco/include/loco/Service/TypeInference.h b/compiler/loco/include/loco/Service/TypeInference.h new file mode 100644 index 00000000000..c2ce1a4c7c3 --- /dev/null +++ b/compiler/loco/include/loco/Service/TypeInference.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_SERVICE_TYPE_INFERENCE_H__ +#define __LOCO_SERVICE_TYPE_INFERENCE_H__ + +#include "loco/IR/DataType.h" + +#include "loco/IR/Node.h" +#include "loco/IR/Dialect.h" +#include "loco/IR/Graph.h" + +#include + +/** + * @file This file implements dialect-agnostic type inference framework. + * + * HOW TO USE: + * + * loco::Graph *g = ...; + * loco::TypeInferenceRule *rule = ...; + * loco::apply(rule).to(g); + * + */ +namespace loco +{ + +struct TypeInferenceRule +{ + virtual ~TypeInferenceRule() = default; + + /// @brief Return true if this rule recognizes a given dialect + virtual bool recognize(const Dialect *) const = 0; + + /** + * Framework guarantees the followings: + * + * 1. Framework tries to infer the data type of each node only after the data type of all of + * its valid (= non-nullptr) argument nodes is inferred. + * 2. The result of preceding "infer" is accessible through below dtype_get method. + * - This holds only when preceding "infer" returns true. + */ + virtual bool infer(const Node *, DataType &) const = 0; +}; + +/** + * @brief Type Inference Rule for Canonical Dialect + */ +struct CanonicalTypeInferenceRule final : public TypeInferenceRule +{ + bool recognize(const Dialect *) const final; + bool infer(const Node *, DataType &) const final; +}; + +/** + * @brief Type Inference Rule for multiple dialects + */ +class MultiDialectTypeInferenceRule final : public TypeInferenceRule +{ +public: + bool recognize(const Dialect *) const final; + bool infer(const Node *, DataType &) const final; + + /// @brief Bind a specific rule to a Dialect + MultiDialectTypeInferenceRule &bind(const Dialect *d, const TypeInferenceRule *rule); + +private: + std::map _rules; +}; + +class TypeInferenceSession +{ +public: + TypeInferenceSession(const TypeInferenceRule *rule) : _rule{rule} + { + // DO NOTHING + } + +public: + bool to(Graph *g) const; + +private: + const TypeInferenceRule *_rule; +}; + +inline TypeInferenceSession apply(TypeInferenceRule *r) { return TypeInferenceSession{r}; } + +struct TypeInference +{ + static bool known(const Node *); + static DataType get(const Node *); + static void erase(Node *); +}; + +inline bool dtype_known(const Node *node) { return TypeInference::known(node); } +inline DataType dtype_get(const Node *node) { return TypeInference::get(node); } +inline void dtype_erase(Node *node) { TypeInference::erase(node); } + +} // namespace loco + +#endif // __LOCO_SERVICE_TYPE_INFERENCE_H__ diff --git a/compiler/loco/src/ADT/AnnotatedItem.test.cpp b/compiler/loco/src/ADT/AnnotatedItem.test.cpp new file mode 100644 index 00000000000..42113ff7bf8 --- /dev/null +++ b/compiler/loco/src/ADT/AnnotatedItem.test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/ADT/AnnotatedItem.h" + +#include +#include + +namespace +{ + +struct Annotation +{ + virtual ~Annotation() = default; +}; + +template struct DerivedAnnotation final : public Annotation +{ + static std::unique_ptr> make(void) + { + return stdex::make_unique>(); + } +}; + +} // namespace + +TEST(AnnotatedItemTest, annotation) +{ + loco::AnnotatedItem<::Annotation> item; + + ASSERT_EQ(item.annot>(), nullptr); + + item.annot(DerivedAnnotation<0>::make()); + + ASSERT_NE(item.annot>(), nullptr); + ASSERT_EQ(item.annot>(), nullptr); + + item.annot>(nullptr); + ASSERT_EQ(item.annot>(), nullptr); + + // Below check guarantees that "annot(nullptr)" is allowed even when there is no annotation. + // This guarantee allows us to simplify code for some cases. + // + // Let us consider the following example: + // + // void f(loco::AnnotatedItem *item) + // { + // /* DO SOMETHING */ + // if (cond) { item->annot(nullptr); + // } + // + // void g(loco::AnnotatedItem *item) + // { + // f(item); + // item->annot(nullptr); + // } + // + // The implementation of "g" gets complicated if annot(nullptr) is not allowed if there is + // no annotation. + // + item.annot>(nullptr); +} diff --git a/compiler/loco/src/ADT/ObjectPool.cpp b/compiler/loco/src/ADT/ObjectPool.cpp new file mode 100644 index 00000000000..d15a30a995d --- /dev/null +++ b/compiler/loco/src/ADT/ObjectPool.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/ADT/ObjectPool.h" + +// This file validates "ObjectPool.h". Pleaes DO NOT remove this file. diff --git a/compiler/loco/src/IR/Algorithm.cpp b/compiler/loco/src/IR/Algorithm.cpp new file mode 100644 index 00000000000..712e2997543 --- /dev/null +++ b/compiler/loco/src/IR/Algorithm.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Algorithm.h" + +#include +#include +#include + +namespace +{ + +class Frame final +{ +public: + Frame(loco::Node *ptr) : _ptr{ptr}, _pos{-1} + { + // DO NOTHING + } + +public: + loco::Node *ptr(void) const { return _ptr; } + int64_t pos(void) const { return _pos; } + + loco::Node &node(void) const { return *_ptr; } + + void advance(void) { _pos += 1; } + +private: + loco::Node *_ptr = nullptr; + int64_t _pos = -1; +}; + +} // namespace + +namespace loco +{ + +// TODO Support cyclic graphs +std::vector postorder_traversal(const std::vector &roots) +{ + std::vector res; + + std::set visited_nodes; + std::stack frames; + + auto visited = [&visited_nodes](loco::Node *node) { + return visited_nodes.find(node) != visited_nodes.end(); + }; + + // NOTE There is not much difference between "auto" and "auto &" as node is of "loco::Node *" + // type. + for (auto node : roots) + { + assert((node != nullptr) && "root is invalid"); + frames.push(Frame{node}); + } + + while (!frames.empty()) + { + auto &top_frame = frames.top(); + + if (top_frame.pos() == -1) + { + if (visited(top_frame.ptr())) + { + frames.pop(); + continue; + } + visited_nodes.insert(top_frame.ptr()); + } + + top_frame.advance(); + + assert(top_frame.pos() >= 0); + + if (top_frame.pos() < static_cast(top_frame.node().arity())) + { + // Let's visit the next argument + // + // NOTE "next" may be nullptr if a graph is under construction. + if (auto next = top_frame.node().arg(top_frame.pos())) + { + frames.push(Frame{next}); + } + } + else + { + // Let's visit the current argument (all the arguments are already visited) + auto curr = top_frame.ptr(); + res.emplace_back(curr); + frames.pop(); + } + } + + return res; +} + +std::set active_nodes(const std::vector &roots) +{ + // This implementation works but may be inefficient + // + // TODO Use efficient implementation if necessary + auto nodes = postorder_traversal(roots); + return std::set{nodes.begin(), nodes.end()}; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/Algorithm.test.cpp b/compiler/loco/src/IR/Algorithm.test.cpp new file mode 100644 index 00000000000..f0a3585c0e3 --- /dev/null +++ b/compiler/loco/src/IR/Algorithm.test.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Algorithm.h" +#include "loco/IR/Graph.h" + +#include + +#include + +namespace +{ + +bool contains(const std::vector &vec, loco::Node *val) +{ + return std::any_of(vec.begin(), vec.end(), [val](loco::Node *node) { return node == val; }); +} + +bool contains(const std::set &s, loco::Node *val) +{ + return std::any_of(s.begin(), s.end(), [val](loco::Node *node) { return node == val; }); +} + +} // namespace + +TEST(AlgorithmTest, postorder_traversal) +{ + auto g = loco::make_graph(); + + auto pull_1 = g->nodes()->create(); + auto push = g->nodes()->create(); + + push->from(pull_1); + + // Create a dummy node unreachable from the above "push" node + g->nodes()->create(); + + auto seq = loco::postorder_traversal({push}); + + ASSERT_EQ(seq.size(), 2); + ASSERT_EQ(seq.at(0), pull_1); + ASSERT_EQ(seq.at(1), push); +} + +TEST(AlgorithmTest, postorder_traversal_visit_once) +{ + auto g = loco::make_graph(); + + // Create a network of the following form: + // + // Push1 Push2 <-- outputs + // \ / + // Pull <-- input + // + auto pull = g->nodes()->create(); + auto push_1 = g->nodes()->create(); + auto push_2 = g->nodes()->create(); + + push_1->from(pull); + push_2->from(pull); + + auto seq = loco::postorder_traversal({push_1, push_2}); + + ASSERT_EQ(seq.size(), 3); + ASSERT_TRUE(contains(seq, pull)); + ASSERT_TRUE(contains(seq, push_1)); + ASSERT_TRUE(contains(seq, push_2)); +} + +TEST(AlgorithmTest, postorder_traversal_incomplte_graph) +{ + auto g = loco::make_graph(); + + // Create a network of the following form: + // + // TensorConcat + // / \ + // Pull X + // + auto pull = g->nodes()->create(); + auto concat = g->nodes()->create(); + + concat->lhs(pull); + + auto seq = loco::postorder_traversal({concat}); + + ASSERT_EQ(seq.size(), 2); + ASSERT_EQ(seq.at(0), pull); + ASSERT_EQ(seq.at(1), concat); +} + +TEST(AlgorithmTest, active_nodes) +{ + auto g = loco::make_graph(); + + auto pull = g->nodes()->create(); + auto push = g->nodes()->create(); + + push->from(pull); + + // NOTE This new Push node is unnecessary to compute "push" + g->nodes()->create(); + + auto s = loco::active_nodes({push}); + + ASSERT_EQ(s.size(), 2); + ASSERT_TRUE(contains(s, pull)); + ASSERT_TRUE(contains(s, push)); +} diff --git a/compiler/loco/src/IR/BiasShape.test.cpp b/compiler/loco/src/IR/BiasShape.test.cpp new file mode 100644 index 00000000000..7f9b8dfedb6 --- /dev/null +++ b/compiler/loco/src/IR/BiasShape.test.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/BiasShape.h" + +#include + +TEST(BiasShapeTest, default_constructor) +{ + loco::BiasShape shape; + + ASSERT_FALSE(shape.length().known()); +} diff --git a/compiler/loco/src/IR/CanonicalDialect.cpp b/compiler/loco/src/IR/CanonicalDialect.cpp new file mode 100644 index 00000000000..f89ea447bf6 --- /dev/null +++ b/compiler/loco/src/IR/CanonicalDialect.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/CanonicalDialect.h" +#include "loco/IR/Graph.h" +#include "loco/IR/Nodes.h" + +#include + +#include + +namespace +{ + +struct GraphOutputIndexQueryServiceImpl final : public loco::GraphOutputIndexQueryService +{ + bool associated(const loco::Node *node) const final + { + if (auto push = dynamic_cast(node)) + { + return push->indexed(); + } + return false; + } + + loco::GraphOutputIndex index(const loco::Node *node) const final + { + assert(associated(node)); + auto push = dynamic_cast(node); + assert(push != nullptr); + return push->index(); + } +}; + +} // namespace + +namespace loco +{ + +CanonicalDialect::CanonicalDialect() +{ + service(stdex::make_unique()); +} + +Dialect *CanonicalDialect::get(void) +{ + static CanonicalDialect d; + return &d; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/CanonicalDialect.test.cpp b/compiler/loco/src/IR/CanonicalDialect.test.cpp new file mode 100644 index 00000000000..96b48218df1 --- /dev/null +++ b/compiler/loco/src/IR/CanonicalDialect.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/CanonicalDialect.h" + +#include + +TEST(CanonicalDialectTest, get) +{ + auto d = loco::CanonicalDialect::get(); + + // get() SHOULD return a valid(non-null) pointer + ASSERT_NE(d, nullptr); + // The return value SHOULD be stable across multiple invocations + ASSERT_EQ(d, loco::CanonicalDialect::get()); +} diff --git a/compiler/loco/src/IR/CanonicalNode.cpp b/compiler/loco/src/IR/CanonicalNode.cpp new file mode 100644 index 00000000000..d5e13a41591 --- /dev/null +++ b/compiler/loco/src/IR/CanonicalNode.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/CanonicalNode.h" +#include "loco/IR/CanonicalDialect.h" + +namespace loco +{ + +const Dialect *CanonicalNode::dialect(void) const { return CanonicalDialect::get(); } + +} // namespace loco diff --git a/compiler/loco/src/IR/CanonicalNode.test.cpp b/compiler/loco/src/IR/CanonicalNode.test.cpp new file mode 100644 index 00000000000..cb61b5e838d --- /dev/null +++ b/compiler/loco/src/IR/CanonicalNode.test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/CanonicalNode.h" + +#include + +TEST(CanonicalNodeTest, visitor_with_user_default_impl) +{ + struct MyVisitor final : public loco::CanonicalNodeVisitor + { + // This visitor returns 128 if it visits a Forward node. + uint32_t visit(const loco::Forward *) final { return 128; } + + // Otherwise, this visitor returns 256. + uint32_t visit(const loco::Node *) final { return 256; } + }; + + loco::Forward forward; + loco::ConstGen constgen; + + MyVisitor v; + + ASSERT_EQ(forward.accept(&v), 128); + ASSERT_EQ(constgen.accept(&v), 256); +} + +TEST(CanonicalNodeTest, visitor) +{ + struct CountingVisitor final : public loco::CanonicalNodeVisitor + { + uint32_t visit(const loco::Forward *) final { return 1; } + }; + + // Visitor can visit constant nodes + const loco::Forward node; + + CountingVisitor v; + + ASSERT_EQ(node.accept(&v), 1); +} + +TEST(CanonicalNodeTest, mutable_visitor) +{ + struct ResetForward final : public loco::CanonicalNodeMutableVisitor + { + void visit(loco::Forward *node) final { node->input(nullptr); } + }; + + loco::Pull pull_node; + loco::Forward forward_node; + + forward_node.input(&pull_node); + + ResetForward v; + forward_node.accept(&v); + + ASSERT_EQ(forward_node.input(), nullptr); +} diff --git a/compiler/loco/src/IR/CanonicalOpcode.cpp b/compiler/loco/src/IR/CanonicalOpcode.cpp new file mode 100644 index 00000000000..6355ecf1fe4 --- /dev/null +++ b/compiler/loco/src/IR/CanonicalOpcode.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/CanonicalOpcode.h" + +// NOTE This file validates "CanonicalOpcode.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/DataType.cpp b/compiler/loco/src/IR/DataType.cpp new file mode 100644 index 00000000000..56794dac736 --- /dev/null +++ b/compiler/loco/src/IR/DataType.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DataType.h" + +// This file validates "DataType.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/DataTypeTraits.test.cpp b/compiler/loco/src/IR/DataTypeTraits.test.cpp new file mode 100644 index 00000000000..76d2515a978 --- /dev/null +++ b/compiler/loco/src/IR/DataTypeTraits.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DataTypeTraits.h" + +#include + +#include + +TEST(DataTypeTraitsTest, FLOAT32) +{ + auto obtained = std::type_index(typeid(loco::DataTypeImpl::Type)); + auto expected = std::type_index(typeid(float)); + + ASSERT_EQ(obtained, expected); +} diff --git a/compiler/loco/src/IR/DepthwiseFilterAxis.cpp b/compiler/loco/src/IR/DepthwiseFilterAxis.cpp new file mode 100644 index 00000000000..9d58795b289 --- /dev/null +++ b/compiler/loco/src/IR/DepthwiseFilterAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DepthwiseFilterAxis.h" + +// NOTE This file validates "DepthwiseFilterAxis.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/DepthwiseFilterCodec.cpp b/compiler/loco/src/IR/DepthwiseFilterCodec.cpp new file mode 100644 index 00000000000..05a7fd723b1 --- /dev/null +++ b/compiler/loco/src/IR/DepthwiseFilterCodec.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DepthwiseFilterCodec.h" + +// NOTE This file validates "DepthwiseFilterCodec.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/DepthwiseFilterIndex.test.cpp b/compiler/loco/src/IR/DepthwiseFilterIndex.test.cpp new file mode 100644 index 00000000000..202647cfcc6 --- /dev/null +++ b/compiler/loco/src/IR/DepthwiseFilterIndex.test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DepthwiseFilterIndex.h" + +#include + +TEST(DepthwiseFilterIndexTest, default_constructor) +{ + loco::DepthwiseFilterIndex index; + + // All the values are 0 at the beginning + ASSERT_EQ(index.channel(), 0); + ASSERT_EQ(index.nth(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); +} + +TEST(DepthwiseFilterIndexTest, settet_and_getter) +{ + loco::DepthwiseFilterIndex index; + + // Set depth + index.channel() = 2; + + ASSERT_EQ(index.channel(), 2); + ASSERT_EQ(index.nth(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set multiplier + index.nth() = 3; + + ASSERT_EQ(index.channel(), 2); + ASSERT_EQ(index.nth(), 3); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set height + index.row() = 4; + + ASSERT_EQ(index.channel(), 2); + ASSERT_EQ(index.nth(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 0); + + // Set width + index.column() = 5; + + ASSERT_EQ(index.channel(), 2); + ASSERT_EQ(index.nth(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 5); +} diff --git a/compiler/loco/src/IR/DepthwiseFilterShape.test.cpp b/compiler/loco/src/IR/DepthwiseFilterShape.test.cpp new file mode 100644 index 00000000000..2b9518c1f5b --- /dev/null +++ b/compiler/loco/src/IR/DepthwiseFilterShape.test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DepthwiseFilterShape.h" + +#include + +TEST(DepthwiseFilterShapeTest, default_constructor) +{ + loco::DepthwiseFilterShape shape; + + ASSERT_FALSE(shape.depth().known()); + ASSERT_FALSE(shape.multiplier().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); +} + +TEST(DepthwiseFilterShapeTest, settet_and_getter) +{ + loco::DepthwiseFilterShape shape; + + // Set depth + shape.depth() = 2; + + ASSERT_TRUE(shape.depth().known()); + ASSERT_FALSE(shape.multiplier().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.depth(), 2); + + // Set multiplier + shape.multiplier() = 3; + + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.multiplier().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.depth(), 2); + ASSERT_EQ(shape.multiplier(), 3); + + // Set height + shape.height() = 4; + + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.multiplier().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.depth(), 2); + ASSERT_EQ(shape.multiplier(), 3); + ASSERT_EQ(shape.height(), 4); + + // Set width + shape.width() = 5; + + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.multiplier().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_TRUE(shape.width().known()); + + ASSERT_EQ(shape.depth(), 2); + ASSERT_EQ(shape.multiplier(), 3); + ASSERT_EQ(shape.height(), 4); + ASSERT_EQ(shape.width(), 5); +} diff --git a/compiler/loco/src/IR/Dialect.cpp b/compiler/loco/src/IR/Dialect.cpp new file mode 100644 index 00000000000..a381b47eb21 --- /dev/null +++ b/compiler/loco/src/IR/Dialect.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Dialect.h" + +// NOTE This file validates "Dialect.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/Dialect.test.cpp b/compiler/loco/src/IR/Dialect.test.cpp new file mode 100644 index 00000000000..312bb52ef17 --- /dev/null +++ b/compiler/loco/src/IR/Dialect.test.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Dialect.h" + +#include + +#include + +TEST(DialectTest, service) +{ + struct S0 final : public loco::DialectService + { + }; + struct S1 final : public loco::DialectService + { + }; + + struct MockDialect final : public loco::Dialect + { + MockDialect() { service(stdex::make_unique()); } + }; + + MockDialect dialect; + + ASSERT_EQ(dialect.service(), nullptr); + ASSERT_NE(dialect.service(), nullptr); +} diff --git a/compiler/loco/src/IR/DialectService.cpp b/compiler/loco/src/IR/DialectService.cpp new file mode 100644 index 00000000000..fb8041e47c1 --- /dev/null +++ b/compiler/loco/src/IR/DialectService.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/DialectService.h" + +// NOTE This file validates "DialectService.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/Dimension.cpp b/compiler/loco/src/IR/Dimension.cpp new file mode 100644 index 00000000000..0d11c83e8c3 --- /dev/null +++ b/compiler/loco/src/IR/Dimension.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Dimension.h" + +namespace loco +{ + +bool operator==(const Dimension &lhs, const Dimension &rhs) +{ + return lhs.known() && rhs.known() && lhs.value() == rhs.value(); +} + +bool operator==(const Dimension &lhs, uint32_t rhs) { return lhs.known() && lhs.value() == rhs; } +bool operator==(uint32_t lhs, const Dimension &rhs) { return rhs.known() && lhs == rhs.value(); } + +Dimension make_dimension(void) { return Dimension{}; } + +} // namespace loco diff --git a/compiler/loco/src/IR/Dimension.test.cpp b/compiler/loco/src/IR/Dimension.test.cpp new file mode 100644 index 00000000000..4faf78ac8bf --- /dev/null +++ b/compiler/loco/src/IR/Dimension.test.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Dimension.h" + +#include + +namespace +{ + +struct DimensionTest : public ::testing::Test +{ +protected: + uint32_t value(void) const { return _value; } + +private: + uint32_t const _value{3}; +}; + +} // namespace + +TEST_F(DimensionTest, default_constructor) +{ + loco::Dimension dim; + + ASSERT_FALSE(dim.known()); +} + +TEST_F(DimensionTest, value_constructor) +{ + loco::Dimension dim{value()}; + + ASSERT_TRUE(dim.known()); + ASSERT_EQ(dim.value(), value()); +} + +TEST_F(DimensionTest, set) +{ + loco::Dimension dim; + + dim.set(value()); + + ASSERT_TRUE(dim.known()); + ASSERT_EQ(dim.value(), value()); +} + +TEST_F(DimensionTest, unset) +{ + loco::Dimension dim{value()}; + + dim.unset(); + + ASSERT_FALSE(dim.known()); +} + +TEST_F(DimensionTest, operator_eq) +{ + loco::Dimension unknown; + loco::Dimension known{3}; + + // Compare uint32_t and an unknown dimension + ASSERT_FALSE(unknown == 3); + ASSERT_FALSE(3 == unknown); + + // Compare uint32_t and a known dimension + ASSERT_TRUE(known == 3); + ASSERT_TRUE(3 == known); + + ASSERT_FALSE(known == 4); + ASSERT_FALSE(4 == known); + + // Compare two known dimensions + loco::Dimension another_known{3}; + ASSERT_TRUE(known == another_known); + + // Compare two unknown dimensions + loco::Dimension unknown_a, unknown_b; + ASSERT_TRUE(unknown_a.known() == false && unknown_b.known() == false); + ASSERT_FALSE(unknown_a == unknown_b); +} + +TEST_F(DimensionTest, make_unknown_dimension) +{ + auto dim = loco::make_dimension(); + + ASSERT_FALSE(dim.known()); +} diff --git a/compiler/loco/src/IR/Domain.cpp b/compiler/loco/src/IR/Domain.cpp new file mode 100644 index 00000000000..7bad04750f1 --- /dev/null +++ b/compiler/loco/src/IR/Domain.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Domain.h" + +// NOTE This file validates "Domain.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/FeatureAxis.cpp b/compiler/loco/src/IR/FeatureAxis.cpp new file mode 100644 index 00000000000..b0f5606770f --- /dev/null +++ b/compiler/loco/src/IR/FeatureAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FeatureAxis.h" + +// NOTE This file validates "FeatureAxis.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/FeatureCodec.cpp b/compiler/loco/src/IR/FeatureCodec.cpp new file mode 100644 index 00000000000..99d39a489a3 --- /dev/null +++ b/compiler/loco/src/IR/FeatureCodec.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FeatureCodec.h" + +// NOTE This file validates "FeatureCodec.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/FeatureIndex.test.cpp b/compiler/loco/src/IR/FeatureIndex.test.cpp new file mode 100644 index 00000000000..82b5639867f --- /dev/null +++ b/compiler/loco/src/IR/FeatureIndex.test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FeatureIndex.h" + +#include + +TEST(FeatureIndexTest, default_constructor) +{ + loco::FeatureIndex index; + + // All the values are 0 at the beginning + ASSERT_EQ(index.batch(), 0); + ASSERT_EQ(index.channel(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); +} + +TEST(FeatureIndexTest, settet_and_getter) +{ + loco::FeatureIndex index; + + // Set count + index.batch() = 2; + + ASSERT_EQ(index.batch(), 2); + ASSERT_EQ(index.channel(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set channel + index.channel() = 3; + + ASSERT_EQ(index.batch(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set height + index.row() = 4; + + ASSERT_EQ(index.batch(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 0); + + // Set width + index.column() = 5; + + ASSERT_EQ(index.batch(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 5); +} diff --git a/compiler/loco/src/IR/FeatureShape.test.cpp b/compiler/loco/src/IR/FeatureShape.test.cpp new file mode 100644 index 00000000000..59e25ac2373 --- /dev/null +++ b/compiler/loco/src/IR/FeatureShape.test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FeatureShape.h" + +#include + +TEST(FeatureShapeTest, default_constructor) +{ + loco::FeatureShape shape; + + ASSERT_FALSE(shape.count().known()); + ASSERT_FALSE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); +} + +TEST(FeatureShapeTest, settet_and_getter) +{ + loco::FeatureShape shape; + + // Set count + shape.count() = 2; + + ASSERT_TRUE(shape.count().known()); + ASSERT_FALSE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + + // Set depth + shape.depth() = 3; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + + // Set height + shape.height() = 4; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + ASSERT_EQ(shape.height(), 4); + + // Set width + shape.width() = 5; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_TRUE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + ASSERT_EQ(shape.height(), 4); + ASSERT_EQ(shape.width(), 5); +} diff --git a/compiler/loco/src/IR/FilterAxis.cpp b/compiler/loco/src/IR/FilterAxis.cpp new file mode 100644 index 00000000000..be4234e6a60 --- /dev/null +++ b/compiler/loco/src/IR/FilterAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FilterAxis.h" + +// NOTE This file validates "FilterAxis.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/FilterCodec.cpp b/compiler/loco/src/IR/FilterCodec.cpp new file mode 100644 index 00000000000..f48cf1821d7 --- /dev/null +++ b/compiler/loco/src/IR/FilterCodec.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FilterCodec.h" + +// NOTE This file validates "FilterCodec.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/FilterIndex.test.cpp b/compiler/loco/src/IR/FilterIndex.test.cpp new file mode 100644 index 00000000000..58f38718eba --- /dev/null +++ b/compiler/loco/src/IR/FilterIndex.test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FilterIndex.h" + +#include + +TEST(FilterIndexTest, default_constructor) +{ + loco::FilterIndex index; + + // All the values are 0 at the beginning + ASSERT_EQ(index.nth(), 0); + ASSERT_EQ(index.channel(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); +} + +TEST(FilterIndexTest, settet_and_getter) +{ + loco::FilterIndex index; + + // Set count + index.nth() = 2; + + ASSERT_EQ(index.nth(), 2); + ASSERT_EQ(index.channel(), 0); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set channel + index.channel() = 3; + + ASSERT_EQ(index.nth(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 0); + ASSERT_EQ(index.column(), 0); + + // Set height + index.row() = 4; + + ASSERT_EQ(index.nth(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 0); + + // Set width + index.column() = 5; + + ASSERT_EQ(index.nth(), 2); + ASSERT_EQ(index.channel(), 3); + ASSERT_EQ(index.row(), 4); + ASSERT_EQ(index.column(), 5); +} diff --git a/compiler/loco/src/IR/FilterShape.test.cpp b/compiler/loco/src/IR/FilterShape.test.cpp new file mode 100644 index 00000000000..ccb60ed7607 --- /dev/null +++ b/compiler/loco/src/IR/FilterShape.test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/FilterShape.h" + +#include + +TEST(FilterShapeTest, default_constructor) +{ + loco::FilterShape shape; + + ASSERT_FALSE(shape.count().known()); + ASSERT_FALSE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); +} + +TEST(FilterShapeTest, settet_and_getter) +{ + loco::FilterShape shape; + + // Set count + shape.count() = 2; + + ASSERT_TRUE(shape.count().known()); + ASSERT_FALSE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + + // Set depth + shape.depth() = 3; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_FALSE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + + // Set height + shape.height() = 4; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_FALSE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + ASSERT_EQ(shape.height(), 4); + + // Set width + shape.width() = 5; + + ASSERT_TRUE(shape.count().known()); + ASSERT_TRUE(shape.depth().known()); + ASSERT_TRUE(shape.height().known()); + ASSERT_TRUE(shape.width().known()); + + ASSERT_EQ(shape.count(), 2); + ASSERT_EQ(shape.depth(), 3); + ASSERT_EQ(shape.height(), 4); + ASSERT_EQ(shape.width(), 5); +} diff --git a/compiler/loco/src/IR/Graph.cpp b/compiler/loco/src/IR/Graph.cpp new file mode 100644 index 00000000000..1d8752252d4 --- /dev/null +++ b/compiler/loco/src/IR/Graph.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Graph.h" + +#include + +#include + +namespace +{ + +std::unique_ptr make_tensor_shape(std::initializer_list dims) +{ + auto tensor_shape = stdex::make_unique(); + + tensor_shape->rank(dims.size()); + { + uint32_t axis = 0; + for (auto it = dims.begin(); it != dims.end(); ++it) + { + tensor_shape->dim(axis++) = *it; + } + assert(axis == dims.size()); + } + + return std::move(tensor_shape); +} + +} // namespace + +namespace loco +{ + +void Mixin::shape(std::initializer_list dims) +{ + shape(make_tensor_shape(dims)); +} + +GraphInput *Graph::InputContext::create(void) +{ + return take(stdex::make_unique(size())); +} + +GraphOutput *Graph::OutputContext::create(void) +{ + return take(stdex::make_unique(size())); +} + +std::set all_nodes(loco::Graph *g) +{ + std::set res; + + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + res.insert(g->nodes()->at(n)); + } + + return res; +} + +std::vector input_nodes(const Graph *g) +{ + std::map table; + + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + auto node = g->nodes()->at(n); + + if (auto service = node->dialect()->service()) + { + if (service->associated(node)) + { + auto input_index = service->index(node); + assert(table.find(input_index) == table.end()); + table[input_index] = node; + } + } + } + + std::vector res; + + for (uint32_t n = 0; n < g->inputs()->size(); ++n) + { + auto it = table.find(n); + res.emplace_back(it == table.end() ? nullptr : it->second); + } + + return res; +} + +std::vector output_nodes(loco::Graph *g) +{ + std::map table; + + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + auto node = g->nodes()->at(n); + + if (auto service = node->dialect()->service()) + { + if (service->associated(node)) + { + auto output_index = service->index(node); + assert(table.find(output_index) == table.end()); + table[output_index] = node; + } + } + } + + std::vector res; + + for (uint32_t n = 0; n < g->outputs()->size(); ++n) + { + auto it = table.find(n); + res.emplace_back(it == table.end() ? nullptr : it->second); + } + + return res; +} + +std::unique_ptr make_graph(void) { return std::unique_ptr{new Graph}; } + +} // namespace loco diff --git a/compiler/loco/src/IR/Graph.test.cpp b/compiler/loco/src/IR/Graph.test.cpp new file mode 100644 index 00000000000..0b77e34f89e --- /dev/null +++ b/compiler/loco/src/IR/Graph.test.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Graph.h" + +#include + +namespace +{ + +// @brief Mockup class for loco::NamedEntity +struct NamedElement final : private loco::NamedEntity +{ + LOCO_NAMED_ENTITY_EXPOSE; +}; + +} // namespace + +TEST(NamedTest, constructor) +{ + NamedElement elem; + + ASSERT_EQ(elem.name(), ""); +} + +TEST(NamedTest, setter_and_getter) +{ + NamedElement elem; + + elem.name("name"); + ASSERT_EQ(elem.name(), "name"); +} + +TEST(DataTypedMixinTest, constructor) +{ + loco::Mixin mixin; + + ASSERT_EQ(mixin.dtype(), loco::DataType::Unknown); +} + +TEST(DataTypedMixinTest, setter_and_getter) +{ + loco::Mixin mixin; + + mixin.dtype(loco::DataType::FLOAT32); + ASSERT_EQ(mixin.dtype(), loco::DataType::FLOAT32); +} + +TEST(TensorShapedMixinTest, setter_and_getter) +{ + loco::Mixin mixin; + + mixin.shape({1, 2, 3, 4}); + ASSERT_NE(mixin.shape(), nullptr); + ASSERT_EQ(mixin.shape()->rank(), 4); + ASSERT_EQ(mixin.shape()->dim(0), 1); + ASSERT_EQ(mixin.shape()->dim(1), 2); + ASSERT_EQ(mixin.shape()->dim(2), 3); + ASSERT_EQ(mixin.shape()->dim(3), 4); +} + +TEST(GraphTest, create_and_destroy_node) +{ + auto g = loco::make_graph(); + + auto pull = g->nodes()->create(); + + ASSERT_NO_THROW(g->nodes()->destroy(pull)); + ASSERT_THROW(g->nodes()->destroy(pull), std::invalid_argument); +} + +TEST(GraphTest, create_input) +{ + auto g = loco::make_graph(); + + auto input = g->inputs()->create(); + + // TODO Add more checks + ASSERT_EQ(input->shape(), nullptr); + ASSERT_EQ(input->index(), 0); +} + +TEST(GraphTest, create_output) +{ + auto g = loco::make_graph(); + + auto output = g->outputs()->create(); + + // TODO Add more checks + ASSERT_EQ(output->shape(), nullptr); + ASSERT_EQ(output->index(), 0); +} + +namespace +{ +// temp node with multple params for ctor. loco::CanonicalOpcode::ReLU is used for simplicity +class ParamCtorNode + : public loco::CanonicalNodeDef::Mixin> +{ +public: + ParamCtorNode(int i, float f) + { + _i = i; + _f = f; + } + + int i() { return _i; } + float f() { return _f; } + +private: + int _i; + float _f; +}; +} // namespace + +TEST(GraphTest, consturctor_with_param_node) +{ + auto g = loco::make_graph(); + + auto test_node = g->nodes()->create(22, 11.11); + + ASSERT_EQ(test_node->graph(), g.get()); + ASSERT_EQ(const_cast(test_node)->graph(), g.get()); + + ASSERT_EQ(test_node->i(), 22); + ASSERT_FLOAT_EQ(test_node->f(), 11.11); + + ASSERT_NO_THROW(g->nodes()->destroy(test_node)); + ASSERT_THROW(g->nodes()->destroy(test_node), std::invalid_argument); +} + +TEST(GraphTest, getters_over_const_instance) +{ + auto g = loco::make_graph(); + + auto pull = g->nodes()->create(); + auto push = g->nodes()->create(); + + loco::link(g->inputs()->create(), pull); + loco::link(g->outputs()->create(), push); + + auto ptr = const_cast(g.get()); + + EXPECT_EQ(ptr->nodes()->size(), 2); + EXPECT_EQ(ptr->inputs()->size(), 1); +} + +TEST(GraphTest, graph_node_enumeration) +{ + auto g = loco::make_graph(); + + auto pull_1 = g->nodes()->create(); + auto push_1 = g->nodes()->create(); + + auto nodes = loco::all_nodes(g.get()); + + // Returns true if "nodes" includes a given node + auto member = [&nodes](loco::Node *node) { return nodes.find(node) != nodes.end(); }; + + ASSERT_EQ(nodes.size(), 2); + ASSERT_TRUE(member(pull_1)); + ASSERT_TRUE(member(push_1)); +} + +TEST(GraphTest, graph_inout_enumeration) +{ + auto g = loco::make_graph(); + + std::vector pull_nodes; + + auto pull_1 = g->nodes()->create(); + auto pull_2 = g->nodes()->create(); + auto pull_3 = g->nodes()->create(); + + auto push_1 = g->nodes()->create(); + auto push_2 = g->nodes()->create(); + auto push_3 = g->nodes()->create(); + + loco::link(g->inputs()->create(), pull_2); + loco::link(g->inputs()->create(), pull_1); + + loco::link(g->outputs()->create(), push_1); + loco::link(g->outputs()->create(), push_3); + + auto output_nodes = loco::output_nodes(g.get()); + + ASSERT_EQ(output_nodes.size(), 2); + ASSERT_EQ(output_nodes.at(0), push_1); + ASSERT_EQ(output_nodes.at(1), push_3); +} diff --git a/compiler/loco/src/IR/GraphInputIndex.cpp b/compiler/loco/src/IR/GraphInputIndex.cpp new file mode 100644 index 00000000000..0c94d704cc9 --- /dev/null +++ b/compiler/loco/src/IR/GraphInputIndex.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/GraphInputIndex.h" + +// NOTE This file validates "GraphInputIndex.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/GraphOutputIndex.cpp b/compiler/loco/src/IR/GraphOutputIndex.cpp new file mode 100644 index 00000000000..e6fdb9f94f0 --- /dev/null +++ b/compiler/loco/src/IR/GraphOutputIndex.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/GraphOutputIndex.h" + +// NOTE This file validates "GraphOutputIndex.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/MatrixAxis.cpp b/compiler/loco/src/IR/MatrixAxis.cpp new file mode 100644 index 00000000000..d0773f75874 --- /dev/null +++ b/compiler/loco/src/IR/MatrixAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/MatrixAxis.h" + +// NOTE This file validates "MatrixAxis.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/MatrixCodec.cpp b/compiler/loco/src/IR/MatrixCodec.cpp new file mode 100644 index 00000000000..87ae4261001 --- /dev/null +++ b/compiler/loco/src/IR/MatrixCodec.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/MatrixCodec.h" + +// NOTE This file validates "MatrixCodec.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/MockupNode.h b/compiler/loco/src/IR/MockupNode.h new file mode 100644 index 00000000000..ec56c90e209 --- /dev/null +++ b/compiler/loco/src/IR/MockupNode.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_IR_MOCKUP_NODE_H__ +#define __LOCO_IR_MOCKUP_NODE_H__ + +#include "loco/IR/Use.h" +#include "loco/IR/Node.h" + +namespace +{ + +struct MockDialect final : public loco::Dialect +{ + static loco::Dialect *get(void) + { + static MockDialect d; + return &d; + } +}; + +// @brief Mockup node for internal testing +class MockupNode final : public loco::Node +{ +public: + MockupNode() = default; + +public: + const loco::Dialect *dialect(void) const final { return MockDialect::get(); } + uint32_t opnum(void) const final { return 0; } + + uint32_t arity(void) const final { return 1; } + Node *arg(uint32_t N) const final { return _arg.node(); } + void drop(void) final { _arg.node(nullptr); } + + Node *in(void)const { return _arg.node(); } + void in(Node *node) { _arg.node(node); } + +private: + loco::Use _arg{this}; +}; + +} // namespace + +#endif // __LOCO_IR_MOCKUP_NODE_H__ diff --git a/compiler/loco/src/IR/Node.cpp b/compiler/loco/src/IR/Node.cpp new file mode 100644 index 00000000000..90ec5c99768 --- /dev/null +++ b/compiler/loco/src/IR/Node.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Node.h" +#include "loco/IR/Use.h" + +#include + +namespace loco +{ + +Node::~Node() +{ + // To detect dangling references + assert(_uses.size() == 0); +} + +std::set preds(const Node *node) +{ + std::set res; + + for (uint32_t n = 0; n < node->arity(); ++n) + { + if (auto pred = node->arg(n)) + { + res.insert(pred); + } + } + + return res; +} + +std::set succs(const Node *node) +{ + std::set res; + + for (auto use : node->_uses) + { + auto user = use->user(); + assert(user != nullptr); + res.insert(user); + } + + return res; +} + +Subst::Subst(Node *from) : _from{from} +{ + // _from SHOULD be valid + assert(_from != nullptr); +} + +void Subst::with(Node *into) const +{ + if (_from == into) + { + return; + } + + auto *uses = &(_from->_uses); + + while (!uses->empty()) + { + auto use = *(uses->begin()); + use->node(into); + } +} + +Subst replace(Node *node) +{ + // Let's create Subst! + return Subst{node}; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/Node.test.cpp b/compiler/loco/src/IR/Node.test.cpp new file mode 100644 index 00000000000..00e44446577 --- /dev/null +++ b/compiler/loco/src/IR/Node.test.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Node.h" + +#include "MockupNode.h" + +#include + +TEST(NodeTest, preds) +{ + ::MockupNode arg; + ::MockupNode node; + + node.in(&arg); + + auto preds = loco::preds(&node); + + ASSERT_EQ(preds.size(), 1); + ASSERT_NE(preds.find(&arg), preds.end()); +} + +TEST(NodeTest, succs) +{ + ::MockupNode node; + ::MockupNode succ_1; + ::MockupNode succ_2; + + succ_1.in(&node); + succ_2.in(&node); + + auto succs = loco::succs(&node); + + ASSERT_EQ(succs.size(), 2); + ASSERT_NE(succs.find(&succ_1), succs.end()); + ASSERT_NE(succs.find(&succ_2), succs.end()); +} + +TEST(NodeTest, replace_with) +{ + ::MockupNode node_1; + ::MockupNode node_2; + + ::MockupNode node_3; + ::MockupNode node_4; + + node_3.in(&node_1); + node_4.in(&node_2); + + // The following holds at this point + // - node_3 USE node_1 + // - node_4 USE node_2 + ASSERT_EQ(node_3.in(), &node_1); + ASSERT_EQ(node_4.in(), &node_2); + + // Replace all the usage of node_1 with node_2 + replace(&node_1).with(&node_2); + + // The following holds at this point + // - node_3 USE node_2 + // - node_4 USE node_2 + ASSERT_EQ(node_3.in(), &node_2); + ASSERT_EQ(node_4.in(), &node_2); +} + +TEST(NodeTest, constructor) +{ + MockupNode node; + + // graph() SHOULD return nullptr if node is not constructed through "Graph" + ASSERT_EQ(node.graph(), nullptr); +} + +// TODO Rewrite this as a FixedAritry mix-in test +#if 0 +TEST(FixedArityNodeTest, constructor) +{ + struct DerivedNode final : public loco::FixedArityNode<1, loco::Node> + { + loco::Dialect *dialect(void) const final { return MockDialect::get(); } + uint32_t opnum(void) const final { return 0; } + }; + + DerivedNode node; + + ASSERT_EQ(node.arity(), 1); + ASSERT_EQ(node.arg(0), nullptr); +} +#endif diff --git a/compiler/loco/src/IR/NodeMixins.cpp b/compiler/loco/src/IR/NodeMixins.cpp new file mode 100644 index 00000000000..66037b17af4 --- /dev/null +++ b/compiler/loco/src/IR/NodeMixins.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/NodeMixins.h" + +// NOTE This file validates "NodeMixins.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/NodePool.cpp b/compiler/loco/src/IR/NodePool.cpp new file mode 100644 index 00000000000..553f15eb5c6 --- /dev/null +++ b/compiler/loco/src/IR/NodePool.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/NodePool.h" + +namespace loco +{ + +NodePool::~NodePool() +{ + // Drop all the references before deallocation + for (uint32_t n = 0; n < size(); ++n) + { + at(n)->drop(); + } +} + +} // namespace loco diff --git a/compiler/loco/src/IR/NodeShape.cpp b/compiler/loco/src/IR/NodeShape.cpp new file mode 100644 index 00000000000..0130cfbdb63 --- /dev/null +++ b/compiler/loco/src/IR/NodeShape.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/NodeShape.h" + +#include +#include + +// +// BiasShape Support +// +namespace loco +{ + +void NodeShape::set(const BiasShape &shape) +{ + _domain = Domain::Bias; + + _dims.resize(1); + _dims.at(0) = shape.length(); +} + +template <> BiasShape NodeShape::as(void) const +{ + assert(_domain == Domain::Bias); + + BiasShape res; + + res.length() = _dims.at(0); + + return res; +} + +} // namespace loco + +// +// DepthwiseFilterShape Support +// +namespace loco +{ + +void NodeShape::set(const DepthwiseFilterShape &shape) +{ + _domain = Domain::DepthwiseFilter; + + _dims.resize(4); + _dims.at(0) = shape.multiplier(); + _dims.at(1) = shape.depth(); + _dims.at(2) = shape.height(); + _dims.at(3) = shape.width(); +} + +template <> DepthwiseFilterShape NodeShape::as(void) const +{ + assert(_domain == Domain::DepthwiseFilter); + + DepthwiseFilterShape res; + + res.multiplier() = _dims.at(0); + res.depth() = _dims.at(1); + res.height() = _dims.at(2); + res.width() = _dims.at(3); + + return res; +} + +} // namespace loco + +// +// FeatureShape Support +// +namespace loco +{ + +void NodeShape::set(const FeatureShape &shape) +{ + _domain = Domain::Feature; + + _dims.resize(4); + _dims.at(0) = shape.count(); + _dims.at(1) = shape.depth(); + _dims.at(2) = shape.height(); + _dims.at(3) = shape.width(); +} + +template <> FeatureShape NodeShape::as(void) const +{ + assert(_domain == Domain::Feature); + + FeatureShape res; + + res.count() = _dims.at(0); + res.depth() = _dims.at(1); + res.height() = _dims.at(2); + res.width() = _dims.at(3); + + return res; +} + +} // namespace loco + +// +// FilterShape Support +// +namespace loco +{ + +void NodeShape::set(const FilterShape &shape) +{ + _domain = Domain::Filter; + + _dims.resize(4); + _dims.at(0) = shape.count(); + _dims.at(1) = shape.depth(); + _dims.at(2) = shape.height(); + _dims.at(3) = shape.width(); +} + +template <> FilterShape NodeShape::as(void) const +{ + assert(_domain == Domain::Filter); + + FilterShape res; + + res.count() = _dims.at(0); + res.depth() = _dims.at(1); + res.height() = _dims.at(2); + res.width() = _dims.at(3); + + return res; +} + +} // namespace loco + +// +// MatrixShape Support +// +namespace loco +{ + +void NodeShape::set(const MatrixShape &shape) +{ + _domain = Domain::Matrix; + + _dims.resize(2); + _dims.at(0) = shape.height(); + _dims.at(1) = shape.width(); +} + +template <> MatrixShape NodeShape::as(void) const +{ + assert(_domain == Domain::Matrix); + + MatrixShape res; + + res.height() = _dims.at(0); + res.width() = _dims.at(1); + + return res; +} + +} // namespace loco + +// +// TensorShape Support +// +namespace loco +{ + +void NodeShape::set(const TensorShape &shape) +{ + _domain = Domain::Tensor; + + _dims.resize(shape.rank()); + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + _dims.at(axis) = shape.dim(axis); + } +} + +template <> TensorShape NodeShape::as(void) const +{ + assert(_domain == Domain::Tensor); + + TensorShape res; + + res.rank(_dims.size()); + for (uint32_t axis = 0; axis < _dims.size(); ++axis) + { + res.dim(axis) = _dims.at(axis); + } + + return res; +} + +} // namespace loco + +namespace loco +{ + +bool operator==(const NodeShape &lhs, const NodeShape &rhs) +{ + if (lhs.domain() != rhs.domain()) + return false; + + switch (lhs.domain()) + { + case loco::Domain::Tensor: + { + auto lhs_t = lhs.as(); + auto rhs_t = rhs.as(); + if (lhs_t.rank() != rhs_t.rank()) + return false; + for (uint32_t axis = 0; axis < lhs_t.rank(); ++axis) + { + if (!(lhs_t.dim(axis) == rhs_t.dim(axis))) + return false; + } + return true; + } + + case loco::Domain::Feature: + { + auto lhs_f = lhs.as(); + auto rhs_f = rhs.as(); + + return (lhs_f.count() == rhs_f.count() && lhs_f.depth() == rhs_f.depth() && + lhs_f.height() == rhs_f.height() && lhs_f.width() == rhs_f.width()); + } + + case loco::Domain::Filter: + { + auto lhs_f = lhs.as(); + auto rhs_f = rhs.as(); + + return (lhs_f.count() == rhs_f.count() && lhs_f.depth() == rhs_f.depth() && + lhs_f.height() == rhs_f.height() && lhs_f.width() == rhs_f.width()); + } + + case loco::Domain::DepthwiseFilter: + { + auto lhs_f = lhs.as(); + auto rhs_f = rhs.as(); + + return (lhs_f.multiplier() == rhs_f.multiplier() && lhs_f.depth() == rhs_f.depth() && + lhs_f.height() == rhs_f.height() && lhs_f.width() == rhs_f.width()); + } + + case loco::Domain::Bias: + { + auto lhs_f = lhs.as(); + auto rhs_f = rhs.as(); + + return (lhs_f.length() == rhs_f.length()); + } + + case loco::Domain::Matrix: + { + auto lhs_f = lhs.as(); + auto rhs_f = rhs.as(); + + return (lhs_f.height() == rhs_f.height() && lhs_f.width() == rhs_f.width()); + } + + default: + throw std::runtime_error("Not supported domain for NodeShape equality"); + } + return false; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/NodeShape.test.cpp b/compiler/loco/src/IR/NodeShape.test.cpp new file mode 100644 index 00000000000..4f092e024e9 --- /dev/null +++ b/compiler/loco/src/IR/NodeShape.test.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/NodeShape.h" + +#include + +TEST(NodeShapeTest, default_constructor) +{ + loco::NodeShape node_shape; + + ASSERT_EQ(node_shape.domain(), loco::Domain::Unknown); +} + +TEST(NodeShapeTest, bias_shape_constructor) +{ + loco::BiasShape bias_shape; + + bias_shape.length() = 4; + + loco::NodeShape node_shape{bias_shape}; + + ASSERT_EQ(node_shape.domain(), loco::Domain::Bias); + ASSERT_EQ(node_shape.as().length(), 4); +} + +TEST(NodeShapeTest, dwfilter_shape_constructor) +{ + loco::DepthwiseFilterShape dwfilter_shape; + + dwfilter_shape.depth() = 2; + dwfilter_shape.multiplier() = 3; + dwfilter_shape.height() = 4; + dwfilter_shape.width() = 5; + + loco::NodeShape node_shape{dwfilter_shape}; + + ASSERT_EQ(node_shape.domain(), loco::Domain::DepthwiseFilter); + ASSERT_EQ(node_shape.as().depth(), 2); + ASSERT_EQ(node_shape.as().multiplier(), 3); + ASSERT_EQ(node_shape.as().height(), 4); + ASSERT_EQ(node_shape.as().width(), 5); +} + +TEST(NodeShapeTest, feature_shape_constructor) +{ + loco::FeatureShape feature_shape; + + feature_shape.count() = 2; + feature_shape.depth() = 3; + feature_shape.height() = 4; + feature_shape.width() = 5; + + loco::NodeShape node_shape{feature_shape}; + + ASSERT_EQ(node_shape.domain(), loco::Domain::Feature); + ASSERT_EQ(node_shape.as().count(), 2); + ASSERT_EQ(node_shape.as().depth(), 3); + ASSERT_EQ(node_shape.as().height(), 4); + ASSERT_EQ(node_shape.as().width(), 5); +} + +TEST(NodeShapeTest, filter_shape_constructor) +{ + loco::FilterShape filter_shape; + + filter_shape.count() = 2; + filter_shape.depth() = 3; + filter_shape.height() = 4; + filter_shape.width() = 5; + + loco::NodeShape node_shape{filter_shape}; + + ASSERT_EQ(node_shape.domain(), loco::Domain::Filter); + ASSERT_EQ(node_shape.as().count(), 2); + ASSERT_EQ(node_shape.as().depth(), 3); + ASSERT_EQ(node_shape.as().height(), 4); + ASSERT_EQ(node_shape.as().width(), 5); +} + +TEST(NodeShapeTest, tensor_shape_constructor) +{ + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + tensor_shape.dim(0) = 4; + tensor_shape.dim(1) = 5; + + loco::NodeShape node_shape{tensor_shape}; + + ASSERT_EQ(node_shape.domain(), loco::Domain::Tensor); + ASSERT_EQ(node_shape.as().rank(), 2); + ASSERT_EQ(node_shape.as().dim(0), 4); + ASSERT_EQ(node_shape.as().dim(1), 5); +} + +TEST(NodeShapeTest, copy_constructible) +{ + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + tensor_shape.dim(0) = 4; + tensor_shape.dim(1) = 5; + + loco::NodeShape orig{tensor_shape}; + loco::NodeShape copy{orig}; // Call Copy Constructor + + ASSERT_EQ(copy.domain(), loco::Domain::Tensor); + ASSERT_EQ(copy.as().rank(), 2); + ASSERT_EQ(copy.as().dim(0), 4); + ASSERT_EQ(copy.as().dim(1), 5); +} diff --git a/compiler/loco/src/IR/Nodes.cpp b/compiler/loco/src/IR/Nodes.cpp new file mode 100644 index 00000000000..133b6943048 --- /dev/null +++ b/compiler/loco/src/IR/Nodes.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Nodes.h" +#include "loco/IR/Graph.h" + +#include +#include + +// This file validates "Nodes.h". Please DO NOT remove this file. +namespace +{ + +/** + * @note This function is currently only used in assert. Compiler will + * warn/error this function as unused in Release build. + * Making inline will make compiler happy. + */ +// Is it possible to update lhs as rhs? +inline bool dtype_assignable(loco::DataType lhs, loco::DataType rhs) +{ + if (lhs == loco::DataType::Unknown) + { + return true; + } + + // lhs is already known, and thus rhs should be matched + return lhs == rhs; +} + +} // namespace + +/** + * Push + */ +namespace loco +{ + +void Push::index(const GraphOutputIndex &index) +{ + // Push internally stores "GraphOutputIndex" as int64_t + _index = static_cast(index); +} + +GraphOutputIndex Push::index(void) const +{ + assert(_index >= std::numeric_limits::min()); + assert(_index <= std::numeric_limits::max()); + return static_cast(_index); +} + +void link(GraphOutput *output, Push *push) { push->index(output->index()); } + +Push *push_node(Graph *g, const GraphOutputIndex &index) +{ + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + if (auto push = dynamic_cast(g->nodes()->at(n))) + { + if (push->indexed() && push->index() == index) + { + return push; + } + } + } + return nullptr; +} + +} // namespace loco + +/** + * Pull + */ +namespace loco +{ + +void Pull::index(const GraphInputIndex &index) +{ + // ASSUMPTION + // + // It is possible to update index multiple times, but only with the same value! + assert(!indexed() or _index == index); + + if (indexed()) + { + assert(_index == index); + return; + } + + // Push internally stores "GraphInputIndex" as int64_t + _index = static_cast(index); + + // ASSUMPTION: The return value of graph() never changes! + if (graph() != nullptr && _dtype != loco::DataType::Unknown) + { + // Update Graph-level input only if it is not yet specified + if (graph()->inputs()->at(_index)->dtype() == DataType::Unknown) + { + graph()->inputs()->at(_index)->dtype(_dtype); + } + assert(graph()->inputs()->at(_index)->dtype() == _dtype); + graph()->inputs()->at(_index)->dtype(_dtype); + + // Reset the locally cached data + _dtype = DataType::Unknown; + } +} + +GraphInputIndex Pull::index(void) const +{ + assert(_index >= std::numeric_limits::min()); + assert(_index <= std::numeric_limits::max()); + return static_cast(_index); +} + +void Pull::dtype(const DataType &dt) +{ + // ASSUMPTION: "dtype" is never invalidated! + assert(dt != loco::DataType::Unknown); + // ASSUMPTION + // + // It is possible to update index multiple times, but only with the same value! + if (indexed()) + { + assert(dtype_assignable(graph()->inputs()->at(_index)->dtype(), dt)); + graph()->inputs()->at(_index)->dtype(dt); + return; + } + + // Use local cache + _dtype = dt; +} + +DataType Pull::dtype(void) const +{ + if (graph() != nullptr and _index >= 0) + { + assert(_dtype == DataType::Unknown); + return graph()->inputs()->at(_index)->dtype(); + } + else + { + return _dtype; + } +} + +void link(GraphInput *input, Pull *pull) { pull->index(input->index()); } + +Pull *pull_node(Graph *g, const GraphInputIndex &index) +{ + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + if (auto pull = dynamic_cast(g->nodes()->at(n))) + { + if (pull->indexed() && pull->index() == index) + { + return pull; + } + } + } + return nullptr; +} + +} // namespace loco + +/** + * ConstGen + */ +namespace loco +{ + +template uint32_t ConstGen::size(void) const +{ + assert(dtype() == DT); + assert(_data.size() % sizeof(typename DataTypeImpl
::Type) == 0); + return _data.size() / sizeof(typename DataTypeImpl
::Type); +} + +template void ConstGen::size(uint32_t l) +{ + assert(dtype() == DT); + _data.resize(l * sizeof(typename DataTypeImpl
::Type)); +} + +template const typename DataTypeImpl
::Type &ConstGen::at(uint32_t n) const +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +template typename DataTypeImpl
::Type &ConstGen::at(uint32_t n) +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +#define INSTANTIATE(DT) \ + template uint32_t ConstGen::size
(void) const; \ + template void ConstGen::size
(uint32_t); \ + template const typename DataTypeImpl
::Type &ConstGen::at
(uint32_t) const; \ + template typename DataTypeImpl
::Type &ConstGen::at
(uint32_t); + +INSTANTIATE(DataType::S32); +INSTANTIATE(DataType::FLOAT32); + +#undef INSTANTIATE + +} // namespace loco + +/** + * TensorBroadcast + */ +namespace loco +{ + +bool TensorBroadcast::Mapping::defined(const TensorAxis &axis) const +{ + return _content.find(axis) != _content.end(); +} + +const Dimension &TensorBroadcast::Mapping::dim(const TensorAxis &axis) const +{ + return _content.at(axis); +} + +Dimension &TensorBroadcast::Mapping::dim(const TensorAxis &axis) { return _content[axis]; } + +} // namespace loco diff --git a/compiler/loco/src/IR/Nodes.test.cpp b/compiler/loco/src/IR/Nodes.test.cpp new file mode 100644 index 00000000000..cd51f46c069 --- /dev/null +++ b/compiler/loco/src/IR/Nodes.test.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Nodes.h" +#include "loco/IR/CanonicalDialect.h" + +#include + +TEST(PushTest, constructor) +{ + loco::Push push_node; + + ASSERT_EQ(push_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(push_node.opcode(), loco::CanonicalOpcode::Push); + + ASSERT_FALSE(push_node.indexed()); +} + +TEST(PushTest, shape) +{ + const std::vector dims{1, 8, 16, 3}; + + loco::Pull push_node; + + push_node.shape({dims[0], dims[1], dims[2], dims[3]}); + + ASSERT_EQ(push_node.rank(), dims.size()); + ASSERT_EQ(push_node.dim(0), dims[0]); + ASSERT_EQ(push_node.dim(1), dims[1]); + ASSERT_EQ(push_node.dim(2), dims[2]); + ASSERT_EQ(push_node.dim(3), dims[3]); +} + +TEST(PullTest, constructor) +{ + loco::Pull pull_node; + + ASSERT_EQ(pull_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(pull_node.opcode(), loco::CanonicalOpcode::Pull); + + ASSERT_FALSE(pull_node.indexed()); + + ASSERT_EQ(pull_node.dtype(), loco::DataType::Unknown); + ASSERT_EQ(pull_node.rank(), 0); +} + +TEST(PullTest, shape) +{ + const std::vector dims{1, 8, 16, 3}; + + loco::Pull pull_node; + + pull_node.shape({dims[0], dims[1], dims[2], dims[3]}); + + ASSERT_EQ(pull_node.rank(), dims.size()); + ASSERT_EQ(pull_node.dim(0), dims[0]); + ASSERT_EQ(pull_node.dim(1), dims[1]); + ASSERT_EQ(pull_node.dim(2), dims[2]); + ASSERT_EQ(pull_node.dim(3), dims[3]); +} + +TEST(ForwardTest, constructor) +{ + loco::Forward forward_node; + + ASSERT_EQ(forward_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(forward_node.opcode(), loco::CanonicalOpcode::Forward); + + ASSERT_EQ(forward_node.input(), nullptr); +} + +TEST(ReLUTest, constructor) +{ + loco::ReLU relu_node; + + ASSERT_EQ(relu_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(relu_node.opcode(), loco::CanonicalOpcode::ReLU); + + ASSERT_EQ(relu_node.input(), nullptr); +} + +TEST(ReLU6Test, constructor) +{ + loco::ReLU6 relu6_node; + + ASSERT_EQ(relu6_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(relu6_node.opcode(), loco::CanonicalOpcode::ReLU6); + + ASSERT_EQ(relu6_node.input(), nullptr); +} + +TEST(ConstGenTest, constructor) +{ + loco::ConstGen constgen_node; + + ASSERT_EQ(constgen_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(constgen_node.opcode(), loco::CanonicalOpcode::ConstGen); + + ASSERT_EQ(constgen_node.dtype(), loco::DataType::Unknown); + ASSERT_EQ(constgen_node.rank(), 0); + + constgen_node.dtype(loco::DataType::FLOAT32); + ASSERT_EQ(constgen_node.dtype(), loco::DataType::FLOAT32); + + constgen_node.rank(2); + ASSERT_EQ(constgen_node.rank(), 2); + + constgen_node.dim(0) = 2; + constgen_node.dim(1) = 3; + + ASSERT_TRUE(constgen_node.dim(0).known()); + ASSERT_TRUE(constgen_node.dim(1).known()); + + ASSERT_EQ(constgen_node.dim(0), 2); + ASSERT_EQ(constgen_node.dim(1), 3); + + constgen_node.size(6); + + ASSERT_EQ(constgen_node.size(), 6); + + constgen_node.at(0) = 0.0f; // Set 0,0 + constgen_node.at(1) = 1.0f; // Set 0,1 + constgen_node.at(2) = 2.0f; // Set 0,2 + constgen_node.at(3) = 3.0f; // Set 1,0 + constgen_node.at(4) = 4.0f; // Set 1,1 + constgen_node.at(5) = 5.0f; // Set 1,2 + + ASSERT_EQ(constgen_node.at(0), 0.0f); + ASSERT_EQ(constgen_node.at(1), 1.0f); + ASSERT_EQ(constgen_node.at(2), 2.0f); + ASSERT_EQ(constgen_node.at(3), 3.0f); + ASSERT_EQ(constgen_node.at(4), 4.0f); + ASSERT_EQ(constgen_node.at(5), 5.0f); +} + +TEST(ConstGenTest, constructor_s32) +{ + loco::ConstGen constgen_node; + + ASSERT_EQ(constgen_node.dtype(), loco::DataType::Unknown); + ASSERT_EQ(constgen_node.rank(), 0); + + constgen_node.dtype(loco::DataType::S32); + ASSERT_EQ(constgen_node.dtype(), loco::DataType::S32); + + constgen_node.rank(2); + ASSERT_EQ(constgen_node.rank(), 2); + + constgen_node.dim(0) = 2; + constgen_node.dim(1) = 3; + + ASSERT_TRUE(constgen_node.dim(0).known()); + ASSERT_TRUE(constgen_node.dim(1).known()); + + ASSERT_EQ(constgen_node.dim(0), 2); + ASSERT_EQ(constgen_node.dim(1), 3); + + constgen_node.size(6); + + ASSERT_EQ(constgen_node.size(), 6); + + constgen_node.at(0) = 0; // Set 0,0 + constgen_node.at(1) = 1; // Set 0,1 + constgen_node.at(2) = 2; // Set 0,2 + constgen_node.at(3) = -3; // Set 1,0 + constgen_node.at(4) = -4; // Set 1,1 + constgen_node.at(5) = -5; // Set 1,2 + + ASSERT_EQ(constgen_node.at(0), 0); + ASSERT_EQ(constgen_node.at(1), 1); + ASSERT_EQ(constgen_node.at(2), 2); + ASSERT_EQ(constgen_node.at(3), -3); + ASSERT_EQ(constgen_node.at(4), -4); + ASSERT_EQ(constgen_node.at(5), -5); +} + +TEST(MaxPool2DTest, constructor) +{ + loco::MaxPool2D maxpool_node; + + ASSERT_EQ(maxpool_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(maxpool_node.opcode(), loco::CanonicalOpcode::MaxPool2D); + + ASSERT_EQ(maxpool_node.ifm(), nullptr); + + ASSERT_EQ(maxpool_node.pad()->top(), 0); + ASSERT_EQ(maxpool_node.pad()->bottom(), 0); + ASSERT_EQ(maxpool_node.pad()->left(), 0); + ASSERT_EQ(maxpool_node.pad()->right(), 0); + + ASSERT_EQ(maxpool_node.window()->vertical(), 1); + ASSERT_EQ(maxpool_node.window()->horizontal(), 1); + + ASSERT_EQ(maxpool_node.stride()->vertical(), 1); + ASSERT_EQ(maxpool_node.stride()->horizontal(), 1); +} + +TEST(MaxPool2DTest, pad) +{ + const uint32_t t = 1; + const uint32_t b = 2; + const uint32_t l = 3; + const uint32_t r = 4; + + loco::MaxPool2D maxpool_node; + + maxpool_node.pad()->top(t); + ASSERT_EQ(maxpool_node.pad()->top(), t); + + maxpool_node.pad()->bottom(b); + ASSERT_EQ(maxpool_node.pad()->bottom(), b); + + maxpool_node.pad()->left(l); + ASSERT_EQ(maxpool_node.pad()->left(), l); + + maxpool_node.pad()->right(r); + ASSERT_EQ(maxpool_node.pad()->right(), r); +} + +TEST(AvgPool2DTest, constructor) +{ + loco::AvgPool2D avgpool_node; + + ASSERT_EQ(avgpool_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(avgpool_node.opcode(), loco::CanonicalOpcode::AvgPool2D); + + ASSERT_EQ(avgpool_node.ifm(), nullptr); + + ASSERT_EQ(avgpool_node.convention(), loco::AvgPool2D::Convention::Unknown); + + ASSERT_EQ(avgpool_node.pad()->top(), 0); + ASSERT_EQ(avgpool_node.pad()->bottom(), 0); + ASSERT_EQ(avgpool_node.pad()->left(), 0); + ASSERT_EQ(avgpool_node.pad()->right(), 0); + + ASSERT_EQ(avgpool_node.window()->vertical(), 1); + ASSERT_EQ(avgpool_node.window()->horizontal(), 1); + + ASSERT_EQ(avgpool_node.stride()->vertical(), 1); + ASSERT_EQ(avgpool_node.stride()->horizontal(), 1); +} + +TEST(FeatureEncodeTest, constructor) +{ + loco::FeatureEncode feature_encode; + + ASSERT_EQ(feature_encode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(feature_encode.opcode(), loco::CanonicalOpcode::FeatureEncode); + + ASSERT_EQ(feature_encode.input(), nullptr); + ASSERT_EQ(feature_encode.encoder(), nullptr); +} + +TEST(FeatureDecodeTest, constructor) +{ + loco::FeatureDecode feature_decode; + + ASSERT_EQ(feature_decode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(feature_decode.opcode(), loco::CanonicalOpcode::FeatureDecode); + + ASSERT_EQ(feature_decode.input(), nullptr); + ASSERT_EQ(feature_decode.decoder(), nullptr); +} + +TEST(Reshape_Fixed_Test, constructor) +{ + loco::Reshape reshape; + + ASSERT_EQ(reshape.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(reshape.opcode(), loco::CanonicalOpcode::FixedReshape); + + ASSERT_EQ(reshape.rank(), 0); +} + +TEST(Reshape_Fixed_Test, shape) +{ + loco::Reshape reshape; + reshape.shape({2, 3}); + + ASSERT_EQ(reshape.rank(), 2); + ASSERT_EQ(reshape.dim(0), 2); + ASSERT_EQ(reshape.dim(1), 3); +} + +TEST(FilterEncodeTest, constructor) +{ + loco::FilterEncode filter_encode; + + ASSERT_EQ(filter_encode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(filter_encode.opcode(), loco::CanonicalOpcode::FilterEncode); + + ASSERT_EQ(filter_encode.input(), nullptr); + ASSERT_EQ(filter_encode.encoder(), nullptr); +} + +TEST(FilterDecodeTest, constructor) +{ + loco::FilterDecode filter_decode; + + ASSERT_EQ(filter_decode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(filter_decode.opcode(), loco::CanonicalOpcode::FilterDecode); + + ASSERT_EQ(filter_decode.input(), nullptr); + ASSERT_EQ(filter_decode.decoder(), nullptr); +} + +TEST(DepthwiseFilterEncodeTest, constructor) +{ + loco::DepthwiseFilterEncode dw_filter_encode; + + ASSERT_EQ(dw_filter_encode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(dw_filter_encode.opcode(), loco::CanonicalOpcode::DepthwiseFilterEncode); + + ASSERT_EQ(dw_filter_encode.input(), nullptr); + ASSERT_EQ(dw_filter_encode.encoder(), nullptr); +} + +TEST(DepthwiseFilterDecodeTest, constructor) +{ + loco::DepthwiseFilterDecode dw_filter_decode; + + ASSERT_EQ(dw_filter_decode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(dw_filter_decode.opcode(), loco::CanonicalOpcode::DepthwiseFilterDecode); + + ASSERT_EQ(dw_filter_decode.input(), nullptr); + ASSERT_EQ(dw_filter_decode.decoder(), nullptr); +} + +TEST(TensorConcatTest, constructor) +{ + loco::TensorConcat tensor_concat; + + ASSERT_EQ(tensor_concat.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(tensor_concat.opcode(), loco::CanonicalOpcode::TensorConcat); + + ASSERT_EQ(tensor_concat.lhs(), nullptr); + ASSERT_EQ(tensor_concat.rhs(), nullptr); + ASSERT_EQ(tensor_concat.axis(), 0); + + tensor_concat.axis(3); + ASSERT_EQ(tensor_concat.axis(), 3); +} + +TEST(Conv2DTest, constructor) +{ + loco::Conv2D conv2d; + + ASSERT_EQ(conv2d.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(conv2d.opcode(), loco::CanonicalOpcode::Conv2D); + + ASSERT_EQ(conv2d.ifm(), nullptr); + ASSERT_EQ(conv2d.ker(), nullptr); + + ASSERT_NE(conv2d.pad(), nullptr); + ASSERT_EQ(conv2d.pad()->top(), 0); + ASSERT_EQ(conv2d.pad()->bottom(), 0); + ASSERT_EQ(conv2d.pad()->left(), 0); + ASSERT_EQ(conv2d.pad()->right(), 0); + + ASSERT_NE(conv2d.stride(), nullptr); + ASSERT_EQ(conv2d.stride()->vertical(), 1); + ASSERT_EQ(conv2d.stride()->horizontal(), 1); +} + +TEST(DepthwiseConv2DTest, constructor) +{ + loco::DepthwiseConv2D dw_conv2d; + + ASSERT_EQ(dw_conv2d.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(dw_conv2d.opcode(), loco::CanonicalOpcode::DepthwiseConv2D); + + ASSERT_EQ(dw_conv2d.ifm(), nullptr); + ASSERT_EQ(dw_conv2d.ker(), nullptr); + + ASSERT_NE(dw_conv2d.pad(), nullptr); + ASSERT_EQ(dw_conv2d.pad()->top(), 0); + ASSERT_EQ(dw_conv2d.pad()->bottom(), 0); + ASSERT_EQ(dw_conv2d.pad()->left(), 0); + ASSERT_EQ(dw_conv2d.pad()->right(), 0); + + ASSERT_NE(dw_conv2d.stride(), nullptr); + ASSERT_EQ(dw_conv2d.stride()->vertical(), 1); + ASSERT_EQ(dw_conv2d.stride()->horizontal(), 1); +} + +TEST(TransposedConv2DTest, constructor) +{ + loco::TransposedConv2D tr_conv2d; + + ASSERT_EQ(tr_conv2d.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(tr_conv2d.opcode(), loco::CanonicalOpcode::TransposedConv2D); + + ASSERT_EQ(tr_conv2d.ifm(), nullptr); + ASSERT_EQ(tr_conv2d.ker(), nullptr); + + ASSERT_NE(tr_conv2d.pad(), nullptr); + ASSERT_EQ(tr_conv2d.pad()->top(), 0); + ASSERT_EQ(tr_conv2d.pad()->bottom(), 0); + ASSERT_EQ(tr_conv2d.pad()->left(), 0); + ASSERT_EQ(tr_conv2d.pad()->right(), 0); + + ASSERT_NE(tr_conv2d.stride(), nullptr); + ASSERT_EQ(tr_conv2d.stride()->vertical(), 1); + ASSERT_EQ(tr_conv2d.stride()->horizontal(), 1); +} + +TEST(BiasEncodeTest, constructor) +{ + loco::BiasEncode bias_encode; + + ASSERT_EQ(bias_encode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(bias_encode.opcode(), loco::CanonicalOpcode::BiasEncode); + + ASSERT_EQ(bias_encode.input(), nullptr); +} + +TEST(TensorBiasAddTest, constructor) +{ + loco::BiasAdd bias_add; + + ASSERT_EQ(bias_add.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(bias_add.opcode(), loco::CanonicalOpcode::TensorBiasAdd); + + ASSERT_EQ(bias_add.value(), nullptr); + ASSERT_EQ(bias_add.bias(), nullptr); + ASSERT_EQ(bias_add.axis(), 0); +} + +TEST(TensorBiasAddTest, alias) +{ + loco::TensorBiasAdd bias_add; + + SUCCEED(); +} + +TEST(FeatureBiasAddTest, constructor) +{ + loco::BiasAdd bias_add; + + ASSERT_EQ(bias_add.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(bias_add.opcode(), loco::CanonicalOpcode::FeatureBiasAdd); + + ASSERT_EQ(bias_add.value(), nullptr); + ASSERT_EQ(bias_add.bias(), nullptr); +} + +TEST(FeatureBiasAddTest, alias) +{ + loco::FeatureBiasAdd bias_add; + + SUCCEED(); +} + +TEST(EltwiseAddTest, constructor) +{ + loco::EltwiseAdd eltwise_add; + + SUCCEED(); +} + +TEST(EltwiseMaxTest, constructor) +{ + loco::EltwiseMax eltwise_max; + + SUCCEED(); +} + +TEST(EltwiseMulTest, constructor) +{ + loco::EltwiseMul eltwise_mul; + + SUCCEED(); +} + +TEST(EltwiseSubTest, constructor) +{ + loco::EltwiseSub eltwise_sub; + + SUCCEED(); +} + +TEST(EltwiseDivTest, constructor) +{ + loco::EltwiseDiv eltwise_div; + + SUCCEED(); +} + +TEST(EltwiseSqrtTest, constructor) +{ + loco::EltwiseSqrt sqrt_node; + + ASSERT_EQ(sqrt_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(sqrt_node.opcode(), loco::CanonicalOpcode::EltwiseSqrt); + + ASSERT_EQ(sqrt_node.input(), nullptr); +} + +TEST(TensorBroadcastTest, constructor) +{ + loco::TensorBroadcast tensor_broadcast_node; + + ASSERT_EQ(tensor_broadcast_node.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(tensor_broadcast_node.opcode(), loco::CanonicalOpcode::TensorBroadcast); + + ASSERT_EQ(tensor_broadcast_node.input(), nullptr); +} + +TEST(TensorBroadcastTest, mapping) +{ + loco::TensorBroadcast tensor_broadcast_node; + + ASSERT_EQ(tensor_broadcast_node.mapping()->defined(0), false); + + tensor_broadcast_node.mapping()->dim(0) = 3; + + ASSERT_EQ(tensor_broadcast_node.mapping()->defined(0), true); + ASSERT_EQ(tensor_broadcast_node.mapping()->dim(0), 3); +} + +TEST(MatrixEncodeTest, constructor) +{ + loco::MatrixEncode matrix_encode; + + ASSERT_EQ(matrix_encode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(matrix_encode.opcode(), loco::CanonicalOpcode::MatrixEncode); + + ASSERT_EQ(matrix_encode.input(), nullptr); +} + +TEST(MatrixDecodeTest, constructor) +{ + loco::MatrixDecode matrix_decode; + + ASSERT_EQ(matrix_decode.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(matrix_decode.opcode(), loco::CanonicalOpcode::MatrixDecode); + + ASSERT_EQ(matrix_decode.input(), nullptr); +} + +TEST(MatMulTest, constructor) +{ + loco::MatMul mat_mul; + + ASSERT_EQ(mat_mul.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(mat_mul.opcode(), loco::CanonicalOpcode::MatMul); + + ASSERT_EQ(mat_mul.lhs(), nullptr); + ASSERT_EQ(mat_mul.rhs(), nullptr); +} + +TEST(TransposeTest, constructor) +{ + loco::TensorTranspose transpose; + + ASSERT_EQ(transpose.dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(transpose.opcode(), loco::CanonicalOpcode::TensorTranspose); + + ASSERT_EQ(transpose.input(), nullptr); + ASSERT_EQ(transpose.perm()->size(), 0); +} + +TEST(TransposeTest, perm) +{ + loco::TensorTranspose transpose; + + transpose.perm()->size(3); + transpose.perm()->axis(0) = 1; + transpose.perm()->axis(1) = 2; + transpose.perm()->axis(2) = 0; + + ASSERT_EQ(transpose.perm()->axis(0), 1); + ASSERT_EQ(transpose.perm()->axis(1), 2); + ASSERT_EQ(transpose.perm()->axis(2), 0); +} diff --git a/compiler/loco/src/IR/Padding2D.test.cpp b/compiler/loco/src/IR/Padding2D.test.cpp new file mode 100644 index 00000000000..2e3d4af87a3 --- /dev/null +++ b/compiler/loco/src/IR/Padding2D.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Padding2D.h" + +#include + +TEST(PadTest, default_constructor_2D) +{ + loco::Padding2D pad; + + ASSERT_EQ(pad.top(), 0); + ASSERT_EQ(pad.bottom(), 0); + ASSERT_EQ(pad.left(), 0); + ASSERT_EQ(pad.right(), 0); +} diff --git a/compiler/loco/src/IR/PaddingND.test.cpp b/compiler/loco/src/IR/PaddingND.test.cpp new file mode 100644 index 00000000000..0e20406ffb3 --- /dev/null +++ b/compiler/loco/src/IR/PaddingND.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/PaddingND.h" + +#include + +TEST(PaddingNDTest, default_constructor_ND) +{ + loco::PaddingND padding; + + padding.rank(1); + padding.front(0) = 1; + padding.back(0) = 2; + + ASSERT_EQ(padding.rank(), 1); + ASSERT_EQ(padding.front(0), 1); + ASSERT_EQ(padding.back(0), 2); +} diff --git a/compiler/loco/src/IR/PermutingCodec.cpp b/compiler/loco/src/IR/PermutingCodec.cpp new file mode 100644 index 00000000000..2857e5e2891 --- /dev/null +++ b/compiler/loco/src/IR/PermutingCodec.cpp @@ -0,0 +1,630 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/PermutingCodec.h" + +#include + +#include +#include +#include + +/** + * Feature Domain + */ +namespace +{ + +using loco::FeatureAxis; + +inline bool valid(const FeatureAxis &axis) +{ + switch (axis) + { + case FeatureAxis::Count: + return true; + case FeatureAxis::Depth: + return true; + case FeatureAxis::Height: + return true; + case FeatureAxis::Width: + return true; + default: + break; + } + + return false; +} + +inline bool valid(const loco::Permutation &perm) +{ + auto check = [&perm](FeatureAxis axis_f) { + if (!perm.mapped(axis_f)) + return false; + return perm.axis(axis_f) < 4; + }; + + if (!check(FeatureAxis::Count)) + return false; + if (!check(FeatureAxis::Depth)) + return false; + if (!check(FeatureAxis::Height)) + return false; + if (!check(FeatureAxis::Width)) + return false; + + // Check whether tensor axes are all distinct + std::set values; + + values.insert(perm[FeatureAxis::Count]); + values.insert(perm[FeatureAxis::Depth]); + values.insert(perm[FeatureAxis::Height]); + values.insert(perm[FeatureAxis::Width]); + + return values.size() == 4; +} + +} // namespace + +namespace loco +{ + +// +// Permutation +// +bool Permutation::mapped(const FeatureAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid feature axis"); + return _map.find(axis_f) != _map.end(); +} + +uint32_t Permutation::axis(const FeatureAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid feature axis"); + assert(mapped(axis_f) && "unmapped feature axis"); + return _map.at(axis_f); +} + +uint32_t &Permutation::axis(const FeatureAxis &axis_f) +{ + assert(valid(axis_f) && "invalid feature axis"); + return _map[axis_f]; +} + +// +// Permuting Encoder +// +FeatureShape PermutingEncoder::shape(const TensorShape &in) const +{ + assert(valid() && "invalid permutation"); + + FeatureShape out; + + out.count() = in.dim(_perm[FeatureAxis::Count]); + out.depth() = in.dim(_perm[FeatureAxis::Depth]); + out.height() = in.dim(_perm[FeatureAxis::Height]); + out.width() = in.dim(_perm[FeatureAxis::Width]); + + return out; +} + +TensorIndex PermutingEncoder::value(const FeatureIndex &in) const +{ + assert(valid() && "invalid permutation"); + + TensorIndex out; + + out.resize(4); + + out.at(_perm[FeatureAxis::Count]) = in.batch(); + out.at(_perm[FeatureAxis::Depth]) = in.channel(); + out.at(_perm[FeatureAxis::Height]) = in.row(); + out.at(_perm[FeatureAxis::Width]) = in.column(); + + return out; +} + +std::unique_ptr PermutingEncoder::clone(void) const +{ + return stdex::make_unique>(_perm); +} + +bool PermutingEncoder::valid(void) const { return ::valid(_perm); } + +// +// Permuting Decoder +// +TensorShape PermutingDecoder::shape(const FeatureShape &in) const +{ + assert(valid() && "invalid permuation"); + + TensorShape out; + + out.rank(4); + + out.dim(_perm[FeatureAxis::Count]) = in.count(); + out.dim(_perm[FeatureAxis::Depth]) = in.depth(); + out.dim(_perm[FeatureAxis::Height]) = in.height(); + out.dim(_perm[FeatureAxis::Width]) = in.width(); + + return out; +} + +FeatureIndex PermutingDecoder::value(const TensorIndex &in) const +{ + assert(valid() && "invalid permutation"); + + FeatureIndex out; + + out.batch() = in.at(_perm[FeatureAxis::Count]); + out.channel() = in.at(_perm[FeatureAxis::Depth]); + out.row() = in.at(_perm[FeatureAxis::Height]); + out.column() = in.at(_perm[FeatureAxis::Width]); + + return out; +} + +std::unique_ptr PermutingDecoder::clone(void) const +{ + return stdex::make_unique>(_perm); +} + +bool PermutingDecoder::valid(void) const { return ::valid(_perm); } + +} // namespace loco + +/** + * Filter Domain + */ +namespace +{ + +using loco::FilterAxis; + +inline bool valid(const FilterAxis &axis) +{ + switch (axis) + { + case FilterAxis::Count: + return true; + case FilterAxis::Depth: + return true; + case FilterAxis::Height: + return true; + case FilterAxis::Width: + return true; + default: + break; + } + + return false; +} + +inline bool valid(const loco::Permutation &perm) +{ + auto check = [&perm](FilterAxis axis_f) { + if (!perm.mapped(axis_f)) + return false; + return perm.axis(axis_f) < 4; + }; + + if (!check(FilterAxis::Count)) + return false; + if (!check(FilterAxis::Depth)) + return false; + if (!check(FilterAxis::Height)) + return false; + if (!check(FilterAxis::Width)) + return false; + + // Check whether tensor axes are all distinct + std::set values; + + values.insert(perm[FilterAxis::Count]); + values.insert(perm[FilterAxis::Depth]); + values.insert(perm[FilterAxis::Height]); + values.insert(perm[FilterAxis::Width]); + + return values.size() == 4; +} + +} // namespace + +namespace loco +{ + +// +// Permutation +// +bool Permutation::mapped(const FilterAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid filter axis"); + return _map.find(axis_f) != _map.end(); +} + +const uint32_t &Permutation::axis(const FilterAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid filter axis"); + assert(mapped(axis_f) && "unmapped filter axis"); + return _map.at(axis_f); +} + +uint32_t &Permutation::axis(const FilterAxis &axis_f) +{ + assert(valid(axis_f) && "invalid filter axis"); + return _map[axis_f]; +} + +// +// Permuting Encoder +// +FilterShape PermutingEncoder::shape(const TensorShape &in) const +{ + assert(valid() && "invalid permutation"); + + FilterShape out; + + out.count() = in.dim(_perm[FilterAxis::Count]); + out.depth() = in.dim(_perm[FilterAxis::Depth]); + out.height() = in.dim(_perm[FilterAxis::Height]); + out.width() = in.dim(_perm[FilterAxis::Width]); + + return out; +} + +TensorIndex PermutingEncoder::value(const FilterIndex &in) const +{ + assert(valid() && "invalid permutation"); + + TensorIndex out; + + out.resize(4); + + out.at(_perm[FilterAxis::Count]) = in.nth(); + out.at(_perm[FilterAxis::Depth]) = in.channel(); + out.at(_perm[FilterAxis::Height]) = in.row(); + out.at(_perm[FilterAxis::Width]) = in.column(); + + return out; +} + +bool PermutingEncoder::valid(void) const { return ::valid(_perm); } + +// +// Permuting Decoder +// +TensorShape PermutingDecoder::shape(const FilterShape &in) const +{ + assert(valid() && "invalid permutation"); + + TensorShape out; + + out.rank(4); + out.dim(_perm[FilterAxis::Count]) = in.count(); + out.dim(_perm[FilterAxis::Depth]) = in.depth(); + out.dim(_perm[FilterAxis::Height]) = in.height(); + out.dim(_perm[FilterAxis::Width]) = in.width(); + + return out; +} + +FilterIndex PermutingDecoder::value(const TensorIndex &in) const +{ + assert(valid() && "invalid permutation"); + + FilterIndex out; + + out.nth() = in.at(_perm[FilterAxis::Count]); + out.channel() = in.at(_perm[FilterAxis::Depth]); + out.row() = in.at(_perm[FilterAxis::Height]); + out.column() = in.at(_perm[FilterAxis::Width]); + + return out; +} + +bool PermutingDecoder::valid(void) const { return ::valid(_perm); } + +} // namespace loco + +/** + * DepthwiseFilter Domain + */ +namespace +{ + +using loco::DepthwiseFilterAxis; + +inline bool valid(const DepthwiseFilterAxis &axis) +{ + switch (axis) + { + case DepthwiseFilterAxis::Depth: + return true; + case DepthwiseFilterAxis::Multiplier: + return true; + case DepthwiseFilterAxis::Height: + return true; + case DepthwiseFilterAxis::Width: + return true; + default: + break; + } + + return false; +} + +inline bool valid(const loco::Permutation &perm) +{ + auto check = [&perm](DepthwiseFilterAxis axis_f) { + if (!perm.mapped(axis_f)) + return false; + return perm.axis(axis_f) < 4; + }; + + if (!check(DepthwiseFilterAxis::Depth)) + return false; + if (!check(DepthwiseFilterAxis::Multiplier)) + return false; + if (!check(DepthwiseFilterAxis::Height)) + return false; + if (!check(DepthwiseFilterAxis::Width)) + return false; + + // Check whether tensor axes are all distinct + std::set values; + + values.insert(perm[DepthwiseFilterAxis::Depth]); + values.insert(perm[DepthwiseFilterAxis::Multiplier]); + values.insert(perm[DepthwiseFilterAxis::Height]); + values.insert(perm[DepthwiseFilterAxis::Width]); + + return values.size() == 4; +} + +} // namespace + +namespace loco +{ + +// +// Permutation +// +bool Permutation::mapped(const DepthwiseFilterAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid depthwise filter axis"); + return _map.find(axis_f) != _map.end(); +} + +const uint32_t &Permutation::axis(const DepthwiseFilterAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid depthwise filter axis"); + assert(mapped(axis_f) && "unmapped depthwise filter axis"); + return _map.at(axis_f); +} + +uint32_t &Permutation::axis(const DepthwiseFilterAxis &axis_f) +{ + assert(valid(axis_f) && "invalid depthwise filter axis"); + return _map[axis_f]; +} + +// +// Permuting Encoder +// +DepthwiseFilterShape PermutingEncoder::shape(const TensorShape &in) const +{ + assert(valid() && "invalid permutation"); + + DepthwiseFilterShape out; + + out.depth() = in.dim(_perm[DepthwiseFilterAxis::Depth]); + out.multiplier() = in.dim(_perm[DepthwiseFilterAxis::Multiplier]); + out.height() = in.dim(_perm[DepthwiseFilterAxis::Height]); + out.width() = in.dim(_perm[DepthwiseFilterAxis::Width]); + + return out; +} + +TensorIndex PermutingEncoder::value(const DepthwiseFilterIndex &in) const +{ + assert(valid() && "invalid permutation"); + + TensorIndex out; + + out.resize(4); + + out.at(_perm[DepthwiseFilterAxis::Depth]) = in.channel(); + out.at(_perm[DepthwiseFilterAxis::Multiplier]) = in.nth(); + out.at(_perm[DepthwiseFilterAxis::Height]) = in.row(); + out.at(_perm[DepthwiseFilterAxis::Width]) = in.column(); + + return out; +} + +bool PermutingEncoder::valid(void) const { return ::valid(_perm); } + +// +// Permuting Decoder +// +TensorShape PermutingDecoder::shape(const DepthwiseFilterShape &in) const +{ + assert(valid() && "invalid permutation"); + + TensorShape out; + out.rank(4); + + out.dim(_perm[DepthwiseFilterAxis::Depth]) = in.depth(); + out.dim(_perm[DepthwiseFilterAxis::Multiplier]) = in.multiplier(); + out.dim(_perm[DepthwiseFilterAxis::Height]) = in.height(); + out.dim(_perm[DepthwiseFilterAxis::Width]) = in.width(); + + return out; +} + +DepthwiseFilterIndex PermutingDecoder::value(const TensorIndex &in) const +{ + assert(valid() && "invalid permutation"); + assert(in.rank() == 4); + + DepthwiseFilterIndex out; + + out.channel() = in.at(_perm[DepthwiseFilterAxis::Depth]); + out.nth() = in.at(_perm[DepthwiseFilterAxis::Multiplier]); + out.row() = in.at(_perm[DepthwiseFilterAxis::Height]); + out.column() = in.at(_perm[DepthwiseFilterAxis::Width]); + + return out; +} + +bool PermutingDecoder::valid(void) const { return ::valid(_perm); } + +} // namespace loco + +/** + * Matrix Domain + */ +namespace +{ + +using loco::MatrixAxis; + +inline bool valid(const MatrixAxis &axis) +{ + switch (axis) + { + case MatrixAxis::Height: + return true; + case MatrixAxis::Width: + return true; + default: + break; + } + + return false; +} + +inline bool valid(const loco::Permutation &perm) +{ + auto check = [&perm](MatrixAxis axis_f) { + if (!perm.mapped(axis_f)) + return false; + return perm.axis(axis_f) < 2; + }; + + if (!check(MatrixAxis::Height)) + return false; + if (!check(MatrixAxis::Width)) + return false; + + // Check whether tensor axes are all distinct + std::set values; + + values.insert(perm[MatrixAxis::Height]); + values.insert(perm[MatrixAxis::Width]); + + return values.size() == 2; +} + +} // namespace + +namespace loco +{ + +// +// Permutation +// +bool Permutation::mapped(const MatrixAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid matrix axis"); + return _map.find(axis_f) != _map.end(); +} + +uint32_t Permutation::axis(const MatrixAxis &axis_f) const +{ + assert(valid(axis_f) && "invalid matrix axis"); + assert(mapped(axis_f) && "unmapped matrix axis"); + return _map.at(axis_f); +} + +uint32_t &Permutation::axis(const MatrixAxis &axis_f) +{ + assert(valid(axis_f) && "invalid matrix axis"); + return _map[axis_f]; +} + +// +// Permuting Encoder +// +MatrixShape PermutingEncoder::shape(const TensorShape &in) const +{ + assert(valid() && "invalid permutation"); + + MatrixShape out; + + out.height() = in.dim(_perm[MatrixAxis::Height]); + out.width() = in.dim(_perm[MatrixAxis::Width]); + + return out; +} + +TensorIndex PermutingEncoder::value(const MatrixIndex &in) const +{ + assert(valid() && "invalid permutation"); + + TensorIndex out; + + out.resize(2); + + out.at(_perm[MatrixAxis::Height]) = in.row(); + out.at(_perm[MatrixAxis::Width]) = in.column(); + + return out; +} + +bool PermutingEncoder::valid(void) const { return ::valid(_perm); } + +// +// Permuting Decoder +// +TensorShape PermutingDecoder::shape(const MatrixShape &in) const +{ + assert(valid() && "invalid permuation"); + + TensorShape out; + + out.rank(2); + + out.dim(_perm[MatrixAxis::Height]) = in.height(); + out.dim(_perm[MatrixAxis::Width]) = in.width(); + + return out; +} + +MatrixIndex PermutingDecoder::value(const TensorIndex &in) const +{ + assert(valid() && "invalid permutation"); + + MatrixIndex out; + + out.row() = in.at(_perm[MatrixAxis::Height]); + out.column() = in.at(_perm[MatrixAxis::Width]); + + return out; +} + +bool PermutingDecoder::valid(void) const { return ::valid(_perm); } + +} // namespace loco diff --git a/compiler/loco/src/IR/PermutingCodec.test.cpp b/compiler/loco/src/IR/PermutingCodec.test.cpp new file mode 100644 index 00000000000..2eff286d049 --- /dev/null +++ b/compiler/loco/src/IR/PermutingCodec.test.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/PermutingCodec.h" + +#include + +using namespace loco; + +TEST(PemutationTest, feature) +{ + Permutation perm; + + // All values are invalid at the beginning + ASSERT_FALSE(perm.mapped(FeatureAxis::Count)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Depth)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Height)); + ASSERT_FALSE(perm.mapped(FeatureAxis::Width)); + + // Update mapping + perm[FeatureAxis::Count] = 5; + perm[FeatureAxis::Depth] = 6; + perm[FeatureAxis::Height] = 7; + perm[FeatureAxis::Width] = 8; + + // Now perm has a mapping for all the axes + ASSERT_TRUE(perm.mapped(FeatureAxis::Count)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Depth)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Height)); + ASSERT_TRUE(perm.mapped(FeatureAxis::Width)); + + // Check the value + ASSERT_EQ(perm[FeatureAxis::Count], 5); + ASSERT_EQ(perm[FeatureAxis::Depth], 6); + ASSERT_EQ(perm[FeatureAxis::Height], 7); + ASSERT_EQ(perm[FeatureAxis::Width], 8); +} + +TEST(PemutationTest, filter) +{ + Permutation perm; + + // All values are invalid at the beginning + ASSERT_FALSE(perm.mapped(FilterAxis::Count)); + ASSERT_FALSE(perm.mapped(FilterAxis::Depth)); + ASSERT_FALSE(perm.mapped(FilterAxis::Height)); + ASSERT_FALSE(perm.mapped(FilterAxis::Width)); + + // Update mapping + perm[FilterAxis::Count] = 5; + perm[FilterAxis::Depth] = 6; + perm[FilterAxis::Height] = 7; + perm[FilterAxis::Width] = 8; + + // Now perm has a mapping for all the axes + ASSERT_TRUE(perm.mapped(FilterAxis::Count)); + ASSERT_TRUE(perm.mapped(FilterAxis::Depth)); + ASSERT_TRUE(perm.mapped(FilterAxis::Height)); + ASSERT_TRUE(perm.mapped(FilterAxis::Width)); + + // Check the value + ASSERT_EQ(perm[FilterAxis::Count], 5); + ASSERT_EQ(perm[FilterAxis::Depth], 6); + ASSERT_EQ(perm[FilterAxis::Height], 7); + ASSERT_EQ(perm[FilterAxis::Width], 8); +} + +TEST(PemutationTest, depthwise_filter) +{ + Permutation perm; + + // All values are invalid at the beginning + ASSERT_FALSE(perm.mapped(DepthwiseFilterAxis::Depth)); + ASSERT_FALSE(perm.mapped(DepthwiseFilterAxis::Multiplier)); + ASSERT_FALSE(perm.mapped(DepthwiseFilterAxis::Height)); + ASSERT_FALSE(perm.mapped(DepthwiseFilterAxis::Width)); + + // Update mapping + perm[DepthwiseFilterAxis::Depth] = 5; + perm[DepthwiseFilterAxis::Multiplier] = 6; + perm[DepthwiseFilterAxis::Height] = 7; + perm[DepthwiseFilterAxis::Width] = 8; + + // Now perm has a mapping for all the axes + ASSERT_TRUE(perm.mapped(DepthwiseFilterAxis::Depth)); + ASSERT_TRUE(perm.mapped(DepthwiseFilterAxis::Multiplier)); + ASSERT_TRUE(perm.mapped(DepthwiseFilterAxis::Height)); + ASSERT_TRUE(perm.mapped(DepthwiseFilterAxis::Width)); + + // Check the value + ASSERT_EQ(perm[DepthwiseFilterAxis::Depth], 5); + ASSERT_EQ(perm[DepthwiseFilterAxis::Multiplier], 6); + ASSERT_EQ(perm[DepthwiseFilterAxis::Height], 7); + ASSERT_EQ(perm[DepthwiseFilterAxis::Width], 8); +} + +TEST(PermutingEncoderTest, feature) +{ + PermutingEncoder enc; + + // Encoder is invalid at the beginning + ASSERT_FALSE(enc.valid()); + + // Set "invalid" mapping + enc.perm()->axis(FeatureAxis::Count) = 0; + enc.perm()->axis(FeatureAxis::Depth) = 6; + enc.perm()->axis(FeatureAxis::Height) = 1; + enc.perm()->axis(FeatureAxis::Width) = 2; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set another "invalid" mapping + enc.perm()->axis(FeatureAxis::Depth) = 1; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set "valid" mapping + enc.perm()->axis(FeatureAxis::Depth) = 3; + + // Encoder is now valid + ASSERT_TRUE(enc.valid()); + + // Let's test with a HD (1280x720) RGB image + TensorShape tensor_shape; + + tensor_shape.rank(4); + tensor_shape.dim(0) = 1; // COUNT + tensor_shape.dim(1) = 720; // HEIGHT + tensor_shape.dim(2) = 1280; // WIDTH + tensor_shape.dim(3) = 3; // DEPTH + + // Get the feature shape corresponding to a given image + auto feature_shape = enc.shape(tensor_shape); + + ASSERT_EQ(feature_shape.count(), 1); + ASSERT_EQ(feature_shape.depth(), 3); + ASSERT_EQ(feature_shape.height(), 720); + ASSERT_EQ(feature_shape.width(), 1280); + + // Let's find a source tensor index! + FeatureIndex feature_index; + + feature_index.batch() = 0; + feature_index.channel() = 1; + feature_index.row() = 2; + feature_index.column() = 3; + + auto tensor_index = enc.value(feature_index); + + ASSERT_EQ(tensor_index.at(0), 0); // BATCH(COUNT) + ASSERT_EQ(tensor_index.at(1), 2); // ROW(HEIGHT) + ASSERT_EQ(tensor_index.at(2), 3); // COLUMN(WIDTH) + ASSERT_EQ(tensor_index.at(3), 1); // CHANNEL(DEPTH) +} + +TEST(PermutingEncoderTest, feature_clone) +{ + PermutingEncoder src_enc; + + auto src_perm = src_enc.perm(); + + src_perm->axis(FeatureAxis::Count) = 0; + src_perm->axis(FeatureAxis::Depth) = 3; + src_perm->axis(FeatureAxis::Height) = 1; + src_perm->axis(FeatureAxis::Width) = 2; + + auto dst_enc = src_enc.clone(); + auto dst_perm = dynamic_cast *>(dst_enc.get())->perm(); + + EXPECT_EQ(dst_perm->axis(FeatureAxis::Count), src_perm->axis(FeatureAxis::Count)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Depth), src_perm->axis(FeatureAxis::Depth)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Height), src_perm->axis(FeatureAxis::Height)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Width), src_perm->axis(FeatureAxis::Width)); + + // Update on cloned encoder SHOULD NOT affect the original encoder + dst_perm->axis(FeatureAxis::Height) += 1; + + EXPECT_EQ(src_perm->axis(FeatureAxis::Height), 1); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Height), 2); +} + +TEST(PermutingEncoderTest, filter) +{ + PermutingEncoder enc; + + // Encoder is invalid at the beginning + ASSERT_FALSE(enc.valid()); + + // Set "invalid" mapping + enc.perm()->axis(FilterAxis::Count) = 0; + enc.perm()->axis(FilterAxis::Depth) = 6; + enc.perm()->axis(FilterAxis::Height) = 1; + enc.perm()->axis(FilterAxis::Width) = 2; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set another "invalid" mapping + enc.perm()->axis(FilterAxis::Depth) = 1; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set "valid" mapping + enc.perm()->axis(FilterAxis::Depth) = 3; + + // Encoder is now valid + ASSERT_TRUE(enc.valid()); + + TensorShape tensor_shape; + + tensor_shape.rank(4); + tensor_shape.dim(0) = 8; // COUNT + tensor_shape.dim(1) = 1; // HEIGHT + tensor_shape.dim(2) = 7; // WIDTH + tensor_shape.dim(3) = 4; // DEPTH + + // Get the corresponding filter shape + auto filter_shape = enc.shape(tensor_shape); + + ASSERT_EQ(filter_shape.count(), 8); + ASSERT_EQ(filter_shape.depth(), 4); + ASSERT_EQ(filter_shape.height(), 1); + ASSERT_EQ(filter_shape.width(), 7); + + // Let's find a source tensor index! + FilterIndex filter_index; + + filter_index.nth() = 1; + filter_index.channel() = 2; + filter_index.row() = 0; + filter_index.column() = 3; + + auto tensor_index = enc.value(filter_index); + + ASSERT_EQ(tensor_index.at(0), 1); // NTH(COUNT) + ASSERT_EQ(tensor_index.at(1), 0); // ROW(HEIGHT) + ASSERT_EQ(tensor_index.at(2), 3); // COLUMN(WIDTH) + ASSERT_EQ(tensor_index.at(3), 2); // CHANNEL(DEPTH) +} + +TEST(PermutingEncoderTest, depthwise_filter) +{ + PermutingEncoder enc; + + // Encoder is invalid at the beginning + ASSERT_FALSE(enc.valid()); + + // Set "invalid" mapping + enc.perm()->axis(DepthwiseFilterAxis::Depth) = 0; + enc.perm()->axis(DepthwiseFilterAxis::Multiplier) = 6; + enc.perm()->axis(DepthwiseFilterAxis::Height) = 1; + enc.perm()->axis(DepthwiseFilterAxis::Width) = 2; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set another "invalid" mapping + enc.perm()->axis(DepthwiseFilterAxis::Multiplier) = 1; + + // Encoder is still invalid + ASSERT_FALSE(enc.valid()); + + // Set "valid" mapping + enc.perm()->axis(DepthwiseFilterAxis::Multiplier) = 3; + + // Encoder is now valid + ASSERT_TRUE(enc.valid()); + + TensorShape tensor_shape; + + tensor_shape.rank(4); + tensor_shape.dim(0) = 8; // DEPTH + tensor_shape.dim(1) = 1; // HEIGHT + tensor_shape.dim(2) = 7; // WIDTH + tensor_shape.dim(3) = 4; // MULTIPLIER + + // Get the corresponding depthwise filter shape + auto filter_shape = enc.shape(tensor_shape); + + ASSERT_EQ(filter_shape.depth(), 8); + ASSERT_EQ(filter_shape.multiplier(), 4); + ASSERT_EQ(filter_shape.height(), 1); + ASSERT_EQ(filter_shape.width(), 7); + + // Let's find a source tensor index! + DepthwiseFilterIndex filter_index; + + filter_index.channel() = 1; + filter_index.nth() = 2; + filter_index.row() = 0; + filter_index.column() = 3; + + auto tensor_index = enc.value(filter_index); + + ASSERT_EQ(tensor_index.at(0), 1); // CHANNEL(DEPTH) + ASSERT_EQ(tensor_index.at(1), 0); // ROW(HEIGHT) + ASSERT_EQ(tensor_index.at(2), 3); // COLUMN(WIDTH) + ASSERT_EQ(tensor_index.at(3), 2); // NTH(MULTIPLIER) +} + +TEST(PermutingEncoderTest, depthwisefilter_init) +{ + Permutation src_perm; + + src_perm.axis(DepthwiseFilterAxis::Multiplier) = 0; + src_perm.axis(DepthwiseFilterAxis::Depth) = 3; + src_perm.axis(DepthwiseFilterAxis::Height) = 1; + src_perm.axis(DepthwiseFilterAxis::Width) = 2; + + PermutingEncoder dst_enc{src_perm}; + auto dst_perm = dst_enc.perm(); + + EXPECT_EQ(dst_perm->axis(DepthwiseFilterAxis::Multiplier), + src_perm.axis(DepthwiseFilterAxis::Multiplier)); + EXPECT_EQ(dst_perm->axis(DepthwiseFilterAxis::Depth), src_perm.axis(DepthwiseFilterAxis::Depth)); + EXPECT_EQ(dst_perm->axis(DepthwiseFilterAxis::Height), + src_perm.axis(DepthwiseFilterAxis::Height)); + EXPECT_EQ(dst_perm->axis(DepthwiseFilterAxis::Width), src_perm.axis(DepthwiseFilterAxis::Width)); + + // Update on dst perm SHOULD NOT affect the src perm + dst_perm->axis(DepthwiseFilterAxis::Height) += 1; + + EXPECT_EQ(src_perm.axis(DepthwiseFilterAxis::Height), 1); + EXPECT_EQ(dst_perm->axis(DepthwiseFilterAxis::Height), 2); +} + +TEST(PermutingDecoderTest, feature) +{ + PermutingDecoder dec; + + // Decoder is invalid at the beginning + ASSERT_FALSE(dec.valid()); + + // Set "invalid" mapping + dec.perm()->axis(FeatureAxis::Count) = 0; + dec.perm()->axis(FeatureAxis::Depth) = 6; + dec.perm()->axis(FeatureAxis::Height) = 1; + dec.perm()->axis(FeatureAxis::Width) = 2; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set another "invalid" mapping + dec.perm()->axis(FeatureAxis::Depth) = 1; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set "valid" mapping + dec.perm()->axis(FeatureAxis::Depth) = 3; + + // Decoder is now valid + ASSERT_TRUE(dec.valid()); + + // Let's test with a HD (1280x720) RGB image + FeatureShape feature_shape; + + feature_shape.count() = 1; + feature_shape.depth() = 3; + feature_shape.height() = 720; + feature_shape.width() = 1280; + + // Get the tensor shape corresponding to a given image + auto tensor_shape = dec.shape(feature_shape); + + ASSERT_EQ(tensor_shape.rank(), 4); + ASSERT_EQ(tensor_shape.dim(0), 1); // COUNT + ASSERT_EQ(tensor_shape.dim(1), 720); // HEIGHT + ASSERT_EQ(tensor_shape.dim(2), 1280); // WIDTH + ASSERT_EQ(tensor_shape.dim(3), 3); // DEPTH + + // Let's find a source feature index! + TensorIndex tensor_index; + + tensor_index.resize(4); + + tensor_index.at(0) = 0; // BATCH(COUNT) + tensor_index.at(3) = 1; // CHANNEL(DEPTH) + tensor_index.at(1) = 2; // ROW(HEIGHT) + tensor_index.at(2) = 3; // COLUMN(WIDTH) + + auto feature_index = dec.value(tensor_index); + + ASSERT_EQ(feature_index.batch(), 0); + ASSERT_EQ(feature_index.channel(), 1); + ASSERT_EQ(feature_index.row(), 2); + ASSERT_EQ(feature_index.column(), 3); +} + +TEST(PermutingDecoderTest, feature_clone) +{ + PermutingDecoder src_enc; + + auto src_perm = src_enc.perm(); + + src_perm->axis(FeatureAxis::Count) = 0; + src_perm->axis(FeatureAxis::Depth) = 3; + src_perm->axis(FeatureAxis::Height) = 1; + src_perm->axis(FeatureAxis::Width) = 2; + + auto dst_enc = src_enc.clone(); + auto dst_perm = dynamic_cast *>(dst_enc.get())->perm(); + + EXPECT_EQ(dst_perm->axis(FeatureAxis::Count), src_perm->axis(FeatureAxis::Count)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Depth), src_perm->axis(FeatureAxis::Depth)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Height), src_perm->axis(FeatureAxis::Height)); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Width), src_perm->axis(FeatureAxis::Width)); + + // Update on cloned decoder SHOULD NOT affect the original decoder + dst_perm->axis(FeatureAxis::Height) += 1; + + EXPECT_EQ(src_perm->axis(FeatureAxis::Height), 1); + EXPECT_EQ(dst_perm->axis(FeatureAxis::Height), 2); +} + +TEST(PermutingDecoderTest, filter) +{ + PermutingDecoder dec; + + // Decoder is invalid at the beginning + ASSERT_FALSE(dec.valid()); + + // Set "invalid" mapping + dec.perm()->axis(FilterAxis::Count) = 0; + dec.perm()->axis(FilterAxis::Depth) = 6; + dec.perm()->axis(FilterAxis::Height) = 1; + dec.perm()->axis(FilterAxis::Width) = 2; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set another "invalid" mapping + dec.perm()->axis(FilterAxis::Depth) = 1; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set "valid" mapping + dec.perm()->axis(FilterAxis::Depth) = 3; + + // Decoder is now valid + ASSERT_TRUE(dec.valid()); + + // Let's test with a small filter + FilterShape filter_shape; + + filter_shape.count() = 10; + filter_shape.depth() = 3; + filter_shape.height() = 6; + filter_shape.width() = 8; + + // Get the tensor shape corresponding to a given image + auto tensor_shape = dec.shape(filter_shape); + + ASSERT_EQ(tensor_shape.rank(), 4); + ASSERT_EQ(tensor_shape.dim(0), 10); // COUNT + ASSERT_EQ(tensor_shape.dim(1), 6); // HEIGHT + ASSERT_EQ(tensor_shape.dim(2), 8); // WIDTH + ASSERT_EQ(tensor_shape.dim(3), 3); // DEPTH + + // Let's find a source filter index! + TensorIndex tensor_index; + + tensor_index.resize(4); + + tensor_index.at(0) = 0; // BATCH(COUNT) + tensor_index.at(3) = 1; // CHANNEL(DEPTH) + tensor_index.at(1) = 2; // ROW(HEIGHT) + tensor_index.at(2) = 3; // COLUMN(WIDTH) + + auto filter_index = dec.value(tensor_index); + + ASSERT_EQ(filter_index.nth(), 0); + ASSERT_EQ(filter_index.channel(), 1); + ASSERT_EQ(filter_index.row(), 2); + ASSERT_EQ(filter_index.column(), 3); +} + +TEST(PermutingDecoderTest, depthwise_filter) +{ + PermutingDecoder dec; + + // Decoder is invalid at the beginning + ASSERT_FALSE(dec.valid()); + + // Set "invalid" mapping + dec.perm()->axis(DepthwiseFilterAxis::Depth) = 0; + dec.perm()->axis(DepthwiseFilterAxis::Multiplier) = 6; + dec.perm()->axis(DepthwiseFilterAxis::Height) = 1; + dec.perm()->axis(DepthwiseFilterAxis::Width) = 2; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set another "invalid" mapping + dec.perm()->axis(DepthwiseFilterAxis::Multiplier) = 1; + + // Decoder is still invalid + ASSERT_FALSE(dec.valid()); + + // Set "valid" mapping + dec.perm()->axis(DepthwiseFilterAxis::Multiplier) = 3; + + // Decoder is now valid + ASSERT_TRUE(dec.valid()); + + DepthwiseFilterShape dw_filter_shape; + + dw_filter_shape.depth() = 8; + dw_filter_shape.multiplier() = 1; + dw_filter_shape.height() = 7; + dw_filter_shape.width() = 4; + + // Get the corresponding depthwise filter shape + auto tensor_shape = dec.shape(dw_filter_shape); + + ASSERT_EQ(tensor_shape.dim(0).value(), 8); + ASSERT_EQ(tensor_shape.dim(1).value(), 7); + ASSERT_EQ(tensor_shape.dim(2).value(), 4); + ASSERT_EQ(tensor_shape.dim(3).value(), 1); + + // Let's find a source tensor index! + TensorIndex tensor_index; + tensor_index.resize(4); + + tensor_index.at(0) = 4; + tensor_index.at(1) = 2; + tensor_index.at(2) = 1; + tensor_index.at(3) = 0; + + auto dw_filter_index = dec.value(tensor_index); + + ASSERT_EQ(dw_filter_index.channel(), 4); + ASSERT_EQ(dw_filter_index.nth(), 0); + ASSERT_EQ(dw_filter_index.row(), 2); + ASSERT_EQ(dw_filter_index.column(), 1); +} diff --git a/compiler/loco/src/IR/Stride.test.cpp b/compiler/loco/src/IR/Stride.test.cpp new file mode 100644 index 00000000000..60deb5c6f6e --- /dev/null +++ b/compiler/loco/src/IR/Stride.test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Stride.h" + +#include + +TEST(StrideTest, default_constructor_2D) +{ + loco::Stride<2> stride; + + ASSERT_EQ(stride.vertical(), 1); + ASSERT_EQ(stride.horizontal(), 1); +} + +TEST(StrideTest, setter_and_getter_2D) +{ + loco::Stride<2> stride; + + stride.vertical(2); + + ASSERT_EQ(stride.vertical(), 2); + ASSERT_EQ(stride.horizontal(), 1); + + stride.horizontal(3); + + ASSERT_EQ(stride.vertical(), 2); + ASSERT_EQ(stride.horizontal(), 3); +} diff --git a/compiler/loco/src/IR/TensorAxis.cpp b/compiler/loco/src/IR/TensorAxis.cpp new file mode 100644 index 00000000000..b083847fc00 --- /dev/null +++ b/compiler/loco/src/IR/TensorAxis.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/TensorAxis.h" + +// NOTE This file validates "TensorAxis.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/TensorAxisSet.cpp b/compiler/loco/src/IR/TensorAxisSet.cpp new file mode 100644 index 00000000000..c58237bf752 --- /dev/null +++ b/compiler/loco/src/IR/TensorAxisSet.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/TensorAxisSet.h" + +// NOTE This file validates "TensorAxisSet.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/TensorIndex.cpp b/compiler/loco/src/IR/TensorIndex.cpp new file mode 100644 index 00000000000..cbd3698ebd6 --- /dev/null +++ b/compiler/loco/src/IR/TensorIndex.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/TensorIndex.h" + +// NOTE This file validates "TensorIndex.h". Please DO NOT remove this file. diff --git a/compiler/loco/src/IR/TensorShape.cpp b/compiler/loco/src/IR/TensorShape.cpp new file mode 100644 index 00000000000..ad30dcbc0ab --- /dev/null +++ b/compiler/loco/src/IR/TensorShape.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/TensorShape.h" + +#include + +namespace loco +{ + +uint32_t element_count(const loco::TensorShape *tensor_shape) +{ + uint32_t res = 1; + + for (uint32_t axis = 0; axis < tensor_shape->rank(); ++axis) + { + // Let's use "assert" here as "caller" is responsible for this check. + // Please refer to the header for details. + assert(tensor_shape->dim(axis).known()); + res *= tensor_shape->dim(axis).value(); + } + + return res; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/TensorShape.test.cpp b/compiler/loco/src/IR/TensorShape.test.cpp new file mode 100644 index 00000000000..448d2aa9db3 --- /dev/null +++ b/compiler/loco/src/IR/TensorShape.test.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/TensorShape.h" + +#include + +TEST(TensorShapeTest, default_constructor) +{ + loco::TensorShape tensor_shape; + + ASSERT_EQ(tensor_shape.rank(), 0); +} + +TEST(TensorShapeTest, rank) +{ + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + + ASSERT_EQ(tensor_shape.rank(), 2); + ASSERT_FALSE(tensor_shape.dim(0).known()); + ASSERT_FALSE(tensor_shape.dim(1).known()); +} + +TEST(TensorShapeTest, dim) +{ + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + + tensor_shape.dim(0) = 3; + + ASSERT_TRUE(tensor_shape.dim(0).known()); + ASSERT_FALSE(tensor_shape.dim(1).known()); + + ASSERT_EQ(tensor_shape.dim(0), 3); +} + +TEST(TensorShapeTest, rank_update) +{ + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + + tensor_shape.dim(1) = 3; + + tensor_shape.rank(4); + + ASSERT_FALSE(tensor_shape.dim(0).known()); + ASSERT_TRUE(tensor_shape.dim(1).known()); + ASSERT_FALSE(tensor_shape.dim(2).known()); + ASSERT_FALSE(tensor_shape.dim(3).known()); + + ASSERT_EQ(tensor_shape.dim(1), 3); +} + +TEST(TensorShapeTest, copy) +{ + loco::TensorShape src; + + src.rank(2); + src.dim(1) = 3; + + loco::TensorShape dst; + + dst = src; + + ASSERT_EQ(dst.rank(), 2); + + ASSERT_FALSE(dst.dim(0).known()); + ASSERT_TRUE(dst.dim(1).known()); + + ASSERT_EQ(dst.dim(1), 3); +} + +TEST(TensorShapeTest, element_count) +{ + // Check Rank-0 case + loco::TensorShape src; + + ASSERT_EQ(loco::element_count(&src), 1); +} diff --git a/compiler/loco/src/IR/Use.cpp b/compiler/loco/src/IR/Use.cpp new file mode 100644 index 00000000000..fed562c65ca --- /dev/null +++ b/compiler/loco/src/IR/Use.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Use.h" +#include "loco/IR/Node.h" + +#include + +namespace loco +{ + +void Use::node(Node *node) +{ + if (_node != nullptr) + { + assert(_node->_uses.find(this) != _node->_uses.end()); + _node->_uses.erase(this); + _node = nullptr; + } + + assert(_node == nullptr); + + if (node != nullptr) + { + _node = node; + _node->_uses.insert(this); + } + + assert(_node == node); +} + +} // namespace loco diff --git a/compiler/loco/src/IR/Use.test.cpp b/compiler/loco/src/IR/Use.test.cpp new file mode 100644 index 00000000000..4a2f1cc25f6 --- /dev/null +++ b/compiler/loco/src/IR/Use.test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Use.h" + +#include "MockupNode.h" + +#include + +TEST(UseTest, constructor) +{ + MockupNode user; + loco::Use use{&user}; + + ASSERT_EQ(use.user(), &user); + ASSERT_EQ(use.node(), nullptr); +} + +TEST(UseTest, link_node) +{ + MockupNode def; + MockupNode user; + loco::Use use{&user}; + + use.node(&def); + + ASSERT_EQ(use.user(), &user); + ASSERT_EQ(use.node(), &def); +} diff --git a/compiler/loco/src/IR/Verifier.cpp b/compiler/loco/src/IR/Verifier.cpp new file mode 100644 index 00000000000..42735a327da --- /dev/null +++ b/compiler/loco/src/IR/Verifier.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Verifier.h" + +#include +#include + +namespace +{ + +using namespace loco; + +struct GraphVerifier final +{ +public: + GraphVerifier(loco::Graph *graph) : _graph{graph} + { + // graph SHOULD NOT BE null + assert(_graph != nullptr); + } + +public: + // ErrorListener SHOULD outlive GraphVerifier + GraphVerifier &enroll(ErrorListener *l) + { + if (l != nullptr) + { + _listeners.insert(l); + } + return (*this); + } + + GraphVerifier &enroll(std::unique_ptr &&l) + { + if (l != nullptr) + { + _listeners.insert(l.get()); + // Take the ownership of a given listener + _owned_listeners.insert(std::move(l)); + } + return (*this); + } + +public: + void run(void) const + { + for (auto node : loco::all_nodes(_graph)) + { + // Verify nodes + for (uint32_t n = 0; n < node->arity(); ++n) + { + if (node->arg(n) == nullptr) + { + notify(ErrorDetail{node, n}); + } + } + } + } + +private: + template void notify(const Error &error) const + { + for (const auto &listener : _listeners) + { + listener->notify(error); + } + } + +private: + loco::Graph *_graph = nullptr; + + // All active error listeners + std::set _listeners; + + // Owned error listeners + std::set> _owned_listeners; +}; + +inline GraphVerifier graph_verifier(loco::Graph *graph) { return GraphVerifier{graph}; } + +} // namespace + +namespace loco +{ + +bool valid(Graph *g, std::unique_ptr &&l) +{ + class ErrorCounter final : public ErrorListener + { + public: + uint32_t count(void) const { return _count; } + + public: + void notify(const ErrorDetail &) { _count += 1; } + + private: + uint32_t _count = 0; + }; + + ErrorCounter counter; + graph_verifier(g).enroll(&counter).enroll(std::move(l)).run(); + return counter.count() == 0; +} + +} // namespace loco diff --git a/compiler/loco/src/IR/Verifier.test.cpp b/compiler/loco/src/IR/Verifier.test.cpp new file mode 100644 index 00000000000..247a5939081 --- /dev/null +++ b/compiler/loco/src/IR/Verifier.test.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Verifier.h" + +#include + +#include +#include + +using stdex::make_unique; + +TEST(VerifierTest, valid_minimal) +{ + auto g = loco::make_graph(); + auto push = g->nodes()->create(); + + ASSERT_FALSE(loco::valid(g.get())); +} + +TEST(VerifierTest, valid_error_reporter) +{ + using namespace loco; + + auto g = loco::make_graph(); + auto push = g->nodes()->create(); + + class Collector final : public loco::ErrorListener + { + public: + Collector(std::vector> *out) : _out{out} + { + // DO NOTHING + } + + public: + void notify(const ErrorDetail &d) override + { + _out->emplace_back(d); + } + + private: + std::vector> *_out; + }; + + std::vector> errors; + ASSERT_FALSE(loco::valid(g.get(), make_unique(&errors))); + ASSERT_EQ(errors.size(), 1); + ASSERT_EQ(errors.at(0).node(), push); + ASSERT_EQ(errors.at(0).index(), 0); +} diff --git a/compiler/loco/src/IR/Window.test.cpp b/compiler/loco/src/IR/Window.test.cpp new file mode 100644 index 00000000000..c112e0f96e6 --- /dev/null +++ b/compiler/loco/src/IR/Window.test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/IR/Window.h" + +#include + +TEST(WindowTest, default_constructor_2D) +{ + loco::Window<2> window; + + ASSERT_EQ(window.vertical(), 1); + ASSERT_EQ(window.horizontal(), 1); +} + +TEST(WindowTest, setter_and_getter_2D) +{ + loco::Window<2> window; + + window.vertical(2); + + ASSERT_EQ(window.vertical(), 2); + ASSERT_EQ(window.horizontal(), 1); + + window.horizontal(3); + + ASSERT_EQ(window.vertical(), 2); + ASSERT_EQ(window.horizontal(), 3); +} diff --git a/compiler/loco/src/Service/CanonicalShapeInferenceRule.cpp b/compiler/loco/src/Service/CanonicalShapeInferenceRule.cpp new file mode 100644 index 00000000000..d30a8279aba --- /dev/null +++ b/compiler/loco/src/Service/CanonicalShapeInferenceRule.cpp @@ -0,0 +1,774 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/CanonicalShapeInferenceRule.h" +#include "loco/Service/ShapeInference.h" + +#include +#include +#include + +#include + +namespace +{ + +struct PlaneShape +{ + loco::Dimension height; + loco::Dimension width; +}; + +PlaneShape make_plane_shape(const loco::FeatureShape &feature_shape) +{ + PlaneShape plane_shape; + + plane_shape.height = feature_shape.height(); + plane_shape.width = feature_shape.width(); + + return plane_shape; +} + +class FeatureShapeUpdater final +{ +public: + FeatureShapeUpdater(loco::FeatureShape *ptr) : _feature_shape_ptr{ptr} + { + // DO NOTHING + } + +public: + void with(const PlaneShape &plane_shape) const + { + _feature_shape_ptr->height() = plane_shape.height; + _feature_shape_ptr->width() = plane_shape.width; + } + +private: + loco::FeatureShape *_feature_shape_ptr; +}; + +/** + * HOW TO USE + * + * loco::FeatureShape feature_shape = ...; + * + * update(feature_shape).with(...) + */ +FeatureShapeUpdater update(loco::FeatureShape &feature_shape) +{ + return FeatureShapeUpdater{&feature_shape}; +} + +loco::Window<2> window_of(const loco::FilterShape &filter_shape) +{ + loco::Window<2> window; + + window.vertical(filter_shape.height().value()); + window.horizontal(filter_shape.width().value()); + + return window; +} + +loco::Window<2> window_of(const loco::DepthwiseFilterShape &depthwise_filter_shape) +{ + loco::Window<2> window; + + window.vertical(depthwise_filter_shape.height().value()); + window.horizontal(depthwise_filter_shape.width().value()); + + return window; +} + +enum class Direction +{ + Forward, + Backward, +}; + +template class PlaneInference; + +template <> class PlaneInference final +{ +public: + PlaneShape operator()(const PlaneShape &in) const + { + assert(_pad != nullptr); + assert(_window != nullptr); + assert(_stride != nullptr); + + uint32_t const raw_input_height = in.height.value(); + uint32_t const raw_input_width = in.width.value(); + + uint32_t const raw_window_height = _window->vertical(); + uint32_t const raw_window_width = _window->horizontal(); + + uint32_t const vertical_padding = _pad->top() + _pad->bottom(); + uint32_t const horizontal_padding = _pad->left() + _pad->right(); + + uint32_t const effective_input_height = raw_input_height + vertical_padding; + uint32_t const effective_input_width = raw_input_width + horizontal_padding; + + // NOTE To support "dilation" later + uint32_t const effective_window_height = raw_window_height; + uint32_t const effective_window_width = raw_window_width; + + uint32_t const vertical_stride = _stride->vertical(); + uint32_t const horizontal_stride = _stride->horizontal(); + + assert((effective_input_height - effective_window_height) % vertical_stride == 0); + assert((effective_input_width - effective_window_width) % horizontal_stride == 0); + + PlaneShape res; + + res.height = (effective_input_height - effective_window_height) / vertical_stride + 1; + res.width = (effective_input_width - effective_window_width) / horizontal_stride + 1; + + return res; + } + +public: + void pad(const loco::Padding2D *value) { _pad = value; } + void window(const loco::Window<2> *value) { _window = value; } + void stride(const loco::Stride<2> *value) { _stride = value; } + +private: + const loco::Padding2D *_pad = nullptr; + const loco::Window<2> *_window = nullptr; + const loco::Stride<2> *_stride = nullptr; +}; + +template <> class PlaneInference final +{ +public: + PlaneShape operator()(const PlaneShape &in) const + { + assert(_pad != nullptr); + assert(_window != nullptr); + assert(_stride != nullptr); + + uint32_t const input_height = in.height.value(); + uint32_t const input_width = in.width.value(); + + uint32_t const vertical_padding = _pad->top() + _pad->bottom(); + uint32_t const horizontal_padding = _pad->left() + _pad->right(); + + uint32_t const raw_window_height = _window->vertical(); + uint32_t const raw_window_width = _window->horizontal(); + + // TODO Support "dilation" + uint32_t const effective_window_height = raw_window_height; + uint32_t const effective_window_width = raw_window_width; + + uint32_t const vertical_stride = _stride->vertical(); + uint32_t const horizontal_stride = _stride->horizontal(); + + PlaneShape res; + + res.height = vertical_stride * (input_height - 1) + effective_window_height - vertical_padding; + res.width = horizontal_stride * (input_width - 1) + effective_window_width - horizontal_padding; + + return res; + } + +public: + void pad(const loco::Padding2D *value) { _pad = value; } + void window(const loco::Window<2> *value) { _window = value; } + void stride(const loco::Stride<2> *value) { _stride = value; } + +private: + const loco::Padding2D *_pad = nullptr; + const loco::Window<2> *_window = nullptr; + const loco::Stride<2> *_stride = nullptr; +}; + +/** + * There are two possible maintenance policies. + * - Introduce a new canonical node first, and then extend this algorithm later + * - Introduce a new canonical node and extend this algorithm at the same time + * + * The current implementation assumes the former one (for historical reason). + * + * TODO Evaluate the impact of the latter one + * + * NOTE "Forward" means that this algorithm computes the ouput shape from inputs shapes + */ +class ForwardShapeInferenceAlgorithm final : public loco::CanonicalNodeVisitor +{ +public: + ForwardShapeInferenceAlgorithm(const loco::ShapeInferenceRule::Context *ctx) : _ctx{ctx} + { + // DO NOTHING + } + +private: + const loco::ShapeInferenceRule::Context *_ctx; + +private: + bool shape_known(const loco::Node *node) const { return _ctx->known(node); } + loco::NodeShape node_shape(const loco::Node *node) const { return _ctx->get(node); } + +private: + loco::NodeShape eltwise_binary_node_shape(const loco::Node *node) + { + // This helper works only for binary node. + assert(node->arity() == 2); + + auto lhs_shape = node_shape(node->arg(0)); + auto rhs_shape = node_shape(node->arg(1)); + + // ASSERT: lhs_shape == rhs_shape + + return lhs_shape; + } + +public: + // CASE: AvgPool2D + loco::NodeShape visit(const loco::AvgPool2D *node) final + { + PlaneInference infer_plane_shape; + + infer_plane_shape.pad(node->pad()); + infer_plane_shape.window(node->window()); + infer_plane_shape.stride(node->stride()); + + auto input_feature_shape = node_shape(node->ifm()).as(); + auto input_plane_shape = make_plane_shape(input_feature_shape); + auto output_plane_shape = infer_plane_shape(input_plane_shape); + auto output_feature_shape = input_feature_shape; // AvgPool2D does not change count/depth + + // Update the height/width of output_feature_shape with that of output_plane_shape + update(output_feature_shape).with(output_plane_shape); + + return loco::NodeShape{output_feature_shape}; + } + + // CASE: BiasDecode + loco::NodeShape visit(const loco::BiasDecode *node) final + { + // The input of BiasDecode SHOULD BE a bias! + assert(node_shape(node->input()).domain() == loco::Domain::Bias); + auto input_bias_shape = node_shape(node->input()).as(); + + loco::TensorShape output_tensor_shape; + + output_tensor_shape.rank(1); + output_tensor_shape.dim(0) = input_bias_shape.length(); + + return loco::NodeShape{output_tensor_shape}; + } + + // CASE: BiasEncode + loco::NodeShape visit(const loco::BiasEncode *node) final + { + // The input of BiasEncode SHOULD BE a tensor! + assert(node_shape(node->input()).domain() == loco::Domain::Tensor); + auto input_tensor_shape = node_shape(node->input()).as(); + + loco::BiasShape output_bias_shape; + + output_bias_shape.length() = input_tensor_shape.dim(0); + + return loco::NodeShape{output_bias_shape}; + } + + // CASE: ConstGen + loco::NodeShape visit(const loco::ConstGen *node) final + { + loco::TensorShape tensor_shape; + + tensor_shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + tensor_shape.dim(axis) = node->dim(axis); + } + + return loco::NodeShape{tensor_shape}; + } + + // CASE: Conv2D + loco::NodeShape visit(const loco::Conv2D *node) final + { + auto filter_shape = node_shape(node->ker()).as(); + auto filter_window = window_of(filter_shape); + + PlaneInference infer_plane_shape; + + infer_plane_shape.pad(node->pad()); + infer_plane_shape.window(&filter_window); + infer_plane_shape.stride(node->stride()); + + auto input_feature_shape = node_shape(node->ifm()).as(); + auto input_plane_shape = make_plane_shape(input_feature_shape); + auto output_plane_shape = infer_plane_shape(input_plane_shape); + + loco::FeatureShape output_feature_shape; + + // "COUNT" does not change + output_feature_shape.count() = input_feature_shape.count(); + // "DEPTH" depends on # of filters + output_feature_shape.depth() = filter_shape.count(); + // Update the height/width of output_feature_shape with that of output_plane_shape + update(output_feature_shape).with(output_plane_shape); + + return loco::NodeShape{output_feature_shape}; + } + + // CASE: DepthwiseConv2D + loco::NodeShape visit(const loco::DepthwiseConv2D *node) final + { + auto depthwise_filter_shape = node_shape(node->ker()).as(); + auto dpethwise_filter_window = window_of(depthwise_filter_shape); + + PlaneInference infer_plane_shape; + + infer_plane_shape.pad(node->pad()); + infer_plane_shape.window(&dpethwise_filter_window); + infer_plane_shape.stride(node->stride()); + + auto input_feature_shape = node_shape(node->ifm()).as(); + auto input_plane_shape = make_plane_shape(input_feature_shape); + auto output_plane_shape = infer_plane_shape(input_plane_shape); + + loco::FeatureShape output_feature_shape; + + // "COUNT" does not change + output_feature_shape.count() = input_feature_shape.count(); + // "DEPTH" depends on [in_channels * channel_multiplier] of filters + output_feature_shape.depth() = loco::Dimension(depthwise_filter_shape.depth().value() * + depthwise_filter_shape.multiplier().value()); + // Update the height/width of output_feature_shape with that of output_plane_shape + update(output_feature_shape).with(output_plane_shape); + + return loco::NodeShape{output_feature_shape}; + } + + // CASE: DepthwiseFilterEncode + loco::NodeShape visit(const loco::DepthwiseFilterEncode *node) final + { + auto input_tensor_shape = node_shape(node->input()).as(); + return loco::NodeShape{node->encoder()->shape(input_tensor_shape)}; + } + + // CASE: DepthwiseFilterDecode + loco::NodeShape visit(const loco::DepthwiseFilterDecode *node) final + { + auto input_dw_filter_shape = node_shape(node->input()).as(); + return loco::NodeShape{node->decoder()->shape(input_dw_filter_shape)}; + } + + // CASE: EltwiseAdd + loco::NodeShape visit(const loco::EltwiseAdd *node) final + { + return eltwise_binary_node_shape(node); + } + + // CASE: EltwiseDiv + loco::NodeShape visit(const loco::EltwiseDiv *node) final + { + return eltwise_binary_node_shape(node); + } + + // CASE: EltwiseMax + loco::NodeShape visit(const loco::EltwiseMax *node) final + { + return eltwise_binary_node_shape(node); + } + + // CASE: EltwiseMul + loco::NodeShape visit(const loco::EltwiseMul *node) final + { + return eltwise_binary_node_shape(node); + } + + // CASE: EltwiseSqrt + loco::NodeShape visit(const loco::EltwiseSqrt *node) final { return node_shape(node->input()); } + + // CASE: EltwiseSub + loco::NodeShape visit(const loco::EltwiseSub *node) final + { + return eltwise_binary_node_shape(node); + } + + // CASE: Forward + loco::NodeShape visit(const loco::Forward *node) final { return node_shape(node->input()); } + + // CASE: FeatureBiasAdd + loco::NodeShape visit(const loco::FeatureBiasAdd *node) final + { + assert(node_shape(node->value()).domain() == loco::Domain::Feature); + assert(node_shape(node->bias()).domain() == loco::Domain::Bias); + + // Q. What to do when there is a mismatch between value's depth and bias's length? + + return node_shape(node->value()); + } + + // CASE: FeatureDecode + loco::NodeShape visit(const loco::FeatureDecode *node) final + { + auto input_node_shape = node_shape(node->input()); + return loco::NodeShape{node->decoder()->shape(input_node_shape.as())}; + } + + // CASE: FeatureEncode + loco::NodeShape visit(const loco::FeatureEncode *node) final + { + auto input_node_shape = node_shape(node->input()); + return loco::NodeShape{node->encoder()->shape(input_node_shape.as())}; + } + + // CASE: FilterDecode + loco::NodeShape visit(const loco::FilterDecode *node) final + { + auto input_filter_shape = node_shape(node->input()).as(); + return loco::NodeShape{node->decoder()->shape(input_filter_shape)}; + } + + // CASE: FilterEncode + loco::NodeShape visit(const loco::FilterEncode *node) final + { + auto input_tensor_shape = node_shape(node->input()).as(); + return loco::NodeShape{node->encoder()->shape(input_tensor_shape)}; + } + + // CASE: FixedReshape + loco::NodeShape visit(const loco::FixedReshape *node) final + { + loco::TensorShape tensor_shape; + + tensor_shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + tensor_shape.dim(axis) = node->dim(axis); + } + + return loco::NodeShape{tensor_shape}; + } + + // CASE: MatMul + loco::NodeShape visit(const loco::MatMul *node) final + { + assert(shape_known(node->lhs())); + assert(shape_known(node->rhs())); + auto const lhs_shape = node_shape(node->lhs()).as(); + auto const rhs_shape = node_shape(node->rhs()).as(); + + loco::MatrixShape out_shape; + + // Checking shape capability for multiplication + assert(lhs_shape.width() == rhs_shape.height()); + + out_shape.height() = lhs_shape.height(); + out_shape.width() = rhs_shape.width(); + + return out_shape; + } + + // CASE: MatrixDecode + loco::NodeShape visit(const loco::MatrixDecode *node) final + { + auto input_node_shape = node_shape(node->input()); + return loco::NodeShape{node->decoder()->shape(input_node_shape.as())}; + } + + // CASE: MatrixEncode + loco::NodeShape visit(const loco::MatrixEncode *node) final + { + auto input_node_shape = node_shape(node->input()); + return loco::NodeShape{node->encoder()->shape(input_node_shape.as())}; + } + + // CASE: MaxPool2D + loco::NodeShape visit(const loco::MaxPool2D *node) final + { + PlaneInference infer_plane_shape; + + infer_plane_shape.pad(node->pad()); + infer_plane_shape.window(node->window()); + infer_plane_shape.stride(node->stride()); + + auto input_feature_shape = node_shape(node->ifm()).as(); + auto input_plane_shape = make_plane_shape(input_feature_shape); + auto output_plane_shape = infer_plane_shape(input_plane_shape); + auto output_feature_shape = input_feature_shape; // MaxPool2D does not change count/depth + + // Update the height/width of output_feature_shape with that of output_plane_shape + update(output_feature_shape).with(output_plane_shape); + + return loco::NodeShape{output_feature_shape}; + } + + // CASE: Push + loco::NodeShape visit(const loco::Push *node) final + { + assert(shape_known(node->from())); + return node_shape(node->from()); + } + + // CASE: Pull + loco::NodeShape visit(const loco::Pull *node) final + { + // Build a tensor shape from "Pull" node + loco::TensorShape tensor_shape; + + tensor_shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + tensor_shape.dim(axis) = node->dim(axis); + } + + return loco::NodeShape{tensor_shape}; + } + + // CASE: ReLU + loco::NodeShape visit(const loco::ReLU *node) final { return node_shape(node->input()); } + + // CASE: ReLU6 + loco::NodeShape visit(const loco::ReLU6 *node) final { return node_shape(node->input()); } + + // CASE: Tanh + loco::NodeShape visit(const loco::Tanh *node) final { return node_shape(node->input()); } + + // CASE: TensorBiasAdd + loco::NodeShape visit(const loco::TensorBiasAdd *node) final + { + assert(node_shape(node->value()).domain() == loco::Domain::Tensor); + assert(node_shape(node->bias()).domain() == loco::Domain::Bias); + + // Q. What to do when there is a mismatch between value's dim and bias's length? + + return node_shape(node->value()); + } + + // CASE: TensorConcat + loco::NodeShape visit(const loco::TensorConcat *node) + { + auto const lhs_shape = node_shape(node->lhs()).as(); + auto const rhs_shape = node_shape(node->rhs()).as(); + + assert(lhs_shape.rank() == rhs_shape.rank()); + uint32_t const out_rank = lhs_shape.rank(); + + loco::TensorShape out_shape; + + out_shape.rank(out_rank); + + for (uint32_t axis = 0; axis < out_rank; ++axis) + { + if (axis == node->axis()) + { + out_shape.dim(axis) = lhs_shape.dim(axis).value() + rhs_shape.dim(axis).value(); + } + else + { + assert(lhs_shape.dim(axis) == rhs_shape.dim(axis)); + out_shape.dim(axis) = lhs_shape.dim(axis); + } + } + + return loco::NodeShape{out_shape}; + } + + // CASE: TensorBroadcast + loco::NodeShape visit(const loco::TensorBroadcast *node) final + { + auto tensor_shape = node_shape(node->input()).as(); + auto const tensor_rank = tensor_shape.rank(); + + for (uint32_t axis = 0; axis < tensor_rank; ++axis) + { + if (node->mapping()->defined(axis)) + { + tensor_shape.dim(axis) = node->mapping()->dim(axis); + } + } + + return loco::NodeShape{tensor_shape}; + } + + // CASE: TensorReduce + loco::NodeShape visit(const loco::TensorReduce *node) final + { + auto tensor_shape = node_shape(node->input()).as(); + auto const tensor_rank = tensor_shape.rank(); + + for (uint32_t d = 0; d < tensor_rank; ++d) + if (node->axes()->defined(d)) + tensor_shape.dim(d) = 1; + + return loco::NodeShape{tensor_shape}; + } + + // CASE: TensorSoftmax + loco::NodeShape visit(const loco::TensorSoftmax *node) final { return node_shape(node->input()); } + + // CASE: TensorTranspose + loco::NodeShape visit(const loco::TensorTranspose *node) final + { + loco::TensorShape output_shape; + + auto input_shape = node_shape(node->input()).as(); + assert(input_shape.rank() == node->perm()->size()); + + output_shape.rank(input_shape.rank()); + + for (uint32_t output_axis = 0; output_axis < output_shape.rank(); output_axis++) + { + auto new_dim = input_shape.dim(node->perm()->axis(output_axis)); + output_shape.dim(output_axis) = new_dim; + } + + return loco::NodeShape(output_shape); + } + + // CASE: TransposedConv2D + loco::NodeShape visit(const loco::TransposedConv2D *node) final + { + auto filter_shape = node_shape(node->ker()).as(); + auto filter_window = window_of(filter_shape); + + PlaneInference infer_plane_shape; + + infer_plane_shape.pad(node->pad()); + infer_plane_shape.window(&filter_window); + infer_plane_shape.stride(node->stride()); + + auto input_feature_shape = node_shape(node->ifm()).as(); + auto input_plane_shape = make_plane_shape(input_feature_shape); + auto output_plane_shape = infer_plane_shape(input_plane_shape); + + loco::FeatureShape output_feature_shape; + + // "COUNT" does not change + output_feature_shape.count() = input_feature_shape.count(); + // Output "DEPTH" depends on count of filters + output_feature_shape.depth() = filter_shape.count(); + // Update the height/width of output_feature_shape with that of output_plane_shape + update(output_feature_shape).with(output_plane_shape); + + return loco::NodeShape{output_feature_shape}; + } + + // CASE: TensorConstantPad + loco::NodeShape visit(const loco::TensorConstantPad *node) final + { + auto const tensor_shape = loco::shape_get(node->input()).as(); + auto padding = node->padding(); + + loco::TensorShape out_shape; + out_shape.rank(tensor_shape.rank()); + for (uint32_t axis = 0; axis < out_shape.rank(); ++axis) + { + out_shape.dim(axis) = + tensor_shape.dim(axis).value() + padding->front(axis) + padding->back(axis); + } + + return loco::NodeShape{out_shape}; + } +}; + +} // namespace + +namespace +{ +namespace compat +{ + +struct Context final : public loco::ShapeInferenceRule::Context +{ + bool known(const loco::Node *node) const final { return loco::shape_known(node); } + loco::NodeShape get(const loco::Node *node) const final { return loco::shape_get(node); } +}; + +class Sink final : public loco::ShapeInferenceRule::Sink +{ +public: + enum Status + { + Unknown, + Okay, + Fail, + }; + +public: + const Status &status(void) const { return _status; } + const loco::NodeShape &shape(void) const { return _shape; } + +public: + void okay(const loco::NodeShape &shape) final + { + _status = Okay; + _shape = shape; + } + + void fail(void) final + { + // Notify failure + _status = Fail; + } + +private: + Status _status = Unknown; + loco::NodeShape _shape; +}; + +} // namespace compat +} // namespace + +namespace loco +{ + +bool CanonicalShapeInferenceRule::support(const API &api) const +{ + return api == API::V1 or api == API::V2; +} + +bool CanonicalShapeInferenceRule::recognize(const Dialect *d) const +{ + return CanonicalDialect::get() == d; +} + +bool CanonicalShapeInferenceRule::infer(const Node *node, NodeShape &shape) const +{ + ::compat::Context ctx; + ::compat::Sink sink; + + infer(&ctx, node, &sink); + + assert(sink.status() == ::compat::Sink::Okay or sink.status() == ::compat::Sink::Fail); + + if (sink.status() == ::compat::Sink::Fail) + { + return false; + } + + shape = sink.shape(); + return true; +} + +void CanonicalShapeInferenceRule::infer(const Context *ctx, const Node *node, Sink *sink) const +{ + assert(node->dialect() == loco::CanonicalDialect::get()); + assert(dynamic_cast(node) != nullptr); + + ForwardShapeInferenceAlgorithm alg{ctx}; + auto shape = dynamic_cast(node)->accept(&alg); + + sink->okay(shape); +} + +} // namespace loco diff --git a/compiler/loco/src/Service/CanonicalShapeInferenceRule.test.cpp b/compiler/loco/src/Service/CanonicalShapeInferenceRule.test.cpp new file mode 100644 index 00000000000..5cc8c380863 --- /dev/null +++ b/compiler/loco/src/Service/CanonicalShapeInferenceRule.test.cpp @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/CanonicalShapeInferenceRule.h" +#include "loco/Service/ShapeInference.h" + +#include "GraphTestcase.h" + +#include + +#include + +TEST(CanonicalShapeInferenceRuleTest, minimal) +{ + // Create a simple identity network, which takes Tensor<1x2x3x4> as input. + GraphTestcase testcase{1, 2, 3, 4}; + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.push_node)); + ASSERT_EQ(loco::shape_get(testcase.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().rank(), 4); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(0), 1); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(1), 2); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(2), 3); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(3), 4); +} + +TEST(CanonicalShapeInferenceRuleTest, const_gen) +{ + // Create a sample network + GraphTestcase testcase; + + testcase.const_node->dtype(loco::DataType::FLOAT32); + testcase.const_node->shape({1, 2}); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.push_node)); + ASSERT_EQ(loco::shape_get(testcase.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().rank(), 2); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(0), 1); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(1), 2); +} + +TEST(CanonicalShapeInferenceRuleTest, relu) +{ + // Create a sample network + GraphTestcase testcase; + + testcase.pull_node->shape({1, 2, 3, 4}); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.push_node)); + ASSERT_EQ(loco::shape_get(testcase.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().rank(), 4); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(0), 1); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(1), 2); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(2), 3); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(3), 4); +} + +TEST(CanonicalShapeInferenceRuleTest, feature_codec) +{ + // Create a sample network + GraphTestcase testcase; + + testcase.pull_node->shape({1, 2, 3, 4}); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.encode_node)); + ASSERT_EQ(loco::shape_get(testcase.encode_node).domain(), loco::Domain::Feature); + + ASSERT_TRUE(loco::shape_known(testcase.decode_node)); + ASSERT_EQ(loco::shape_get(testcase.decode_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.decode_node).as().rank(), 4); + ASSERT_EQ(loco::shape_get(testcase.decode_node).as().dim(0), 1); + ASSERT_EQ(loco::shape_get(testcase.decode_node).as().dim(1), 2); + ASSERT_EQ(loco::shape_get(testcase.decode_node).as().dim(2), 3); + ASSERT_EQ(loco::shape_get(testcase.decode_node).as().dim(3), 4); +} + +TEST(CanonicalShapeInferenceRuleTest, avgpool2d) +{ + using namespace loco; + + // Create a sample network + GraphTestcase testcase; + + auto perm = make_NHWC_perm(); + + testcase.pull_node->shape({1, 8, 4, 3}); + + testcase.encode_node->encoder(stdex::make_unique>(perm)); + + testcase.avgpool2d_node->window()->vertical(2); + testcase.avgpool2d_node->window()->horizontal(2); + + testcase.avgpool2d_node->stride()->vertical(2); + testcase.avgpool2d_node->stride()->horizontal(2); + + testcase.decode_node->decoder(stdex::make_unique>(perm)); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + // + // NOTE AvgPool2D testcase assumes NHWC layout + ASSERT_TRUE(loco::shape_known(testcase.avgpool2d_node)); + ASSERT_EQ(loco::shape_get(testcase.avgpool2d_node).domain(), loco::Domain::Feature); + ASSERT_EQ(loco::shape_get(testcase.avgpool2d_node).as().count(), 1); + ASSERT_EQ(loco::shape_get(testcase.avgpool2d_node).as().depth(), 3); + ASSERT_EQ(loco::shape_get(testcase.avgpool2d_node).as().height(), 4); + ASSERT_EQ(loco::shape_get(testcase.avgpool2d_node).as().width(), 2); +} + +TEST(CanonicalShapeInferenceRuleTest, depthwiseconv2d) +{ + using namespace loco; + + // Create a sample network + GraphTestcase testcase; + + testcase.pull_node->shape({1, 4, 4, 3}); + + testcase.const_node->dtype(loco::DataType::FLOAT32); + testcase.const_node->shape({2, 2, 3, 2}); + + testcase.depthwiseconv2d_node->stride()->vertical(1); + testcase.depthwiseconv2d_node->stride()->horizontal(1); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + // + // NOTE DepthwiseConv2D testcase assumes NHWC layout + ASSERT_TRUE(loco::shape_known(testcase.depthwiseconv2d_node)); + ASSERT_EQ(loco::shape_get(testcase.depthwiseconv2d_node).domain(), loco::Domain::Feature); + ASSERT_EQ(loco::shape_get(testcase.depthwiseconv2d_node).as().count(), 1); + ASSERT_EQ(loco::shape_get(testcase.depthwiseconv2d_node).as().depth(), 6); + ASSERT_EQ(loco::shape_get(testcase.depthwiseconv2d_node).as().height(), 3); + ASSERT_EQ(loco::shape_get(testcase.depthwiseconv2d_node).as().width(), 3); +} + +TEST(CanonicalShapeInferenceRuleTest, transposedconv2d) +{ + using namespace loco; + + // Create a sample network + GraphTestcase testcase; + + testcase.pull_node->shape({1, 270, 480, 24}); // NHWC + + testcase.const_node->dtype(loco::DataType::FLOAT32); + testcase.const_node->shape({3, 3, 24, 12}); // HWCN (or HWIO) + + testcase.tr_conv2d_node->stride()->vertical(2); + testcase.tr_conv2d_node->stride()->horizontal(2); + + testcase.tr_conv2d_node->pad()->top(0); + testcase.tr_conv2d_node->pad()->bottom(1); + testcase.tr_conv2d_node->pad()->left(0); + testcase.tr_conv2d_node->pad()->right(1); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.tr_conv2d_node)); + ASSERT_EQ(loco::shape_get(testcase.tr_conv2d_node).domain(), loco::Domain::Feature); + ASSERT_EQ(loco::shape_get(testcase.tr_conv2d_node).as().count(), 1); + ASSERT_EQ(loco::shape_get(testcase.tr_conv2d_node).as().height(), 540); + ASSERT_EQ(loco::shape_get(testcase.tr_conv2d_node).as().width(), 960); + ASSERT_EQ(loco::shape_get(testcase.tr_conv2d_node).as().depth(), 12); +} + +TEST(CanonicalShapeInferenceRuleTest, maxpool2d) +{ + using namespace loco; + + // Create a sample network + GraphTestcase testcase; + + auto perm = make_NHWC_perm(); + + testcase.pull_node->shape({1, 8, 4, 3}); + + testcase.encode_node->encoder(stdex::make_unique>(perm)); + + testcase.maxpool2d_node->window()->vertical(2); + testcase.maxpool2d_node->window()->horizontal(2); + + testcase.maxpool2d_node->stride()->vertical(2); + testcase.maxpool2d_node->stride()->horizontal(2); + + testcase.decode_node->decoder(stdex::make_unique>(perm)); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + // + // NOTE MaxPool2D testcase assumes NHWC layout + ASSERT_TRUE(loco::shape_known(testcase.maxpool2d_node)); + ASSERT_EQ(loco::shape_get(testcase.maxpool2d_node).domain(), loco::Domain::Feature); + ASSERT_EQ(loco::shape_get(testcase.maxpool2d_node).as().count(), 1); + ASSERT_EQ(loco::shape_get(testcase.maxpool2d_node).as().depth(), 3); + ASSERT_EQ(loco::shape_get(testcase.maxpool2d_node).as().height(), 4); + ASSERT_EQ(loco::shape_get(testcase.maxpool2d_node).as().width(), 2); +} + +TEST(CanonicalShapeInferenceRuleTest, tensor_concat) +{ + using namespace loco; + + // Create a sample network + GraphTestcase testcase; + + testcase.lhs_node->shape({1, 2, 3}); + testcase.rhs_node->shape({1, 4, 3}); + testcase.concat_node->axis(1); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.concat_node)); + ASSERT_EQ(loco::shape_get(testcase.concat_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.concat_node).as().rank(), 3); + ASSERT_EQ(loco::shape_get(testcase.concat_node).as().dim(0), 1); + ASSERT_EQ(loco::shape_get(testcase.concat_node).as().dim(1), 6); + ASSERT_EQ(loco::shape_get(testcase.concat_node).as().dim(2), 3); +} + +TEST(CanonicalShapeInferenceRuleTest, fixed_reshape) +{ + // Create a sample network + GraphTestcase testcase; + + testcase.pull_node->shape({6, 6}); + testcase.reshape_node->shape({4, 9}); + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.push_node)); + ASSERT_EQ(loco::shape_get(testcase.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().rank(), 2); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(0), 4); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(1), 9); +} + +TEST(CanonicalShapeInferenceRuleTest, tensor_broadcast) +{ + // Create a sample network + GraphTestcase testcase{1, 2}; + + testcase.broadcast_node->mapping()->dim(0) = 4; + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(testcase.push_node)); + ASSERT_EQ(loco::shape_get(testcase.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().rank(), 2); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(0), 4); + ASSERT_EQ(loco::shape_get(testcase.push_node).as().dim(1), 2); +} + +TEST(CanonicalShapeInferenceRuleTest, tensor_transpose) +{ + // Create a sample network + GraphTestcase tc; + + tc.pull_node->shape({10, 20, 30, 40}); + + tc.transpose_node->perm()->size(4); + tc.transpose_node->perm()->axis(0) = 2; + tc.transpose_node->perm()->axis(1) = 3; + tc.transpose_node->perm()->axis(2) = 0; + tc.transpose_node->perm()->axis(3) = 1; + + // Run Inference + loco::CanonicalShapeInferenceRule rule; + + loco::apply(&rule).to(tc.graph()); + + // Verify! + ASSERT_TRUE(loco::shape_known(tc.push_node)); + ASSERT_EQ(loco::shape_get(tc.push_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(tc.push_node).as().rank(), 4); + ASSERT_EQ(loco::shape_get(tc.push_node).as().dim(0), 30); + ASSERT_EQ(loco::shape_get(tc.push_node).as().dim(1), 40); + ASSERT_EQ(loco::shape_get(tc.push_node).as().dim(2), 10); + ASSERT_EQ(loco::shape_get(tc.push_node).as().dim(3), 20); +} + +namespace +{ + +struct MockContext final : public loco::ShapeInferenceRule::Context +{ + bool known(const loco::Node *node) const final { return _content.find(node) != _content.end(); } + loco::NodeShape get(const loco::Node *node) const final { return _content.at(node); } + + std::map _content; +}; + +struct MockSink final : public loco::ShapeInferenceRule::Sink +{ + void okay(const loco::NodeShape &res) final { shape = res; } + void fail(void) final { return; } + + loco::NodeShape shape; +}; + +} // namespace + +TEST(CanonicalShapeInferenceRuleTest, infer_v2) +{ + auto g = loco::make_graph(); + + // Create an incomplete graph + auto relu_1 = g->nodes()->create(); + auto relu_2 = g->nodes()->create(); + + relu_2->input(relu_1); + + // Set up Context + MockContext ctx; + + loco::TensorShape tensor_shape; + + tensor_shape.rank(2); + tensor_shape.dim(0) = 4; + tensor_shape.dim(1) = 5; + + ctx._content[relu_1] = tensor_shape; + + // Create a Sink + MockSink sink; + + loco::CanonicalShapeInferenceRule rule; + + rule.infer(&ctx, relu_2, &sink); + + ASSERT_EQ(sink.shape.domain(), loco::Domain::Tensor); + ASSERT_EQ(sink.shape.as().rank(), 2); + ASSERT_EQ(sink.shape.as().dim(0), 4); + ASSERT_EQ(sink.shape.as().dim(1), 5); +} diff --git a/compiler/loco/src/Service/GraphBuilder.h b/compiler/loco/src/Service/GraphBuilder.h new file mode 100644 index 00000000000..71084673c00 --- /dev/null +++ b/compiler/loco/src/Service/GraphBuilder.h @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BUILDER_H__ +#define __GRAPH_BUILDER_H__ + +// loco-internal headers +#include "loco/IR/Graph.h" + +// repo-internal headers +#include + +// C++ standard headers +#include + +// +// This file includes a stack-based loco graph builder +// +// HOW TO USE +// +// loco::Graph *g = ... +// auto builder = make_graph_builder(g); +// +// builder->push(...); +// + +class GraphBuilder final +{ +public: + class Stack final + { + public: + Stack() = default; + + public: + loco::Node *top(void) const { return _content.top(); } + + public: + loco::Node *pop(void) + { + auto ret = top(); + _content.pop(); + return ret; + } + + public: + void push(loco::Node *node) { _content.push(node); } + + private: + std::stack _content; + }; + + class Context final + { + public: + Context(loco::Graph *graph) : _graph{graph} + { + // DO NOTHING + } + + public: + loco::Graph *graph(void) { return _graph; } + Stack *stack(void) { return &_stack; } + + private: + loco::Graph *_graph = nullptr; + Stack _stack; + }; + +public: + GraphBuilder(loco::Graph *graph) : _context{graph} + { + // DO NOTHING + } + +public: + // "Layer" is in theory a subgraph builder. + template + auto push(Args &&... args) + -> decltype(static_cast(nullptr)->operator()(static_cast(nullptr))) + { + Layer layer{std::forward(args)...}; + return layer(ctx()); + } + +public: + loco::Node *pop(void) { return ctx()->stack()->pop(); } + +private: + Context *ctx(void) { return &_context; } + +private: + Context _context; +}; + +static inline std::unique_ptr make_graph_builder(loco::Graph *g) +{ + return stdex::make_unique(g); +} + +// "InputLayer" creates both GraphInput and Pull node at once +struct InputLayer final +{ + class Return + { + public: + Return(loco::GraphInput *input, loco::Pull *node) : _input{input}, _node{node} + { + // DO NOTHING + } + + public: + loco::Pull *node(void) { return _node; } + + public: + Return *name(const std::string &value) + { + _input->name(value); + return this; + } + + public: + Return *shape(std::initializer_list dims) + { + // TODO Uncomment this line when GraphInput is ready + // _graph_input->shape(dims) + _node->shape(dims); + return this; + } + + private: + loco::GraphInput *_input = nullptr; + loco::Pull *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto input_index = ctx->graph()->inputs()->size(); + auto graph_input = ctx->graph()->inputs()->create(); + + auto pull_node = ctx->graph()->nodes()->create(); + + pull_node->index(input_index); + + loco::link(graph_input, pull_node); + + ctx->stack()->push(pull_node); + + return stdex::make_unique(graph_input, pull_node); + } +}; + +// "OutputLayer" creates both GraphOutput and Push node at once. +struct OutputLayer final +{ + class Return + { + public: + Return(loco::GraphOutput *output, loco::Push *node) : _output{output}, _node{node} + { + // DO NOTHING + } + + public: + loco::Push *node(void) { return _node; } + + public: + Return *name(const std::string &value) + { + // TODO Uncomment this line when GraphOutput is ready + // _graph_output->shape(dims) + _output->name(value); + return this; + } + + private: + loco::GraphOutput *_output = nullptr; + loco::Push *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto output_index = ctx->graph()->outputs()->size(); + auto graph_output = ctx->graph()->outputs()->create(); + + auto push_node = ctx->graph()->nodes()->create(); + + push_node->from(ctx->stack()->pop()); + push_node->index(output_index); + + loco::link(graph_output, push_node); + + ctx->stack()->push(push_node); + + return stdex::make_unique(graph_output, push_node); + } +}; + +struct ReLULayer final +{ + // This "Return" is unnecessary for ReLU as ReLU has no attributes), but + // introduced for consistency. + class Return + { + public: + Return(loco::ReLU *node) : _node{node} + { + // DO NOTHING + } + + public: + loco::ReLU *node(void) { return _node; } + + private: + loco::ReLU *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto relu_node = ctx->graph()->nodes()->create(); + + relu_node->input(ctx->stack()->pop()); + + ctx->stack()->push(relu_node); + + return stdex::make_unique(relu_node); + } +}; + +struct ConstGenLayer final +{ + class Return + { + public: + Return(loco::ConstGen *node) : _node{node} + { + // DO NOTHING + } + + public: + loco::ConstGen *node(void) { return _node; } + + private: + loco::ConstGen *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto const_node = ctx->graph()->nodes()->create(); + + ctx->stack()->push(const_node); + + return stdex::make_unique(const_node); + } +}; + +#include "loco/IR/PermutingCodec.h" + +struct FeatureEncodeLayer final +{ + class Return + { + public: + Return(loco::FeatureEncode *node) : _node{node} + { + // DO NOTHING + } + + public: + Return *perm(const loco::Permutation &perm) + { + using namespace loco; + _node->encoder(stdex::make_unique>(perm)); + return this; + } + + public: + loco::FeatureEncode *node(void) { return _node; } + + private: + loco::FeatureEncode *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto encode_node = ctx->graph()->nodes()->create(); + + encode_node->input(ctx->stack()->pop()); + + ctx->stack()->push(encode_node); + + return stdex::make_unique(encode_node); + } +}; + +struct FeatureDecodeLayer final +{ + class Return + { + public: + Return(loco::FeatureDecode *node) : _node{node} + { + // DO NOTHING + } + + public: + Return *perm(const loco::Permutation &perm) + { + using namespace loco; + _node->decoder(stdex::make_unique>(perm)); + return this; + } + + public: + loco::FeatureDecode *node(void) { return _node; } + + private: + loco::FeatureDecode *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + using namespace loco; + + auto decode_node = ctx->graph()->nodes()->create(); + + decode_node->input(ctx->stack()->pop()); + + ctx->stack()->push(decode_node); + + return stdex::make_unique(decode_node); + } +}; + +struct FilterEncodeLayer final +{ + class Return + { + public: + Return(loco::FilterEncode *node) : _node{node} + { + // DO NOTHING + } + + public: + Return *perm(const loco::Permutation &perm) + { + auto encoder = stdex::make_unique>(); + encoder->perm(perm); + _node->encoder(std::move(encoder)); + return this; + } + + public: + loco::FilterEncode *node(void) { return _node; } + + private: + loco::FilterEncode *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto encode_node = ctx->graph()->nodes()->create(); + + encode_node->input(ctx->stack()->pop()); + + ctx->stack()->push(encode_node); + + return stdex::make_unique(encode_node); + } +}; + +struct DepthwiseFilterEncodeLayer final +{ + class Return + { + public: + Return(loco::DepthwiseFilterEncode *node) : _node{node} + { + // DO NOTHING + } + + public: + Return *perm(const loco::Permutation &perm) + { + using namespace loco; + _node->encoder(stdex::make_unique>(perm)); + return this; + } + + public: + loco::DepthwiseFilterEncode *node(void) { return _node; } + + private: + loco::DepthwiseFilterEncode *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto encode_node = ctx->graph()->nodes()->create(); + + encode_node->input(ctx->stack()->pop()); + + ctx->stack()->push(encode_node); + + return stdex::make_unique(encode_node); + } +}; + +struct DepthwiseConv2DLayer final +{ + class Return + { + public: + Return(loco::DepthwiseConv2D *node) : _node{node} + { + // DO NOTHING + } + + public: + loco::DepthwiseConv2D *node(void) { return _node; } + + private: + loco::DepthwiseConv2D *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto depthwiseconv2d_node = ctx->graph()->nodes()->create(); + + depthwiseconv2d_node->ker(ctx->stack()->pop()); + depthwiseconv2d_node->ifm(ctx->stack()->pop()); + + ctx->stack()->push(depthwiseconv2d_node); + + return stdex::make_unique(depthwiseconv2d_node); + } +}; + +struct TransposedConv2DLayer final +{ + class Return + { + public: + Return(loco::TransposedConv2D *node) : _node{node} + { + // DO NOTHING + } + + public: + loco::TransposedConv2D *node(void) { return _node; } + + private: + loco::TransposedConv2D *_node; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto tr_conv2d_node = ctx->graph()->nodes()->create(); + + tr_conv2d_node->ker(ctx->stack()->pop()); + tr_conv2d_node->ifm(ctx->stack()->pop()); + + ctx->stack()->push(tr_conv2d_node); + + return stdex::make_unique(tr_conv2d_node); + } +}; + +struct FixedReshapeLayer final +{ + class Return + { + public: + Return(loco::FixedReshape *node) : _node{node} + { + // DO NOTHING + } + + public: + Return *shape(std::initializer_list dims) + { + _node->shape(dims); + return this; + } + + public: + loco::FixedReshape *node(void) { return _node; } + + private: + loco::FixedReshape *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto reshape_node = ctx->graph()->nodes()->create(); + + reshape_node->input(ctx->stack()->pop()); + + ctx->stack()->push(reshape_node); + + return stdex::make_unique(reshape_node); + } +}; + +struct TensorBroadcastLayer final +{ + class Return + { + public: + Return(loco::TensorBroadcast *node) : _node{node} + { + // DO NOTHING + } + + public: + loco::TensorBroadcast *node(void) { return _node; } + + private: + loco::TensorBroadcast *_node = nullptr; + }; + + std::unique_ptr operator()(GraphBuilder::Context *ctx) + { + auto broadcast_node = ctx->graph()->nodes()->create(); + + broadcast_node->input(ctx->stack()->pop()); + ctx->stack()->push(broadcast_node); + + return stdex::make_unique(broadcast_node); + } +}; + +#endif // __GRAPH_BUILDER_H__ diff --git a/compiler/loco/src/Service/GraphBuilder.test.cpp b/compiler/loco/src/Service/GraphBuilder.test.cpp new file mode 100644 index 00000000000..7b2ea5198c7 --- /dev/null +++ b/compiler/loco/src/Service/GraphBuilder.test.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "GraphBuilder.h" + +#include "loco/IR/Nodes.h" +#include "loco/IR/CanonicalDialect.h" +#include "loco/IR/CanonicalOpcode.h" + +#include + +TEST(GraphBuilderTest, Usecase_000) +{ + struct SampleLayer final + { + loco::Node *operator()(GraphBuilder::Context *ctx) + { + auto node = ctx->graph()->nodes()->create(); + ctx->stack()->push(node); + return node; + } + }; + + auto g = loco::make_graph(); + auto gbuilder = make_graph_builder(g.get()); + + gbuilder->push(); + + auto node = gbuilder->pop(); + + ASSERT_EQ(g->nodes()->size(), 1); + ASSERT_EQ(node->dialect(), loco::CanonicalDialect::get()); + ASSERT_EQ(node->opnum(), static_cast(loco::CanonicalOpcode::ConstGen)); +} diff --git a/compiler/loco/src/Service/GraphTestcase.h b/compiler/loco/src/Service/GraphTestcase.h new file mode 100644 index 00000000000..6743b9a14d0 --- /dev/null +++ b/compiler/loco/src/Service/GraphTestcase.h @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_TESTCASE_H__ +#define __GRAPH_TESTCASE_H__ + +#include "loco/IR/Graph.h" +#include "loco/IR/PermutingCodec.h" + +#include "GraphBuilder.h" + +#include + +enum class GraphCode +{ + Identity, + ConstGen, + Relu, + FeatureCodec, + AvgPool2D, + DepthwiseConv2D, + TransposedConv2D, + MaxPool2D, + TensorBroadcast, + TensorConcat, + TensorTranspose, + FixedReshape, +}; + +namespace +{ + +template loco::Permutation make_NHWC_perm(void); + +template <> loco::Permutation make_NHWC_perm(void) +{ + loco::Permutation perm; + + perm[loco::FeatureAxis::Count] = 0; + perm[loco::FeatureAxis::Height] = 1; + perm[loco::FeatureAxis::Width] = 2; + perm[loco::FeatureAxis::Depth] = 3; + + return perm; +} + +template loco::Permutation make_HWCN_perm(void); + +// @note Also known as HWIO permutation +template <> loco::Permutation make_HWCN_perm(void) +{ + loco::Permutation perm; + + perm[loco::FilterAxis::Height] = 0; + perm[loco::FilterAxis::Width] = 1; + perm[loco::FilterAxis::Depth] = 2; + perm[loco::FilterAxis::Count] = 3; + + return perm; +} + +template loco::Permutation make_HWCM_perm(void); + +template <> loco::Permutation make_HWCM_perm(void) +{ + loco::Permutation perm; + + perm[loco::DepthwiseFilterAxis::Height] = 0; + perm[loco::DepthwiseFilterAxis::Width] = 1; + perm[loco::DepthwiseFilterAxis::Depth] = 2; + perm[loco::DepthwiseFilterAxis::Multiplier] = 3; + + return perm; +} + +} // namespace + +template class GraphTestcase; + +template <> class GraphTestcase final +{ +private: + void init(std::initializer_list dims) + { + // Create a sample network + _graph = loco::make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->shape(dims)->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + // NOTE This default constructor guarantees backward compatbility. + GraphTestcase() { init({1, 4, 8, 3}); } + GraphTestcase(std::initializer_list dims) { init(dims); } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + _graph = loco::make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + const_node = graph_builder->push()->node(); + + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::ConstGen *const_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + // Create a sample network + _graph = loco::make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->node(); + relu_node = graph_builder->push()->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::ReLU *relu_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + Permutation perm; + + perm[FeatureAxis::Count] = 0; + perm[FeatureAxis::Height] = 1; + perm[FeatureAxis::Width] = 2; + perm[FeatureAxis::Depth] = 3; + + // Create a sample network + _graph = make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->node(); + encode_node = graph_builder->push()->perm(perm)->node(); + decode_node = graph_builder->push()->perm(perm)->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FeatureEncode *encode_node = nullptr; + loco::FeatureDecode *decode_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + // Create a sample network + _graph = make_graph(); + + // Create Graph Input/Output + auto graph_input = _graph->inputs()->create(); + auto graph_output = _graph->outputs()->create(); + + graph_input->name("input"); + graph_output->name("output"); + + // Create and connect nodes + pull_node = _graph->nodes()->create(); + pull_node->index(0); + + encode_node = _graph->nodes()->create(); + encode_node->input(pull_node); + + avgpool2d_node = _graph->nodes()->create(); + avgpool2d_node->ifm(encode_node); + + decode_node = _graph->nodes()->create(); + decode_node->input(avgpool2d_node); + + push_node = _graph->nodes()->create(); + push_node->index(0); + push_node->from(decode_node); + + // Create a link between input/output and corresponding nodes + loco::link(graph_input, pull_node); + loco::link(graph_output, push_node); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FeatureEncode *encode_node = nullptr; + loco::AvgPool2D *avgpool2d_node = nullptr; + loco::FeatureDecode *decode_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + _graph = make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + Permutation perm = make_NHWC_perm(); + Permutation filter_perm = make_HWCM_perm(); + + pull_node = graph_builder->push()->name("input")->node(); + encode_node = graph_builder->push()->perm(perm)->node(); + + const_node = graph_builder->push()->node(); + + filter_encode_node = + graph_builder->push()->perm(filter_perm)->node(); + + depthwiseconv2d_node = graph_builder->push()->node(); + + decode_node = graph_builder->push()->perm(perm)->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FeatureEncode *encode_node = nullptr; + loco::ConstGen *const_node = nullptr; + loco::DepthwiseFilterEncode *filter_encode_node = nullptr; + loco::DepthwiseConv2D *depthwiseconv2d_node = nullptr; + loco::FeatureDecode *decode_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + // Prepare permutations + Permutation feature_perm = make_NHWC_perm(); + Permutation filter_perm = make_HWCN_perm(); + + // Build graph + _graph = make_graph(); + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->node(); + encode_node = graph_builder->push()->perm(feature_perm)->node(); + const_node = graph_builder->push()->node(); + filter_encode_node = graph_builder->push()->perm(filter_perm)->node(); + tr_conv2d_node = graph_builder->push()->node(); + decode_node = graph_builder->push()->perm(feature_perm)->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FeatureEncode *encode_node = nullptr; + loco::ConstGen *const_node = nullptr; + loco::FilterEncode *filter_encode_node = nullptr; + loco::TransposedConv2D *tr_conv2d_node = nullptr; + loco::FeatureDecode *decode_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + // Create a sample network + _graph = make_graph(); + + // Create Graph Input/Output + auto graph_input = _graph->inputs()->create(); + auto graph_output = _graph->outputs()->create(); + + graph_input->name("input"); + graph_output->name("output"); + + // Create and connect nodes + pull_node = _graph->nodes()->create(); + pull_node->index(0); + + encode_node = _graph->nodes()->create(); + encode_node->input(pull_node); + + maxpool2d_node = _graph->nodes()->create(); + maxpool2d_node->ifm(encode_node); + + decode_node = _graph->nodes()->create(); + decode_node->input(maxpool2d_node); + + push_node = _graph->nodes()->create(); + push_node->index(0); + push_node->from(decode_node); + + // Create a link between input/output and corresponding nodes + loco::link(graph_input, pull_node); + loco::link(graph_output, push_node); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FeatureEncode *encode_node = nullptr; + loco::MaxPool2D *maxpool2d_node = nullptr; + loco::FeatureDecode *decode_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + // Create a sample network + _graph = make_graph(); + + // Create Graph Input/Output + auto graph_lhs = _graph->inputs()->create(); + auto graph_rhs = _graph->inputs()->create(); + auto graph_out = _graph->outputs()->create(); + + graph_lhs->name("lhs"); + graph_rhs->name("rhs"); + graph_out->name("output"); + + // Create and connect nodes + lhs_node = _graph->nodes()->create(); + lhs_node->index(0); + + rhs_node = _graph->nodes()->create(); + rhs_node->index(1); + + concat_node = _graph->nodes()->create(); + concat_node->lhs(lhs_node); + concat_node->rhs(rhs_node); + + push_node = _graph->nodes()->create(); + push_node->index(0); + push_node->from(concat_node); + + // Create a link between input/output and corresponding nodes + loco::link(graph_lhs, lhs_node); + loco::link(graph_rhs, rhs_node); + loco::link(graph_out, push_node); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *lhs_node = nullptr; + loco::Pull *rhs_node = nullptr; + loco::TensorConcat *concat_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + _graph = loco::make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->node(); + reshape_node = graph_builder->push()->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::FixedReshape *reshape_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase(std::initializer_list dims) + { + _graph = loco::make_graph(); + + auto graph_builder = make_graph_builder(_graph.get()); + + pull_node = graph_builder->push()->name("input")->shape(dims)->node(); + broadcast_node = graph_builder->push()->node(); + push_node = graph_builder->push()->name("output")->node(); + } + +public: + loco::Graph *graph(void) { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::TensorBroadcast *broadcast_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +template <> class GraphTestcase final +{ +public: + GraphTestcase() + { + using namespace loco; + + // Create a sample network + _graph = make_graph(); + + // Create Graph Input/Output + auto graph_input = _graph->inputs()->create(); + auto graph_output = _graph->outputs()->create(); + + graph_input->name("input"); + graph_output->name("output"); + + // Create and connect nodes + pull_node = _graph->nodes()->create(); + pull_node->index(0); + + transpose_node = _graph->nodes()->create(); + transpose_node->input(pull_node); + + push_node = _graph->nodes()->create(); + push_node->index(0); + push_node->from(transpose_node); + + // Create a link between input/output and corresponding nodes + loco::link(graph_input, pull_node); + loco::link(graph_output, push_node); + } + +public: + loco::Graph *graph() { return _graph.get(); } + + loco::Pull *pull_node = nullptr; + loco::TensorTranspose *transpose_node = nullptr; + loco::Push *push_node = nullptr; + +private: + std::unique_ptr _graph; +}; + +#endif // __GRAPH_TESTCASE_H__ diff --git a/compiler/loco/src/Service/MultiDialectShapeInferenceRule.cpp b/compiler/loco/src/Service/MultiDialectShapeInferenceRule.cpp new file mode 100644 index 00000000000..2178f5d050e --- /dev/null +++ b/compiler/loco/src/Service/MultiDialectShapeInferenceRule.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/MultiDialectShapeInferenceRule.h" +#include "loco/Service/ShapeInferenceRule.h" + +#include +#include +#include + +#include + +namespace loco +{ + +bool MultiDialectShapeInferenceRule::recognize(const Dialect *d) const +{ + const auto found = _rules.find(d); + + if (found == _rules.cend()) + return false; + + auto rule = found->second; + auto result = rule->recognize(d); + + return result; +} + +bool MultiDialectShapeInferenceRule::infer(const Node *node, NodeShape &shape) const +{ + const auto found = _rules.find(node->dialect()); + + if (found == _rules.cend()) + return false; + + auto rule = found->second; + if (rule->infer(node, shape)) + return true; + + return false; +} + +MultiDialectShapeInferenceRule &MultiDialectShapeInferenceRule::bind(const Dialect *d, + const ShapeInferenceRule *rule) +{ + assert(_rules.find(d) == _rules.end()); + assert(rule->recognize(d)); + + _rules[d] = rule; + + return (*this); +} + +} // namespace loco diff --git a/compiler/loco/src/Service/MultiDialectShapeInferenceRule.test.cpp b/compiler/loco/src/Service/MultiDialectShapeInferenceRule.test.cpp new file mode 100644 index 00000000000..ffa9ee5caa6 --- /dev/null +++ b/compiler/loco/src/Service/MultiDialectShapeInferenceRule.test.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/CanonicalShapeInferenceRule.h" +#include "loco/Service/MultiDialectShapeInferenceRule.h" +#include "loco/Service/ShapeInference.h" + +#include +#include + +#include + +#include +#include + +// mockup for MultiDialectShapeInferenceRule +// Each class is dedicated for handling shape { D1, D2 } and D1, D2 are declared as a template +namespace +{ + +template class TestDialect final : public loco::Dialect +{ +public: + static Dialect *get(void) + { + static TestDialect d; + return &d; + } +}; + +template +struct TestOpNode final : public loco::FixedArity<1>::Mixin, + public loco::NodeMixin +{ + void input(Node *node) { at(0)->node(node); } + const loco::Dialect *dialect(void) const final { return TestDialect::get(); } + uint32_t opnum(void) const final { return static_cast(D1); /* not used */ } +}; + +template +struct TestShapeInferenceRule final : public loco::ShapeInferenceRule +{ +public: + bool recognize(const loco::Dialect *d) const final { return (d == TestDialect::get()); } + + bool infer(const loco::Node *node, loco::NodeShape &node_shape) const final + { + assert(recognize(node->dialect())); + auto test_node = dynamic_cast *>(node); + assert(test_node != nullptr); + + loco::TensorShape ts; + { + ts.rank(2); + ts.dim(0) = D1; + ts.dim(1) = D2; // making shape : { D1, D2 } + } + + node_shape.set(ts); + + return true; + } +}; + +} // namespace + +TEST(MultiDialectShapeInferenceRuleTest, test1) +{ + // Create a simple network : Pull ------- t23<2,3> ------------ t45<4,5> ---------- Push + // TensorShape({2, 3}) TensorShape({4, 5}) + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + auto t23_node = g->nodes()->create>(); + auto t45_node = g->nodes()->create>(); + auto push_node = g->nodes()->create(); + + t23_node->input(pull_node); + t45_node->input(t23_node); + push_node->from(t45_node); + + auto graph_input = g->inputs()->create(); + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + graph_output->name("output"); + loco::link(graph_output, push_node); + + // initially they don't have shape info + ASSERT_FALSE(loco::shape_known(t23_node)); + ASSERT_FALSE(loco::shape_known(t45_node)); + + // Run Type Inference + loco::CanonicalShapeInferenceRule canonical_rule; + TestShapeInferenceRule<2, 3> t23_rule; + TestShapeInferenceRule<4, 5> t45_rule; + + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(TestDialect<2, 3>::get(), &t23_rule) + .bind(TestDialect<4, 5>::get(), &t45_rule); + + loco::apply(&rules).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::shape_known(t23_node)); + auto t23_shape = loco::shape_get(t23_node); + ASSERT_EQ(t23_shape.domain(), loco::Domain::Tensor); + ASSERT_EQ(t23_shape.as().rank(), 2); + ASSERT_EQ(t23_shape.as().dim(0), 2); + ASSERT_EQ(t23_shape.as().dim(1), 3); + + ASSERT_TRUE(loco::shape_known(t45_node)); + auto t45_shape = loco::shape_get(t45_node); + ASSERT_EQ(t45_shape.domain(), loco::Domain::Tensor); + ASSERT_EQ(t45_shape.as().rank(), 2); + ASSERT_EQ(t45_shape.as().dim(0), 4); + ASSERT_EQ(t45_shape.as().dim(1), 5); +} diff --git a/compiler/loco/src/Service/ShapeInference.cpp b/compiler/loco/src/Service/ShapeInference.cpp new file mode 100644 index 00000000000..84eb10963d3 --- /dev/null +++ b/compiler/loco/src/Service/ShapeInference.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/ShapeInference.h" +#include "loco/IR/Algorithm.h" + +#include + +#include + +namespace +{ + +bool inputs_shape_ready(loco::Node *node) +{ + assert(node != nullptr); + + for (uint32_t arity = 0; arity < node->arity(); ++arity) + { + if (!loco::ShapeInference::known(node->arg(arity))) + { + return false; + } + } + return true; +} + +} // namespace + +// +// Infrastructure +// +namespace +{ + +struct ShapeAnnotation : public loco::NodeAnnotation +{ +public: + ShapeAnnotation(const loco::NodeShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + const loco::NodeShape &shape(void) const { return _shape; } + +private: + loco::NodeShape _shape; +}; + +} // namespace + +namespace loco +{ + +bool ShapeInferenceSession::to(Graph *g) const +{ + assert(_rule->support(ShapeInferenceRule::API::V1) && "API v1 is unavailable"); + + bool changed = false; + + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + if (_rule->recognize(node->dialect())) + { + loco::NodeShape shape; + + if (!shape_known(node) && inputs_shape_ready(node)) + { + if (_rule->infer(node, shape)) + { + node->annot(stdex::make_unique(shape)); + changed = true; + } + } + } + } + + return changed; +} + +bool ShapeInference::known(const Node *node) { return node->annot() != nullptr; } + +NodeShape ShapeInference::get(const Node *node) +{ + assert(known(node)); + return node->annot()->shape(); +} + +void ShapeInference::erase(Node *node) { node->annot(nullptr); } + +} // namespace loco diff --git a/compiler/loco/src/Service/ShapeInference.test.cpp b/compiler/loco/src/Service/ShapeInference.test.cpp new file mode 100644 index 00000000000..e10b98844ce --- /dev/null +++ b/compiler/loco/src/Service/ShapeInference.test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/ShapeInference.h" +#include "GraphTestcase.h" + +#include + +#include + +// This test validates whether framework works as expected. +TEST(ShapeInferenceTest, framework) +{ + // Mock-up Shape Inference Rule + struct SampleShapeInferenceRule final : public loco::ShapeInferenceRule + { + public: + SampleShapeInferenceRule(std::vector *nodes) : _nodes{nodes} + { + // DO NOTHING + } + + public: + // Accept all the dialects + bool recognize(const loco::Dialect *) const final { return true; } + + bool infer(const loco::Node *node, loco::NodeShape &shape) const final + { + // Record the order of inference + _nodes->emplace_back(node); + + if (_nodes->size() != 1) + { + return false; + } + + // Set the first node as Tensor<1> + loco::TensorShape tensor_shape; + + tensor_shape.rank(1); + tensor_shape.dim(0) = 4; + + shape.set(tensor_shape); + + return true; + } + + private: + std::vector *_nodes; + }; + + GraphTestcase testcase; + + std::vector nodes; + + SampleShapeInferenceRule rule{&nodes}; + + loco::apply(&rule).to(testcase.graph()); + + // Framework SHOULD visit all the nodes + ASSERT_EQ(nodes.size(), 2); + // Framework SHOULD visit "pull" before "push" + ASSERT_EQ(nodes.at(0), testcase.pull_node); + ASSERT_EQ(nodes.at(1), testcase.push_node); + + // Framework SHOULD make an annotation if "rule" returns TRUE + ASSERT_TRUE(loco::shape_known(testcase.pull_node)); + ASSERT_EQ(loco::shape_get(testcase.pull_node).domain(), loco::Domain::Tensor); + ASSERT_EQ(loco::shape_get(testcase.pull_node).as().rank(), 1); + ASSERT_EQ(loco::shape_get(testcase.pull_node).as().dim(0), 4); + + // Framework SHOULD NOT make any annotation if "rule" returns FALSE + ASSERT_FALSE(loco::shape_known(testcase.push_node)); +} diff --git a/compiler/loco/src/Service/ShapeInferenceRule.cpp b/compiler/loco/src/Service/ShapeInferenceRule.cpp new file mode 100644 index 00000000000..bed841260a5 --- /dev/null +++ b/compiler/loco/src/Service/ShapeInferenceRule.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/ShapeInferenceRule.h" + +#include + +// This file validates "ShapeInferenceRule.h". Please DO NOT remove this file. + +namespace loco +{ + +void ShapeInferenceRule::infer(const Context *, const Node *, Sink *) const +{ + throw std::runtime_error{"API v2 is not supported"}; +} + +} // namespace loco diff --git a/compiler/loco/src/Service/TypeInference.cpp b/compiler/loco/src/Service/TypeInference.cpp new file mode 100644 index 00000000000..fbf0033ee2e --- /dev/null +++ b/compiler/loco/src/Service/TypeInference.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/TypeInference.h" + +#include "loco/IR/Algorithm.h" + +#include + +#include + +namespace +{ + +struct DataTypeAnnotation : public loco::NodeAnnotation +{ +public: + DataTypeAnnotation(const loco::DataType &dtype) : _dtype{dtype} + { + // DO NOTHING + } + +public: + const loco::DataType &dtype(void) const { return _dtype; } + +private: + loco::DataType _dtype; +}; + +bool inputs_dtype_ready(loco::Node *node) +{ + assert(node != nullptr); + + for (uint32_t arity = 0; arity < node->arity(); ++arity) + { + if (!loco::TypeInference::known(node->arg(arity))) + { + return false; + } + } + return true; +} + +} // namespace + +namespace loco +{ + +bool TypeInferenceSession::to(Graph *g) const +{ + bool changed = false; + + for (auto node : postorder_traversal(output_nodes(g))) + { + if (_rule->recognize(node->dialect())) + { + DataType dtype = DataType::Unknown; + + if (!dtype_known(node) && inputs_dtype_ready(node)) + { + if (_rule->infer(node, dtype)) + { + node->annot(stdex::make_unique(dtype)); + changed = true; + } + } + } + } + + return changed; +} + +bool TypeInference::known(const Node *node) { return node->annot() != nullptr; } + +DataType TypeInference::get(const Node *node) +{ + assert(known(node)); + return node->annot()->dtype(); +} + +void TypeInference::erase(Node *node) { return node->annot(nullptr); } + +} // namespace loco + +// +// Canonical (Data) Type Inference Rule +// +#include +#include +#include + +namespace +{ + +/** + * There are two possible maintenance policies. + * - Introduce a new canonical node first, and then extend this algorithm later + * - Introduce a new canonical node and extend this algorithm at the same time + * + * The current implementation assumes the former one (for historical reason). + * + * TODO Evaluate the impact of the latter one + */ +struct CanonicalTypeForwardAlgorithm final : public loco::CanonicalNodeVisitor +{ + loco::DataType visit(const loco::AvgPool2D *node) { return loco::dtype_get(node->ifm()); } + loco::DataType visit(const loco::BiasDecode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::BiasEncode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::ConstGen *node) { return node->dtype(); } + loco::DataType visit(const loco::Conv2D *node) { return loco::dtype_get(node->ifm()); } + loco::DataType visit(const loco::DepthwiseConv2D *node) { return loco::dtype_get(node->ifm()); } + loco::DataType visit(const loco::DepthwiseFilterEncode *node) + { + return loco::dtype_get(node->input()); + } + loco::DataType visit(const loco::DepthwiseFilterDecode *node) + { + return loco::dtype_get(node->input()); + } + loco::DataType visit(const loco::EltwiseAdd *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::EltwiseDiv *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::EltwiseMax *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::EltwiseMul *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::EltwiseSqrt *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::EltwiseSub *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::Forward *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::FeatureBiasAdd *node) { return loco::dtype_get(node->value()); } + loco::DataType visit(const loco::FeatureDecode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::FeatureEncode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::FilterDecode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::FilterEncode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::FixedReshape *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::MatrixDecode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::MatrixEncode *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::MatMul *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::MaxPool2D *node) { return loco::dtype_get(node->ifm()); } + loco::DataType visit(const loco::Push *node) { return loco::dtype_get(node->from()); } + loco::DataType visit(const loco::Pull *node) { return node->dtype(); } + loco::DataType visit(const loco::ReLU *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::ReLU6 *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::Tanh *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::TensorConcat *node) { return loco::dtype_get(node->lhs()); } + loco::DataType visit(const loco::TensorConstantPad *node) + { + return loco::dtype_get(node->input()); + } + loco::DataType visit(const loco::TensorBiasAdd *node) { return loco::dtype_get(node->value()); } + loco::DataType visit(const loco::TensorBroadcast *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::TensorReduce *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::TensorSoftmax *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::TensorTranspose *node) { return loco::dtype_get(node->input()); } + loco::DataType visit(const loco::TransposedConv2D *node) { return loco::dtype_get(node->ifm()); } +}; + +} // namespace + +namespace loco +{ + +bool CanonicalTypeInferenceRule::recognize(const Dialect *d) const +{ + // This rule recognizes only "loco.canonical" dialect! + return CanonicalDialect::get() == d; +} + +bool CanonicalTypeInferenceRule::infer(const Node *node, DataType &dtype) const +{ + assert(node->dialect() == loco::CanonicalDialect::get()); + assert(dynamic_cast(node) != nullptr); + + CanonicalTypeForwardAlgorithm alg; + dtype = dynamic_cast(node)->accept(&alg); + + return true; +} + +bool MultiDialectTypeInferenceRule::recognize(const Dialect *d) const +{ + const auto found = _rules.find(d); + + if (found == _rules.cend()) + return false; + + auto rule = found->second; + auto result = rule->recognize(d); + + return result; +} + +bool MultiDialectTypeInferenceRule::infer(const Node *node, DataType &dtype) const +{ + const auto found = _rules.find(node->dialect()); + + if (found == _rules.cend()) + return false; + + auto rule = found->second; + if (rule->infer(node, dtype)) + return true; + + return false; +} + +MultiDialectTypeInferenceRule &MultiDialectTypeInferenceRule::bind(const Dialect *d, + const TypeInferenceRule *rule) +{ + assert(_rules.find(d) == _rules.end()); + assert(rule->recognize(d)); + + _rules[d] = rule; + + return (*this); +} + +} // namespace loco diff --git a/compiler/loco/src/Service/TypeInference.test.cpp b/compiler/loco/src/Service/TypeInference.test.cpp new file mode 100644 index 00000000000..4660401db30 --- /dev/null +++ b/compiler/loco/src/Service/TypeInference.test.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco/Service/TypeInference.h" + +#include "GraphTestcase.h" + +#include +#include + +#include + +#include + +// This test validates whether framework works as expected. +TEST(TypeInferenceTest, framework) +{ + // Create a sample network + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + auto push_node = g->nodes()->create(); + + push_node->from(pull_node); + + // Create Graph Input & Output + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + loco::link(graph_output, push_node); + + // Mock-up Type Inference Rule + struct SampleTypeInferenceRule final : public loco::TypeInferenceRule + { + public: + SampleTypeInferenceRule(std::vector *nodes) : _nodes{nodes} + { + // DO NOTHING + } + + public: + bool recognize(const loco::Dialect *) const final + { + // Accept all the dialects + return true; + } + + bool infer(const loco::Node *node, loco::DataType &dtype) const final + { + // Record the order of inference + _nodes->emplace_back(node); + + if (_nodes->size() != 1) + { + return false; + } + + // Annotate the first node as "U8" + dtype = loco::DataType::U8; + return true; + } + + private: + std::vector *_nodes; + }; + + std::vector nodes; + + SampleTypeInferenceRule rule{&nodes}; + + loco::apply(&rule).to(g.get()); + + ASSERT_EQ(nodes.size(), 2); // Framework SHOULD visit all the nodes + ASSERT_EQ(nodes.at(0), pull_node); // Framework SHOULD visit "pull" before "push" + ASSERT_EQ(nodes.at(1), push_node); + + // Framework SHOULD NOT make any annotation if "rule" returns FALSE + ASSERT_TRUE(loco::dtype_known(pull_node)); + // Framework SHOULD make an annotation if "rule" returns TRUE + ASSERT_EQ(loco::dtype_get(pull_node), loco::DataType::U8); + ASSERT_FALSE(loco::dtype_known(push_node)); +} + +TEST(CanonicalTypeInferenceRuleTest, minimal) +{ + // Create a simple network + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + + pull_node->dtype(loco::DataType::U8); + + auto push_node = g->nodes()->create(); + + push_node->from(pull_node); + + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + loco::link(graph_output, push_node); + + // Run Type Inference + loco::CanonicalTypeInferenceRule rule; + + loco::apply(&rule).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::dtype_known(push_node)); + ASSERT_EQ(loco::dtype_get(push_node), loco::DataType::U8); +} + +TEST(CanonicalTypeInferenceRuleTest, relu6) +{ + // Create a simple Relu6 network + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + + pull_node->dtype(loco::DataType::FLOAT32); + + auto relu6_node = g->nodes()->create(); + + relu6_node->input(pull_node); + + auto push_node = g->nodes()->create(); + + push_node->from(relu6_node); + + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + loco::link(graph_output, push_node); + + // Run Type Inference + loco::CanonicalTypeInferenceRule rule; + + loco::apply(&rule).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::dtype_known(relu6_node)); + ASSERT_EQ(loco::dtype_get(relu6_node), loco::DataType::FLOAT32); +} + +TEST(CanonicalTypeInferenceRuleTest, tensor_broadcast) +{ + // Create a sample network + GraphTestcase testcase{1, 2}; + + testcase.graph()->inputs()->at(0)->dtype(loco::DataType::U8); + + // Run Type Inference + loco::CanonicalTypeInferenceRule rule; + + loco::apply(&rule).to(testcase.graph()); + + // Verify! + ASSERT_TRUE(loco::dtype_known(testcase.push_node)); + ASSERT_EQ(loco::dtype_get(testcase.push_node), loco::DataType::U8); +} + +// mockup for MultiDialectTypeInferenceRule +// OpNode of a specific loco datatype (defined in template) will be used. +// And a Dialect for the OpNode and its inference rules are created. +#include + +namespace +{ + +template class TestDialect final : public loco::Dialect +{ +public: + static Dialect *get(void) + { + static TestDialect d; + return &d; + } +}; + +template +struct TestOpNode final : public loco::FixedArity<1>::Mixin, + public loco::NodeMixin +{ + void input(Node *node) { at(0)->node(node); } + const loco::Dialect *dialect(void) const final { return TestDialect::get(); } + uint32_t opnum(void) const final { return static_cast(N); } +}; + +template struct TestTypeInferenceRule final : public loco::TypeInferenceRule +{ +public: + bool recognize(const loco::Dialect *d) const final { return (d == TestDialect::get()); } + + bool infer(const loco::Node *node, loco::DataType &dtype) const final + { + assert(node->dialect() == TestDialect::get()); + auto test_node = dynamic_cast *>(node); + assert(test_node != nullptr); + + dtype = N; + return true; + } +}; + +} // namespace + +TEST(MultiDialectTypeInferenceRuleTest, test1) +{ + // Create a simple network : Pull - S8 - U8 - Push + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + pull_node->dtype(loco::DataType::FLOAT32); + + auto s8_node = g->nodes()->create>(); + s8_node->input(pull_node); + + auto u8_node = g->nodes()->create>(); + u8_node->input(s8_node); + + auto push_node = g->nodes()->create(); + push_node->from(u8_node); + + auto graph_input = g->inputs()->create(); + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + graph_output->name("output"); + loco::link(graph_output, push_node); + + // initially they don't have type info + ASSERT_FALSE(loco::dtype_known(s8_node)); + ASSERT_FALSE(loco::dtype_known(u8_node)); + + // Run Type Inference + TestTypeInferenceRule u8_rule; + TestTypeInferenceRule s8_rule; + loco::CanonicalTypeInferenceRule canon_rule; + + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(TestDialect::get(), &s8_rule) + .bind(TestDialect::get(), &u8_rule) + .bind(loco::CanonicalDialect::get(), &canon_rule); + + loco::apply(&rules).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::dtype_known(s8_node)); + ASSERT_EQ(loco::dtype_get(s8_node), loco::DataType::S8); + + ASSERT_TRUE(loco::dtype_known(u8_node)); + ASSERT_EQ(loco::dtype_get(u8_node), loco::DataType::U8); +} diff --git a/compiler/loco/src/loco.test.cpp b/compiler/loco/src/loco.test.cpp new file mode 100644 index 00000000000..4c4f51aa572 --- /dev/null +++ b/compiler/loco/src/loco.test.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "loco.h" + +#include + +// This test shows how to create an "identity" network with loco. +// +// What is "identity" network? +// - A network simply passes its input as its output +// +// TODO Create "Ouput" first and then create "Push" later +TEST(LOCO, identity_network) +{ + auto g = loco::make_graph(); + + // Create a "pull" node as an input + auto pull_node = g->nodes()->create(); + + // Set "data type" + pull_node->dtype(loco::DataType::FLOAT32); + + // Set "data shape" + pull_node->rank(2); + pull_node->dim(0) = 3; + pull_node->dim(1) = 4; + + // Create a "push" node as an output + auto push_node = g->nodes()->create(); + + // Set "source" + push_node->from(pull_node); + + // Create Graph Input & Output + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + loco::link(graph_input, pull_node); + graph_input->dtype(loco::DataType::FLOAT32); + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + loco::link(graph_output, push_node); + + // loco::link SHOULD update "index" + ASSERT_EQ(pull_node->index(), 0); + ASSERT_EQ(graph_input->dtype(), loco::DataType::FLOAT32); + + // loco::link SHOULD update "index" + ASSERT_EQ(push_node->index(), 0); +} + +#if 0 +"identity_network_V2" test shows how to use loco when loco.core and loco.canonical are decoupled. + +NOTE "identity_network" test is left for backward compatiblity check +TODO Remove "identity_network" test once all the clients are migrated. +#endif +TEST(LOCO, identity_network_V2) +{ + auto g = loco::make_graph(); + + // Create Graph Input & Output + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + graph_input->dtype(loco::DataType::FLOAT32); + // TODO Set Shape + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + graph_output->dtype(loco::DataType::FLOAT32); + // TODO Set Shape + + // Create a "pull" node as an input + auto pull_node = g->nodes()->create(); + + pull_node->index(0); + + // Create a "push" node as an output + auto push_node = g->nodes()->create(); + + push_node->index(0); + push_node->from(pull_node); + + ASSERT_EQ(pull_node->dtype(), loco::DataType::FLOAT32); + // TODO Check Shape of pull_node + // TODO Check Shape of push_node + + ASSERT_EQ(loco::pull_node(g.get(), 0), pull_node); + ASSERT_EQ(loco::push_node(g.get(), 0), push_node); +} diff --git a/compiler/loco/src/tensorflow.test.cpp b/compiler/loco/src/tensorflow.test.cpp new file mode 100644 index 00000000000..f534aee7bba --- /dev/null +++ b/compiler/loco/src/tensorflow.test.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +/** + * @brief This file includes various tests that shows how to encode TensorFlow models using loco. + * + * @note All the python examples below assume TensorFlow v1.13 + */ +#include "loco.h" + +#include + +#include + +using stdex::make_unique; + +namespace +{ + +loco::Permutation make_NHWC_permutation(void) +{ + loco::Permutation NHWC; + + NHWC.axis(loco::FeatureAxis::Count) = 0; + NHWC.axis(loco::FeatureAxis::Height) = 1; + NHWC.axis(loco::FeatureAxis::Width) = 2; + NHWC.axis(loco::FeatureAxis::Depth) = 3; + + return NHWC; +} + +/** + * @brief Create a HxWxIxO (or HxWxCxN) permutation which tf.nn.conv2d uses + * + * Reference: [tf.nn.conv2d](https://www.tensorflow.org/api_docs/python/tf/nn/conv2d) + * > Given an input tensor of shape [batch, in_height, in_width, in_channels] and a filter / + * > kernel tensor of shape [filter_height, filter_width, in_channels, out_channels], ... + * + * NOTE "HWIO" is borrowed from TensorFlow Lite Converter + * + * https://github.com/tensorflow/tensorflow/blob/v1.13.1/tensorflow/lite/toco/model.h#L169 + */ +loco::Permutation make_HWIO_permutation(void) +{ + loco::Permutation HWIO; + + HWIO.axis(loco::FilterAxis::Height) = 0; // H + HWIO.axis(loco::FilterAxis::Width) = 1; // W + HWIO.axis(loco::FilterAxis::Depth) = 2; // I, a.k.a. C + HWIO.axis(loco::FilterAxis::Count) = 3; // O, a.k.a. N + + return HWIO; +} + +} // nemaspace + +#if 0 +>>> MaxPool_Float_000 testcase + +MaxPool_Float_000 test guarantees that loco is expressive enough to encode the following example. + +Python: +``` +import tensorflow as tf +value = tf.placeholder(dtype=tf.float32, shape=[1, 16, 16, 2], name="value") +maxpool = tf.nn.max_pool(value, [1, 3, 3, 1], [1, 1, 1, 1], 'VALID', name="maxpool") +tf.get_default_graph().as_graph_def() +``` + +The above code produces the following TensorFlow GraphDef: + +node { + name: "value" + op: "Placeholder" + attr { + key: "dtype" + value { type: DT_FLOAT } + } + attr { + key: "shape" + value { + shape { + dim { size: 1 } + dim { size: 16 } + dim { size: 16 } + dim { size: 2 } + } + } + } +} +node { + name: "maxpool" + op: "MaxPool" + input: "Placeholder" + attr { + key: "T" + value { type: DT_FLOAT } + } + attr { + key: "data_format" + value { s: "NHWC" } + } + attr { + key: "ksize" + value { list { i: 1 i: 3 i: 3 i: 1 } } + } + attr { + key: "padding" + value { s: "VALID" } + } + attr { + key: "strides" + value { list { i: 1 i: 1 i: 1 i: 1 } } + } +} + +Below test guarantees that loco is expressive enough to encode this example. +#endif +TEST(TensorFlowTest, MaxPool_Float_000) +{ + auto g = loco::make_graph(); + + // The first "value" node corresponds to the following "Pull" node. + // + // %value = Pull(dtype: FLOAT32, shape: [1, 16, 16, 2]) + auto value = g->nodes()->create(); + + value->dtype(loco::DataType::FLOAT32); + value->shape({1, 16, 16, 2}); + + // The next "maxpool" node corresponds to a sequence of the following loco nodes: + // - "FeatureEncode" + // - "MaxPool2D + // - "FeatureDecode" + // + // "maxpool.data_format" is 'NHWC' which corresponds to the following permutation + // Count <-> 0 + // Height <-> 1 + // Width <-> 2 + // Depth <-> 3 + loco::Permutation NHWC; + + NHWC.axis(loco::FeatureAxis::Count) = 0; + NHWC.axis(loco::FeatureAxis::Height) = 1; + NHWC.axis(loco::FeatureAxis::Width) = 2; + NHWC.axis(loco::FeatureAxis::Depth) = 3; + + auto encoder = make_unique>(); + + encoder->perm(NHWC); + + auto decoder = make_unique>(); + + decoder->perm(NHWC); + + // %node_0 = FeatureEncode(%value, perm { Count = 0, Height = 1, Width = 2, Depth = 3 }) + auto node_0 = g->nodes()->create(); + + node_0->input(value); + node_0->encoder(std::move(encoder)); + + // %node_1 = MaxPool(%node_0, window.H: 3, window.W: 3, stride.H: 1, stride.W : 1) + auto node_1 = g->nodes()->create(); + + node_1->ifm(node_0); + + // From "ksize" attributes + node_1->window()->horizontal(3); + node_1->window()->vertical(3); + + // From "strides" attributes + node_1->stride()->horizontal(1); + node_1->stride()->vertical(1); + + // %output = FeatureDecode(%node_1, perm { Count = 0, Height = 1, Width = 2, Depth = 3 }) + auto output = g->nodes()->create(); + + output->input(node_1); + output->decoder(std::move(decoder)); + + // %push = Push(%output) + auto push = g->nodes()->create(); + + push->from(output); + + // + // Mark network-level input/output + // + auto input_0 = g->inputs()->create(); + loco::link(input_0, value); + + auto output_0 = g->outputs()->create(); + loco::link(output_0, push); + + // NOTE This example SHOULD BE valid. + ASSERT_TRUE(loco::valid(g.get())); +} + +#if 0 +>>> Conv2D_Float_000 testcase + +Conv2D_Float_000 test guarantees that loco is expressive enough to encode the following example. + +Python: +``` +import tensorflow as tf +inp = tf.placeholder(dtype=tf.float32, shape=[1, 16, 16, 2], name="inp") +ker = tf.constant(value=[1.0], dtype=tf.float32, shape=[7, 1, 2, 4], name="ker") +conv2d = tf.nn.conv2d(input=inp, filter=ker, strides=[1, 1, 1, 1], padding='VALID', name="conv2d") +tf.get_default_graph().as_graph_def() +``` + +TensorFlow GraphDef: +``` +node { + name: "inp" + op: "Placeholder" + attr { + key: "dtype" + value { type: DT_FLOAT } + } + attr { + key: "shape" + value { + shape { + dim { size: 1 } + dim { size: 16 } + dim { size: 16 } + dim { size: 2 } + } + } + } +} +node { + name: "ker" + op: "Const" + attr { + key: "dtype" + value { type: DT_FLOAT } + } + attr { + key: "value" + value { + tensor { + dtype: DT_FLOAT + tensor_shape { + dim { size: 7 } + dim { size: 1 } + dim { size: 2 } + dim { size: 4 } + } + float_val: 1.0 + } + } + } +} +node { + name: "conv2d" + op: "Conv2D" + input: "inp" + input: "ker" + attr { + key: "T" + value { type: DT_FLOAT } + } + attr { + key: "data_format" + value { s: "NHWC" } + } + attr { + key: "dilations" + value { list { i: 1 i: 1 i: 1 i: 1 } } + } + attr { + key: "padding" + value { s: "VALID" } + } + attr { + key: "strides" + value { list { i: 1 i: 1 i: 1 i: 1 } } + } +} +``` +#endif +TEST(TensorFlowTest, Conv2D_Float_000) +{ + auto g = loco::make_graph(); + + // The first "inp" node corresponds to "Pull" + auto inp = g->nodes()->create(); + { + inp->dtype(loco::DataType::FLOAT32); + inp->shape({1, 16, 16, 2}); + } + + // The seoncd "ker" node corresponds to "ConstGen" + auto ker = g->nodes()->create(); + { + ker->dtype(loco::DataType::FLOAT32); + // 'I' denotes IFM DEPTH, and 'O' denotes OFM DEPTH + ker->shape({7 /*H*/, 1 /*W*/, 2 /*I*/, 3 /*O*/}); + ker->size(7 * 1 * 2 * 3); + for (uint32_t n = 0; n < 7 * 1 * 2 * 3; ++n) + { + // NOTE TensorFlow uses the last value to fill unspecified region + ker->at(n) = 1.0f; + } + } + + // The next "conv2d" node is decomposed into the following loco nodes + // - "FeatureEncode" + // - "FilterEncode" + // - "Conv2D" + // - "FeatureDecode" + auto encoded_ifm = g->nodes()->create(); + { + // From "conv2d.data_format" attribute + auto encoder = make_unique>(); + encoder->perm(make_NHWC_permutation()); + + encoded_ifm->input(inp); + encoded_ifm->encoder(std::move(encoder)); + } + + auto encoded_ker = g->nodes()->create(); + { + // From "tf.nn.conv2d" specification + auto encoder = make_unique>(); + encoder->perm(make_HWIO_permutation()); + + encoded_ker->input(ker); + encoded_ker->encoder(std::move(encoder)); + } + + auto conv2d = g->nodes()->create(); + { + conv2d->ifm(encoded_ifm); + conv2d->ker(encoded_ker); + + // From "stride" attribute + conv2d->stride()->horizontal(1); + conv2d->stride()->vertical(1); + } + + // "decoded_ofm" corresponds to the output of "conv2d" node. + auto decoded_ofm = g->nodes()->create(); + { + // From "conv2d.data_format" attribute + auto decoder = make_unique>(); + decoder->perm(make_NHWC_permutation()); + + decoded_ofm->input(conv2d); + decoded_ofm->decoder(std::move(decoder)); + } + + // Makr "conv2d" as a network-level output with Push + auto push = g->nodes()->create(); + { + push->from(decoded_ofm); + } + + // + // Mark network-level input/output + // + auto input_0 = g->inputs()->create(); + loco::link(input_0, inp); + + auto output_0 = g->outputs()->create(); + loco::link(output_0, push); + + // NOTE This example SHOULD BE valid. + ASSERT_TRUE(loco::valid(g.get())); +} diff --git a/compiler/locoex-customop/CMakeLists.txt b/compiler/locoex-customop/CMakeLists.txt new file mode 100644 index 00000000000..df1e01526be --- /dev/null +++ b/compiler/locoex-customop/CMakeLists.txt @@ -0,0 +1,18 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(locoex_customop SHARED ${SOURCES}) +target_include_directories(locoex_customop PUBLIC include) +target_link_libraries(locoex_customop PUBLIC loco) +target_link_libraries(locoex_customop PRIVATE stdex locop pepper_str) +install(TARGETS locoex_customop DESTINATION lib) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(locoex_customop_test ${TESTS}) +target_link_libraries(locoex_customop_test loco locoex_customop stdex) diff --git a/compiler/locoex-customop/README.md b/compiler/locoex-customop/README.md new file mode 100644 index 00000000000..c2b22e3db1d --- /dev/null +++ b/compiler/locoex-customop/README.md @@ -0,0 +1,9 @@ +# locoex + +_locoex_ is an extention of loco. Classes with `COp` prefix enables *Custom Operation*. +In this version, a *custom operation* means one of the following: + +1. an op that is supported by Tensorflow but not supported both by the moco and the neurun +1. an op that is not supported by Tensorflow, moco, and the neurun + +`COpCall` node will represent IR entity that calls custom operations and kernels. diff --git a/compiler/locoex-customop/include/locoex/COpAttrTypes.h b/compiler/locoex-customop/include/locoex/COpAttrTypes.h new file mode 100644 index 00000000000..9fbd125d9aa --- /dev/null +++ b/compiler/locoex-customop/include/locoex/COpAttrTypes.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_COPATTRTYPES_H__ +#define __LOCOEX_COPATTRTYPES_H__ + +#include + +namespace locoex +{ + +/** + * @brief Tensorflow attribute type + * Refer to https://www.tensorflow.org/guide/extend/op#attr_types + */ +enum class COpAttrType +{ + Int, + Float, + // TODO Support more attr types such as String, Bool, DataType, Tensor, Shape, List +}; + +/** + * @brief Struct that holds attr type + */ +struct COpAttrData +{ +protected: + COpAttrData(COpAttrType attr_type) : _type(attr_type) {} + +public: + virtual ~COpAttrData() = default; + +public: + COpAttrType type() const { return _type; } + void type(COpAttrType attr_type) { _type = attr_type; } + +private: + COpAttrType _type; +}; + +/** + * @brief Struct that holds attr data of int type + */ +struct COpAttrInt final : public COpAttrData +{ +public: + COpAttrInt(int tf_val) : COpAttrData(COpAttrType::Int) { _val = tf_val; } + + int val() const { return _val; } + void val(int val) { _val = val; } + +private: + int _val; +}; + +/** + * @brief Struct that holds attr data of float type + */ +struct COpAttrFloat final : public COpAttrData +{ +public: + COpAttrFloat(float tf_val) : COpAttrData(COpAttrType::Float) { _val = tf_val; } + + float val() const { return _val; } + void val(float val) { _val = val; } + +private: + float _val; +}; + +template struct AttrTypeTrait; + +template <> struct AttrTypeTrait +{ + using Type = COpAttrFloat; +}; + +template <> struct AttrTypeTrait +{ + using Type = COpAttrInt; +}; + +// TODO support more attr types + +} // namespace locoex + +#endif // __LOCOEX_COPATTRTYPES_H__ diff --git a/compiler/locoex-customop/include/locoex/COpCall.h b/compiler/locoex-customop/include/locoex/COpCall.h new file mode 100644 index 00000000000..197fd8d0c22 --- /dev/null +++ b/compiler/locoex-customop/include/locoex/COpCall.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_COPCALL_H__ +#define __LOCOEX_COPCALL_H__ + +#include "VariadicArityNode.h" +#include "locoex/COpAttrTypes.h" +#include "locoex/COpNode.h" + +#include + +#include +#include + +namespace locoex +{ + +/** + * @brief Class to calls custom operation + */ +class COpCall final : public VariadicArityNode, + public loco::NodeMixin, + public loco::NodeMixin +{ +public: + COpCall(unsigned arity) : VariadicArityNode(arity) {} + +public: + void op(const std::string &op) { _op.assign(op); } + const std::string &op() { return _op; } + + void name(const std::string &name) { _name.assign(name); } + const std::string &name() { return _name; } + + void input(uint32_t nth, loco::Node *node) { at(nth)->node(node); } + loco::Node *input(uint32_t nth) const { return at(nth)->node(); } + + /// @brief Store [attr_name, attr_data] + void attr(const std::string &attr_name, std::unique_ptr &&attr_data); + + /// @brief Retrieve attr_data stored with attr_name + template + const typename AttrTypeTrait::Type *attr(const std::string &attr_name) const; + + /// @brief get all the names of attr + std::vector attr_names() const; + +private: + std::string _op; + std::string _name; + + std::map> _attrs; +}; + +} // namespace locoex + +#endif // __LOCOEX_COPCALL_H__ diff --git a/compiler/locoex-customop/include/locoex/COpDialect.h b/compiler/locoex-customop/include/locoex/COpDialect.h new file mode 100644 index 00000000000..86ca5a7a114 --- /dev/null +++ b/compiler/locoex-customop/include/locoex/COpDialect.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_COPDIALECT_H__ +#define __LOCOEX_COPDIALECT_H__ + +#include + +namespace locoex +{ + +/** + * @brief A singleton for locoex custom op Dialect + */ +class COpDialect final : public loco::Dialect +{ +private: + COpDialect() = default; + +public: + COpDialect(const Dialect &) = delete; + COpDialect(Dialect &&) = delete; + +public: + static loco::Dialect *get(void); +}; + +} // namespace locoex + +#endif // __LOCOEX_COPDIALECT_H__ diff --git a/compiler/locoex-customop/include/locoex/COpNode.h b/compiler/locoex-customop/include/locoex/COpNode.h new file mode 100644 index 00000000000..fce99d2d9f3 --- /dev/null +++ b/compiler/locoex-customop/include/locoex/COpNode.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_COPNODE_DECL_H__ +#define __LOCOEX_COPNODE_DECL_H__ + +#include +#include + +namespace locoex +{ + +struct COpNode : public loco::Node +{ + virtual ~COpNode() = default; + + const loco::Dialect *dialect(void) const final; + + uint32_t opnum(void) const final { return 0; /* opnum for custom op */ } +}; + +} // namespace locoex + +#endif // __LOCOEX_COPNODE_DECL_H__ diff --git a/compiler/locoex-customop/include/locoex/Service/COpFormattedGraph.h b/compiler/locoex-customop/include/locoex/Service/COpFormattedGraph.h new file mode 100644 index 00000000000..5decf4ecc5e --- /dev/null +++ b/compiler/locoex-customop/include/locoex/Service/COpFormattedGraph.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_FORMATTED_GRAPH_H__ +#define __LOCOEX_SERVICE_FORMATTED_GRAPH_H__ + +#include + +#include + +namespace locoex +{ + +class COpNodeSummaryBuilder final : public locop::NodeSummaryBuilder +{ +public: + COpNodeSummaryBuilder(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *node, locop::NodeSummary &s) const final; + +private: + bool summary(const locoex::COpCall *, locop::NodeSummary &) const; + +private: + const locop::SymbolTable *_tbl; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_FORMATTED_GRAPH_H__ diff --git a/compiler/locoex-customop/include/locoex/Service/COpShapeInferenceRule.h b/compiler/locoex-customop/include/locoex/Service/COpShapeInferenceRule.h new file mode 100644 index 00000000000..d2a332da4cd --- /dev/null +++ b/compiler/locoex-customop/include/locoex/Service/COpShapeInferenceRule.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_COP_SHAPE_INFERENCE_RULE_H__ +#define __LOCOEX_SERVICE_COP_SHAPE_INFERENCE_RULE_H__ + +#include +#include +#include +#include + +namespace locoex +{ + +/** + * @brief Shape inference rule for COpDialect + * + * @note the shape of inputs and output of CopCall must belong to loco::Domain::Tensor + */ +struct COpShapeInferenceRule final : public loco::ShapeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::NodeShape &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_COP_SHAPE_INFERENCE_RULE_H__ diff --git a/compiler/locoex-customop/include/locoex/Service/COpTypeInference.h b/compiler/locoex-customop/include/locoex/Service/COpTypeInference.h new file mode 100644 index 00000000000..13163a5deb3 --- /dev/null +++ b/compiler/locoex-customop/include/locoex/Service/COpTypeInference.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_SERVICE_TYPE_INFERENCE_H__ +#define __LOCOEX_SERVICE_TYPE_INFERENCE_H__ + +#include + +namespace locoex +{ + +/** + * @brief Type Inference Rule for COpDialect + */ +struct COpTypeInferenceRule final : public loco::TypeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::DataType &) const final; +}; + +} // namespace locoex + +#endif // __LOCOEX_SERVICE_TYPE_INFERENCE_H__ diff --git a/compiler/locoex-customop/include/locoex/VariadicArityNode.h b/compiler/locoex-customop/include/locoex/VariadicArityNode.h new file mode 100644 index 00000000000..fce754cdeb9 --- /dev/null +++ b/compiler/locoex-customop/include/locoex/VariadicArityNode.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOEX_VARIADICARITYNODES_OP_H__ +#define __LOCOEX_VARIADICARITYNODES_OP_H__ + +#include +#include + +#include +#include +#include + +namespace locoex +{ + +/** + * @brief Nodes with the variadic inputs + */ +template class VariadicArityNode : public Base +{ +public: + VariadicArityNode(uint32_t arity) + { + for (uint32_t n = 0; n < arity; ++n) + { + _args.emplace_back(std::move(std::unique_ptr{new loco::Use{this}})); + } + }; + + virtual ~VariadicArityNode() = default; + +public: + uint32_t arity(void) const final { return _args.size(); } + + loco::Node *arg(uint32_t n) const final + { + assert(n < _args.size()); + return _args.at(n)->node(); + } + + void drop(void) final + { + for (uint32_t n = 0; n < _args.size(); ++n) + { + _args.at(n)->node(nullptr); + } + } + +protected: + // This API allows inherited classes to access "_args" field. + loco::Use *at(uint32_t n) const + { + assert(n < _args.size()); + return _args.at(n).get(); + } + +private: + std::vector> _args; +}; + +} // namespace locoex + +#endif // __LOCOEX_VARIADICARITYNODES_OP_H__ diff --git a/compiler/locoex-customop/requires.cmake b/compiler/locoex-customop/requires.cmake new file mode 100644 index 00000000000..9127144f2c0 --- /dev/null +++ b/compiler/locoex-customop/requires.cmake @@ -0,0 +1,4 @@ +require("loco") +require("stdex") +require("locop") +require("pepper-str") diff --git a/compiler/locoex-customop/src/COpCall.cpp b/compiler/locoex-customop/src/COpCall.cpp new file mode 100644 index 00000000000..0299147581d --- /dev/null +++ b/compiler/locoex-customop/src/COpCall.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/COpCall.h" + +#include "locoex/COpAttrTypes.h" + +namespace locoex +{ + +template +const typename AttrTypeTrait::Type *COpCall::attr(const std::string &attr_name) const +{ + COpAttrData *attr_data; + auto found = _attrs.find(attr_name); + if (found != _attrs.end()) + { + attr_data = found->second.get(); + return dynamic_cast::Type *>(attr_data); + } + else + throw std::runtime_error("Cannot find requested attr"); +} + +void COpCall::attr(const std::string &attr_name, std::unique_ptr &&attr_data) +{ + if (_attrs.find(attr_name) == _attrs.end()) + _attrs[attr_name] = std::move(attr_data); + else + throw std::runtime_error("Attr already inserted"); +} + +std::vector COpCall::attr_names() const +{ + std::vector attr_names; + + for (auto it = _attrs.cbegin(); it != _attrs.cend(); ++it) + { + attr_names.emplace_back(it->first); + } + + return attr_names; +} + +#define INSTANTIATE(AT) \ + template const typename AttrTypeTrait::Type *COpCall::attr(const std::string &attr_name) \ + const; + +INSTANTIATE(COpAttrType::Float) +INSTANTIATE(COpAttrType::Int) + +#undef INSTANTIATE + +} // namespace locoex diff --git a/compiler/locoex-customop/src/COpCall.test.cpp b/compiler/locoex-customop/src/COpCall.test.cpp new file mode 100644 index 00000000000..d5f01d22db9 --- /dev/null +++ b/compiler/locoex-customop/src/COpCall.test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/COpCall.h" +#include "locoex/COpAttrTypes.h" + +#include +#include + +#include + +#include + +TEST(CallTest, Test_01) +{ + using namespace locoex; + + // attr name + std::string int_attr = "my_int"; + std::string float_attr = "my_float"; + + int int_val = 100; + float float_val = 3.14; + + // building loco test graph + auto g = loco::make_graph(); + + // generating input + auto inp = g->nodes()->create(); + { + inp->dtype(loco::DataType::FLOAT32); + inp->shape({1, 2}); + } + + // generating custom op + auto custom = g->nodes()->create(2U); + { + custom->input(0, inp); + custom->input(1, inp); + + custom->attr(int_attr, stdex::make_unique(int_val)); + custom->attr(float_attr, stdex::make_unique(float_val)); + } + + // access custom op input + loco::Node *input0 = custom->input(0); + loco::Node *input1 = custom->input(1); + + ASSERT_EQ(custom->arity(), 2); + ASSERT_EQ(dynamic_cast(input0), inp); + ASSERT_EQ(dynamic_cast(input1), inp); + + // access custom op attrs + auto names = custom->attr_names(); + + bool int_cheched = false, float_cheched = false; + + for (const auto &name : names) + { + if (auto int_attr = custom->attr(name)) + { + ASSERT_EQ(int_attr->val(), int_val); + int_cheched = true; + } + else if (auto float_attr = custom->attr(name)) + { + ASSERT_FLOAT_EQ(float_attr->val(), float_val); + float_cheched = true; + } + else + { + FAIL(); + } + } + + ASSERT_TRUE(int_cheched && float_cheched); +} diff --git a/compiler/locoex-customop/src/COpDialect.cpp b/compiler/locoex-customop/src/COpDialect.cpp new file mode 100644 index 00000000000..46b7f8dd835 --- /dev/null +++ b/compiler/locoex-customop/src/COpDialect.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/COpDialect.h" + +namespace locoex +{ + +loco::Dialect *COpDialect::get(void) +{ + static COpDialect d; + return &d; +} + +} // namespace locoex diff --git a/compiler/locoex-customop/src/COpDialect.test.cpp b/compiler/locoex-customop/src/COpDialect.test.cpp new file mode 100644 index 00000000000..b00bf21a949 --- /dev/null +++ b/compiler/locoex-customop/src/COpDialect.test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/COpDialect.h" + +#include + +TEST(COpDialectTest, get) +{ + auto d = locoex::COpDialect::get(); + + // get() SHOULD return a valid(non-null) pointer + ASSERT_NE(d, nullptr); + // The return value SHOULD be stable across multiple invocations + ASSERT_EQ(d, locoex::COpDialect::get()); +} diff --git a/compiler/locoex-customop/src/COpNode.cpp b/compiler/locoex-customop/src/COpNode.cpp new file mode 100644 index 00000000000..c489eedbcfc --- /dev/null +++ b/compiler/locoex-customop/src/COpNode.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/COpNode.h" +#include "locoex/COpDialect.h" + +namespace locoex +{ + +const loco::Dialect *COpNode::dialect(void) const { return COpDialect::get(); } + +} // namespace locoex diff --git a/compiler/locoex-customop/src/Service/COpFormattedGraph.cpp b/compiler/locoex-customop/src/Service/COpFormattedGraph.cpp new file mode 100644 index 00000000000..916663ec086 --- /dev/null +++ b/compiler/locoex-customop/src/Service/COpFormattedGraph.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/Service/COpFormattedGraph.h" + +#include +#include +#include + +#include + +#include +#include + +namespace locoex +{ + +bool COpNodeSummaryBuilder::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (node->dialect() != locoex::COpDialect::get()) + return false; + + if (auto call_node = dynamic_cast(node)) + { + return summary(call_node, s); + } + + return false; +} + +bool COpNodeSummaryBuilder::summary(const locoex::COpCall *node, locop::NodeSummary &s) const +{ + assert(node != nullptr); + + s.opname("COp.Call"); + for (uint32_t i = 0; i < node->arity(); i++) + s.args().append(pepper::str("input_", i), _tbl->lookup(node->arg(i))); + + for (auto name : node->attr_names()) + { + if (auto int_attr = node->attr(name)) + s.args().append(name, pepper::str(int_attr->val())); + else if (auto float_attr = node->attr(name)) + s.args().append(name, pepper::str(float_attr->val())); + else + throw std::runtime_error("Not yet supported Attr Type"); + } + + s.state(locop::NodeSummary::State::Complete); + return true; +} + +} // namespace locoex diff --git a/compiler/locoex-customop/src/Service/COpShapeInferenceRule.cpp b/compiler/locoex-customop/src/Service/COpShapeInferenceRule.cpp new file mode 100644 index 00000000000..4dc8f461f9b --- /dev/null +++ b/compiler/locoex-customop/src/Service/COpShapeInferenceRule.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/Service/COpShapeInferenceRule.h" + +#include "locoex/COpDialect.h" +#include "locoex/COpNode.h" +#include "locoex/COpCall.h" + +#include + +#include + +namespace locoex +{ + +bool COpShapeInferenceRule::recognize(const loco::Dialect *d) const +{ + return COpDialect::get() == d; +} + +bool COpShapeInferenceRule::infer(const loco::Node *node, loco::NodeShape &shape) const +{ + assert(node->dialect() == COpDialect::get()); + assert(dynamic_cast(node) != nullptr); + + auto cop_call = dynamic_cast(node); + + // Note that the shape of custom op is considered as TensorShape + // TODO Decide how to deal with this shape error cases + for (uint32_t n = 0; n < cop_call->arity(); n++) + if (loco::shape_get(cop_call->input(n)).domain() != loco::Domain::Tensor) + throw std::runtime_error("Input of custom op must belong to Tensor domain."); + + loco::TensorShape out_shape; + + out_shape.rank(cop_call->rank()); + for (uint32_t d = 0; d < cop_call->rank(); d++) + out_shape.dim(d) = cop_call->dim(d); + + shape.set(out_shape); + + return true; +} + +} // namespace locoex diff --git a/compiler/locoex-customop/src/Service/COpShapeInferenceRule.test.cpp b/compiler/locoex-customop/src/Service/COpShapeInferenceRule.test.cpp new file mode 100644 index 00000000000..c86931ba7ca --- /dev/null +++ b/compiler/locoex-customop/src/Service/COpShapeInferenceRule.test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/Service/COpShapeInferenceRule.h" +#include "locoex/COpCall.h" +#include + +#include + +TEST(COpShapeInferenceRuleTest, minimal) +{ + // Create a simple network + auto g = loco::make_graph(); + + auto call_node = g->nodes()->create(0); + call_node->shape({1, 3}); + + auto push_node = g->nodes()->create(); + push_node->from(call_node); + + auto graph_output = g->outputs()->create(); + graph_output->name("output"); + loco::link(graph_output, push_node); + + // pre-check + ASSERT_FALSE(loco::shape_known(call_node)); + + // Run Shape Inference + locoex::COpShapeInferenceRule rule; + + loco::apply(&rule).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::shape_known(call_node)); + ASSERT_EQ(loco::shape_get(call_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(call_node).as(); + ASSERT_EQ(shape.rank(), 2); + ASSERT_EQ(shape.dim(0), 1); + ASSERT_EQ(shape.dim(1), 3); +} diff --git a/compiler/locoex-customop/src/Service/COpTypeInference.cpp b/compiler/locoex-customop/src/Service/COpTypeInference.cpp new file mode 100644 index 00000000000..b41454eb276 --- /dev/null +++ b/compiler/locoex-customop/src/Service/COpTypeInference.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/Service/COpTypeInference.h" + +#include "locoex/COpDialect.h" +#include "locoex/COpCall.h" + +#include + +namespace locoex +{ + +bool COpTypeInferenceRule::recognize(const loco::Dialect *d) const +{ + // This rule recognizes only "COpDialect" dialect! + return COpDialect::get() == d; +} + +bool COpTypeInferenceRule::infer(const loco::Node *node, loco::DataType &dtype) const +{ + assert(node->dialect() == COpDialect::get()); + + auto customop = dynamic_cast(node); + + assert(customop != nullptr); + assert(customop->dtype() != loco::DataType::Unknown); + + dtype = customop->dtype(); + + return true; +} + +} // namespace locoex diff --git a/compiler/locoex-customop/src/Service/COpTypeInference.test.cpp b/compiler/locoex-customop/src/Service/COpTypeInference.test.cpp new file mode 100644 index 00000000000..97ddd861830 --- /dev/null +++ b/compiler/locoex-customop/src/Service/COpTypeInference.test.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include +#include +#include + +#include +#include + +#include + +TEST(TypeInferenceRuleTest, COpTypeInference) +{ + // Create a simple Relu6 network + auto g = loco::make_graph(); + + auto pull_node = g->nodes()->create(); + pull_node->dtype(loco::DataType::FLOAT32); + + auto call_node = g->nodes()->create(1); + call_node->input(0, pull_node); + call_node->dtype(loco::DataType::FLOAT32); + + auto push_node = g->nodes()->create(); + push_node->from(call_node); + + auto graph_input = g->inputs()->create(); + + graph_input->name("input"); + loco::link(graph_input, pull_node); + + auto graph_output = g->outputs()->create(); + + graph_output->name("output"); + loco::link(graph_output, push_node); + + // Run Type Inference + locoex::COpTypeInferenceRule cop_rule; + loco::CanonicalTypeInferenceRule canon_rule; + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(locoex::COpDialect::get(), &cop_rule).bind(loco::CanonicalDialect::get(), &canon_rule); + + loco::apply(&rules).to(g.get()); + + // Verify! + ASSERT_TRUE(loco::dtype_known(call_node)); + ASSERT_EQ(loco::dtype_get(call_node), loco::DataType::FLOAT32); +} diff --git a/compiler/locoex-customop/src/VariadicArityNode.test.cpp b/compiler/locoex-customop/src/VariadicArityNode.test.cpp new file mode 100644 index 00000000000..a618824e53a --- /dev/null +++ b/compiler/locoex-customop/src/VariadicArityNode.test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locoex/VariadicArityNode.h" + +#include + +#include + +namespace +{ +using namespace locoex; + +class TestNode : public VariadicArityNode +{ +public: + TestNode(uint32_t arity) : VariadicArityNode(arity) {} + + void input(uint32_t idx, loco::Node *node) { at(idx)->node(node); } + loco::Node *input(uint32_t idx) const { return at(idx)->node(); } + + const loco::Dialect *dialect(void) const { return nullptr; } // this won't be called for testing + uint32_t opnum(void) const { return -1; } // this won't be called for testing +}; + +class ZeroInputNode : public TestNode +{ +public: + ZeroInputNode() : TestNode(0) {} +}; + +class BinaryInputNode : public TestNode +{ +public: + BinaryInputNode() : TestNode(2) {} +}; +} + +TEST(CustomOpTest, VariadicArityNode_arity_0) +{ + loco::Pull pull; + + ZeroInputNode z_node; + + ASSERT_EQ(z_node.arity(), 0); +} + +TEST(CustomOpTest, VariadicArityNode_arity_2) +{ + loco::Pull pull_00, pull_01; + + BinaryInputNode b_node; + b_node.input(0, &pull_00); + b_node.input(1, &pull_01); + + ASSERT_EQ(b_node.arity(), 2); + ASSERT_EQ(b_node.input(0), &pull_00); + ASSERT_EQ(b_node.input(1), &pull_01); +} diff --git a/compiler/locomotiv/CMakeLists.txt b/compiler/locomotiv/CMakeLists.txt new file mode 100644 index 00000000000..5c0156b78b7 --- /dev/null +++ b/compiler/locomotiv/CMakeLists.txt @@ -0,0 +1,29 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(locomotiv STATIC ${SOURCES}) +set_target_properties(locomotiv PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(locomotiv PUBLIC include) +target_include_directories(locomotiv PRIVATE src) +target_link_libraries(locomotiv PUBLIC loco) +target_link_libraries(locomotiv PUBLIC angkor) +target_link_libraries(locomotiv PRIVATE stdex) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(locomotiv PRIVATE nncc_common) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(locomotiv_test ${TESTS}) +target_include_directories(locomotiv_test PRIVATE src) +target_link_libraries(locomotiv_test locomotiv) + +add_test(locomotiv_test locomotiv_test) diff --git a/compiler/locomotiv/README.md b/compiler/locomotiv/README.md new file mode 100644 index 00000000000..9569f6ea3a7 --- /dev/null +++ b/compiler/locomotiv/README.md @@ -0,0 +1,90 @@ +# locomotiv +_locomotiv_ is a reference interpreter for _loco_ IR. + +# Purpose +- _locomotiv_ would serve as code level specification and reference implementation for loco IR. +- _locomotiv_ is required for loco-related tools to be tested. + +# Sample code to use locomotiv library +This sample code shows how to use locomotiv. Please refer to `src/Session.test.cpp` as well for actual usage. +```cpp +template using Buffer = nncc::core::ADT::tensor::Buffer + +loco::Graph *graph; +// ... building graph ... + +// Open interpreter session +locomotiv::Session sess(graph); + +for (uint32_t i = 0; i < s.input_size(); ++i) +{ + Buffer buffer; + // ... building buffer ... + + locomotiv::NodeData input_data = locomotiv::make_data(buffer); + + sess.set_input(i, input_data); +} + +// Run inference +sess.infer(); + +// Query inferred output +locomotiv::NodeData *output_data = sess.get_output(query_index); + +// Get buffer according to data type +switch(output_data->dtype()) +{ +case loco::DataType::S32: +{ + Buffer output_buffer = output_data->as_s32_bufptr(); + // Do something + break; +} +case loco::DataType::FLOAT32: +{ + Buffer output_buffer = output_data->as_f32_bufptr(); + // Do something + break; +} +// ... +} +``` + +# How to support new loco node execution: recommended guide + +## Steps to support new loco node +1. First of all, understand semantics of the node to newly support, especially on calculation spec and valid use cases. +2. Add the node to `locomotiv/src/Node.lst`. Please keep alphabetical order. This automatically declares `NodeExecution::execute(TheNode *)` and updates `NodeExecution::run()` to deal with the node. +3. Define `execute(loco::TheNode *)` at `locomotiv/src/Node/TheNode.cpp`. +4. Test new node execution at `locomotiv/src/Node/TheNode.test.cpp` if possible. + +### Note on internal data layout rule +For each domain(see `loco::Domain`), `locomotiv` has fixed layout rule on how to store its data in memory. +- Feature is represented as NHWC layout + - That is number of batch(N), height(H), width(W) and channel depth(C) +- Filter is represented as NHWC layout + - That is number of filter(N), height(H), width(W) and input channel depth(C) +- DepthwiseFilter is represented as HWCM layout + - That is height(H), width(W), input channel depth(C) and depth multiplier(M) +- Matrix is represented as HW layout + - That is height(H), width(W) + +### Notes on step 3 +- Mocking Tensorflow lite `reference_op.h` might be a good place to start. +- `execute()` can be called multiple time. It just recalculates and updates annotated data. So it should `erase_annot_data()` before newly `annot_data()`. +- Most node execution behaviour would be implemented for each data type. +- `execute()` should throw runtime error on invalid cases. Some of these cases are explained: + - Invalid argument node + - e.g.) Pull -> MaxPool2D is invalid as MaxPool2D requires feature map as its argument. + - Lack of argument data + - e.g.) Given 'Pull -> Push' graph. On execution of Push, if no NodeData annotated to Pull, it is invalid. + - Mismatch of argument shapes + - e.g.) Addition between 2x2 and 3x3 tensor is invalid + - e.g.) MaxPool2D expects its ifm to be 4D feature, otherwise invalid. + - Mismatch between node's own information and inferred information + - Some node already have attributes like shape or data type. If inferred information is different with existing node's, it is invalid. + +### Recommendation on step 4 (test) +- If the node has no arguments, create a node object and `NodeExecution::run()` on it. Check whether it operates correctly. +- If the node has N(>= 1) arguments, make N pull node inputs, source them to the node to be tested. FeatureEncode or FilterEncode node may be required inbetween depending on the node's argument type. Then annotate N pull nodes with its data, `NodeExecution::run()` on the node to test, and check whether it operates correctly. diff --git a/compiler/locomotiv/include/locomotiv/NodeData.h b/compiler/locomotiv/include/locomotiv/NodeData.h new file mode 100644 index 00000000000..c9960db46f2 --- /dev/null +++ b/compiler/locomotiv/include/locomotiv/NodeData.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_NODEDATA_H_ +#define _LOCOMOTIV_NODEDATA_H_ + +#include +#include +#include + +#include + +namespace locomotiv +{ + +/** + * @brief Read-only no-template wrapper for 'Buffer'. Serves interface for input + * and output of 'Session'. + * + * @note Once NodeData is created, it is not modifiable. + */ +struct NodeData +{ + template using Buffer = nncc::core::ADT::tensor::Buffer; + using Shape = nncc::core::ADT::tensor::Shape; + + virtual ~NodeData() = default; + + virtual const loco::DataType &dtype() const = 0; + + virtual const Shape *shape() const = 0; + + // TODO Support more data types + virtual const Buffer *as_s32_bufptr() const = 0; + virtual const Buffer *as_f32_bufptr() const = 0; +}; + +/** + * @brief Copy buffer to make NodeData + * + * @note NodeData is read-only. You may prepare buffer with ALL data, then call + * this function to make data. + */ +template std::unique_ptr make_data(const NodeData::Buffer
&buffer); + +} // namespace locomotiv + +#endif // _LOCOMOTIV_NODEDATA_H_ diff --git a/compiler/locomotiv/include/locomotiv/Session.h b/compiler/locomotiv/include/locomotiv/Session.h new file mode 100644 index 00000000000..3268d60b3c7 --- /dev/null +++ b/compiler/locomotiv/include/locomotiv/Session.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_SESSION_H_ +#define _LOCOMOTIV_SESSION_H_ + +#include "locomotiv/NodeData.h" + +#include + +#include +#include + +namespace locomotiv +{ + +/** + * @brief Session for loco graph inference + */ +class Session final +{ +public: + Session() = delete; + + /// @brief Make Session for graph with graph outputs themselves + Session(loco::Graph *g) : Session(g, loco::output_nodes(g)) + { + // DO NOTHING + } + + /** + * @brief Make Session for graph with selective custom outputs. Only + * subgraph to calculate given outputs would be executed. + * + * @note Set required inputs for given outputs, or inference may fail. + * @note custom_outputs don't need to be graph output, but can be any nodes + * in the middle of the graph. + * @warn This approach may fail in case of graph with control flow + */ + Session(loco::Graph *g, const std::vector &custom_outputs) + : _graph(g), _outputs(custom_outputs) + { + // DO NOTHING + } + + /// @brief Make Session by range + template + Session(loco::Graph *g, InputIt begin, InputIt end) : _graph(g), _outputs(begin, end) + { + // DO NOTHING + } + + /// @brief Free all node annotations of the graph assigned by this Session + ~Session(); + + /// @brief Get number of graph inputs held by this Session + uint32_t input_size() const { return _graph->inputs()->size(); } + + /** + * @brief Set graph input at specific index by NodeData. + * + * @throw runtime_error In case when another NodeData already annotated for the + * input, and when given data type or shape are not + * congruent with loco node information. + */ + void set_input(uint32_t index, std::unique_ptr &&data); + + /** + * @brief Do inference for this session and graph + * + * @note Multiple run is possible. Abort program when inputs are not fully set + * or invalid calculation found in the middle. + */ + void infer(); + + /// @brief Get number of graph outputs held by this Session + uint32_t output_size() const { return _outputs.size(); } + + /** + * @brief Get output of graph as NodeData + * + * @note May return nullptr, for example, when graph output not yet calculated + */ + const NodeData *get_output(uint32_t index); + + const loco::Node *get_output_node(uint32_t index) { return _outputs.at(index); } + +private: + loco::Graph *_graph; + std::vector _outputs; +}; + +} // namespace locomotiv + +#endif // _LOCOMOTIV_SESSION_H_ diff --git a/compiler/locomotiv/requires.cmake b/compiler/locomotiv/requires.cmake new file mode 100644 index 00000000000..1c09aa13dd3 --- /dev/null +++ b/compiler/locomotiv/requires.cmake @@ -0,0 +1,2 @@ +require("angkor") +require("stdex") diff --git a/compiler/locomotiv/src/Node.lst b/compiler/locomotiv/src/Node.lst new file mode 100644 index 00000000000..be3b105204a --- /dev/null +++ b/compiler/locomotiv/src/Node.lst @@ -0,0 +1,40 @@ +#ifndef NODE +#error Define NODE first +#endif // NODE + +// NODE(Name) : alphabetic order please + +NODE(AvgPool2D) +NODE(BiasAdd) +NODE(BiasAdd) +NODE(BiasEncode) +NODE(ConstGen) +NODE(Conv2D) +NODE(DepthwiseConv2D) +NODE(DepthwiseFilterEncode) +NODE(EltwiseAdd) +NODE(EltwiseDiv) +NODE(EltwiseMax) +NODE(EltwiseMul) +NODE(EltwiseSqrt) +NODE(EltwiseSub) +NODE(FeatureDecode) +NODE(FeatureEncode) +NODE(FilterEncode) +NODE(Forward) +NODE(MatrixDecode) +NODE(MatrixEncode) +NODE(MatMul) +NODE(MaxPool2D) +NODE(Pull) +NODE(Push) +NODE(ReLU) +NODE(ReLU6) +NODE(Reshape) +NODE(Tanh) +NODE(TensorBroadcast) +NODE(TensorConcat) +NODE(TensorConstantPad) +NODE(TensorReduce) +NODE(TensorSoftmax) +NODE(TransposedConv2D) diff --git a/compiler/locomotiv/src/Node/AvgPool2D.cpp b/compiler/locomotiv/src/Node/AvgPool2D.cpp new file mode 100644 index 00000000000..ad603badf4a --- /dev/null +++ b/compiler/locomotiv/src/Node/AvgPool2D.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +/** + * @brief Compute 1D output size based on given 1D arguments. + * + * @param whole_pad Sum of front and back pad + */ +inline uint32_t compute_out_size(uint32_t image_size, uint32_t whole_pad, uint32_t filter_size, + uint32_t stride) +{ + assert((image_size + whole_pad - filter_size) % stride == 0); + return (image_size + whole_pad - filter_size) / stride + 1; +} + +template +nncc::core::ADT::tensor::Buffer avgPool2D(const loco::AvgPool2D *avgpool2d, + const Buffer *ifm_buf) +{ + assert(avgpool2d->convention() == loco::AvgPool2D::Convention::Valid || + avgpool2d->convention() == loco::AvgPool2D::Convention::Full); + + auto ifm_shape = ifm_buf->shape(); + + const uint32_t batches = ifm_shape.dim(0); + const uint32_t depth = ifm_shape.dim(3); + + const uint32_t ifm_height = ifm_shape.dim(1); + const uint32_t ifm_width = ifm_shape.dim(2); + + const uint32_t window_height = avgpool2d->window()->vertical(); + const uint32_t window_width = avgpool2d->window()->horizontal(); + + const uint32_t stride_height = avgpool2d->stride()->vertical(); + const uint32_t stride_width = avgpool2d->stride()->horizontal(); + + const uint32_t pad_top = avgpool2d->pad()->top(); + const uint32_t pad_bottom = avgpool2d->pad()->bottom(); + + const uint32_t pad_left = avgpool2d->pad()->left(); + const uint32_t pad_right = avgpool2d->pad()->right(); + + const uint32_t output_height = + compute_out_size(ifm_height, pad_top + pad_bottom, window_height, stride_height); + const uint32_t output_width = + compute_out_size(ifm_width, pad_left + pad_right, window_width, stride_width); + + // prepare output buffer + Shape output_shape{batches, output_height, output_width, depth}; + auto output_buf = make_buffer(output_shape); + + for (uint32_t batch = 0; batch < batches; ++batch) + { + for (uint32_t out_y = 0; out_y < output_height; ++out_y) + { + for (uint32_t out_x = 0; out_x < output_width; ++out_x) + { + for (uint32_t channel = 0; channel < depth; ++channel) + { + const int in_x_origin = (out_x * stride_width) - pad_left; + const int in_y_origin = (out_y * stride_height) - pad_top; + + uint32_t f_x0, f_x1, f_y0, f_y1; + if (avgpool2d->convention() == loco::AvgPool2D::Convention::Valid) + { + f_x0 = std::max(0, -in_x_origin); + f_x1 = std::min(window_width, ifm_width - in_x_origin); + f_y0 = std::max(0, -in_y_origin); + f_y1 = std::min(window_height, ifm_height - in_y_origin); + } + else + { + throw std::runtime_error("TODO support AvgPool2D::Convention::Full"); + } + const uint32_t filter_x_start = f_x0; + const uint32_t filter_x_end = f_x1; + + const uint32_t filter_y_start = f_y0; + const uint32_t filter_y_end = f_y1; + + T total = 0; + uint32_t filter_ele_count = 0; + + for (uint32_t filter_y = filter_y_start; filter_y < filter_y_end; ++filter_y) + { + for (uint32_t filter_x = filter_x_start; filter_x < filter_x_end; ++filter_x) + { + const uint32_t in_x = in_x_origin + filter_x; + const uint32_t in_y = in_y_origin + filter_y; + total += ifm_buf->at(Index({batch, in_y, in_x, channel})); + filter_ele_count++; + } + } + + assert(filter_ele_count > 0); + output_buf.at(Index({batch, out_y, out_x, channel})) = total / filter_ele_count; + } + } + } + } + + return output_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::AvgPool2D *avgpool2d) +{ + auto ifm_data = annot_data(avgpool2d->ifm()); + + validate(ifm_data, "Can't find input data of AvgPool2D"); + validate(ifm_data->shape()->rank() == 4, "IFM rank should be 4"); + validate(annot_domain(avgpool2d->ifm()) == loco::Domain::Feature, + "ifm of AvgPool2D is not Feature"); + + std::unique_ptr avgpool2d_data = nullptr; + + switch (ifm_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto ifm_buf = ifm_data->as_f32_bufptr(); + + auto avgpool2d_buf = avgPool2D(avgpool2d, ifm_buf); + + avgpool2d_data = make_data(avgpool2d_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(avgpool2d_data != nullptr); + + annot_data(avgpool2d, std::move(avgpool2d_data)); + annot_domain(avgpool2d, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/AvgPool2D.test.cpp b/compiler/locomotiv/src/Node/AvgPool2D.test.cpp new file mode 100644 index 00000000000..89e10a35ed8 --- /dev/null +++ b/compiler/locomotiv/src/Node/AvgPool2D.test.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +void run_test(const float *ifm, const float *expected_ofm, const Shape &ifm_shape, + const Shape &ofm_shape, const uint32_t window_v, const uint32_t window_h, + const uint32_t stride_v, const uint32_t stride_h, const uint32_t pad_top, + const uint32_t pad_bottom, const uint32_t pad_left, const uint32_t pad_right) +{ + // Let's make FeatureEncode-AvgPool2D graph + auto g = loco::make_graph(); + auto enc = g->nodes()->create(); + + // Fill output data of FeatureEncode from ifm + auto enc_buf = make_buffer(ifm_shape); + + auto ifm_overlay = make_overlay(ifm_shape, const_cast(ifm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ifm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + enc_buf.at(ind) = ifm_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(enc_buf); + locomotiv::annot_data(enc, std::move(enc_data)); + locomotiv::annot_domain(enc, loco::Domain::Feature); + + // build TF AvgPool2D + auto avgpool2d = g->nodes()->create(); + avgpool2d->ifm(enc); + avgpool2d->convention(loco::AvgPool2D::Convention::Valid); + avgpool2d->window()->vertical(window_v); + avgpool2d->window()->horizontal(window_h); + avgpool2d->stride()->vertical(stride_v); + avgpool2d->stride()->horizontal(stride_h); + avgpool2d->pad()->top(pad_top); + avgpool2d->pad()->bottom(pad_bottom); + avgpool2d->pad()->left(pad_left); + avgpool2d->pad()->right(pad_right); + + // run interpreter + locomotiv::NodeExecution::get().run(avgpool2d); + + // get result of calculation + auto avgpool2d_data = locomotiv::annot_data(avgpool2d); + + // check the result + ASSERT_NE(avgpool2d_data, nullptr); + ASSERT_TRUE(avgpool2d_data->dtype() == loco::DataType::FLOAT32); + ASSERT_TRUE(*(avgpool2d_data->shape()) == ofm_shape); + + auto ofm_overlay = + make_overlay(ofm_shape, const_cast(expected_ofm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ofm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ASSERT_FLOAT_EQ(avgpool2d_data->as_f32_bufptr()->at(ind), ofm_overlay.at(ind)); + } + + ASSERT_EQ(locomotiv::annot_domain(avgpool2d), loco::Domain::Feature); +} + +} // namespace + +// clang-format off +/* ifm and ofm are from the code below: +import tensorflow as tf + +value = tf.constant([[[[-0.281157], [-1.0601869], [-0.622261], [-1.1777412]], + [[1.4411974], [0.01408334], [0.06958964], [-0.08663343]], + [[1.3424183], [-0.89015573], [0.2520576], [0.04843695]], + [[-1.6668711], [-0.02187406], [1.9362065], [1.3341236]]]]) +avgpool = tf.nn.avg_pool(value, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding= 'VALID', + data_format="NHWC") +with tf.Session() as sess: + print(sess.run(avgpool)) +*/ +TEST(NodeExecution_AvgPool2D, f32_1x4x4x1_calculation) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + -0.281157, -1.0601869, -0.622261, -1.1777412, + 1.4411974, 0.01408334, 0.06958964, -0.08663343, + 1.3424183, -0.89015573, 0.2520576, 0.04843695, + -1.6668711, -0.02187406, 1.9362065, 1.3341236 + }; + + const float ofm[] = + { + 0.02848421, -0.45426148, + -0.30912063, 0.89270616 + }; + + run_test(ifm, ofm, + Shape{1, 4, 4, 1}, Shape{1, 2, 2, 1}, // input shape , output shape + 2, 2, // kernel + 2, 2, // stride + 0, 0, 0, 0 // padding + ); +} +// clang-format on + +// clang-format off +/* ifm and ofm are from the code below: +import tensorflow as tf + +value = tf.constant([[[[-0.281157], [-1.0601869], [-0.622261]], + [[1.4411974], [0.01408334], [0.06958964]], + [[1.3424183], [-0.89015573], [0.2520576]]]]) +avgpool = tf.nn.avg_pool(value, ksize = [1, 2, 2, 1], strides = [1, 1, 1, 1], padding= 'SAME', + data_format="NHWC") +with tf.Session() as sess: + print(sess.run(avgpool)) +*/ +TEST(NodeExecution_AvgPool2D, f32_1x3x3x1_calculation) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + -0.281157, -1.0601869, -0.622261, + 1.4411974, 0.01408334, 0.06958964, + 1.3424183, -0.89015573, 0.2520576 + }; + + const float ofm[] = + { + 0.02848421, -0.39969373, -0.2763357, + 0.4768858, -0.13860628, 0.16082363, + 0.22613129, -0.31904906, 0.2520576 + }; + + run_test(ifm, ofm, + Shape{1, 3, 3, 1}, Shape{1, 3, 3, 1}, // input shape , output shape + 2, 2, // kernel + 1, 1, // stride + 0, 1, 0, 1 // padding + ); +} +// clang-format on diff --git a/compiler/locomotiv/src/Node/BiasAdd.cpp b/compiler/locomotiv/src/Node/BiasAdd.cpp new file mode 100644 index 00000000000..0724fb7286c --- /dev/null +++ b/compiler/locomotiv/src/Node/BiasAdd.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include + +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +#include +#include + +namespace +{ +using locomotiv::NodeData; + +std::unique_ptr calc(const NodeData *input_data, const NodeData *bias_data, + uint32_t axis); + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::BiasAdd *bias_add) +{ + auto input_data = locomotiv::annot_data(bias_add->value()); + auto bias_data = locomotiv::annot_data(bias_add->bias()); + + validate(input_data && bias_data, "Input not ready"); + validate(locomotiv::annot_domain(bias_add->value()) == loco::Domain::Tensor && + locomotiv::annot_domain(bias_add->bias()) == loco::Domain::Bias, + "Wrong input domain"); + + std::unique_ptr bias_add_data = calc(input_data, bias_data, bias_add->axis()); + + assert(bias_add_data != nullptr); + annot_data(bias_add, std::move(bias_add_data)); + annot_domain(bias_add, annot_domain(bias_add->value())); +} + +void NodeExecution::execute(loco::BiasAdd *bias_add) +{ + auto input_data = locomotiv::annot_data(bias_add->value()); + auto bias_data = locomotiv::annot_data(bias_add->bias()); + + validate(input_data && bias_data, "Input not ready"); + validate(locomotiv::annot_domain(bias_add->value()) == loco::Domain::Feature && + locomotiv::annot_domain(bias_add->bias()) == loco::Domain::Bias, + "Wrong input domain"); + + std::unique_ptr bias_add_data = calc(input_data, bias_data, 3); + + assert(bias_add_data != nullptr); + annot_data(bias_add, std::move(bias_add_data)); + annot_domain(bias_add, loco::Domain::Feature); +} + +} // namespace locomotiv + +namespace +{ +using locomotiv::NodeData; +using locomotiv::validate; +using locomotiv::make_data; + +std::unique_ptr calc(const NodeData *input_data, const NodeData *bias_data, uint32_t axis) +{ + validate(input_data->shape()->dim(axis) == bias_data->shape()->dim(0), "Bias size mismatch"); + + std::unique_ptr bias_add_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto bias_bufptr = bias_data->as_f32_bufptr(); + auto bias_add_buf = make_buffer(*input_data->shape()); + + auto *shape = input_data->shape(); + + for (IndexEnumerator e{*shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + nncc::core::ADT::tensor::Index bias_index({index.at(axis)}); + bias_add_buf.at(index) = input_bufptr->at(index) + bias_bufptr->at(bias_index); + } + + bias_add_data = make_data(bias_add_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + return bias_add_data; +} + +} // namespace diff --git a/compiler/locomotiv/src/Node/BiasAdd.test.cpp b/compiler/locomotiv/src/Node/BiasAdd.test.cpp new file mode 100644 index 00000000000..0ca826673c5 --- /dev/null +++ b/compiler/locomotiv/src/Node/BiasAdd.test.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + + inp = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) + bias = tf.constant([1.1, 2.1], shape=[2], dtype=tf.float32) + out = tf.nn.bias_add(inp, bias) + + with tf.Session() as sess: + print(sess.run(out)) + */ + +TEST(NodeExecution_TensorBiasAdd, f32) +{ + float in_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float bias_val[] = {1.1, 2.1}; + float out_val[] = {2.1, 4.1, 4.1, 6.1, 6.1, 8.1, 8.1, 10.1, 10.1, + 12.1, 12.1, 14.1, 14.1, 16.1, 16.1, 18.1, 18.1, 20.1}; + + // make BiasAdd(Pull, Const) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp = g->nodes()->create(); + { + inp->dtype(loco::DataType::FLOAT32); + inp->shape({1, 3, 3, 2}); + } + + auto bias = g->nodes()->create(); + { + // nothing to do + } + + auto bias_add = g->nodes()->create>(); + { + bias_add->value(inp); + bias_add->bias(bias); + bias_add->axis(3); // axis(3) means C in NHWC + } + + // Make and assign data to pull node + auto inp_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_buf.shape()}; e.valid(); e.advance()) + { + inp_buf.at(e.current()) = in_val[n++]; + } + } + + auto bias_buf = make_buffer(Shape{2}); + { + int n = 0; + for (IndexEnumerator e{bias_buf.shape()}; e.valid(); e.advance()) + { + bias_buf.at(e.current()) = bias_val[n++]; + } + } + + auto inp_data = locomotiv::make_data(inp_buf); + locomotiv::annot_data(inp, std::move(inp_data)); + locomotiv::annot_domain(inp, loco::Domain::Tensor); + + auto bias_data = locomotiv::make_data(bias_buf); + locomotiv::annot_data(bias, std::move(bias_data)); + locomotiv::annot_domain(bias, loco::Domain::Bias); + + locomotiv::NodeExecution::get().run(bias_add); + + auto bias_add_data = locomotiv::annot_data(bias_add); + + // comparing the result + ASSERT_NE(bias_add_data, nullptr); + ASSERT_EQ(bias_add_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(bias_add_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(bias_add_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(bias_add_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(bias_add), loco::Domain::Tensor); +} + +/* +test case generated from the following: + + inp = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) + bias = tf.constant([1.1, 2.1], shape=[2], dtype=tf.float32) + out = tf.nn.bias_add(inp, bias) + + with tf.Session() as sess: + print(sess.run(out)) + */ + +TEST(NodeExecution_FeatureBiasAdd, f32) +{ + float in_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float bias_val[] = {1.1, 2.1}; + float out_val[] = {2.1, 4.1, 4.1, 6.1, 6.1, 8.1, 8.1, 10.1, 10.1, + 12.1, 12.1, 14.1, 14.1, 16.1, 16.1, 18.1, 18.1, 20.1}; + + // make FeatureBiasAdd(FeatureEncode, BiasEncode) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto feature_encode = g->nodes()->create(); + { + // setting values is ignored for testing + } + + auto bias = g->nodes()->create(); + { + // nothing to do + } + + auto feature_bias_add = g->nodes()->create>(); + { + feature_bias_add->value(feature_encode); + feature_bias_add->bias(bias); + } + + // Make and assign data to pull node + auto inp_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_buf.shape()}; e.valid(); e.advance()) + { + inp_buf.at(e.current()) = in_val[n++]; + } + } + + auto bias_buf = make_buffer(Shape{2}); + { + int n = 0; + for (IndexEnumerator e{bias_buf.shape()}; e.valid(); e.advance()) + { + bias_buf.at(e.current()) = bias_val[n++]; + } + } + + auto inp_data = locomotiv::make_data(inp_buf); + locomotiv::annot_data(feature_encode, std::move(inp_data)); + locomotiv::annot_domain(feature_encode, loco::Domain::Feature); + + auto bias_data = locomotiv::make_data(bias_buf); + locomotiv::annot_data(bias, std::move(bias_data)); + locomotiv::annot_domain(bias, loco::Domain::Bias); + + locomotiv::NodeExecution::get().run(feature_bias_add); + + auto bias_add_data = locomotiv::annot_data(feature_bias_add); + + // comparing the result + ASSERT_NE(bias_add_data, nullptr); + ASSERT_EQ(bias_add_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(bias_add_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(bias_add_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(bias_add_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(feature_bias_add), loco::Domain::Feature); +} diff --git a/compiler/locomotiv/src/Node/BiasEncode.cpp b/compiler/locomotiv/src/Node/BiasEncode.cpp new file mode 100644 index 00000000000..c2f2b44c02a --- /dev/null +++ b/compiler/locomotiv/src/Node/BiasEncode.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::BiasEncode *bias_enc) +{ + auto input_data = annot_data(bias_enc->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(bias_enc->input()) == loco::Domain::Tensor, + "Input domain should be Tensor"); + validate(input_data->shape()->rank() == 1, "Input data rank must be 1"); + + std::unique_ptr bias_enc_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_bufptr = input_data->as_s32_bufptr(); + bias_enc_data = make_data(*input_bufptr); + break; + } + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + bias_enc_data = make_data(*input_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(bias_enc_data != nullptr); + annot_data(bias_enc, std::move(bias_enc_data)); + annot_domain(bias_enc, loco::Domain::Bias); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/BiasEncode.test.cpp b/compiler/locomotiv/src/Node/BiasEncode.test.cpp new file mode 100644 index 00000000000..73e2af8a84d --- /dev/null +++ b/compiler/locomotiv/src/Node/BiasEncode.test.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Buffer; + +namespace +{ +template loco::DataType loco_dtype() { throw std::runtime_error("Not supported yet"); } +template <> loco::DataType loco_dtype() { return loco::DataType::S32; } +template <> loco::DataType loco_dtype() { return loco::DataType::FLOAT32; } + +template const Buffer *as_bufptr(const locomotiv::NodeData *data) +{ + throw std::runtime_error("Not supported yet"); +} +template <> const Buffer *as_bufptr(const locomotiv::NodeData *data) +{ + return data->as_s32_bufptr(); +} +template <> const Buffer *as_bufptr(const locomotiv::NodeData *data) +{ + return data->as_f32_bufptr(); +} + +template void test() +{ + // Make pull-BiasEncode graph + auto g = loco::make_graph(); + + auto pull = g->nodes()->create(); + { + pull->dtype(loco_dtype()); + pull->shape({1}); + } + + auto bias_enc = g->nodes()->create(); + { + bias_enc->input(pull); + } + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + { + pull_buf.at(Index{0}) = static_cast(100); + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + } + + locomotiv::NodeExecution::get().run(bias_enc); + + // check + auto bias_enc_data = locomotiv::annot_data(bias_enc); + + ASSERT_NE(bias_enc_data, nullptr); + ASSERT_EQ(bias_enc_data->dtype(), loco_dtype()); + ASSERT_EQ(*(bias_enc_data->shape()), Shape{1}); + ASSERT_EQ(as_bufptr(bias_enc_data)->at(Index{0}), pull_buf.at(Index{0})); + + ASSERT_EQ(locomotiv::annot_domain(bias_enc), loco::Domain::Bias); +} +} // namespace + +TEST(NodeExecution_BiasEncode, s32) { test(); } + +TEST(NodeExecution_BiasEncode, f32) { test(); } diff --git a/compiler/locomotiv/src/Node/ConstGen.cpp b/compiler/locomotiv/src/Node/ConstGen.cpp new file mode 100644 index 00000000000..0360b9fef16 --- /dev/null +++ b/compiler/locomotiv/src/Node/ConstGen.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include + +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +namespace +{ + +/** + * @brief Get offset based on given shape and index. Assume lexical layout. + * + * examples) + * For shape = {3, 4} and index = {1, 2}, + * offset would be 6 ( = 1 * (4) + 2 ) + * For shape = {2, 3, 4} and index = {1, 0, 2}, + * offset would be 14 ( = 1 * (3*4) + 0 *(4) + 2 ) + */ +inline uint32_t offset_by_index(const Shape &shape, const Index &index) +{ + static const nncc::core::ADT::tensor::LexicalLayout l; + return l.offset(shape, index); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::ConstGen *constgen) +{ + uint32_t volume = 1; + + Shape shape; + shape.resize(constgen->rank()); + for (uint32_t i = 0; i < shape.rank(); ++i) + { + shape.dim(i) = constgen->dim(i).value(); + volume *= shape.dim(i); + } + + std::unique_ptr data = nullptr; + + switch (constgen->dtype()) + { + case loco::DataType::S32: + { + assert(volume == constgen->size()); + + auto buf = make_buffer(shape); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + uint32_t offset = ::offset_by_index(shape, index); + buf.at(index) = constgen->at(offset); + } + + data = locomotiv::make_data(buf); + break; + } + case loco::DataType::FLOAT32: + { + assert(volume == constgen->size()); + + auto buf = make_buffer(shape); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + uint32_t offset = ::offset_by_index(shape, index); + buf.at(index) = constgen->at(offset); + } + + data = locomotiv::make_data(buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(data != nullptr); + annot_data(constgen, std::move(data)); + annot_domain(constgen, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/ConstGen.test.cpp b/compiler/locomotiv/src/Node/ConstGen.test.cpp new file mode 100644 index 00000000000..838f4c11de5 --- /dev/null +++ b/compiler/locomotiv/src/Node/ConstGen.test.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_ConstGen, s32) +{ + // Make ConstGen node + loco::ConstGen constgen; + + constgen.dtype(loco::DataType::S32); + constgen.shape({2, 3}); + constgen.size(6); + + constgen.at(0) = 0; // Set 0,0 + constgen.at(1) = 1; // Set 0,1 + constgen.at(2) = 2; // Set 0,2 + constgen.at(3) = -3; // Set 1,0 + constgen.at(4) = -4; // Set 1,1 + constgen.at(5) = -5; // Set 1,2 + + // run execution + locomotiv::NodeExecution::get().run(&constgen); + + // test + auto data = locomotiv::annot_data(&constgen); + ASSERT_NE(data, nullptr); + ASSERT_EQ(data->dtype(), loco::DataType::S32); + ASSERT_EQ(*data->shape(), Shape({2, 3})); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{0, 0}), 0); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{0, 1}), 1); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{0, 2}), 2); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{1, 0}), -3); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{1, 1}), -4); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{1, 2}), -5); + + ASSERT_EQ(locomotiv::annot_domain(&constgen), loco::Domain::Tensor); +} + +TEST(NodeExecution_ConstGen, f32) +{ + // Make ConstGen node + loco::ConstGen constgen; + + constgen.dtype(loco::DataType::FLOAT32); + constgen.shape({2, 3}); + constgen.size(6); + + constgen.at(0) = 0.0f; // Set 0,0 + constgen.at(1) = 1.0f; // Set 0,1 + constgen.at(2) = 2.0f; // Set 0,2 + constgen.at(3) = 3.0f; // Set 1,0 + constgen.at(4) = 4.0f; // Set 1,1 + constgen.at(5) = 5.0f; // Set 1,2 + + // run execution + locomotiv::NodeExecution::get().run(&constgen); + + // test + auto data = locomotiv::annot_data(&constgen); + ASSERT_NE(data, nullptr); + ASSERT_EQ(data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*data->shape(), Shape({2, 3})); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{0, 0}), 0.0f); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{0, 1}), 1.0f); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{0, 2}), 2.0f); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{1, 0}), 3.0f); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{1, 1}), 4.0f); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{1, 2}), 5.0f); + + ASSERT_EQ(locomotiv::annot_domain(&constgen), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/Conv2D.cpp b/compiler/locomotiv/src/Node/Conv2D.cpp new file mode 100644 index 00000000000..2e4185574cb --- /dev/null +++ b/compiler/locomotiv/src/Node/Conv2D.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ +// image size includes padding. +inline uint32_t compute_out_size(uint32_t image_size, uint32_t filter_size, uint32_t stride) +{ + assert((image_size + stride - filter_size) % stride == 0); + return (image_size + stride - filter_size) / stride; +} + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +/** + * @brief Calculates Conv2D + * @note Both input_buf and filter_buf have NHWC format + */ +template +Buffer calc_conv2D(const loco::Conv2D *conv2d, const Buffer *input_buf, + const Buffer *filter_buf) +{ + auto input_shape = input_buf->shape(); + auto filter_shape = filter_buf->shape(); + + locomotiv::validate(input_shape.rank() == 4, "ifm rank must be 4"); + locomotiv::validate(filter_shape.rank() == 4, "filter rank must be 4"); + locomotiv::validate(input_shape.dim(3) == filter_shape.dim(3), + "channel value mismatch"); // should have same channel values + + const uint32_t input_height = input_shape.dim(1); + const uint32_t input_width = input_shape.dim(2); + + const uint32_t filter_height = filter_shape.dim(1); + const uint32_t filter_width = filter_shape.dim(2); + + const uint32_t stride_width = conv2d->stride()->horizontal(); + const uint32_t stride_height = conv2d->stride()->vertical(); + + // TODO Enable dilations. Let's set these to 1 for now. + const uint32_t dilation_width_factor = 1; + const uint32_t dilation_height_factor = 1; + + const uint32_t pad_top = conv2d->pad()->top(); + const uint32_t pad_bottom = conv2d->pad()->bottom(); + + const uint32_t pad_left = conv2d->pad()->left(); + const uint32_t pad_right = conv2d->pad()->right(); + + const uint32_t output_height = + compute_out_size(input_height + pad_top + pad_bottom, filter_height, stride_height); + const uint32_t output_width = + compute_out_size(input_width + pad_left + pad_right, filter_width, stride_width); + + const uint32_t batches = input_shape.dim(0); + const uint32_t input_depth = input_shape.dim(3); + const uint32_t output_depth = filter_shape.dim(0); + + Shape output_shape{batches, output_height, output_width, output_depth}; + auto output_buf = make_buffer(output_shape); + + for (uint32_t batch = 0; batch < batches; ++batch) + { + for (uint32_t out_y = 0; out_y < output_height; ++out_y) + { + for (uint32_t out_x = 0; out_x < output_width; ++out_x) + { + for (uint32_t out_channel = 0; out_channel < output_depth; ++out_channel) + { + const int in_x_origin = (out_x * stride_width) - pad_left; + const int in_y_origin = (out_y * stride_height) - pad_top; + + RET_T total = static_cast(0); + + for (uint32_t filter_y = 0; filter_y < filter_height; ++filter_y) + { + for (uint32_t filter_x = 0; filter_x < filter_width; ++filter_x) + { + for (uint32_t in_channel = 0; in_channel < input_depth; ++in_channel) + { + const int32_t in_x = in_x_origin + dilation_width_factor * filter_x; + const int32_t in_y = in_y_origin + dilation_height_factor * filter_y; + + // If the location is outside the bounds of the input image, + // use zero as a default value. + if ((in_x >= 0) && ((unsigned)in_x < input_width) && (in_y >= 0) && + ((unsigned)in_y < input_height)) + { + auto input_value = + input_buf->at(Index({batch, (unsigned)in_y, (unsigned)in_x, in_channel})); + auto filter_value = + filter_buf->at(Index({out_channel, filter_y, filter_x, in_channel})); + total += (input_value * filter_value); + } + } + } + } + output_buf.at(Index({batch, out_y, out_x, out_channel})) = total; + } + } + } + } + return output_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Conv2D *conv2d) +{ + auto ifm_data = annot_data(conv2d->ifm()); + auto ker_data = annot_data(conv2d->ker()); + + validate(ifm_data, "Can't find input data of Conv2D"); + validate(ifm_data->shape()->rank() == 4, "ifm rank must be 4"); + + validate(ker_data, "Can't find kernel data of Conv2D"); + validate(ker_data->shape()->rank() == 4, "Kernel rank must be 4"); + + validate(annot_domain(conv2d->ifm()) == loco::Domain::Feature, "IFM of Conv2D is not feature"); + validate(annot_domain(conv2d->ker()) == loco::Domain::Filter, "Kernel of Conv2D is not filter"); + + std::unique_ptr conv2d_result = nullptr; + + if (ifm_data->dtype() == loco::DataType::FLOAT32 && ker_data->dtype() == loco::DataType::FLOAT32) + { + auto ifm_buf = ifm_data->as_f32_bufptr(); + auto ker_buf = ker_data->as_f32_bufptr(); + + auto conv2d_buf = calc_conv2D(conv2d, ifm_buf, ker_buf); + + conv2d_result = make_data(conv2d_buf); + } + else + throw std::runtime_error("NYI for these DataTypes"); + + assert(conv2d_result != nullptr); + + annot_data(conv2d, std::move(conv2d_result)); + annot_domain(conv2d, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Conv2D.test.cpp b/compiler/locomotiv/src/Node/Conv2D.test.cpp new file mode 100644 index 00000000000..83d7fc268a1 --- /dev/null +++ b/compiler/locomotiv/src/Node/Conv2D.test.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +void run_test(const float *ifm, const float *ker, const float *expected_ofm, const Shape &ifm_shape, + const Shape ker_shape, const Shape ofm_shape, const uint32_t stride_v, + const uint32_t stride_h, const uint32_t pad_top = 0, const uint32_t pad_bottom = 0, + const uint32_t pad_left = 0, const uint32_t pad_right = 0) +{ + auto g = loco::make_graph(); + + // Fill output data of FeatureEncode from ifm + auto ifm_enc = g->nodes()->create(); + { + auto ifm_enc_buf = make_buffer(ifm_shape); + auto ifm_overlay = make_overlay(ifm_shape, const_cast(ifm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ifm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ifm_enc_buf.at(ind) = ifm_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ifm_enc_buf); + locomotiv::annot_data(ifm_enc, std::move(enc_data)); + locomotiv::annot_domain(ifm_enc, loco::Domain::Feature); + } + + // Fill output data of FilterEncode from ker + auto ker_enc = g->nodes()->create(); + { + auto ker_enc_buf = make_buffer(ker_shape); + auto ker_overlay = make_overlay(ker_shape, const_cast(ker)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ker_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ker_enc_buf.at(ind) = ker_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ker_enc_buf); + locomotiv::annot_data(ker_enc, std::move(enc_data)); + locomotiv::annot_domain(ker_enc, loco::Domain::Filter); + } + + // build Conv2D + auto conv2d = g->nodes()->create(); + conv2d->ifm(ifm_enc); + conv2d->ker(ker_enc); + conv2d->stride()->vertical(stride_v); + conv2d->stride()->horizontal(stride_h); + conv2d->pad()->top(pad_top); + conv2d->pad()->bottom(pad_bottom); + conv2d->pad()->left(pad_left); + conv2d->pad()->right(pad_right); + + // run interpreter + locomotiv::NodeExecution::get().run(conv2d); + + // get result of calculation + auto conv2d_result = locomotiv::annot_data(conv2d); + + // check the result + ASSERT_NE(conv2d_result, nullptr); + ASSERT_TRUE(conv2d_result->dtype() == loco::DataType::FLOAT32); + ASSERT_TRUE(*(conv2d_result->shape()) == ofm_shape); + + auto ofm_overlay = + make_overlay(ofm_shape, const_cast(expected_ofm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ofm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ASSERT_FLOAT_EQ(conv2d_result->as_f32_bufptr()->at(ind), ofm_overlay.at(ind)); + } + + ASSERT_EQ(locomotiv::annot_domain(conv2d), loco::Domain::Feature); +} + +} // namespace + +// clang-format off +/* ifm and ofm are from the code below: + +ifm = tf.random_normal([1, 5, 5, 1], stddev=1) +ker = tf.random_normal([3, 3, 1, 1], stddev=1) +out = tf.nn.conv2d(ifm, ker, strides = [1, 2, 2, 1], padding= 'VALID') + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_Conv2D, f32_1x5x5x1_calculation) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + -0.48850584, 1.4292705, -1.3424522, -0.7441476, -1.8964586, + 1.7021934, -0.39246717, 0.6248314, 0.12724274, 1.3915083, + 0.382255, 0.7725081, 0.9171561, -1.1847119, 0.61858755, + 1.1530193, -0.476239, -0.9038663, -0.48764458, 0.339963, + 2.2817912, -0.8464133, -1.0598192, 0.8361126, 1.2344601 + }; + + const float ker[] = + { + -0.0830195, 0.21088193, -0.11781317, + 0.07755677, 1.6337638, 1.0792778, + -1.6922939, -1.5437212, 0.96667504 + }; + + const float ofm[] = + { + -0.28752697, 2.8108592, + -5.220376 , 0.7973861 + }; + + run_test(ifm, ker, ofm, + Shape{1, 5, 5, 1}, Shape{1, 3, 3, 1}, Shape{1, 2, 2, 1}, // shapes of input, ker, output + 2, 2 // stride + ); +} + +TEST(NodeExecution_Conv2D, f32_multiple_channel) +{ + // testing channel != 1, stride = [1,1] + using nncc::core::ADT::tensor::Shape; + + float ifm[1*5*5*3]; + for (int n = 0; n < 5*5*3; n++) ifm[n] = 2.2; + + float ker[2*2*2*3]; // nhwc + for (int n = 0; n < 2*2*2*3; n++) ker[n] = 1.1; + + float ofm[1*4*4*2]; + for (int n = 0; n < 1*4*4*2; n++) ofm[n] = 29.04; + + run_test(ifm, ker, ofm, + Shape{1, 5, 5, 3}, Shape{2, 2, 2, 3}, Shape{1, 4, 4, 2}, // shapes of input, ker, output + 1, 1 // stride + ); +} + +/* ifm and ofm are from the code below: +tensorflow version : 1.12.0 + +import tensorflow as tf + +ifm = tf.constant([-1.3653529, 0.4160791, 0.5059157, 0.7649683, 0.39364856, + -1.0164733, 1.506766, -1.1413091, 1.2766701, -0.9253511, + 1.3570246, 0.32089928, -0.9898171, 1.983792, -0.3423274, + -1.1901658, 1.2288222, -0.47401968, -0.01369802, 0.4136331, + 0.06960588, -0.16537654, -0.65015996, -0.555224, 0.7140603 +], shape=[1, 5, 5, 1]) + +ker = tf.constant([2.3490515, -0.4572366, 0.05790535, + 0.3672005, 0.52679914, 0.74607974, + -1.7211207, 1.1174419, -0.59663385 +], shape=[3, 3, 1, 1]) + +ofm = tf.nn.conv2d(ifm, ker, strides=[1, 1, 1, 1], padding='SAME') + +with tf.Session() as sess: + print(sess.run(ofm)) +*/ +TEST(NodeExecution_Conv2D, with_padding) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + -1.3653529, 0.4160791, 0.5059157, 0.7649683, 0.39364856, + -1.0164733, 1.506766, -1.1413091, 1.2766701, -0.9253511, + 1.3570246, 0.32089928, -0.9898171, 1.983792, -0.3423274, + -1.1901658, 1.2288222, -0.47401968, -0.01369802, 0.4136331, + 0.06960588, -0.16537654, -0.65015996, -0.555224, 0.7140603 + }; + + const float ker[] = + { + 2.3490515, -0.4572366, 0.05790535, + 0.3672005, 0.52679914, 0.74607974, + -1.7211207, 1.1174419, -0.59663385 + }; + + const float ofm[] = + { + -2.443676, 4.2094254, -3.6403496, 4.8254814, -2.743059, + 2.5620093, -5.185688, -1.1470609, 4.54913, -2.1985974, + -0.5567835, 0.49045527, 2.5752437, -2.3383713, 4.455967, + -0.13562866, 2.9236434, 1.4019353, -3.0521483, 6.782954, + 0.5286269, -3.9317036, 2.285041, -1.0817666, -0.04901773 + }; + + run_test(ifm, ker, ofm, + Shape{1, 5, 5, 1}, Shape{1, 3, 3, 1}, Shape{1, 5, 5, 1}, // shapes of input, ker, output + 1, 1, // stride + 1, 1, 1, 1 // padding + ); +} +// clang-format on diff --git a/compiler/locomotiv/src/Node/DepthwiseConv2D.cpp b/compiler/locomotiv/src/Node/DepthwiseConv2D.cpp new file mode 100644 index 00000000000..92d5aa16177 --- /dev/null +++ b/compiler/locomotiv/src/Node/DepthwiseConv2D.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + +/** + * @brief Compute 1D output size based on given 1D arguments. + * + * @param whole_pad Sum of front and back pad + */ +inline uint32_t compute_out_size(uint32_t image_size, uint32_t whole_pad, uint32_t filter_size, + uint32_t stride) +{ + assert((image_size + whole_pad - filter_size) % stride == 0); + return (image_size + whole_pad - filter_size) / stride + 1; +} + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +/** + * @brief Calculates DepthwiseConv2D + * @note ifm_buf has NHWC and ker_buf HWCM format + * (Please check locomotiv README for further information) + */ +template +Buffer calc_dw_conv2d(const loco::DepthwiseConv2D *dw_conv2d, const Buffer *ifm_buf, + const Buffer *ker_buf) +{ + auto ifm_shape = ifm_buf->shape(); + auto ker_shape = ker_buf->shape(); + + locomotiv::validate(ifm_shape.rank() == 4, "ifm rank must be 4"); + locomotiv::validate(ker_shape.rank() == 4, "depthwise filter rank must be 4"); + locomotiv::validate(ifm_shape.dim(3 /* of NHWC */) == ker_shape.dim(2 /* of HWCM */), + "channel value mismatch"); // should have same channel values + + const uint32_t ifm_height = ifm_shape.dim(1); + const uint32_t ifm_width = ifm_shape.dim(2); + + const uint32_t ker_height = ker_shape.dim(0); + const uint32_t ker_width = ker_shape.dim(1); + + const uint32_t stride_width = dw_conv2d->stride()->horizontal(); + const uint32_t stride_height = dw_conv2d->stride()->vertical(); + + // TODO Enable dilations. Let's set these to 1 for now. + const uint32_t dilation_width_factor = 1; + const uint32_t dilation_height_factor = 1; + + const uint32_t pad_top = dw_conv2d->pad()->top(); + const uint32_t pad_bottom = dw_conv2d->pad()->bottom(); + + const uint32_t pad_left = dw_conv2d->pad()->left(); + const uint32_t pad_right = dw_conv2d->pad()->right(); + + const uint32_t ofm_height = + compute_out_size(ifm_height, pad_top + pad_bottom, ker_height, stride_height); + const uint32_t ofm_width = + compute_out_size(ifm_width, pad_left + pad_right, ker_width, stride_width); + + const uint32_t batches = ifm_shape.dim(0); + const uint32_t ifm_depth = ifm_shape.dim(3); + const uint32_t multiplier = ker_shape.dim(3); + const uint32_t ofm_depth = ifm_depth * multiplier; + + Shape ofm_shape{batches, ofm_height, ofm_width, ofm_depth}; + auto ofm_buf = make_buffer(ofm_shape); + + for (uint32_t batch = 0; batch < batches; ++batch) + { + for (uint32_t ofm_y = 0; ofm_y < ofm_height; ++ofm_y) + { + for (uint32_t ofm_x = 0; ofm_x < ofm_width; ++ofm_x) + { + for (uint32_t ch = 0; ch < ifm_depth; ++ch) + { + for (uint32_t nth = 0; nth < multiplier; nth++) + { + const int in_x_origin = (ofm_x * stride_width) - pad_left; + const int in_y_origin = (ofm_y * stride_height) - pad_top; + float total = 0.f; + for (uint32_t ker_y = 0; ker_y < ker_height; ++ker_y) + { + for (uint32_t ker_x = 0; ker_x < ker_width; ++ker_x) + { + const int in_x = in_x_origin + dilation_width_factor * ker_x; + const int in_y = in_y_origin + dilation_height_factor * ker_y; + // If the location is outside the bounds of the input image, + // use zero as a default value. + if ((in_x >= 0) && ((unsigned)in_x < ifm_width) && (in_y >= 0) && + ((unsigned)in_y < ifm_height)) + { + auto ifm_value = ifm_buf->at(Index({batch, (unsigned)in_y, (unsigned)in_x, ch})); + auto ker_value = ker_buf->at(Index({ker_y, ker_x, ch, nth})); + total += (ifm_value * ker_value); + } + } + } + uint32_t ofm_channel = ch * multiplier + nth; + ofm_buf.at(Index({batch, ofm_y, ofm_x, ofm_channel})) = total; + } + } + } + } + } + return ofm_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::DepthwiseConv2D *dw_conv2d) +{ + auto ifm_data = annot_data(dw_conv2d->ifm()); + auto ker_data = annot_data(dw_conv2d->ker()); + + validate(ifm_data, "Can't find input data of DepthwiseConv2D"); + validate(ifm_data->shape()->rank() == 4, "ifm rank must be 4"); + + validate(ker_data, "Can't find kernel data of DepthwiseConv2D"); + validate(ker_data->shape()->rank() == 4, "Kernel rank must be 4"); + + validate(annot_domain(dw_conv2d->ifm()) == loco::Domain::Feature, + "IFM of DepthwiseConv2D is not feature"); + validate(annot_domain(dw_conv2d->ker()) == loco::Domain::DepthwiseFilter, + "Kernel of DepthwiseConv2D is not depthwise filter"); + + std::unique_ptr dw_conv2d_result = nullptr; + + if (ifm_data->dtype() == loco::DataType::FLOAT32 && ker_data->dtype() == loco::DataType::FLOAT32) + { + auto ifm_buf = ifm_data->as_f32_bufptr(); + auto ker_buf = ker_data->as_f32_bufptr(); + + auto dw_conv2d_buf = calc_dw_conv2d(dw_conv2d, ifm_buf, ker_buf); + + dw_conv2d_result = make_data(dw_conv2d_buf); + } + else + throw std::runtime_error("NYI for these DataTypes"); + + assert(dw_conv2d_result != nullptr); + + annot_data(dw_conv2d, std::move(dw_conv2d_result)); + annot_domain(dw_conv2d, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/DepthwiseConv2D.test.cpp b/compiler/locomotiv/src/Node/DepthwiseConv2D.test.cpp new file mode 100644 index 00000000000..48824c2e043 --- /dev/null +++ b/compiler/locomotiv/src/Node/DepthwiseConv2D.test.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +void run_test(const float *ifm, const float *ker, const float *expected_ofm, const Shape &ifm_shape, + const Shape ker_shape, const Shape ofm_shape, const uint32_t stride_v, + const uint32_t stride_h, const uint32_t pad_top = 0, const uint32_t pad_bottom = 0, + const uint32_t pad_left = 0, const uint32_t pad_right = 0) +{ + auto g = loco::make_graph(); + + // Fill output data of FeatureEncode from ifm + auto ifm_enc = g->nodes()->create(); + { + auto ifm_enc_buf = make_buffer(ifm_shape); + auto ifm_overlay = make_overlay(ifm_shape, const_cast(ifm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ifm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ifm_enc_buf.at(ind) = ifm_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ifm_enc_buf); + locomotiv::annot_data(ifm_enc, std::move(enc_data)); + locomotiv::annot_domain(ifm_enc, loco::Domain::Feature); + } + + // Fill output data of DepthwiseFilterEncode from ker + auto ker_enc = g->nodes()->create(); + { + auto ker_enc_buf = make_buffer(ker_shape); + auto ker_overlay = make_overlay(ker_shape, const_cast(ker)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ker_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ker_enc_buf.at(ind) = ker_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ker_enc_buf); + locomotiv::annot_data(ker_enc, std::move(enc_data)); + locomotiv::annot_domain(ker_enc, loco::Domain::DepthwiseFilter); + } + + // build DepthwiseConv2D + auto dw_conv2d = g->nodes()->create(); + dw_conv2d->ifm(ifm_enc); + dw_conv2d->ker(ker_enc); + dw_conv2d->stride()->vertical(stride_v); + dw_conv2d->stride()->horizontal(stride_h); + dw_conv2d->pad()->top(pad_top); + dw_conv2d->pad()->bottom(pad_bottom); + dw_conv2d->pad()->left(pad_left); + dw_conv2d->pad()->right(pad_right); + + // run interpreter + locomotiv::NodeExecution::get().run(dw_conv2d); + + // get result of calculation + auto dw_conv2d_result = locomotiv::annot_data(dw_conv2d); + + // check the result + ASSERT_NE(dw_conv2d_result, nullptr); + ASSERT_TRUE(dw_conv2d_result->dtype() == loco::DataType::FLOAT32); + ASSERT_TRUE(*(dw_conv2d_result->shape()) == ofm_shape); + + auto ofm_overlay = + make_overlay(ofm_shape, const_cast(expected_ofm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ofm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ASSERT_FLOAT_EQ(dw_conv2d_result->as_f32_bufptr()->at(ind), ofm_overlay.at(ind)); + } + + ASSERT_EQ(locomotiv::annot_domain(dw_conv2d), loco::Domain::Feature); +} + +} // namespace + +// clang-format off + +/* ifm, ker and ofm are from the code below: + +ifm = tf.random_normal([1, 5, 5, 2], stddev=1.1) +ker = tf.random_normal([4, 4, 2, 3], stddev=1.1) +out = tf.nn.depthwise_conv2d(ifm, ker, strides = [1, 1, 1, 1], padding= 'VALID') + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_DepthwiseConv2D, f32_random_valid) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = {0.8122538, 1.209147, 0.6903842, -0.26646265, 1.516799, -1.8540707, + -0.74240327, 1.7811562, -0.03699546, -0.44468504, -1.4982721, -1.1858582, + -0.21140318, -0.974522, 1.0000849, -1.294535, -0.6108882, 0.25827602, + 1.3631831, -0.5180266, 0.20870179, 0.18333802, -0.42263857, -1.6694735, + 0.0415236, -0.3903758, 2.0933757, -0.29660916, 2.1218338, -1.1599928, + 0.57163256, 0.48865932, -1.3622656, 0.35924262, 1.2951899, -0.1769997, + 0.74513537, -0.31920406, -1.2902768, -0.7095059, 1.9157801, -0.41028237, + 1.2502829, 0.3354887, 1.4199319, -0.20366786, -0.8828556, 0.5173567, + 1.7708117, -0.30096334}; + const float ker[] = { + -0.19805557, 0.58464956, -0.7804337, 0.06974592, 0.45790604, 0.24833807, 0.43393376, + 0.2541043, -0.04406675, -0.32167575, 1.0546446, -1.4978354, 0.20829494, 1.1659569, + 0.37908667, -0.94137955, 0.293349, -1.1023049, 0.76133233, 0.55595005, 1.4458209, + 1.6128604, 1.5655615, -2.183877, -0.90535915, -0.49858555, 1.7168728, -1.1590382, + 0.6706056, 1.2215618, -0.06603386, 0.16559464, 0.541991, -0.44488335, 0.766181, + 1.0227629, -0.6352362, -1.670828, -0.63334507, 0.0313305, -0.6721083, 0.50112915, + -0.15218066, 0.67222077, -0.3613627, -0.08516614, -0.5024078, -0.9503976, -2.1892295, + 1.8308185, -0.15187284, 1.5761136, 0.24869336, -1.7378871, -0.22518761, 1.0175673, + 0.7084485, -0.74157554, -1.8185995, -1.3330095, -0.04427439, 1.0556892, -0.68243974, + 0.32001218, 2.0901792, -1.1612813, 0.7294674, 0.05740008, -0.00832882, 1.0446658, + 0.4477195, -0.09174404, -1.0176039, 1.5066665, -2.148343, 0.29421416, 0.93011874, + -0.15737922, -1.6444012, 0.25780794, -0.6545867, -0.3488956, 0.26167992, -0.154414, + 0.2798124, -0.8590068, 2.0494444, 0.48268002, 0.81941164, -0.4848027, 0.76870304, + 0.7102261, 0.45778143, 0.23214905, -0.17742023, -0.75016516}; + const float ofm[] = {4.474646, 0.6792067, -1.9799856, 7.484751, 4.3087378, -1.905938, + 1.4887369, 0.4361322, 0.79539883, -3.8583446, -4.502204, 4.356392, + -5.3030324, 3.493003, -4.349277, 2.3069482, -3.8881323, -0.73901534, + -0.6629516, 2.1247253, -4.9229584, 1.6716996, -3.0208125, 1.0597891}; + + run_test(ifm, ker, ofm, + Shape{1, 5, 5, 2}, Shape{4, 4, 2, 3}, Shape{1, 2, 2, 6}, // shapes of input, ker, output + 1, 1 // stride + ); +} + +// TODO Add same padding test + +// clang-format on diff --git a/compiler/locomotiv/src/Node/DepthwiseFilterEncode.cpp b/compiler/locomotiv/src/Node/DepthwiseFilterEncode.cpp new file mode 100644 index 00000000000..17004901fc4 --- /dev/null +++ b/compiler/locomotiv/src/Node/DepthwiseFilterEncode.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +/** + * @brief Encode input tensor into depthwise filter represented in "HWCM" layout + * + * (Please check locomotiv README for further information) + */ +template +std::unique_ptr dw_filter_encode(const loco::DepthwiseFilterEncode *node, + const Buffer *input_buf) +{ + auto encoder = node->encoder(); + + // Make TensorShape from input + loco::TensorShape input_shape; + input_shape.rank(input_buf->shape().rank()); + assert(input_shape.rank() == 4); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + { + input_shape.dim(i) = input_buf->shape().dim(i); + } + + loco::DepthwiseFilterShape node_shape = encoder->shape(input_shape); + + // Make HWCM (i.e. height, width, depth, multiplier) buffer from DepthwiseFilterShape + Buffer node_buf = make_buffer( + Shape{node_shape.height().value(), node_shape.width().value(), node_shape.depth().value(), + node_shape.multiplier().value()}); + + // Copy buffer in an order arranged by encoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::DepthwiseFilterIndex index; + index.row() = e.current().at(0); + index.column() = e.current().at(1); + index.channel() = e.current().at(2); + index.nth() = e.current().at(3); + + node_buf.at(e.current()) = input_buf->at(encoder->value(index)); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::DepthwiseFilterEncode *enc) +{ + auto input_data = annot_data(enc->input()); + + validate(input_data, "Input of DepthwiseFilterEncode not ready"); + validate(annot_domain(enc->input()) == loco::Domain::Tensor, + "Input of DepthwiseFilterEncode is not Tensor"); + validate(input_data->shape()->rank() == 4, "Input shape mismatch"); + + std::unique_ptr enc_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + enc_data = dw_filter_encode(enc, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(enc_data != nullptr); + annot_data(enc, std::move(enc_data)); + annot_domain(enc, loco::Domain::DepthwiseFilter); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/DepthwiseFilterEncode.test.cpp b/compiler/locomotiv/src/Node/DepthwiseFilterEncode.test.cpp new file mode 100644 index 00000000000..db828c08bc9 --- /dev/null +++ b/compiler/locomotiv/src/Node/DepthwiseFilterEncode.test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include + +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +TEST(NodeExecution_DepthwiseFilterEncode, f32) +{ + const uint32_t H = 2; + const uint32_t W = 3; + const uint32_t C = 4; + const uint32_t M = 5; + + auto g = loco::make_graph(); + + // Pull + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + + // Make and assign "MHWC" data to pull node + auto pull_buf = make_buffer(Shape{M, H, W, C}); + float f = 1; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = f; + f += 0.1f; // Doesn't matter what it is + } + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + // Encoder to correctly read input tensor as MHWC + auto encoder = std::unique_ptr>( + new loco::PermutingEncoder); + encoder->perm()->axis(loco::DepthwiseFilterAxis::Multiplier) = 0; + encoder->perm()->axis(loco::DepthwiseFilterAxis::Height) = 1; + encoder->perm()->axis(loco::DepthwiseFilterAxis::Width) = 2; + encoder->perm()->axis(loco::DepthwiseFilterAxis::Depth) = 3; + + // DepthwiseFilterEncode + auto enc = g->nodes()->create(); + enc->input(pull); + enc->encoder(std::move(encoder)); + + locomotiv::NodeExecution::get().run(enc); + + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(enc_data->shape()), (Shape{H, W, C, M})); // locomotiv depthwise filter is HWCM + auto enc_buf = enc_data->as_f32_bufptr(); + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + for (uint32_t m = 0; m < M; ++m) + ASSERT_FLOAT_EQ(pull_buf.at(Index{m, h, w, c}), enc_buf->at(Index{h, w, c, m})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::DepthwiseFilter); +} diff --git a/compiler/locomotiv/src/Node/EltwiseAdd.cpp b/compiler/locomotiv/src/Node/EltwiseAdd.cpp new file mode 100644 index 00000000000..e5e2d67c734 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseAdd.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseAdd *eltwise_add) +{ + struct Func final : public BinaryFunc + { + float apply(float lhs, float rhs) const { return lhs + rhs; } + }; + + Func f; + + eltwise_binary(eltwise_add, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseAdd.test.cpp b/compiler/locomotiv/src/Node/EltwiseAdd.test.cpp new file mode 100644 index 00000000000..2899dccdd9b --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseAdd.test.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + +x = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +y = tf.constant([-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18], + shape=[1, 3, 3, 2], dtype=tf.float32) +out = tf.math.add(x, y) + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_EltwiseAdd, f32) +{ + float x_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float y_val[] = {-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18}; + float out_val[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + // make EltwiseAdd(Pull, Pull) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp_lhs = g->nodes()->create(); + { + inp_lhs->dtype(loco::DataType::FLOAT32); + inp_lhs->shape({1, 3, 3, 2}); + } + + auto inp_rhs = g->nodes()->create(); + { + inp_rhs->dtype(loco::DataType::FLOAT32); + inp_rhs->shape({1, 3, 3, 2}); + } + + auto eltwise_add = g->nodes()->create(); + { + eltwise_add->lhs(inp_lhs); + eltwise_add->rhs(inp_rhs); + } + + // Make and assign data to two pull nodes + auto inp_lhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_lhs_buf.shape()}; e.valid(); e.advance()) + { + inp_lhs_buf.at(e.current()) = x_val[n++]; + } + } + + auto inp_rhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_rhs_buf.shape()}; e.valid(); e.advance()) + { + inp_rhs_buf.at(e.current()) = y_val[n++]; + } + } + + auto inp_lhs_data = locomotiv::make_data(inp_lhs_buf); + locomotiv::annot_data(inp_lhs, std::move(inp_lhs_data)); + locomotiv::annot_domain(inp_lhs, loco::Domain::Tensor); + + auto inp_rhs_data = locomotiv::make_data(inp_rhs_buf); + locomotiv::annot_data(inp_rhs, std::move(inp_rhs_data)); + locomotiv::annot_domain(inp_rhs, loco::Domain::Tensor); + + // run the network + locomotiv::NodeExecution::get().run(eltwise_add); + + // get result + auto eltwise_add_data = locomotiv::annot_data(eltwise_add); + + // comparing the result + ASSERT_NE(eltwise_add_data, nullptr); + ASSERT_EQ(eltwise_add_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(eltwise_add_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(eltwise_add_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(eltwise_add_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(eltwise_add), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/EltwiseDiv.cpp b/compiler/locomotiv/src/Node/EltwiseDiv.cpp new file mode 100644 index 00000000000..a054d9a97d0 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseDiv.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseDiv *eltwise_div) +{ + struct Func final : public BinaryFunc + { + float apply(float lhs, float rhs) const { return lhs / rhs; } + }; + + Func f; + + eltwise_binary(eltwise_div, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseDiv.test.cpp b/compiler/locomotiv/src/Node/EltwiseDiv.test.cpp new file mode 100644 index 00000000000..60950c15bbb --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseDiv.test.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + +x = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +y = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +out = tf.div(x, y) + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_EltwiseDiv, f32) +{ + float x_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float y_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float out_val[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + + // make EltwiseDiv(Pull, Pull) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp_lhs = g->nodes()->create(); + { + inp_lhs->dtype(loco::DataType::FLOAT32); + inp_lhs->shape({1, 3, 3, 2}); + } + + auto inp_rhs = g->nodes()->create(); + { + inp_rhs->dtype(loco::DataType::FLOAT32); + inp_rhs->shape({1, 3, 3, 2}); + } + + auto eltwise_div = g->nodes()->create(); + { + eltwise_div->lhs(inp_lhs); + eltwise_div->rhs(inp_rhs); + } + + // Make and assign data to two pull nodes + auto inp_lhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_lhs_buf.shape()}; e.valid(); e.advance()) + { + inp_lhs_buf.at(e.current()) = x_val[n++]; + } + } + + auto inp_rhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_rhs_buf.shape()}; e.valid(); e.advance()) + { + inp_rhs_buf.at(e.current()) = y_val[n++]; + } + } + + auto inp_lhs_data = locomotiv::make_data(inp_lhs_buf); + locomotiv::annot_data(inp_lhs, std::move(inp_lhs_data)); + locomotiv::annot_domain(inp_lhs, loco::Domain::Tensor); + + auto inp_rhs_data = locomotiv::make_data(inp_rhs_buf); + locomotiv::annot_data(inp_rhs, std::move(inp_rhs_data)); + locomotiv::annot_domain(inp_rhs, loco::Domain::Tensor); + + // run the network + locomotiv::NodeExecution::get().run(eltwise_div); + + // get result + auto eltwise_div_data = locomotiv::annot_data(eltwise_div); + + // comparing the result + ASSERT_NE(eltwise_div_data, nullptr); + ASSERT_EQ(eltwise_div_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(eltwise_div_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(eltwise_div_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(eltwise_div_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(eltwise_div), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/EltwiseMax.cpp b/compiler/locomotiv/src/Node/EltwiseMax.cpp new file mode 100644 index 00000000000..ec44fd6fa77 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseMax.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseMax *eltwise_max) +{ + struct Func final : public BinaryFunc + { + float apply(float lhs, float rhs) const { return std::max(lhs, rhs); } + }; + + Func f; + + eltwise_binary(eltwise_max, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseMax.test.cpp b/compiler/locomotiv/src/Node/EltwiseMax.test.cpp new file mode 100644 index 00000000000..c64db89946f --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseMax.test.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + +x = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +y = tf.constant([18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], + shape=[1, 3, 3, 2], dtype=tf.float32) +out = tf.math.maximum(x, y) + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_EltwiseMax, f32) +{ + float x_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float y_val[] = {18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + float out_val[] = {18, 17, 16, 15, 14, 13, 12, 11, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + + // make EltwiseMax(Pull, Pull) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp_lhs = g->nodes()->create(); + { + inp_lhs->dtype(loco::DataType::FLOAT32); + inp_lhs->shape({1, 3, 3, 2}); + } + + auto inp_rhs = g->nodes()->create(); + { + inp_rhs->dtype(loco::DataType::FLOAT32); + inp_rhs->shape({1, 3, 3, 2}); + } + + auto eltwise_max = g->nodes()->create(); + { + eltwise_max->lhs(inp_lhs); + eltwise_max->rhs(inp_rhs); + } + + // Make and assign data to two pull nodes + auto inp_lhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_lhs_buf.shape()}; e.valid(); e.advance()) + { + inp_lhs_buf.at(e.current()) = x_val[n++]; + } + } + + auto inp_rhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_rhs_buf.shape()}; e.valid(); e.advance()) + { + inp_rhs_buf.at(e.current()) = y_val[n++]; + } + } + + auto inp_lhs_data = locomotiv::make_data(inp_lhs_buf); + locomotiv::annot_data(inp_lhs, std::move(inp_lhs_data)); + locomotiv::annot_domain(inp_lhs, loco::Domain::Tensor); + + auto inp_rhs_data = locomotiv::make_data(inp_rhs_buf); + locomotiv::annot_data(inp_rhs, std::move(inp_rhs_data)); + locomotiv::annot_domain(inp_rhs, loco::Domain::Tensor); + + // run the network + locomotiv::NodeExecution::get().run(eltwise_max); + + // get result + auto eltwise_max_data = locomotiv::annot_data(eltwise_max); + + // comparing the result + ASSERT_NE(eltwise_max_data, nullptr); + ASSERT_EQ(eltwise_max_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(eltwise_max_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(eltwise_max_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(eltwise_max_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(eltwise_max), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/EltwiseMul.cpp b/compiler/locomotiv/src/Node/EltwiseMul.cpp new file mode 100644 index 00000000000..6720ab92fbd --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseMul.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseMul *eltwise_mul) +{ + struct Func final : public BinaryFunc + { + float apply(float lhs, float rhs) const { return lhs * rhs; } + }; + + Func f; + + eltwise_binary(eltwise_mul, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseMul.test.cpp b/compiler/locomotiv/src/Node/EltwiseMul.test.cpp new file mode 100644 index 00000000000..b768883008a --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseMul.test.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + +x = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +y = tf.constant([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, + 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], shape=[1, 3, 3, 2], + dtype=tf.float32) +out = tf.math.multiply(x, y) + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_EltwiseMul, f32) +{ + float x_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float y_val[] = {0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, + 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1}; + float out_val[] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, + 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8}; + + // make EltwiseMul(Pull, Pull) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp_lhs = g->nodes()->create(); + { + inp_lhs->dtype(loco::DataType::FLOAT32); + inp_lhs->shape({1, 3, 3, 2}); + } + + auto inp_rhs = g->nodes()->create(); + { + inp_rhs->dtype(loco::DataType::FLOAT32); + inp_rhs->shape({1, 3, 3, 2}); + } + + auto eltwise_mul = g->nodes()->create(); + { + eltwise_mul->lhs(inp_lhs); + eltwise_mul->rhs(inp_rhs); + } + + // Make and assign data to two pull nodes + auto inp_lhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_lhs_buf.shape()}; e.valid(); e.advance()) + { + inp_lhs_buf.at(e.current()) = x_val[n++]; + } + } + + auto inp_rhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_rhs_buf.shape()}; e.valid(); e.advance()) + { + inp_rhs_buf.at(e.current()) = y_val[n++]; + } + } + + auto inp_lhs_data = locomotiv::make_data(inp_lhs_buf); + locomotiv::annot_data(inp_lhs, std::move(inp_lhs_data)); + locomotiv::annot_domain(inp_lhs, loco::Domain::Tensor); + + auto inp_rhs_data = locomotiv::make_data(inp_rhs_buf); + locomotiv::annot_data(inp_rhs, std::move(inp_rhs_data)); + locomotiv::annot_domain(inp_rhs, loco::Domain::Tensor); + + // run the network + locomotiv::NodeExecution::get().run(eltwise_mul); + + // get result + auto eltwise_mul_data = locomotiv::annot_data(eltwise_mul); + + // comparing the result + ASSERT_NE(eltwise_mul_data, nullptr); + ASSERT_EQ(eltwise_mul_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(eltwise_mul_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(eltwise_mul_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(eltwise_mul_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(eltwise_mul), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/EltwiseSqrt.cpp b/compiler/locomotiv/src/Node/EltwiseSqrt.cpp new file mode 100644 index 00000000000..b4625a757df --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseSqrt.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include + +namespace +{ + +inline float sqrt_ew(float val) { return sqrt(val); } + +struct Func final : public locomotiv::UnaryFunc +{ + float apply(float v) const final { return sqrt_ew(v); } +}; + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseSqrt *sqrt_node) +{ + Func f; + + eltwise_unary(sqrt_node, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseSqrt.test.cpp b/compiler/locomotiv/src/Node/EltwiseSqrt.test.cpp new file mode 100644 index 00000000000..adb1b853ec4 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseSqrt.test.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_EltwiseSqrt, f32) +{ + // Make Pull-EltwiseSqrt graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({4}); + auto sqrt = g->nodes()->create(); + sqrt->input(pull); + + // Make and assign data to Pull node + auto pull_buf = make_buffer(Shape{4}); + pull_buf.at(Index{0}) = 4.0f; + pull_buf.at(Index{1}) = 9.0f; + pull_buf.at(Index{2}) = 0.0f; + pull_buf.at(Index{3}) = -1.0f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(sqrt); + + auto sqrt_data = locomotiv::annot_data(sqrt); + ASSERT_NE(sqrt_data, nullptr); + ASSERT_EQ(sqrt_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(sqrt_data->shape()), Shape{4}); + ASSERT_FLOAT_EQ(sqrt_data->as_f32_bufptr()->at(Index{0}), 2.0f); + ASSERT_FLOAT_EQ(sqrt_data->as_f32_bufptr()->at(Index{1}), 3.0f); + ASSERT_FLOAT_EQ(sqrt_data->as_f32_bufptr()->at(Index{2}), 0.0f); + ASSERT_TRUE(std::isnan(sqrt_data->as_f32_bufptr()->at(Index{3}))); + + ASSERT_EQ(locomotiv::annot_domain(sqrt), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/EltwiseSub.cpp b/compiler/locomotiv/src/Node/EltwiseSub.cpp new file mode 100644 index 00000000000..7943f950b63 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseSub.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +namespace locomotiv +{ + +void NodeExecution::execute(loco::EltwiseSub *eltwise_sub) +{ + struct Func final : public BinaryFunc + { + float apply(float lhs, float rhs) const { return lhs - rhs; } + }; + + Func f; + + eltwise_binary(eltwise_sub, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/EltwiseSub.test.cpp b/compiler/locomotiv/src/Node/EltwiseSub.test.cpp new file mode 100644 index 00000000000..7eff90f9e31 --- /dev/null +++ b/compiler/locomotiv/src/Node/EltwiseSub.test.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +/* +test case generated from the following: + +x = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +y = tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], + shape=[1, 3, 3, 2], dtype=tf.float32) +out = tf.math.subtract(x, y) + +with tf.Session() as sess: + print(sess.run(out)) +*/ +TEST(NodeExecution_EltwiseSub, f32) +{ + float x_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float y_val[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + float out_val[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + // make EltwiseSub(Pull, Pull) + auto g = loco::make_graph(); + Shape input_shape{1, 3, 3, 2}; // NHWC + + auto inp_lhs = g->nodes()->create(); + { + inp_lhs->dtype(loco::DataType::FLOAT32); + inp_lhs->shape({1, 3, 3, 2}); + } + + auto inp_rhs = g->nodes()->create(); + { + inp_rhs->dtype(loco::DataType::FLOAT32); + inp_rhs->shape({1, 3, 3, 2}); + } + + auto eltwise_sub = g->nodes()->create(); + { + eltwise_sub->lhs(inp_lhs); + eltwise_sub->rhs(inp_rhs); + } + + // Make and assign data to two pull nodes + auto inp_lhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_lhs_buf.shape()}; e.valid(); e.advance()) + { + inp_lhs_buf.at(e.current()) = x_val[n++]; + } + } + + auto inp_rhs_buf = make_buffer(input_shape); + { + int n = 0; + for (IndexEnumerator e{inp_rhs_buf.shape()}; e.valid(); e.advance()) + { + inp_rhs_buf.at(e.current()) = y_val[n++]; + } + } + + auto inp_lhs_data = locomotiv::make_data(inp_lhs_buf); + locomotiv::annot_data(inp_lhs, std::move(inp_lhs_data)); + locomotiv::annot_domain(inp_lhs, loco::Domain::Tensor); + + auto inp_rhs_data = locomotiv::make_data(inp_rhs_buf); + locomotiv::annot_data(inp_rhs, std::move(inp_rhs_data)); + locomotiv::annot_domain(inp_rhs, loco::Domain::Tensor); + + // run the network + locomotiv::NodeExecution::get().run(eltwise_sub); + + // get result + auto eltwise_sub_data = locomotiv::annot_data(eltwise_sub); + + // comparing the result + ASSERT_NE(eltwise_sub_data, nullptr); + ASSERT_EQ(eltwise_sub_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(eltwise_sub_data->shape()), Shape({1, 3, 3, 2})); + + uint32_t n = 0; + for (IndexEnumerator e{*(eltwise_sub_data->shape())}; e.valid(); e.advance()) + { + ASSERT_FLOAT_EQ(eltwise_sub_data->as_f32_bufptr()->at(e.current()), out_val[n++]); + } + + ASSERT_EQ(locomotiv::annot_domain(eltwise_sub), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/FeatureCodec.test.cpp b/compiler/locomotiv/src/Node/FeatureCodec.test.cpp new file mode 100644 index 00000000000..c35f0e69acc --- /dev/null +++ b/compiler/locomotiv/src/Node/FeatureCodec.test.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include + +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::Buffer; + +// This file is intended to test FeatureEncode and FeatureDecode at once +namespace +{ + +class NodeExecution_FeatureCodec : public ::testing::Test +{ +private: + loco::Graph g; + +protected: + /// @brief Make Pull node and set data by given buffer and data type + template loco::Pull *pull_layer(Buffer
&pull_buf, loco::DataType dtype) + { + auto pull = g.nodes()->create(); + pull->dtype(dtype); + + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + return pull; + } + + /// @brief Make FeatureEncode node with given input and encoding permutation + loco::FeatureEncode *feature_encode_layer(loco::Node *input, + const loco::Permutation &perm) + { + auto encoder = std::unique_ptr>( + new loco::PermutingEncoder); + + encoder->perm(perm); + + auto enc = g.nodes()->create(); + enc->input(input); + enc->encoder(std::move(encoder)); + + return enc; + } + + /// @brief Make FeatureDecode node with given input and decoding permutation + loco::FeatureDecode *feature_decode_layer(loco::Node *input, + const loco::Permutation &perm) + { + auto decoder = std::unique_ptr>( + new loco::PermutingDecoder); + + decoder->perm(perm); + + auto dec = g.nodes()->create(); + dec->input(input); + dec->decoder(std::move(decoder)); + + return dec; + } +}; + +} // namespace + +TEST_F(NodeExecution_FeatureCodec, s32) +{ + const uint32_t N = 2; + const uint32_t H = 3; + const uint32_t W = 4; + const uint32_t C = 5; + + // Make "NCHW" data for pull node + auto pull_buf = make_buffer(Shape{N, C, H, W}); + int32_t i = 0; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = i; + ++i; // Doesn't matter what it is + } + + // Make NCHW permutation for encoder and decoder + loco::Permutation NCHW; + + NCHW.axis(loco::FeatureAxis::Count) = 0; + NCHW.axis(loco::FeatureAxis::Depth) = 1; + NCHW.axis(loco::FeatureAxis::Height) = 2; + NCHW.axis(loco::FeatureAxis::Width) = 3; + + // Pull + auto pull = pull_layer(pull_buf, loco::DataType::S32); + + // FeatureEncode + auto enc = feature_encode_layer(pull, NCHW); + locomotiv::NodeExecution::get().run(enc); + + // Test FeatureEncode + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(enc_data->shape()), (Shape{N, H, W, C})); // locomotiv feature is NHWC + auto enc_buf = enc_data->as_s32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_EQ(pull_buf.at(Index{n, c, h, w}), enc_buf->at(Index{n, h, w, c})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Feature); + + // FeatureDecode + auto dec = feature_decode_layer(enc, NCHW); + locomotiv::NodeExecution::get().run(dec); + + // Test FeatureDecode: Encode -> Decode == identity + auto dec_data = locomotiv::annot_data(dec); + ASSERT_NE(dec_data, nullptr); + ASSERT_EQ(dec_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(dec_data->shape()), (Shape{N, C, H, W})); + auto dec_buf = dec_data->as_s32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_EQ(pull_buf.at(Index{n, c, h, w}), dec_buf->at(Index{n, c, h, w})); + + ASSERT_EQ(locomotiv::annot_domain(dec), loco::Domain::Tensor); +} + +TEST_F(NodeExecution_FeatureCodec, f32) +{ + const uint32_t N = 2; + const uint32_t H = 3; + const uint32_t W = 4; + const uint32_t C = 5; + + // Make crazy "CHNW" data for pull node + auto pull_buf = make_buffer(Shape{C, H, N, W}); + float f = 0.0f; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = f; + f += 0.1f; // Doesn't matter what it is + } + + // Make CHNW permutation for encoder and decoder + loco::Permutation CHNW; + + CHNW.axis(loco::FeatureAxis::Depth) = 0; + CHNW.axis(loco::FeatureAxis::Height) = 1; + CHNW.axis(loco::FeatureAxis::Count) = 2; + CHNW.axis(loco::FeatureAxis::Width) = 3; + + // Pull + auto pull = pull_layer(pull_buf, loco::DataType::FLOAT32); + + // FeatureEncode + auto enc = feature_encode_layer(pull, CHNW); + locomotiv::NodeExecution::get().run(enc); + + // Test FeatureEncode + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(enc_data->shape()), (Shape{N, H, W, C})); // locomotiv feature is NHWC + auto enc_buf = enc_data->as_f32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_FLOAT_EQ(pull_buf.at(Index{c, h, n, w}), enc_buf->at(Index{n, h, w, c})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Feature); + + // FeatureDecode + auto dec = feature_decode_layer(enc, CHNW); + locomotiv::NodeExecution::get().run(dec); + + // Test FeatureDecode: Encode -> Decode == identity + auto dec_data = locomotiv::annot_data(dec); + ASSERT_NE(dec_data, nullptr); + ASSERT_EQ(dec_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(dec_data->shape()), (Shape{C, H, N, W})); + auto dec_buf = dec_data->as_f32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_FLOAT_EQ(pull_buf.at(Index{c, h, n, w}), dec_buf->at(Index{c, h, n, w})); + + ASSERT_EQ(locomotiv::annot_domain(dec), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/FeatureDecode.cpp b/compiler/locomotiv/src/Node/FeatureDecode.cpp new file mode 100644 index 00000000000..8a56a56b24f --- /dev/null +++ b/compiler/locomotiv/src/Node/FeatureDecode.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::Index; + +template +std::unique_ptr feature_decode(const loco::FeatureDecode *node, + const Buffer *input_buf) +{ + auto decoder = node->decoder(); + + // Make FeatureShape from input. Note that feature in locomotiv represented as NHWC + loco::FeatureShape input_shape; + assert(input_buf->shape().rank() == 4); + input_shape.count() = input_buf->shape().dim(0); + input_shape.height() = input_buf->shape().dim(1); + input_shape.width() = input_buf->shape().dim(2); + input_shape.depth() = input_buf->shape().dim(3); + + loco::TensorShape node_shape = decoder->shape(input_shape); + + // Make tensor buffer from TensorShape + Buffer node_buf = + make_buffer(Shape{node_shape.dim(0).value(), node_shape.dim(1).value(), + node_shape.dim(2).value(), node_shape.dim(3).value()}); + + // Copy buffer in an order arranged by decoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::FeatureIndex feature_index = decoder->value(e.current()); + Index buf_index({feature_index.batch(), feature_index.row(), feature_index.column(), + feature_index.channel()}); + + node_buf.at(e.current()) = input_buf->at(buf_index); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::FeatureDecode *dec) +{ + auto input_data = annot_data(dec->input()); + + validate(input_data, "Input of FeatureDecode not ready"); + validate(annot_domain(dec->input()) == loco::Domain::Feature, + "Input of FeatureDecode is not Feature"); + validate(input_data->shape()->rank() == 4, "Input shape mismatch"); + + std::unique_ptr dec_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_buf = input_data->as_s32_bufptr(); + dec_data = feature_decode(dec, input_buf); + break; + } + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + dec_data = feature_decode(dec, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(dec_data != nullptr); + annot_data(dec, std::move(dec_data)); + annot_domain(dec, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/FeatureEncode.cpp b/compiler/locomotiv/src/Node/FeatureEncode.cpp new file mode 100644 index 00000000000..406de76ff16 --- /dev/null +++ b/compiler/locomotiv/src/Node/FeatureEncode.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +template +std::unique_ptr feature_encode(const loco::FeatureEncode *node, + const Buffer *input_buf) +{ + auto encoder = node->encoder(); + + // Make TensorShape from input + loco::TensorShape input_shape; + input_shape.rank(input_buf->shape().rank()); + assert(input_shape.rank() == 4); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + { + input_shape.dim(i) = input_buf->shape().dim(i); + } + + loco::FeatureShape node_shape = encoder->shape(input_shape); + + // Make NHWC buffer from FeatureShape + Buffer node_buf = + make_buffer(Shape{node_shape.count().value(), node_shape.height().value(), + node_shape.width().value(), node_shape.depth().value()}); + + // Copy buffer in an order arranged by encoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::FeatureIndex index; + index.batch() = e.current().at(0); + index.row() = e.current().at(1); + index.column() = e.current().at(2); + index.channel() = e.current().at(3); + + node_buf.at(e.current()) = input_buf->at(encoder->value(index)); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::FeatureEncode *enc) +{ + auto input_data = annot_data(enc->input()); + + validate(input_data, "Input of FeatureEncode not ready"); + validate(annot_domain(enc->input()) == loco::Domain::Tensor, + "Input of FeatureEncode is not Tensor"); + validate(input_data->shape()->rank() == 4, "Input shape mismatch"); + + std::unique_ptr enc_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_buf = input_data->as_s32_bufptr(); + enc_data = feature_encode(enc, input_buf); + break; + } + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + enc_data = feature_encode(enc, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(enc_data != nullptr); + annot_data(enc, std::move(enc_data)); + annot_domain(enc, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/FilterEncode.cpp b/compiler/locomotiv/src/Node/FilterEncode.cpp new file mode 100644 index 00000000000..cd9d708dc28 --- /dev/null +++ b/compiler/locomotiv/src/Node/FilterEncode.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +template +std::unique_ptr filter_encode(const loco::FilterEncode *node, + const Buffer *input_buf) +{ + auto encoder = node->encoder(); + + // Make TensorShape from input + loco::TensorShape input_shape; + input_shape.rank(input_buf->shape().rank()); + assert(input_shape.rank() == 4); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + { + input_shape.dim(i) = input_buf->shape().dim(i); + } + + loco::FilterShape node_shape = encoder->shape(input_shape); + + // Make NHWC buffer from FilterShape + Buffer node_buf = + make_buffer(Shape{node_shape.count().value(), node_shape.height().value(), + node_shape.width().value(), node_shape.depth().value()}); + + // Copy buffer in an order arranged by encoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::FilterIndex index; + index.nth() = e.current().at(0); + index.row() = e.current().at(1); + index.column() = e.current().at(2); + index.channel() = e.current().at(3); + + node_buf.at(e.current()) = input_buf->at(encoder->value(index)); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::FilterEncode *enc) +{ + auto input_data = annot_data(enc->input()); + + validate(input_data, "Input of FilterEncode not ready"); + validate(annot_domain(enc->input()) == loco::Domain::Tensor, + "Input of FilterEncode is not Tensor"); + validate(input_data->shape()->rank() == 4, "Input shape mismatch"); + + std::unique_ptr enc_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_buf = input_data->as_s32_bufptr(); + enc_data = filter_encode(enc, input_buf); + break; + } + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + enc_data = filter_encode(enc, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(enc_data != nullptr); + annot_data(enc, std::move(enc_data)); + annot_domain(enc, loco::Domain::Filter); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/FilterEncode.test.cpp b/compiler/locomotiv/src/Node/FilterEncode.test.cpp new file mode 100644 index 00000000000..79b8308e229 --- /dev/null +++ b/compiler/locomotiv/src/Node/FilterEncode.test.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include + +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; + +TEST(NodeExecution_FilterEncode, s32) +{ + const uint32_t N = 2; + const uint32_t H = 3; + const uint32_t W = 4; + const uint32_t C = 5; + + auto g = loco::make_graph(); + + // Pull + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::S32); + + // Make and assign "NCHW" data to pull node + auto pull_buf = make_buffer(Shape{N, C, H, W}); + int32_t i = 1; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = i; + ++i; // Doesn't matter what it is + } + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + // Encoder to correctly read input tensor as NCHW + auto encoder = std::unique_ptr>( + new loco::PermutingEncoder); + encoder->perm()->axis(loco::FilterAxis::Count) = 0; + encoder->perm()->axis(loco::FilterAxis::Depth) = 1; + encoder->perm()->axis(loco::FilterAxis::Height) = 2; + encoder->perm()->axis(loco::FilterAxis::Width) = 3; + + // FilterEncode + auto enc = g->nodes()->create(); + enc->input(pull); + enc->encoder(std::move(encoder)); + + locomotiv::NodeExecution::get().run(enc); + + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(enc_data->shape()), (Shape{N, H, W, C})); // locomotiv filter is NHWC + auto enc_buf = enc_data->as_s32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_EQ(pull_buf.at(Index{n, c, h, w}), enc_buf->at(Index{n, h, w, c})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Filter); +} + +TEST(NodeExecution_FilterEncode, f32) +{ + const uint32_t N = 2; + const uint32_t H = 3; + const uint32_t W = 4; + const uint32_t C = 5; + + auto g = loco::make_graph(); + + // Pull + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + + // Make and assign crazy "CHNW" data to pull node + auto pull_buf = make_buffer(Shape{C, H, N, W}); + float f = 1; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = f; + f += 0.1f; // Doesn't matter what it is + } + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + // Encoder to correctly read input tensor as CHNW + auto encoder = std::unique_ptr>( + new loco::PermutingEncoder); + encoder->perm()->axis(loco::FilterAxis::Depth) = 0; + encoder->perm()->axis(loco::FilterAxis::Height) = 1; + encoder->perm()->axis(loco::FilterAxis::Count) = 2; + encoder->perm()->axis(loco::FilterAxis::Width) = 3; + + // FilterEncode + auto enc = g->nodes()->create(); + enc->input(pull); + enc->encoder(std::move(encoder)); + + locomotiv::NodeExecution::get().run(enc); + + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(enc_data->shape()), (Shape{N, H, W, C})); // locomotiv filter is NHWC + auto enc_buf = enc_data->as_f32_bufptr(); + for (uint32_t n = 0; n < N; ++n) + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + for (uint32_t c = 0; c < C; ++c) + ASSERT_FLOAT_EQ(pull_buf.at(Index{c, h, n, w}), enc_buf->at(Index{n, h, w, c})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Filter); +} diff --git a/compiler/locomotiv/src/Node/Forward.cpp b/compiler/locomotiv/src/Node/Forward.cpp new file mode 100644 index 00000000000..eb7d44a5982 --- /dev/null +++ b/compiler/locomotiv/src/Node/Forward.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Forward *forward) +{ + auto input_data = annot_data(forward->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(forward->input()) != loco::Domain::Unknown, + "Input domain must not Unknown"); + + std::unique_ptr forward_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_bufptr = input_data->as_s32_bufptr(); + forward_data = make_data(*input_bufptr); + break; + } + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + forward_data = make_data(*input_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(forward_data != nullptr); + annot_data(forward, std::move(forward_data)); + annot_domain(forward, annot_domain(forward->input())); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Forward.test.cpp b/compiler/locomotiv/src/Node/Forward.test.cpp new file mode 100644 index 00000000000..73d37139aca --- /dev/null +++ b/compiler/locomotiv/src/Node/Forward.test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Forward, s32) +{ + // Make pull-forward graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::S32); + pull->shape({1}); + auto forward = g->nodes()->create(); + forward->input(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + pull_buf.at(Index{0}) = 42; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(forward); + + auto forward_data = locomotiv::annot_data(forward); + ASSERT_NE(forward_data, nullptr); + ASSERT_EQ(forward_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(forward_data->shape()), Shape{1}); + ASSERT_EQ(forward_data->as_s32_bufptr()->at(Index{0}), pull_buf.at(Index{0})); + + ASSERT_EQ(locomotiv::annot_domain(forward), loco::Domain::Tensor); +} + +TEST(NodeExecution_Forward, f32) +{ + // Make pull-forward graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1}); + auto forward = g->nodes()->create(); + forward->input(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + pull_buf.at(Index{0}) = 3.14f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(forward); + + auto forward_data = locomotiv::annot_data(forward); + ASSERT_NE(forward_data, nullptr); + ASSERT_EQ(forward_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(forward_data->shape()), Shape{1}); + ASSERT_FLOAT_EQ(forward_data->as_f32_bufptr()->at(Index{0}), pull_buf.at(Index{0})); + + ASSERT_EQ(locomotiv::annot_domain(forward), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/MatMul.cpp b/compiler/locomotiv/src/Node/MatMul.cpp new file mode 100644 index 00000000000..77b7315a91a --- /dev/null +++ b/compiler/locomotiv/src/Node/MatMul.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +/** + * @brief Calculate Matrix Multiplication + */ +template Buffer calc_mat_mul(const Buffer *lhs_buf, const Buffer *rhs_buf) +{ + const auto lhs_shape = lhs_buf->shape(); + const auto rhs_shape = rhs_buf->shape(); + + assert(lhs_shape.rank() == 2 && "lhs rank must be 2"); + assert(rhs_shape.rank() == 2 && "rhs rank must be 2"); + // lhs width should be the same as rhs height + assert(lhs_shape.dim(1) == rhs_shape.dim(0) && "height/width mismatch"); + + const uint32_t lhs_height = lhs_shape.dim(0); + const uint32_t lhs_width = lhs_shape.dim(1); + + const uint32_t rhs_width = rhs_shape.dim(1); + + const uint32_t output_height = lhs_height; + const uint32_t output_width = rhs_width; + + Shape output_shape{output_height, output_width}; + auto output_buf = make_buffer(output_shape); + + for (uint32_t out_y = 0; out_y < output_height; ++out_y) + { + for (uint32_t out_x = 0; out_x < output_width; ++out_x) + { + T total = static_cast(0); // accumulator + // Accumulate through axis + for (uint32_t axis = 0; axis < lhs_width; ++axis) + { + total += lhs_buf->at(Index({out_y, axis})) * rhs_buf->at(Index({axis, out_x})); + } + // Set output value + output_buf.at(Index({out_y, out_x})) = total; + } + } + + return output_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::MatMul *mat_mul) +{ + auto lhs_data = annot_data(mat_mul->lhs()); + auto rhs_data = annot_data(mat_mul->rhs()); + + validate(lhs_data, "Can't find left matrix data of MatMul"); + validate(lhs_data->shape()->rank() == 2, "lhs rank must be 2"); + + validate(rhs_data, "Can't find right matrix data of MatMul"); + validate(rhs_data->shape()->rank() == 2, "rhs rank must be 2"); + + validate(annot_domain(mat_mul->lhs()) == loco::Domain::Matrix, + "Left matrix of MatMul is not a Matrix"); + validate(annot_domain(mat_mul->rhs()) == loco::Domain::Matrix, + "Right matrix of MatMul is not a Matrix"); + + std::unique_ptr mat_mul_result = nullptr; + + if (lhs_data->dtype() == loco::DataType::FLOAT32 && rhs_data->dtype() == loco::DataType::FLOAT32) + { + const auto lhs_buf = lhs_data->as_f32_bufptr(); + const auto rhs_buf = rhs_data->as_f32_bufptr(); + + auto mat_mul_buf = calc_mat_mul(lhs_buf, rhs_buf); + + mat_mul_result = make_data(mat_mul_buf); + } + else if (lhs_data->dtype() == loco::DataType::S32 && rhs_data->dtype() == loco::DataType::S32) + { + const auto lhs_buf = lhs_data->as_s32_bufptr(); + const auto rhs_buf = rhs_data->as_s32_bufptr(); + + auto mat_mul_buf = calc_mat_mul(lhs_buf, rhs_buf); + + mat_mul_result = make_data(mat_mul_buf); + } + else + throw std::runtime_error("NYI for these DataTypes"); + + assert(mat_mul_result != nullptr); + + annot_data(mat_mul, std::move(mat_mul_result)); + annot_domain(mat_mul, loco::Domain::Matrix); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/MatMul.test.cpp b/compiler/locomotiv/src/Node/MatMul.test.cpp new file mode 100644 index 00000000000..bd480f7c7ae --- /dev/null +++ b/compiler/locomotiv/src/Node/MatMul.test.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +template +void run_test(const T *lhs, const T *rhs, const T *expected_output, const Shape &lhs_shape, + const Shape &rhs_shape, const Shape &out_shape, loco::DataType expected_datatype) +{ + auto g = loco::make_graph(); + // Fill lhs MatrixEncode + auto lhs_enc = g->nodes()->create(); + { + auto lhs_enc_buf = make_buffer(lhs_shape); + auto lhs_overlay = make_overlay(lhs_shape, const_cast(lhs)); + for (nncc::core::ADT::tensor::IndexEnumerator e{lhs_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + lhs_enc_buf.at(ind) = lhs_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(lhs_enc_buf); + locomotiv::annot_data(lhs_enc, std::move(enc_data)); + locomotiv::annot_domain(lhs_enc, loco::Domain::Matrix); + } + // Fill rhs MatrixEncode + auto rhs_enc = g->nodes()->create(); + { + auto rhs_enc_buf = make_buffer(rhs_shape); + auto rhs_overlay = make_overlay(rhs_shape, const_cast(rhs)); + for (nncc::core::ADT::tensor::IndexEnumerator e{rhs_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + rhs_enc_buf.at(ind) = rhs_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(rhs_enc_buf); + locomotiv::annot_data(rhs_enc, std::move(enc_data)); + locomotiv::annot_domain(rhs_enc, loco::Domain::Matrix); + } + + // build MatMul + auto mat_mul = g->nodes()->create(); + mat_mul->lhs(lhs_enc); + mat_mul->rhs(rhs_enc); + + // run interpreter + locomotiv::NodeExecution::get().run(mat_mul); + + // get result of calculation + auto mat_mul_result = locomotiv::annot_data(mat_mul); + + // check the result + ASSERT_NE(mat_mul_result, nullptr); + ASSERT_TRUE(mat_mul_result->dtype() == expected_datatype); + ASSERT_TRUE(*(mat_mul_result->shape()) == out_shape); + + auto out_overlay = make_overlay(out_shape, const_cast(expected_output)); + for (nncc::core::ADT::tensor::IndexEnumerator e{out_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + if (expected_datatype == loco::DataType::FLOAT32) + ASSERT_FLOAT_EQ(mat_mul_result->as_f32_bufptr()->at(ind), out_overlay.at(ind)); + else if (expected_datatype == loco::DataType::S32) + ASSERT_EQ(mat_mul_result->as_s32_bufptr()->at(ind), out_overlay.at(ind)); + else + throw std::runtime_error("NYI for these DataTypes"); + } + + ASSERT_EQ(locomotiv::annot_domain(mat_mul), loco::Domain::Matrix); +} + +} // namespace + +// clang-format off +/* from the code below: + +import numpy as np + +a = [[-0.48850584, 1.4292705, -1.3424522], + [1.7021934, -0.39246717, 0.6248314]] + +b = [[-0.0830195, 0.21088193, -0.11781317], + [0.07755677, 1.6337638, 1.0792778], + [-1.6922939, -1.5437212, 0.96667504]] + +print(np.array(a) @ np.array(b)) +*/ +TEST(NodeExecution_MatMul, f32_2x3_3x3) +{ + using nncc::core::ADT::tensor::Shape; + + const float lhs[] = + { + -0.48850584, 1.4292705, -1.3424522, + 1.7021934, -0.39246717, 0.6248314 + }; + + const float rhs[] = + { + -0.0830195, 0.21088193, -0.11781317, + 0.07755677, 1.6337638, 1.0792778, + -1.6922939, -1.5437212, 0.96667504 + }; + + const float out[] = + { + 2.42322878, 4.30444527, 0.30241731, + -1.2291521, -1.2468023, -0.02011299 + }; + + run_test(lhs, rhs, out, Shape{2, 3}, Shape{3, 3}, Shape{2, 3}, loco::DataType::FLOAT32); +} + +/* from the code below: + +import numpy as np + +a = np.random.randint(10000, size=(4, 2)) + +b = np.random.randint(10000, size=(2, 6)) + +print(a) +print(b) +print(np.array(a) @ np.array(b)) +*/ +TEST(NodeExecution_MatMul, s32_4x2_2x6) +{ + using nncc::core::ADT::tensor::Shape; + + const int32_t lhs[] = + { + 6392, 4993, + 54, 9037, + 3947, 5820, + 5800, 4181 + }; + + const int32_t rhs[] = + { + 2694, 8376, 8090, 1285, 7492, 1652, + 5427, 8798, 7634, 2229, 5439, 6999 + }; + + const int32_t out[] = + { + 44317059, 97467806, 89827842, 19343117, 75045791, 45505591, + 49189275, 79959830, 69425318, 20212863, 49556811, 63339171, + 42218358, 84264432, 76361110, 18044675, 61225904, 47254624, + 38315487, 85365238, 78839754, 16772449, 66194059, 38844419 + }; + + run_test(lhs, rhs, out, Shape{4, 2}, Shape{2, 6}, Shape{4, 6}, loco::DataType::S32); +} + +// clang-format on diff --git a/compiler/locomotiv/src/Node/MatrixCodec.test.cpp b/compiler/locomotiv/src/Node/MatrixCodec.test.cpp new file mode 100644 index 00000000000..8fc5d593b27 --- /dev/null +++ b/compiler/locomotiv/src/Node/MatrixCodec.test.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include + +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::Buffer; + +// This file is intended to test MatrixEncode and MatrixDecode at once +namespace +{ + +class NodeExecution_MatrixCodec : public ::testing::Test +{ +private: + loco::Graph g; + +protected: + /// @brief Make Pull node and set data by given buffer and data type + template loco::Pull *pull_layer(Buffer
&pull_buf, loco::DataType dtype) + { + auto pull = g.nodes()->create(); + pull->dtype(dtype); + + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + return pull; + } + + /// @brief Make MatrixEncode node with given input and encoding permutation + loco::MatrixEncode *matrix_encode_layer(loco::Node *input, + const loco::Permutation &perm) + { + auto encoder = std::unique_ptr>( + new loco::PermutingEncoder); + + encoder->perm(perm); + + auto enc = g.nodes()->create(); + enc->input(input); + enc->encoder(std::move(encoder)); + + return enc; + } + + /// @brief Make MatrixDecode node with given input and decoding permutation + loco::MatrixDecode *matrix_decode_layer(loco::Node *input, + const loco::Permutation &perm) + { + auto decoder = std::unique_ptr>( + new loco::PermutingDecoder); + + decoder->perm(perm); + + auto dec = g.nodes()->create(); + dec->input(input); + dec->decoder(std::move(decoder)); + + return dec; + } +}; + +} // namespace + +TEST_F(NodeExecution_MatrixCodec, HW_s32) +{ + const uint32_t H = 3; + const uint32_t W = 4; + + // Make HW data for pull node + auto pull_buf = make_buffer(Shape{H, W}); + int32_t i = 0; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = i; + ++i; // Doesn't matter what it is + } + + // Make HW permutation for encoder and decoder + loco::Permutation HW; + + HW.axis(loco::MatrixAxis::Height) = 0; + HW.axis(loco::MatrixAxis::Width) = 1; + + // Pull + auto pull = pull_layer(pull_buf, loco::DataType::S32); + + // MatrixEncode + auto enc = matrix_encode_layer(pull, HW); + locomotiv::NodeExecution::get().run(enc); + + // Test MatrixEncode + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(enc_data->shape()), (Shape{H, W})); // locomotiv matrix is HW + auto enc_buf = enc_data->as_s32_bufptr(); + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + ASSERT_EQ(pull_buf.at(Index{h, w}), enc_buf->at(Index{h, w})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Matrix); + + // MatrixDecode + auto dec = matrix_decode_layer(enc, HW); + locomotiv::NodeExecution::get().run(dec); + + // Test MatrixDecode: Encode -> Decode == identity + auto dec_data = locomotiv::annot_data(dec); + ASSERT_NE(dec_data, nullptr); + ASSERT_EQ(dec_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(dec_data->shape()), (Shape{H, W})); + auto dec_buf = dec_data->as_s32_bufptr(); + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + ASSERT_EQ(pull_buf.at(Index{h, w}), dec_buf->at(Index{h, w})); + + ASSERT_EQ(locomotiv::annot_domain(dec), loco::Domain::Tensor); +} + +TEST_F(NodeExecution_MatrixCodec, WH_f32) +{ + const uint32_t W = 6; + const uint32_t H = 5; + + // Make crazy WH data for pull node + auto pull_buf = make_buffer(Shape{W, H}); + float f = 0.0f; + for (IndexEnumerator e{pull_buf.shape()}; e.valid(); e.advance()) + { + pull_buf.at(e.current()) = f; + f += 0.1f; // Doesn't matter what it is + } + + // Make WH permutation for encoder and decoder + loco::Permutation WH; + + WH.axis(loco::MatrixAxis::Width) = 0; + WH.axis(loco::MatrixAxis::Height) = 1; + + // Pull + auto pull = pull_layer(pull_buf, loco::DataType::FLOAT32); + + // MatrixEncode + auto enc = matrix_encode_layer(pull, WH); + locomotiv::NodeExecution::get().run(enc); + + // Test MatrixEncode + auto enc_data = locomotiv::annot_data(enc); + ASSERT_NE(enc_data, nullptr); + ASSERT_EQ(enc_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(enc_data->shape()), (Shape{H, W})); // locomotiv matrix is HW + auto enc_buf = enc_data->as_f32_bufptr(); + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + ASSERT_FLOAT_EQ(pull_buf.at(Index{w, h}), enc_buf->at(Index{h, w})); + + ASSERT_EQ(locomotiv::annot_domain(enc), loco::Domain::Matrix); + + // MatrixDecode + auto dec = matrix_decode_layer(enc, WH); + locomotiv::NodeExecution::get().run(dec); + + // Test MatrixDecode: Encode -> Decode == identity + auto dec_data = locomotiv::annot_data(dec); + ASSERT_NE(dec_data, nullptr); + ASSERT_EQ(dec_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(dec_data->shape()), (Shape{W, H})); + auto dec_buf = dec_data->as_f32_bufptr(); + for (uint32_t h = 0; h < H; ++h) + for (uint32_t w = 0; w < W; ++w) + ASSERT_FLOAT_EQ(pull_buf.at(Index{w, h}), dec_buf->at(Index{w, h})); + + ASSERT_EQ(locomotiv::annot_domain(dec), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/MatrixDecode.cpp b/compiler/locomotiv/src/Node/MatrixDecode.cpp new file mode 100644 index 00000000000..c591676aede --- /dev/null +++ b/compiler/locomotiv/src/Node/MatrixDecode.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::Index; + +template +std::unique_ptr matrix_decode(const loco::MatrixDecode *node, + const Buffer *input_buf) +{ + auto decoder = node->decoder(); + + // Make MatrixShape from input. Note that matrix in locomotiv represented as HW + loco::MatrixShape input_shape; + assert(input_buf->shape().rank() == 2); + input_shape.height() = input_buf->shape().dim(0); + input_shape.width() = input_buf->shape().dim(1); + + loco::TensorShape node_shape = decoder->shape(input_shape); + + // Make tensor buffer from TensorShape + Buffer node_buf = + make_buffer(Shape{node_shape.dim(0).value(), node_shape.dim(1).value()}); + + // Copy buffer in an order arranged by decoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::MatrixIndex matrix_index = decoder->value(e.current()); + Index buf_index({matrix_index.row(), matrix_index.column()}); + + node_buf.at(e.current()) = input_buf->at(buf_index); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::MatrixDecode *matrix_dec) +{ + auto input_data = annot_data(matrix_dec->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(matrix_dec->input()) == loco::Domain::Matrix, + "Input domain should be Matrix"); + validate(input_data->shape()->rank() == 2, "Input data rank must be 2"); + + std::unique_ptr matrix_dec_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_buf = input_data->as_s32_bufptr(); + matrix_dec_data = matrix_decode(matrix_dec, input_buf); + break; + } + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + matrix_dec_data = matrix_decode(matrix_dec, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(matrix_dec_data != nullptr); + + annot_data(matrix_dec, std::move(matrix_dec_data)); + annot_domain(matrix_dec, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/MatrixEncode.cpp b/compiler/locomotiv/src/Node/MatrixEncode.cpp new file mode 100644 index 00000000000..e3554e15a3a --- /dev/null +++ b/compiler/locomotiv/src/Node/MatrixEncode.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::IndexEnumerator; + +template +std::unique_ptr matrix_encode(const loco::MatrixEncode *node, + const Buffer *input_buf) +{ + auto encoder = node->encoder(); + + // Make TensorShape from input + loco::TensorShape input_shape; + input_shape.rank(input_buf->shape().rank()); + assert(input_shape.rank() == 2); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + { + input_shape.dim(i) = input_buf->shape().dim(i); + } + + loco::MatrixShape node_shape = encoder->shape(input_shape); + + // Make HW buffer from MatrixShape + Buffer node_buf = + make_buffer(Shape{node_shape.height().value(), node_shape.width().value()}); + + // Copy buffer in an order arranged by encoder + for (IndexEnumerator e{node_buf.shape()}; e.valid(); e.advance()) + { + loco::MatrixIndex index; + index.row() = e.current().at(0); + index.column() = e.current().at(1); + + node_buf.at(e.current()) = input_buf->at(encoder->value(index)); + } + + return locomotiv::make_data(node_buf); +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::MatrixEncode *matrix_enc) +{ + auto input_data = annot_data(matrix_enc->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(matrix_enc->input()) == loco::Domain::Tensor, + "Input domain should be Tensor"); + validate(input_data->shape()->rank() == 2, "Input data rank must be 2"); + + std::unique_ptr matrix_enc_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_buf = input_data->as_s32_bufptr(); + matrix_enc_data = matrix_encode(matrix_enc, input_buf); + break; + } + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + matrix_enc_data = matrix_encode(matrix_enc, input_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(matrix_enc_data != nullptr); + + annot_data(matrix_enc, std::move(matrix_enc_data)); + annot_domain(matrix_enc, loco::Domain::Matrix); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/MaxPool2D.cpp b/compiler/locomotiv/src/Node/MaxPool2D.cpp new file mode 100644 index 00000000000..5d92f89f503 --- /dev/null +++ b/compiler/locomotiv/src/Node/MaxPool2D.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + +/** + * @brief Compute 1D output size based on given 1D arguments. + * + * @param whole_pad Sum of front and back pad + */ +inline uint32_t compute_out_size(uint32_t image_size, uint32_t whole_pad, uint32_t filter_size, + uint32_t stride) +{ + assert((image_size + whole_pad - filter_size) % stride == 0); + return (image_size + whole_pad - filter_size) / stride + 1; +} + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +template +nncc::core::ADT::tensor::Buffer maxPool2D(const loco::MaxPool2D *maxpool2d, + const Buffer *ifm_buf) +{ + auto ifm_shape = ifm_buf->shape(); + + const uint32_t batches = ifm_shape.dim(0); + const uint32_t depth = ifm_shape.dim(3); + + const uint32_t ifm_height = ifm_shape.dim(1); + const uint32_t ifm_width = ifm_shape.dim(2); + + const uint32_t window_height = maxpool2d->window()->vertical(); + const uint32_t window_width = maxpool2d->window()->horizontal(); + + const uint32_t stride_height = maxpool2d->stride()->vertical(); + const uint32_t stride_width = maxpool2d->stride()->horizontal(); + + const uint32_t pad_top = maxpool2d->pad()->top(); + const uint32_t pad_bottom = maxpool2d->pad()->bottom(); + + const uint32_t pad_left = maxpool2d->pad()->left(); + const uint32_t pad_right = maxpool2d->pad()->right(); + + const uint32_t output_height = + compute_out_size(ifm_height, pad_top + pad_bottom, window_height, stride_height); + const uint32_t output_width = + compute_out_size(ifm_width, pad_left + pad_right, window_width, stride_width); + + // prepare output buffer + Shape output_shape{batches, output_height, output_width, depth}; + auto output_buf = make_buffer(output_shape); + + for (uint32_t batch = 0; batch < batches; ++batch) + { + for (uint32_t out_y = 0; out_y < output_height; ++out_y) + { + for (uint32_t out_x = 0; out_x < output_width; ++out_x) + { + for (uint32_t channel = 0; channel < depth; ++channel) + { + const int in_x_origin = (out_x * stride_width) - pad_left; + const int in_y_origin = (out_y * stride_height) - pad_top; + + // Compute the boundaries of the filter region clamped so as to + // ensure that the filter window fits in the input array. + const uint32_t filter_x_start = std::max(0, -in_x_origin); + const uint32_t filter_x_end = std::min(window_width, ifm_width - in_x_origin); + + const uint32_t filter_y_start = std::max(0, -in_y_origin); + const uint32_t filter_y_end = std::min(window_height, ifm_height - in_y_origin); + + T max = std::numeric_limits::lowest(); + + for (uint32_t filter_y = filter_y_start; filter_y < filter_y_end; ++filter_y) + { + for (uint32_t filter_x = filter_x_start; filter_x < filter_x_end; ++filter_x) + { + const uint32_t in_x = in_x_origin + filter_x; + const uint32_t in_y = in_y_origin + filter_y; + max = std::max(max, ifm_buf->at(Index({batch, in_y, in_x, channel}))); + } + } + + output_buf.at(Index({batch, out_y, out_x, channel})) = max; + } + } + } + } + + return output_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::MaxPool2D *maxpool2d) +{ + auto ifm_data = annot_data(maxpool2d->ifm()); + + validate(ifm_data, "Can't find input data of MaxPool2D"); + validate(ifm_data->shape()->rank() == 4, "IFM rank should be 4"); + validate(annot_domain(maxpool2d->ifm()) == loco::Domain::Feature, + "ifm of MaxPool2D is not Feature"); + + std::unique_ptr maxpool2d_data = nullptr; + + switch (ifm_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto ifm_buf = ifm_data->as_f32_bufptr(); + + auto maxpool2d_buf = maxPool2D(maxpool2d, ifm_buf); + + maxpool2d_data = make_data(maxpool2d_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(maxpool2d_data != nullptr); + + annot_data(maxpool2d, std::move(maxpool2d_data)); + annot_domain(maxpool2d, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/MaxPool2D.test.cpp b/compiler/locomotiv/src/Node/MaxPool2D.test.cpp new file mode 100644 index 00000000000..9d877a96bea --- /dev/null +++ b/compiler/locomotiv/src/Node/MaxPool2D.test.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +void run_test(const float *ifm, const float *expected_ofm, const Shape &ifm_shape, + const Shape &ofm_shape, const uint32_t window_v, const uint32_t window_h, + const uint32_t stride_v, const uint32_t stride_h, const uint32_t pad_top, + const uint32_t pad_bottom, const uint32_t pad_left, const uint32_t pad_right) +{ + // Let's make FeatureEncode-MaxPool2D graph + auto g = loco::make_graph(); + auto enc = g->nodes()->create(); + + // Fill output data of FeatureEncode from ifm + auto enc_buf = make_buffer(ifm_shape); + + auto ifm_overlay = make_overlay(ifm_shape, const_cast(ifm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ifm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + enc_buf.at(ind) = ifm_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(enc_buf); + locomotiv::annot_data(enc, std::move(enc_data)); + locomotiv::annot_domain(enc, loco::Domain::Feature); + + // build MaxPool2D + auto maxpool2d = g->nodes()->create(); + maxpool2d->ifm(enc); + maxpool2d->window()->vertical(window_v); + maxpool2d->window()->horizontal(window_h); + maxpool2d->stride()->vertical(stride_v); + maxpool2d->stride()->horizontal(stride_h); + maxpool2d->pad()->top(pad_top); + maxpool2d->pad()->bottom(pad_bottom); + maxpool2d->pad()->left(pad_left); + maxpool2d->pad()->right(pad_right); + + // run interpreter + locomotiv::NodeExecution::get().run(maxpool2d); + + // get result of calculation + auto maxpool2d_data = locomotiv::annot_data(maxpool2d); + + // check the result + ASSERT_NE(maxpool2d_data, nullptr); + ASSERT_TRUE(maxpool2d_data->dtype() == loco::DataType::FLOAT32); + ASSERT_TRUE(*(maxpool2d_data->shape()) == ofm_shape); + + auto ofm_overlay = + make_overlay(ofm_shape, const_cast(expected_ofm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ofm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ASSERT_FLOAT_EQ(maxpool2d_data->as_f32_bufptr()->at(ind), ofm_overlay.at(ind)); + } + + ASSERT_EQ(locomotiv::annot_domain(maxpool2d), loco::Domain::Feature); +} + +} // namespace + +// clang-format off +/* ifm and ofm are from the code below: + + value = tf.random_normal([1, 3, 3, 1], stddev=1) + maxpool = tf.nn.max_pool(value, ksize = [1, 2, 2, 1], strides = [1, 1, 1, 1], padding= 'VALID', + data_format="NHWC") + with tf.Session() as sess: + print(sess.run(maxpool)) +*/ + +TEST(NodeExecution_MaxPool2D, f32_1x3x3x1_calculation) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + -1.5510627, 0.3653609, 1.9002001, + -0.15861237, -0.32944828, 1.2053918, + 0.50054574, -0.8533826, 0.131492, + }; + + const float ofm[] = + { + 0.3653609, 1.9002001, + 0.50054574, 1.2053918 + }; + + run_test(ifm, ofm, + Shape{1, 3, 3, 1}, Shape{1, 2, 2, 1}, // input shape , output shape + 2, 2, // kernel + 1, 1, // stride + 0, 0, 0, 0 // padding + ); +} + +TEST(NodeExecution_MaxPool2D, with_padding) +{ + using nncc::core::ADT::tensor::Shape; + + const float ifm[] = + { + 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25 + }; + + const float ofm[] = + { + 7, 9, 10, + 17, 19, 20, + 22, 24, 25 + }; + + run_test(ifm, ofm, + Shape{1, 5, 5, 1}, Shape{1, 3, 3, 1}, // input shape , output shape + 3, 3, // kernel + 2, 2, // stride + 1, 1, 1, 1 // padding - this mimics SAME padding + ); +} +// clang-format on diff --git a/compiler/locomotiv/src/Node/Pull.cpp b/compiler/locomotiv/src/Node/Pull.cpp new file mode 100644 index 00000000000..c482d8b04eb --- /dev/null +++ b/compiler/locomotiv/src/Node/Pull.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "UserData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Pull *pull) +{ +// TODO Remove deprecated code +#if 0 + validate(annot_data(pull), "Data for Pull is not ready"); + + validate(annot_domain(pull) == loco::Domain::Tensor, "Domain for Pull is not Tensor"); + + // DO NOTHING +#endif + + auto input_data = user_data(pull); + + validate(input_data, "Input not ready"); + // User always passes a "Tensor" + + std::unique_ptr pull_data = nullptr; + + // Q. Is it possible to use generic one? + switch (input_data->dtype()) + { + case loco::DataType::S32: + { + auto input_bufptr = input_data->as_s32_bufptr(); + pull_data = make_data(*input_bufptr); + break; + } + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + pull_data = make_data(*input_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(pull_data != nullptr); + annot_data(pull, std::move(pull_data)); + annot_domain(pull, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Pull.test.cpp b/compiler/locomotiv/src/Node/Pull.test.cpp new file mode 100644 index 00000000000..53e78776b52 --- /dev/null +++ b/compiler/locomotiv/src/Node/Pull.test.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "UserData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Pull, check_data_ready) +{ + // Make graph with Pull node only + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + + // Data not ready yet + ASSERT_ANY_THROW(locomotiv::NodeExecution::get().run(pull)); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::user_data(pull, std::move(pull_data)); + +// The behavior of Pull is now consistent with that of other nodes. +// - annot_data and annot_domain is available after evaluating that "pull" node. +// TODO Remove this +#if 0 + // Domain not ready yet + ASSERT_ANY_THROW(locomotiv::NodeExecution::get().run(pull)); + + // Set Domain + locomotiv::annot_domain(pull, loco::Domain::Tensor); +#endif + + // Valid run + ASSERT_NO_THROW(locomotiv::NodeExecution::get().run(pull)); +} diff --git a/compiler/locomotiv/src/Node/Push.cpp b/compiler/locomotiv/src/Node/Push.cpp new file mode 100644 index 00000000000..fc5808b152b --- /dev/null +++ b/compiler/locomotiv/src/Node/Push.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Push *push) +{ + auto from_data = annot_data(push->from()); + + validate(from_data, "Ingredient not ready"); + validate(annot_domain(push->from()) == loco::Domain::Tensor, "Ingredient of Push is not tensor"); + + std::unique_ptr push_data = nullptr; + + switch (from_data->dtype()) + { + case loco::DataType::S32: + { + auto from_bufptr = from_data->as_s32_bufptr(); + push_data = make_data(*from_bufptr); + break; + } + case loco::DataType::FLOAT32: + { + auto from_bufptr = from_data->as_f32_bufptr(); + push_data = make_data(*from_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(push_data != nullptr); + annot_data(push, std::move(push_data)); + annot_domain(push, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Push.test.cpp b/compiler/locomotiv/src/Node/Push.test.cpp new file mode 100644 index 00000000000..be8f1e4e940 --- /dev/null +++ b/compiler/locomotiv/src/Node/Push.test.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Push, s32) +{ + // Make pull-push graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::S32); + pull->shape({1}); + auto push = g->nodes()->create(); + push->from(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + pull_buf.at(Index{0}) = 42; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(push); + + auto push_data = locomotiv::annot_data(push); + ASSERT_NE(push_data, nullptr); + ASSERT_EQ(push_data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(push_data->shape()), Shape{1}); + ASSERT_EQ(push_data->as_s32_bufptr()->at(Index{0}), pull_buf.at(Index{0})); + + ASSERT_EQ(locomotiv::annot_domain(push), loco::Domain::Tensor); +} + +TEST(NodeExecution_Push, f32) +{ + // Make pull-push graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1}); + auto push = g->nodes()->create(); + push->from(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1}); + pull_buf.at(Index{0}) = 3.14f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(push); + + auto push_data = locomotiv::annot_data(push); + ASSERT_NE(push_data, nullptr); + ASSERT_EQ(push_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(push_data->shape()), Shape{1}); + ASSERT_FLOAT_EQ(push_data->as_f32_bufptr()->at(Index{0}), pull_buf.at(Index{0})); + + ASSERT_EQ(locomotiv::annot_domain(push), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/ReLU.cpp b/compiler/locomotiv/src/Node/ReLU.cpp new file mode 100644 index 00000000000..c0f8620e70c --- /dev/null +++ b/compiler/locomotiv/src/Node/ReLU.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +namespace +{ + +inline float relu_ew(float val) { return val > 0.0f ? val : 0.0f; } + +struct Func final : public locomotiv::UnaryFunc +{ + float apply(float v) const final { return relu_ew(v); } +}; + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::ReLU *relu) +{ + Func f; + + eltwise_unary(relu, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/ReLU.test.cpp b/compiler/locomotiv/src/Node/ReLU.test.cpp new file mode 100644 index 00000000000..0ddd01d0f1f --- /dev/null +++ b/compiler/locomotiv/src/Node/ReLU.test.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_ReLU, f32) +{ + // Make pull-relu graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2}); + auto relu = g->nodes()->create(); + relu->input(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{2}); + pull_buf.at(Index{0}) = -10.0f; + pull_buf.at(Index{1}) = 10.0f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(relu); + + auto relu_data = locomotiv::annot_data(relu); + ASSERT_NE(relu_data, nullptr); + ASSERT_EQ(relu_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(relu_data->shape()), Shape{2}); + ASSERT_FLOAT_EQ(relu_data->as_f32_bufptr()->at(Index{0}), 0.0f); + ASSERT_FLOAT_EQ(relu_data->as_f32_bufptr()->at(Index{1}), 10.0f); + + ASSERT_EQ(locomotiv::annot_domain(relu), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/ReLU6.cpp b/compiler/locomotiv/src/Node/ReLU6.cpp new file mode 100644 index 00000000000..586c015fc36 --- /dev/null +++ b/compiler/locomotiv/src/Node/ReLU6.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +// TODO Remove deprecated code +#if 0 +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include + +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +#include +#include +#endif + +namespace +{ + +inline float relu6_ew(float val) { return val < 0.0f ? 0.0f : (val < 6.0f ? val : 6.0f); } + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::ReLU6 *relu6) +{ +// TODO Remove deprecated code +#if 0 + auto input_data = annot_data(relu6->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(relu6->input()) != loco::Domain::Unknown, + "Input domain of ReLU is Unknown"); + + std::unique_ptr relu6_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto *shape = input_data->shape(); + auto relu6_buf = make_buffer(*shape); + + for (IndexEnumerator e{*shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + relu6_buf.at(index) = relu6_ew(input_bufptr->at(index)); + } + + relu6_data = make_data(relu6_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(relu6_data != nullptr); + annot_data(relu6, std::move(relu6_data)); + annot_domain(relu6, annot_domain(relu6->input())); +#endif + + struct Func final : public UnaryFunc + { + float apply(float v) const final { return relu6_ew(v); } + }; + + Func f; + + eltwise_unary(relu6, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/ReLU6.test.cpp b/compiler/locomotiv/src/Node/ReLU6.test.cpp new file mode 100644 index 00000000000..07f6af23f35 --- /dev/null +++ b/compiler/locomotiv/src/Node/ReLU6.test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_ReLU6, f32) +{ + // Make pull-relu6 graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2, 2}); + auto relu6 = g->nodes()->create(); + relu6->input(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{2, 2}); + pull_buf.at(Index{0, 0}) = -5.0f; + pull_buf.at(Index{0, 1}) = 6.0f; + pull_buf.at(Index{1, 0}) = 7.0f; + pull_buf.at(Index{1, 1}) = -8.0f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(relu6); + + auto relu6_data = locomotiv::annot_data(relu6); + ASSERT_NE(relu6_data, nullptr); + ASSERT_EQ(relu6_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(relu6_data->shape()), Shape({2, 2})); + ASSERT_FLOAT_EQ(relu6_data->as_f32_bufptr()->at(Index{0, 0}), 0.0f); + ASSERT_FLOAT_EQ(relu6_data->as_f32_bufptr()->at(Index{0, 1}), 6.0f); + ASSERT_FLOAT_EQ(relu6_data->as_f32_bufptr()->at(Index{1, 0}), 6.0f); + ASSERT_FLOAT_EQ(relu6_data->as_f32_bufptr()->at(Index{1, 1}), 0.0f); + + ASSERT_EQ(locomotiv::annot_domain(relu6), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/Reshape.cpp b/compiler/locomotiv/src/Node/Reshape.cpp new file mode 100644 index 00000000000..ac16720244d --- /dev/null +++ b/compiler/locomotiv/src/Node/Reshape.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::num_elements; + +#include +#include +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Reshape *reshape) +{ + auto input_data = annot_data(reshape->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(reshape->input()) == loco::Domain::Tensor, + "Input domain of Reshape is not Tensor"); + + std::unique_ptr reshape_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto *input_shape = input_data->shape(); + + using Shape = nncc::core::ADT::tensor::Shape; + std::unique_ptr output_shape(new Shape()); + + output_shape->resize(reshape->rank()); + for (uint32_t axis = 0; axis < output_shape->rank(); ++axis) + { + output_shape->dim(axis) = reshape->dim(axis).value(); + } + + auto reshape_bufptr = make_buffer(*output_shape); + + float *input_ptr = const_cast(input_bufptr->base()); + uint64_t input_len = num_elements(*input_shape) * sizeof(float); + + float *output_ptr = reshape_bufptr.base(); + + assert(input_len == num_elements(*output_shape) * sizeof(float)); + memcpy(output_ptr, input_ptr, input_len); + + reshape_data = make_data(reshape_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(reshape_data != nullptr); + annot_data(reshape, std::move(reshape_data)); + annot_domain(reshape, annot_domain(reshape->input())); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Reshape.test.cpp b/compiler/locomotiv/src/Node/Reshape.test.cpp new file mode 100644 index 00000000000..8e54a16dfe9 --- /dev/null +++ b/compiler/locomotiv/src/Node/Reshape.test.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Reshape, f32) +{ + // Make pull-reshape graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({4}); + auto reshape = g->nodes()->create>(); + reshape->input(pull); + reshape->shape({2, 2}); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{4}); + pull_buf.at(Index{0}) = 0.0f; + pull_buf.at(Index{1}) = 1.1f; + pull_buf.at(Index{2}) = 2.2f; + pull_buf.at(Index{3}) = 3.3f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(reshape); + + auto reshape_data = locomotiv::annot_data(reshape); + ASSERT_NE(reshape_data, nullptr); + ASSERT_EQ(reshape_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(reshape_data->shape()), (Shape{2, 2})); + ASSERT_FLOAT_EQ(reshape_data->as_f32_bufptr()->at(Index{0, 0}), 0.0f); + ASSERT_FLOAT_EQ(reshape_data->as_f32_bufptr()->at(Index{0, 1}), 1.1f); + ASSERT_FLOAT_EQ(reshape_data->as_f32_bufptr()->at(Index{1, 0}), 2.2f); + ASSERT_FLOAT_EQ(reshape_data->as_f32_bufptr()->at(Index{1, 1}), 3.3f); + + ASSERT_EQ(locomotiv::annot_domain(reshape), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/Softmax.cpp b/compiler/locomotiv/src/Node/Softmax.cpp new file mode 100644 index 00000000000..352598b27f6 --- /dev/null +++ b/compiler/locomotiv/src/Node/Softmax.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Shape; + +#include +#include +#include + +namespace +{ + +Index reduce_index(const Index &index, uint32_t axis) +{ + Index r_index; + + r_index.resize(index.rank()); + for (uint32_t i = 0; i < index.rank(); ++i) + r_index.at(i) = index.at(i); + r_index.at(axis) = 0; + + return r_index; +} + +Shape reduce_shape(const Shape &shape, uint32_t axis) +{ + Shape r_shape; + + r_shape.resize(shape.rank()); + for (uint32_t i = 0; i < shape.rank(); ++i) + r_shape.dim(i) = shape.dim(i); + r_shape.dim(axis) = 1; + + return r_shape; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TensorSoftmax *softmax) +{ + auto input_data = annot_data(softmax->input()); + + validate(input_data, "Input not ready"); + validate(annot_domain(softmax->input()) == loco::Domain::Tensor, + "Input domain of TensorSoftmax is not Tensor"); + + std::unique_ptr softmax_data = nullptr; + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto axis = softmax->axis(); + + auto *input_shape = input_data->shape(); + auto input_bufptr = input_data->as_f32_bufptr(); + auto softmax_buf = make_buffer(*input_data->shape()); + + auto reduce_sum_shape = reduce_shape(*input_shape, axis); + auto reduce_sum_bufptr = make_buffer(reduce_sum_shape); + + for (IndexEnumerator e{*input_shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + const auto r_index = reduce_index(index, axis); + + reduce_sum_bufptr.at(r_index) += exp(input_bufptr->at(index)); + } + + for (IndexEnumerator e{*input_shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + const auto r_index = reduce_index(index, axis); + + softmax_buf.at(index) = exp(input_bufptr->at(index)) / reduce_sum_bufptr.at(r_index); + } + + softmax_data = make_data(softmax_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(softmax_data != nullptr); + annot_data(softmax, std::move(softmax_data)); + annot_domain(softmax, annot_domain(softmax->input())); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Softmax.test.cpp b/compiler/locomotiv/src/Node/Softmax.test.cpp new file mode 100644 index 00000000000..21d24027512 --- /dev/null +++ b/compiler/locomotiv/src/Node/Softmax.test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Softmax, f32) +{ + // Make pull-softmax graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({2, 2}); + auto softmax = g->nodes()->create(); + softmax->input(pull); + softmax->axis(1); + + // Make and assign data to pull node + auto pull_buf = make_buffer({2, 2}); + pull_buf.at(Index{0, 0}) = 1.1f; + pull_buf.at(Index{0, 1}) = 1.1f; + pull_buf.at(Index{1, 0}) = 3.3f; + pull_buf.at(Index{1, 1}) = 3.3f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(softmax); + + auto kShape = Shape{2, 2}; + auto softmax_data = locomotiv::annot_data(softmax); + ASSERT_NE(softmax_data, nullptr); + ASSERT_EQ(softmax_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(softmax_data->shape()), kShape); + ASSERT_FLOAT_EQ(softmax_data->as_f32_bufptr()->at(Index{0, 0}), 0.5f); + ASSERT_FLOAT_EQ(softmax_data->as_f32_bufptr()->at(Index{0, 1}), 0.5f); + ASSERT_FLOAT_EQ(softmax_data->as_f32_bufptr()->at(Index{1, 0}), 0.5f); + ASSERT_FLOAT_EQ(softmax_data->as_f32_bufptr()->at(Index{1, 1}), 0.5f); + + ASSERT_EQ(locomotiv::annot_domain(softmax), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/Tanh.cpp b/compiler/locomotiv/src/Node/Tanh.cpp new file mode 100644 index 00000000000..78d329e7c57 --- /dev/null +++ b/compiler/locomotiv/src/Node/Tanh.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include + +namespace +{ + +struct Func final : public locomotiv::UnaryFunc +{ + float apply(float v) const final { return std::tanh(v); } +}; + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::Tanh *tanh) +{ + Func f; + + eltwise_unary(tanh, f); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/Tanh.test.cpp b/compiler/locomotiv/src/Node/Tanh.test.cpp new file mode 100644 index 00000000000..78c3a13baea --- /dev/null +++ b/compiler/locomotiv/src/Node/Tanh.test.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Tanh, f32) +{ + // Make pull-Tanh graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({3}); + auto tanh = g->nodes()->create(); + tanh->input(pull); + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{3}); + pull_buf.at(Index{0}) = 0.0f; + pull_buf.at(Index{1}) = 1.0f; + pull_buf.at(Index{2}) = -1.0f; + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(tanh); + + auto tanh_data = locomotiv::annot_data(tanh); + ASSERT_NE(tanh_data, nullptr); + ASSERT_EQ(tanh_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(tanh_data->shape()), Shape{3}); + ASSERT_FLOAT_EQ(tanh_data->as_f32_bufptr()->at(Index{0}), 0.0f); + ASSERT_FLOAT_EQ(tanh_data->as_f32_bufptr()->at(Index{1}), 0.761594f); + ASSERT_FLOAT_EQ(tanh_data->as_f32_bufptr()->at(Index{2}), -0.761594f); + + ASSERT_EQ(locomotiv::annot_domain(tanh), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/TensorBroadcast.cpp b/compiler/locomotiv/src/Node/TensorBroadcast.cpp new file mode 100644 index 00000000000..010ca68215c --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorBroadcast.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Shape; + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TensorBroadcast *tensor_broadcast) +{ + auto input_data = annot_data(tensor_broadcast->input()); + + // Calculate output shape + Shape input_shape = *(input_data->shape()); + + // TODO Reuse "ShapeInferenceService" + Shape output_shape; + + output_shape.resize(input_shape.rank()); + for (uint32_t axis = 0; axis < input_shape.rank(); ++axis) + { + if (tensor_broadcast->mapping()->defined(axis)) + { + assert(input_shape.dim(axis) == 1); // Required by TensorBroadcast definition + output_shape.dim(axis) = tensor_broadcast->mapping()->dim(axis).value(); + } + else + { + output_shape.dim(axis) = input_shape.dim(axis); + } + } + + assert(input_shape.rank() == output_shape.rank()); + + uint32_t const rank = input_shape.rank(); + + std::unique_ptr output_data = nullptr; + + switch (input_data->dtype()) + { + // TODO Use type-generic implementation! + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto output_buf = make_buffer(output_shape); + + for (IndexEnumerator e{output_shape}; e.valid(); e.advance()) + { + auto input_index = e.current(); + const auto &output_index = e.current(); + + for (uint32_t axis = 0; axis < rank; ++axis) + { + if (tensor_broadcast->mapping()->defined(axis)) + { + input_index.at(axis) = 0; + } + } + + output_buf.at(output_index) = input_bufptr->at(input_index); + } + + output_data = make_data(output_buf); + break; + } + default: + throw std::runtime_error("Not yet supported"); + } + + assert(output_data != nullptr); + annot_data(tensor_broadcast, std::move(output_data)); + annot_domain(tensor_broadcast, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/TensorBroadcast.test.cpp b/compiler/locomotiv/src/Node/TensorBroadcast.test.cpp new file mode 100644 index 00000000000..e8347d73791 --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorBroadcast.test.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_TensorBroadcast, f32) +{ + // Create a sample graph w/ TensorBroadcast + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->shape({1, 1}); + auto broadcast = g->nodes()->create(); + broadcast->input(pull); + broadcast->mapping()->dim(0) = 2; + + // Make and assign data to pull node + auto pull_buf = make_buffer(Shape{1, 1}); + pull_buf.at(Index{0, 0}) = -1.0f; + + auto pull_data = locomotiv::make_data(pull_buf); + locomotiv::annot_data(pull, std::move(pull_data)); + locomotiv::annot_domain(pull, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(broadcast); + + auto broadcast_data = locomotiv::annot_data(broadcast); + ASSERT_NE(broadcast_data, nullptr); + ASSERT_EQ(broadcast_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ((*(broadcast_data->shape())), (Shape{2, 1})); + ASSERT_FLOAT_EQ(broadcast_data->as_f32_bufptr()->at(Index{0, 0}), -1.0f); + ASSERT_FLOAT_EQ(broadcast_data->as_f32_bufptr()->at(Index{1, 0}), -1.0f); + + ASSERT_EQ(locomotiv::annot_domain(broadcast), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/TensorConcat.cpp b/compiler/locomotiv/src/Node/TensorConcat.cpp new file mode 100644 index 00000000000..5097e55c6bb --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorConcat.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Shape; + +#include +#include + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TensorConcat *tensor_concat) +{ + auto lhs_data = annot_data(tensor_concat->lhs()); + auto rhs_data = annot_data(tensor_concat->rhs()); + auto axis = tensor_concat->axis(); + + validate(lhs_data && rhs_data, "Ingredient not ready"); + validate(lhs_data->dtype() == rhs_data->dtype(), "lhs and rhs of Concat should have same dtype"); + + validate(annot_domain(tensor_concat->lhs()) == loco::Domain::Tensor && + annot_domain(tensor_concat->rhs()) == loco::Domain::Tensor, + "Some ingredients of TensorConcat is not Tensor"); + + // Calculate output shape + Shape lhs_shape = *lhs_data->shape(); + Shape rhs_shape = *rhs_data->shape(); + Shape concat_shape; + + assert(lhs_shape.rank() == rhs_shape.rank()); + concat_shape.resize(lhs_shape.rank()); + for (uint32_t index = 0; index < lhs_shape.rank(); ++index) + { + if (index == axis) + concat_shape.dim(index) = lhs_shape.dim(index) + rhs_shape.dim(index); + else + { + assert(lhs_shape.dim(index) == rhs_shape.dim(index)); + concat_shape.dim(index) = lhs_shape.dim(index); + } + } + auto left_dim_size = lhs_shape.dim(axis); + + // Copy data from two inputs LHS and RHS to Concat + std::unique_ptr concat_data = nullptr; + switch (lhs_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto lhs_bufptr = lhs_data->as_f32_bufptr(); + auto rhs_bufptr = rhs_data->as_f32_bufptr(); + auto concat_buf = make_buffer(concat_shape); + + for (IndexEnumerator e{concat_shape}; e.valid(); e.advance()) + { + const auto &e_index = e.current(); + + if (e_index.at(axis) < left_dim_size) + { + // Left index is same as output index + concat_buf.at(e_index) = lhs_bufptr->at(e_index); + } + else + { + // Adjust right index to valid range + Index r_index = e_index; + r_index.at(axis) -= left_dim_size; + concat_buf.at(e_index) = rhs_bufptr->at(r_index); + } + } + + concat_data = make_data(concat_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(concat_data != nullptr); + annot_data(tensor_concat, std::move(concat_data)); + annot_domain(tensor_concat, loco::Domain::Tensor); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/TensorConcat.test.cpp b/compiler/locomotiv/src/Node/TensorConcat.test.cpp new file mode 100644 index 00000000000..d71b51524a7 --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorConcat.test.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_TensorConcat, f32) +{ + // Make (pull, pull)-concat graph + auto g = loco::make_graph(); + auto pull_l = g->nodes()->create(); + pull_l->dtype(loco::DataType::FLOAT32); + pull_l->shape({1, 2}); + auto pull_r = g->nodes()->create(); + pull_r->dtype(loco::DataType::FLOAT32); + pull_r->shape({1, 2}); + auto tconcat = g->nodes()->create(); + tconcat->lhs(pull_l); + tconcat->rhs(pull_r); + tconcat->axis(0); + + // Make and assign data to pull node + auto pull_l_buf = make_buffer(Shape{1, 2}); + pull_l_buf.at(Index{0, 0}) = -1.0f; + pull_l_buf.at(Index{0, 1}) = -2.0f; + auto pull_r_buf = make_buffer(Shape{1, 2}); + pull_r_buf.at(Index{0, 0}) = 3.0f; + pull_r_buf.at(Index{0, 1}) = 4.0f; + + auto pull_l_data = locomotiv::make_data(pull_l_buf); + locomotiv::annot_data(pull_l, std::move(pull_l_data)); + locomotiv::annot_domain(pull_l, loco::Domain::Tensor); + auto pull_r_data = locomotiv::make_data(pull_r_buf); + locomotiv::annot_data(pull_r, std::move(pull_r_data)); + locomotiv::annot_domain(pull_r, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(tconcat); + + auto concat_data = locomotiv::annot_data(tconcat); + ASSERT_NE(concat_data, nullptr); + ASSERT_EQ(concat_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ((*(concat_data->shape())), (Shape{2, 2})); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{0, 0}), -1.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{0, 1}), -2.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{1, 0}), 3.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{1, 1}), 4.0f); + + ASSERT_EQ(locomotiv::annot_domain(tconcat), loco::Domain::Tensor); +} + +TEST(NodeExecution_TensorConcat, f32_2) +{ + // Make (pull, pull)-concat graph + auto g = loco::make_graph(); + auto pull_l = g->nodes()->create(); + pull_l->dtype(loco::DataType::FLOAT32); + pull_l->shape({1, 2}); + auto pull_r = g->nodes()->create(); + pull_r->dtype(loco::DataType::FLOAT32); + pull_r->shape({3, 2}); + auto tconcat = g->nodes()->create(); + tconcat->lhs(pull_l); + tconcat->rhs(pull_r); + tconcat->axis(0); + + // Make and assign data to pull node + auto pull_l_buf = make_buffer(Shape{1, 2}); + pull_l_buf.at(Index{0, 0}) = -1.0f; + pull_l_buf.at(Index{0, 1}) = -2.0f; + auto pull_r_buf = make_buffer(Shape{3, 2}); + pull_r_buf.at(Index{0, 0}) = 3.0f; + pull_r_buf.at(Index{0, 1}) = 4.0f; + pull_r_buf.at(Index{1, 0}) = -3.0f; + pull_r_buf.at(Index{1, 1}) = -4.0f; + pull_r_buf.at(Index{2, 0}) = 5.0f; + pull_r_buf.at(Index{2, 1}) = 6.0f; + + auto pull_l_data = locomotiv::make_data(pull_l_buf); + locomotiv::annot_data(pull_l, std::move(pull_l_data)); + locomotiv::annot_domain(pull_l, loco::Domain::Tensor); + auto pull_r_data = locomotiv::make_data(pull_r_buf); + locomotiv::annot_data(pull_r, std::move(pull_r_data)); + locomotiv::annot_domain(pull_r, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(tconcat); + + auto concat_data = locomotiv::annot_data(tconcat); + ASSERT_NE(concat_data, nullptr); + ASSERT_EQ(concat_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ((*(concat_data->shape())), (Shape{4, 2})); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{0, 0}), -1.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{0, 1}), -2.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{1, 0}), 3.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{1, 1}), 4.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{2, 0}), -3.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{2, 1}), -4.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{3, 0}), 5.0f); + ASSERT_FLOAT_EQ(concat_data->as_f32_bufptr()->at(Index{3, 1}), 6.0f); + + ASSERT_EQ(locomotiv::annot_domain(tconcat), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/TensorConstantPad.cpp b/compiler/locomotiv/src/Node/TensorConstantPad.cpp new file mode 100644 index 00000000000..989afaf9412 --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorConstantPad.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include + +#include + +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TensorConstantPad *pad) +{ + auto input_data = annot_data(pad->input()); + auto input_domain = annot_domain(pad->input()); + validate(input_data, "Input not ready"); + validate(input_domain == loco::Domain::Tensor, "Input domain of TensorConstantPad is not Tensor"); + + auto input_shape = input_data->shape(); + const uint32_t input_rank = input_shape->rank(); + + auto padding = pad->padding(); + validate(input_rank == padding->rank(), "input and padding should have same rank"); + + auto constant_node = pad->constant(); + auto constant_data = annot_data(constant_node); + validate(constant_data->dtype() == input_data->dtype(), "constant and input have same data type"); + validate(constant_data->shape()->rank() == 1 && constant_data->shape()->dim(0) == 1, + "constant should have one rank with one dimension at zero axis"); + + std::unique_ptr pad_data = nullptr; + Index base_index; + base_index.resize(input_rank); + + // Tensor is padded by relocating its base. + // padded output index = input index + base index + for (uint32_t axis = 0; axis < padding->rank(); axis++) + { + base_index.at(axis) = padding->front(axis); + } + + // calculate output shape + Shape output_shape; + output_shape.resize(input_rank); + for (uint32_t i = 0; i < input_rank; i++) + { + output_shape.dim(i) = input_shape->dim(i) + padding->front(i) + padding->back(i); + } + + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_buf = input_data->as_f32_bufptr(); + auto constant_data_buf = constant_data->as_f32_bufptr(); + const auto constant_value = constant_data_buf->at(Index{0}); + + auto output_buf = make_buffer(output_shape); + + for (IndexEnumerator ie{*input_shape}, oe{output_shape}; oe.valid(); oe.advance()) + { + auto input_index = ie.current(); + auto output_index = oe.current(); + + if ((input_index + base_index) == output_index) + { + output_buf.at(output_index) = input_buf->at(input_index); + ie.advance(); + } + else + { + output_buf.at(output_index) = constant_value; + } + } + + pad_data = make_data(output_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(pad_data != nullptr); + annot_data(pad, std::move(pad_data)); + annot_domain(pad, annot_domain(pad->input())); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/TensorConstantPad.test.cpp b/compiler/locomotiv/src/Node/TensorConstantPad.test.cpp new file mode 100644 index 00000000000..0f60c5f853a --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorConstantPad.test.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Shape; + +TEST(NodeExecution_Pad, tensor_constant_pad_4_dim) +{ + auto g = loco::make_graph(); + + auto inputTensor = g->nodes()->create(); + inputTensor->dtype(loco::DataType::FLOAT32); + inputTensor->shape({1, 2, 2, 1}); + auto inputTensor_buf = make_buffer(Shape{1, 2, 2, 1}); + inputTensor_buf.at(Index{0, 0, 0, 0}) = 1.0f; + inputTensor_buf.at(Index{0, 0, 1, 0}) = 2.0f; + inputTensor_buf.at(Index{0, 1, 0, 0}) = 3.0f; + inputTensor_buf.at(Index{0, 1, 1, 0}) = 4.0f; + auto inputTensor_data = locomotiv::make_data(inputTensor_buf); + locomotiv::annot_data(inputTensor, std::move(inputTensor_data)); + locomotiv::annot_domain(inputTensor, loco::Domain::Tensor); + + auto constant = g->nodes()->create(); + constant->dtype(loco::DataType::FLOAT32); + constant->shape({1}); + auto constant_buf = make_buffer(Shape{1}); + constant_buf.at(Index{0}) = 0.0f; + auto constant_data = locomotiv::make_data(constant_buf); + locomotiv::annot_data(constant, std::move(constant_data)); + locomotiv::annot_domain(constant, loco::Domain::Tensor); + + auto pad = g->nodes()->create(); + pad->input(inputTensor); + pad->constant(constant); + + auto padding = pad->padding(); + padding->rank(4); + padding->front(0) = 0; + padding->back(0) = 0; + padding->front(1) = 3; + padding->back(1) = 1; + padding->front(2) = 1; + padding->back(2) = 1; + padding->front(3) = 0; + padding->back(3) = 0; + + locomotiv::NodeExecution::get().run(pad); + + auto pad_data = locomotiv::annot_data(pad); + ASSERT_NE(pad_data, nullptr); + ASSERT_EQ(pad_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(pad_data->shape()), Shape({1, 6, 4, 1})); + + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0, 3, 1, 0}), 1.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0, 3, 2, 0}), 2.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0, 4, 1, 0}), 3.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0, 4, 2, 0}), 4.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0, 0, 0, 0}), 0.0f); + + ASSERT_EQ(locomotiv::annot_domain(pad), loco::Domain::Tensor); +} + +TEST(NodeExecution_Pad, tensor_constant_pad_1_dim) +{ + auto g = loco::make_graph(); + + auto inputTensor = g->nodes()->create(); + inputTensor->dtype(loco::DataType::FLOAT32); + inputTensor->shape({3}); + auto inputTensor_buf = make_buffer(Shape{3}); + inputTensor_buf.at(Index{0}) = 1.0f; + inputTensor_buf.at(Index{1}) = 5.0f; + inputTensor_buf.at(Index{2}) = 3.0f; + auto inputTensor_data = locomotiv::make_data(inputTensor_buf); + locomotiv::annot_data(inputTensor, std::move(inputTensor_data)); + locomotiv::annot_domain(inputTensor, loco::Domain::Tensor); + + auto constant = g->nodes()->create(); + constant->dtype(loco::DataType::FLOAT32); + constant->shape({1}); + auto constant_buf = make_buffer(Shape{1}); + constant_buf.at(Index{0}) = 0.0f; + auto constant_data = locomotiv::make_data(constant_buf); + locomotiv::annot_data(constant, std::move(constant_data)); + locomotiv::annot_domain(constant, loco::Domain::Tensor); + + auto pad = g->nodes()->create(); + pad->input(inputTensor); + pad->constant(constant); + auto padding = pad->padding(); + padding->rank(1); + padding->front(0) = 2; + padding->back(0) = 1; + + locomotiv::NodeExecution::get().run(pad); + + auto pad_data = locomotiv::annot_data(pad); + ASSERT_NE(pad_data, nullptr); + ASSERT_EQ(pad_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(pad_data->shape()), Shape({6})); + + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{0}), 0.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1}), 0.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{2}), 1.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{3}), 5.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{4}), 3.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{5}), 0.0f); + + ASSERT_EQ(locomotiv::annot_domain(pad), loco::Domain::Tensor); +} + +TEST(NodeExecution_Pad, tensor_constant_pad_6_dim) +{ + auto g = loco::make_graph(); + + auto inputTensor = g->nodes()->create(); + inputTensor->dtype(loco::DataType::FLOAT32); + inputTensor->shape({2, 1, 3, 2, 1, 2}); + auto inputTensor_buf = make_buffer(Shape{2, 1, 3, 2, 1, 2}); + int a, b, c, d, e, f; + float dummy = 1.0f; + for (uint32_t a = 0; a < 2; a++) + { + for (uint32_t b = 0; b < 1; b++) + { + for (uint32_t c = 0; c < 3; c++) + { + for (uint32_t d = 0; d < 2; d++) + { + for (uint32_t e = 0; e < 1; e++) + { + for (uint32_t f = 0; f < 2; f++) + { + inputTensor_buf.at(Index{a, b, c, d, e, f}) = dummy++; + } + } + } + } + } + } + auto inputTensor_data = locomotiv::make_data(inputTensor_buf); + locomotiv::annot_data(inputTensor, std::move(inputTensor_data)); + locomotiv::annot_domain(inputTensor, loco::Domain::Tensor); + + auto constant = g->nodes()->create(); + constant->dtype(loco::DataType::FLOAT32); + constant->shape({1}); + auto constant_buf = make_buffer(Shape{1}); + constant_buf.at(Index{0}) = 0.0f; + auto constant_data = locomotiv::make_data(constant_buf); + locomotiv::annot_data(constant, std::move(constant_data)); + locomotiv::annot_domain(constant, loco::Domain::Tensor); + + auto pad = g->nodes()->create(); + pad->input(inputTensor); + pad->constant(constant); + auto padding = pad->padding(); + + padding->rank(6); + padding->front(0) = 1; + padding->back(0) = 1; + padding->front(1) = 0; + padding->back(1) = 0; + padding->front(2) = 1; + padding->back(2) = 2; + padding->front(3) = 2; + padding->back(3) = 1; + padding->front(4) = 0; + padding->back(4) = 0; + padding->front(5) = 1; + padding->back(5) = 2; + + locomotiv::NodeExecution::get().run(pad); + + auto pad_data = locomotiv::annot_data(pad); + ASSERT_NE(pad_data, nullptr); + ASSERT_EQ(pad_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(pad_data->shape()), Shape({4, 1, 6, 5, 1, 5})); + + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 1, 2, 0, 1}), 1.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 1, 2, 0, 2}), 2.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 1, 3, 0, 1}), 3.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 1, 3, 0, 2}), 4.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 2, 2, 0, 1}), 5.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 2, 2, 0, 2}), 6.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 2, 3, 0, 1}), 7.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 2, 3, 0, 2}), 8.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 3, 2, 0, 1}), 9.0f); + ASSERT_FLOAT_EQ(pad_data->as_f32_bufptr()->at(Index{1, 0, 3, 2, 0, 2}), 10.0f); + + ASSERT_EQ(locomotiv::annot_domain(pad), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/TensorReduce.cpp b/compiler/locomotiv/src/Node/TensorReduce.cpp new file mode 100644 index 00000000000..fae7a75c54c --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorReduce.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Buffer; + +#include +#include + +namespace +{ + +Index reduced_index(const Index &index, const loco::TensorAxisSet &axes) +{ + Index r_index; + + r_index.resize(index.rank()); + for (uint32_t i = 0; i < index.rank(); ++i) + r_index.at(i) = (axes.defined(i)) ? 0 : index.at(i); + + return r_index; +} + +Shape reduced_shape(const Shape &shape, const loco::TensorAxisSet &axes) +{ + Shape r_shape; + + r_shape.resize(shape.rank()); + for (uint32_t i = 0; i < shape.rank(); ++i) + r_shape.dim(i) = (axes.defined(i)) ? 1 : shape.dim(i); + + return r_shape; +} + +} // namespace + +namespace +{ + +template struct ReduceFunction +{ + static void apply(Buffer &lhs, const Buffer &rhs, const loco::TensorAxisSet &axes) + { + throw std::runtime_error("Not supported ReduceFunc type"); + } +}; + +template struct ReduceFunction +{ + static void apply(Buffer &lhs, const Buffer &rhs, const loco::TensorAxisSet &axes) + { + for (IndexEnumerator e{rhs.shape()}; e.valid(); e.advance()) + { + const auto &index = e.current(); + const auto r_index = reduced_index(index, axes); + + lhs.at(r_index) += rhs.at(index); + } + + uint32_t r_cnt = 1; + for (uint32_t i = 0; i < rhs.shape().rank(); ++i) + if (axes.defined(i)) + r_cnt *= rhs.shape().dim(i); + + for (IndexEnumerator e{lhs.shape()}; e.valid(); e.advance()) + { + const auto &index = e.current(); + lhs.at(index) /= static_cast(r_cnt); + } + } +}; + +template +void apply(Buffer &lhs, const Buffer &rhs, const loco::TensorReduce &node) +{ + switch (node.func()) + { + case loco::ReduceFunc::Mean: + ReduceFunction::apply(lhs, rhs, *node.axes()); + break; + + // TODO Support more ReduceFunc type + default: + break; + } +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TensorReduce *node) +{ + auto input_data = annot_data(node->input()); + auto input_shape = input_data->shape(); + + validate(input_data, "Input not ready"); + validate(annot_domain(node->input()) == loco::Domain::Tensor, + "Input domain of TensorReduce is not Tensor"); + + std::unique_ptr reduce_data = nullptr; + Shape r_shape = reduced_shape(*input_shape, *node->axes()); + switch (input_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto reduce_buf = make_buffer(r_shape); + + apply(reduce_buf, *input_bufptr, *node); + + reduce_data = make_data(reduce_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(reduce_data != nullptr); + annot_data(node, std::move(reduce_data)); + annot_domain(node, annot_domain(node->input())); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/TensorReduce.test.cpp b/compiler/locomotiv/src/Node/TensorReduce.test.cpp new file mode 100644 index 00000000000..68398cacded --- /dev/null +++ b/compiler/locomotiv/src/Node/TensorReduce.test.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeExecution_Fixed_Reduce_Mean, f32_0) +{ + // Make pull-TensorReduce(Mean) graph + auto g = loco::make_graph(); + auto pull_input = g->nodes()->create(); + pull_input->dtype(loco::DataType::FLOAT32); + pull_input->shape({1, 2, 2}); + auto reduce_node = g->nodes()->create(); + reduce_node->input(pull_input); + reduce_node->axes()->insert(0); + reduce_node->axes()->insert(1); + reduce_node->func(loco::ReduceFunc::Mean); + + // Make and assign data to pull node + auto pull_input_buf = make_buffer({1, 2, 2}); + pull_input_buf.at(Index{0, 0, 0}) = 1.1f; + pull_input_buf.at(Index{0, 0, 1}) = 2.2f; + pull_input_buf.at(Index{0, 1, 0}) = 5.5f; + pull_input_buf.at(Index{0, 1, 1}) = 6.6f; + auto pull_input_data = locomotiv::make_data(pull_input_buf); + locomotiv::annot_data(pull_input, std::move(pull_input_data)); + locomotiv::annot_domain(pull_input, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(reduce_node); + + auto kShape = Shape{1, 1, 2}; + auto reduce_data = locomotiv::annot_data(reduce_node); + ASSERT_NE(reduce_data, nullptr); + ASSERT_EQ(reduce_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(reduce_data->shape()), kShape); + ASSERT_FLOAT_EQ(reduce_data->as_f32_bufptr()->at(Index{0, 0, 0}), 3.3f); + ASSERT_FLOAT_EQ(reduce_data->as_f32_bufptr()->at(Index{0, 0, 1}), 4.4f); + + ASSERT_EQ(locomotiv::annot_domain(reduce_node), loco::Domain::Tensor); +} + +TEST(NodeExecution_Fixed_Reduce_Mean, f32_1) +{ + // Make pull-TensorReduce(Mean) graph + auto g = loco::make_graph(); + auto pull_input = g->nodes()->create(); + pull_input->dtype(loco::DataType::FLOAT32); + pull_input->shape({1, 2, 2}); + auto reduce_node = g->nodes()->create(); + reduce_node->input(pull_input); + reduce_node->axes()->insert(1); + reduce_node->axes()->insert(2); + reduce_node->func(loco::ReduceFunc::Mean); + + // Make and assign data to pull node + auto pull_input_buf = make_buffer({1, 2, 2}); + pull_input_buf.at(Index{0, 0, 0}) = 1.1f; + pull_input_buf.at(Index{0, 0, 1}) = 2.2f; + pull_input_buf.at(Index{0, 1, 0}) = 5.5f; + pull_input_buf.at(Index{0, 1, 1}) = 6.6f; + auto pull_input_data = locomotiv::make_data(pull_input_buf); + locomotiv::annot_data(pull_input, std::move(pull_input_data)); + locomotiv::annot_domain(pull_input, loco::Domain::Tensor); + + locomotiv::NodeExecution::get().run(reduce_node); + + auto kShape = Shape{1, 1, 1}; + auto reduce_data = locomotiv::annot_data(reduce_node); + ASSERT_NE(reduce_data, nullptr); + ASSERT_EQ(reduce_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(reduce_data->shape()), kShape); + ASSERT_FLOAT_EQ(reduce_data->as_f32_bufptr()->at(Index{0, 0, 0}), 3.85f); + + ASSERT_EQ(locomotiv::annot_domain(reduce_node), loco::Domain::Tensor); +} diff --git a/compiler/locomotiv/src/Node/TransposedConv2D.cpp b/compiler/locomotiv/src/Node/TransposedConv2D.cpp new file mode 100644 index 00000000000..3ea4f071d9d --- /dev/null +++ b/compiler/locomotiv/src/Node/TransposedConv2D.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2018 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDataImpl.h" +#include "NodeDomain.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + +using nncc::core::ADT::tensor::Buffer; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +/** + * @brief Compute 1D output size for transposed convolution based on given 1D arguments. + * + * @param whole_pad Sum of front and rear pad + */ +inline uint32_t compute_transposed_out_size(uint32_t input_size, uint32_t whole_pad, + uint32_t filter_size, uint32_t stride) +{ + return stride * (input_size - 1) + filter_size - whole_pad; +} + +/** + * @brief Calculates TransposedConv2D + * @note Both input_buf and filter_buf have NHWC format + */ +template +Buffer calc_tr_conv2D(const loco::TransposedConv2D *tr_conv2d, + const Buffer *input_buf, const Buffer *filter_buf) +{ + auto input_shape = input_buf->shape(); + auto filter_shape = filter_buf->shape(); + + locomotiv::validate(input_shape.rank() == 4, "ifm rank must be 4"); + locomotiv::validate(filter_shape.rank() == 4, "filter rank must be 4"); + locomotiv::validate(input_shape.dim(3) /* depth of input */ == + filter_shape.dim(3) /* depth of filter */, + "channel value mismatch"); + + const uint32_t input_height = input_shape.dim(1); + const uint32_t input_width = input_shape.dim(2); + + const uint32_t filter_height = filter_shape.dim(1); + const uint32_t filter_width = filter_shape.dim(2); + + const uint32_t stride_width = tr_conv2d->stride()->horizontal(); + const uint32_t stride_height = tr_conv2d->stride()->vertical(); + + const uint32_t pad_top = tr_conv2d->pad()->top(); + const uint32_t pad_bottom = tr_conv2d->pad()->bottom(); + + const uint32_t pad_left = tr_conv2d->pad()->left(); + const uint32_t pad_right = tr_conv2d->pad()->right(); + + // TODO Support dilations + + const uint32_t output_height = + compute_transposed_out_size(input_height, pad_top + pad_bottom, filter_height, stride_height); + const uint32_t output_width = + compute_transposed_out_size(input_width, pad_left + pad_right, filter_width, stride_width); + + const uint32_t batches = input_shape.dim(0); + const uint32_t input_depth = input_shape.dim(3); + const uint32_t output_depth = filter_shape.dim(0); // count of filter + + Shape output_shape{batches, output_height, output_width, output_depth}; + auto output_buf = make_buffer(output_shape); + + // initialize output + for (IndexEnumerator e{output_shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + output_buf.at(index) = static_cast(0); + } + + // Loop through input elements one at a time. + for (uint32_t batch = 0; batch < batches; ++batch) + { + for (uint32_t in_y = 0; in_y < input_height; ++in_y) + { + for (uint32_t in_x = 0; in_x < input_width; ++in_x) + { + for (uint32_t in_channel = 0; in_channel < input_depth; ++in_channel) + { + // Loop through the output elements it will influence + const int out_x_origin = (in_x * stride_width) - pad_left; + const int out_y_origin = (in_y * stride_height) - pad_top; + for (uint32_t filter_y = 0; filter_y < filter_height; ++filter_y) + { + for (uint32_t filter_x = 0; filter_x < filter_width; ++filter_x) + { + for (uint32_t out_channel = 0; out_channel < output_depth; ++out_channel) + { + // Compute output element location + const int out_x = out_x_origin + filter_x; + const int out_y = out_y_origin + filter_y; + // We cannot accumulate out of bounds + if ((out_x >= 0) && ((unsigned)out_x < output_width) && (out_y >= 0) && + ((unsigned)out_y < output_height)) + { + auto input_value = input_buf->at(Index({batch, in_y, in_x, in_channel})); + auto filter_value = + filter_buf->at(Index({out_channel, filter_y, filter_x, in_channel})); + output_buf.at(Index({batch, (unsigned)out_y, (unsigned)out_x, out_channel})) += + input_value * filter_value; + } + } + } + } + } + } + } + } + return output_buf; +} + +} // namespace + +namespace locomotiv +{ + +void NodeExecution::execute(loco::TransposedConv2D *tr_conv2d) +{ + auto ifm_data = annot_data(tr_conv2d->ifm()); + auto ker_data = annot_data(tr_conv2d->ker()); + + validate(ifm_data, "Can't find input data of TransposedConv2D"); + validate(ifm_data->shape()->rank() == 4, "ifm rank must be 4"); + + validate(ker_data, "Can't find kernel data of TransposedConv2D"); + validate(ker_data->shape()->rank() == 4, "Kernel rank must be 4"); + + validate(annot_domain(tr_conv2d->ifm()) == loco::Domain::Feature, + "IFM of TransposedConv2D is not feature"); + validate(annot_domain(tr_conv2d->ker()) == loco::Domain::Filter, + "Kernel of TransposedConv2D is not filter"); + + std::unique_ptr tr_conv2d_result = nullptr; + + if (ifm_data->dtype() == loco::DataType::FLOAT32 && ker_data->dtype() == loco::DataType::FLOAT32) + { + auto ifm_buf = ifm_data->as_f32_bufptr(); + auto ker_buf = ker_data->as_f32_bufptr(); + + auto tr_conv2d_buf = calc_tr_conv2D(tr_conv2d, ifm_buf, ker_buf); + + tr_conv2d_result = make_data(tr_conv2d_buf); + } + else + throw std::runtime_error("NYI for these DataTypes"); + + assert(tr_conv2d_result != nullptr); + + annot_data(tr_conv2d, std::move(tr_conv2d_result)); + annot_domain(tr_conv2d, loco::Domain::Feature); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Node/TransposedConv2D.test.cpp b/compiler/locomotiv/src/Node/TransposedConv2D.test.cpp new file mode 100644 index 00000000000..bd955a06bd5 --- /dev/null +++ b/compiler/locomotiv/src/Node/TransposedConv2D.test.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +#include +#include +#include +#include +#include "nncc/core/ADT/tensor/IndexEnumerator.h" + +#include + +namespace +{ +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; +using nncc::core::ADT::tensor::make_overlay; + +void run_test(const float *ifm, const float *ker, const float *expected_ofm, const Shape &ifm_shape, + const Shape ker_shape, const Shape ofm_shape, const uint32_t stride_v, + const uint32_t stride_h, const uint32_t pad_top = 0, const uint32_t pad_bottom = 0, + const uint32_t pad_left = 0, const uint32_t pad_right = 0) +{ + auto g = loco::make_graph(); + + // Fill output data of FeatureEncode from ifm + auto ifm_enc = g->nodes()->create(); + { + auto ifm_enc_buf = make_buffer(ifm_shape); + auto ifm_overlay = make_overlay(ifm_shape, const_cast(ifm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ifm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ifm_enc_buf.at(ind) = ifm_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ifm_enc_buf); + locomotiv::annot_data(ifm_enc, std::move(enc_data)); + locomotiv::annot_domain(ifm_enc, loco::Domain::Feature); + } + + // Fill output data of FilterEncode from ker + auto ker_enc = g->nodes()->create(); + { + auto ker_enc_buf = make_buffer(ker_shape); + auto ker_overlay = make_overlay(ker_shape, const_cast(ker)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ker_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ker_enc_buf.at(ind) = ker_overlay.at(ind); + } + + auto enc_data = locomotiv::make_data(ker_enc_buf); + locomotiv::annot_data(ker_enc, std::move(enc_data)); + locomotiv::annot_domain(ker_enc, loco::Domain::Filter); + } + + // build TransposedConv2D + auto tr_conv2d = g->nodes()->create(); + tr_conv2d->ifm(ifm_enc); + tr_conv2d->ker(ker_enc); + tr_conv2d->stride()->vertical(stride_v); + tr_conv2d->stride()->horizontal(stride_h); + tr_conv2d->pad()->top(pad_top); + tr_conv2d->pad()->bottom(pad_bottom); + tr_conv2d->pad()->left(pad_left); + tr_conv2d->pad()->right(pad_right); + + // run interpreter + locomotiv::NodeExecution::get().run(tr_conv2d); + + // get result of calculation + auto conv2d_result = locomotiv::annot_data(tr_conv2d); + + // check the result + ASSERT_NE(conv2d_result, nullptr); + ASSERT_TRUE(conv2d_result->dtype() == loco::DataType::FLOAT32); + ASSERT_TRUE(*(conv2d_result->shape()) == ofm_shape); + + auto ofm_overlay = + make_overlay(ofm_shape, const_cast(expected_ofm)); + for (nncc::core::ADT::tensor::IndexEnumerator e{ofm_shape}; e.valid(); e.advance()) + { + const auto &ind = e.current(); + ASSERT_FLOAT_EQ(conv2d_result->as_f32_bufptr()->at(ind), ofm_overlay.at(ind)); + } + + ASSERT_EQ(locomotiv::annot_domain(tr_conv2d), loco::Domain::Feature); +} + +} // namespace + +// clang-format off +/* +ifm = tf.constant(1.1, shape = [1, 2, 2, 4]) +ker = tf.constant(2.2, shape = [3, 3, 2, 4]) +tr_conv = tf.nn.conv2d_transpose(ifm, ker, output_shape = (1, 5, 5, 2), strides = [1, 2, 2, 1], padding = "VALID") + +with tf.Session() as session: + tr_conv_data = session.run(tr_conv) + */ +TEST(NodeExecution_TransposedConv2D, f32) +{ + using nncc::core::ADT::tensor::Shape; + + float ifm[1 * 2 * 2 * 4]; + for (int n = 0; n < 1 * 2 * 2 * 4; n++) + ifm[n] = 1.1; + + float ker[2 * 3 * 3 * 4]; // NHWC + for (int n = 0; n < 2 * 3 * 3 * 4; n++) + ker[n] = 2.2; + + float ofm[1 * 5 * 5 * 2] = {9.68, 9.68, 9.68, 9.68, 19.36, 19.36, 9.68, 9.68, 9.68, 9.68, + 9.68, 9.68, 9.68, 9.68, 19.36, 19.36, 9.68, 9.68, 9.68, 9.68, + 19.36, 19.36, 19.36, 19.36, 38.72, 38.72, 19.36, 19.36, 19.36, 19.36, + 9.68, 9.68, 9.68, 9.68, 19.36, 19.36, 9.68, 9.68, 9.68, 9.68, + 9.68, 9.68, 9.68, 9.68, 19.36, 19.36, 9.68, 9.68, 9.68, 9.68}; + + run_test(ifm, ker, ofm, + Shape{1, 2, 2, 4}, Shape{2, 3, 3, 4}, Shape{1, 5, 5, 2}, // shapes of ifm, ker, ofm + 2, 2 // stride + ); +} +// clang-format on diff --git a/compiler/locomotiv/src/NodeData.cpp b/compiler/locomotiv/src/NodeData.cpp new file mode 100644 index 00000000000..69ba4a1c2d8 --- /dev/null +++ b/compiler/locomotiv/src/NodeData.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" + +namespace locomotiv +{ + +template <> std::unique_ptr make_data(const NodeData::Buffer &buf) +{ + return std::unique_ptr(new NodeDataImpl(buf)); +} + +template <> std::unique_ptr make_data(const NodeData::Buffer &buf) +{ + return std::unique_ptr(new NodeDataImpl(buf)); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/NodeData.test.cpp b/compiler/locomotiv/src/NodeData.test.cpp new file mode 100644 index 00000000000..b1c9832d5ed --- /dev/null +++ b/compiler/locomotiv/src/NodeData.test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locomotiv/NodeData.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeData, as_s32_buffer_wrapper) +{ + const Shape shape{1}; + auto buf = make_buffer(shape); + buf.at(Index{0}) = 42; + + auto data = locomotiv::make_data(buf); + + ASSERT_EQ(data->dtype(), loco::DataType::S32); + ASSERT_EQ(*(data->shape()), shape); + ASSERT_EQ(data->as_s32_bufptr()->at(Index{0}), 42); +} + +TEST(NodeData, as_f32_buffer_wrapper) +{ + const Shape shape{1}; + auto buf = make_buffer(shape); + buf.at(Index{0}) = 3.14f; + + auto data = locomotiv::make_data(buf); + + ASSERT_EQ(data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(data->shape()), shape); + ASSERT_FLOAT_EQ(data->as_f32_bufptr()->at(Index{0}), 3.14f); +} diff --git a/compiler/locomotiv/src/NodeDataImpl.cpp b/compiler/locomotiv/src/NodeDataImpl.cpp new file mode 100644 index 00000000000..2efebe5a96a --- /dev/null +++ b/compiler/locomotiv/src/NodeDataImpl.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeDataImpl.h" + +#include + +#include + +namespace +{ + +class NodeDataAnnotation final : public loco::NodeAnnotation +{ +public: + NodeDataAnnotation(std::unique_ptr &&data) : _data{std::move(data)} + { + // DO NOTHING + } + +public: + const locomotiv::NodeData *data(void) const { return _data.get(); } + +private: + std::unique_ptr _data; +}; + +} // namespace + +namespace locomotiv +{ + +template <> NodeDataImpl::NodeDataImpl(const Buffer &buf) +{ + _dtype = loco::DataType::S32; + _s32.reset(new Buffer(buf)); + _shape = const_cast(&(_s32->shape())); +} + +template <> NodeDataImpl::NodeDataImpl(const Buffer &buf) +{ + _dtype = loco::DataType::FLOAT32; + _f32.reset(new Buffer(buf)); + _shape = const_cast(&(_f32->shape())); +} + +void annot_data(loco::Node *node, std::unique_ptr &&data) +{ + node->annot(stdex::make_unique(std::move(data))); +} + +const NodeData *annot_data(const loco::Node *node) +{ + if (auto annot = node->annot()) + { + return annot->data(); + } + + return nullptr; +} + +void erase_annot_data(loco::Node *node) { node->annot(nullptr); } + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/NodeDataImpl.h b/compiler/locomotiv/src/NodeDataImpl.h new file mode 100644 index 00000000000..bdd9db386c1 --- /dev/null +++ b/compiler/locomotiv/src/NodeDataImpl.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_NODEDATAIMPL_H_ +#define _LOCOMOTIV_NODEDATAIMPL_H_ + +#include "locomotiv/NodeData.h" + +namespace locomotiv +{ + +/** + * @brief An implementation of NodeData interface + */ +class NodeDataImpl final : public NodeData +{ +public: + template using Buffer = nncc::core::ADT::tensor::Buffer; + using Shape = nncc::core::ADT::tensor::Shape; + + template NodeDataImpl(const Buffer
&buf); + + const loco::DataType &dtype() const override { return _dtype; } + + const Shape *shape() const override { return _shape; } + + const Buffer *as_s32_bufptr() const override { return _s32.get(); } + + const Buffer *as_f32_bufptr() const override { return _f32.get(); } + +private: + loco::DataType _dtype = loco::DataType::Unknown; + Shape *_shape = nullptr; + std::unique_ptr> _s32 = nullptr; + std::unique_ptr> _f32 = nullptr; +}; + +/// @brief Bind "NodeData" to "Node" +void annot_data(loco::Node *node, std::unique_ptr &&data); + +/** + * @brief Get "NodeData" for a given node + * + * NOTE Returns nullptr if "NodeData" is not binded yet + */ +const NodeData *annot_data(const loco::Node *node); + +/// @brief Release "NodeData" bound to a given node +void erase_annot_data(loco::Node *node); + +} // namespace locomotiv + +#endif // _LOCOMOTIV_NODEDATAIMPL_H_ diff --git a/compiler/locomotiv/src/NodeDataImpl.test.cpp b/compiler/locomotiv/src/NodeDataImpl.test.cpp new file mode 100644 index 00000000000..b859560638e --- /dev/null +++ b/compiler/locomotiv/src/NodeDataImpl.test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locomotiv/NodeData.h" +#include "NodeDataImpl.h" + +#include +#include +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(NodeDataImpl, as_annotation) +{ + const Shape shape{1}; + auto buf = make_buffer(shape); + buf.at(Index{0}) = 3.14f; + + std::unique_ptr data = locomotiv::make_data(buf); + + auto g = loco::make_graph(); + auto node = g->nodes()->create(); + + ASSERT_EQ(locomotiv::annot_data(node), nullptr); + + // Set annotation + locomotiv::annot_data(node, std::move(data)); + + // Get annotation + const locomotiv::NodeData *obtained = locomotiv::annot_data(node); + ASSERT_NE(obtained, nullptr); + + ASSERT_EQ(obtained->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(obtained->shape()), shape); + ASSERT_FLOAT_EQ(obtained->as_f32_bufptr()->at(Index{0}), 3.14f); + + // Erase annotation + locomotiv::erase_annot_data(node); + ASSERT_EQ(locomotiv::annot_data(node), nullptr); +} diff --git a/compiler/locomotiv/src/NodeDomain.cpp b/compiler/locomotiv/src/NodeDomain.cpp new file mode 100644 index 00000000000..709b9fe3461 --- /dev/null +++ b/compiler/locomotiv/src/NodeDomain.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeDomain.h" + +#include + +namespace locomotiv +{ + +struct NodeDomain final : public loco::NodeAnnotation +{ + NodeDomain(const loco::Domain &domain) : value(domain) + { + // DO NOTHING + } + + loco::Domain value = loco::Domain::Unknown; +}; + +void annot_domain(loco::Node *node, const loco::Domain &domain) +{ + assert(domain != loco::Domain::Unknown); + auto node_domain = std::unique_ptr(new NodeDomain(domain)); + assert(node_domain); + node->annot(std::move(node_domain)); +} + +loco::Domain annot_domain(const loco::Node *node) +{ + auto node_domain = node->annot(); + if (node_domain) + return node_domain->value; + else + return loco::Domain::Unknown; +} + +void erase_annot_domain(loco::Node *node) { node->annot(nullptr); } + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/NodeDomain.h b/compiler/locomotiv/src/NodeDomain.h new file mode 100644 index 00000000000..fc93f77f7a4 --- /dev/null +++ b/compiler/locomotiv/src/NodeDomain.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_NODEDOMAIN_H_ +#define _LOCOMOTIV_NODEDOMAIN_H_ + +#include +#include + +namespace locomotiv +{ + +/// @brief Wrapper to annotate domain to node. Cannot annotate unknown domain. +void annot_domain(loco::Node *node, const loco::Domain &domain); + +/// @brief Wrapper to get domain annotation of node +loco::Domain annot_domain(const loco::Node *node); + +/// @brief Erase already annotated node domain +void erase_annot_domain(loco::Node *node); + +} // namespace locomotiv + +#endif // _LOCOMOTIV_NODEDOMAIN_H_ diff --git a/compiler/locomotiv/src/NodeDomain.test.cpp b/compiler/locomotiv/src/NodeDomain.test.cpp new file mode 100644 index 00000000000..9cfcf2eb880 --- /dev/null +++ b/compiler/locomotiv/src/NodeDomain.test.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeDomain.h" + +#include + +TEST(NodeDomain, as_annotation) +{ + loco::Pull node; + + ASSERT_EQ(locomotiv::annot_domain(&node), loco::Domain::Unknown); + + // Set annotation + locomotiv::annot_domain(&node, loco::Domain::Tensor); + + // Get annotation + const loco::Domain obtained = locomotiv::annot_domain(&node); + ASSERT_EQ(obtained, loco::Domain::Tensor); + + // Erase annotation + locomotiv::erase_annot_domain(&node); + ASSERT_EQ(locomotiv::annot_domain(&node), loco::Domain::Unknown); +} diff --git a/compiler/locomotiv/src/NodeExecution.cpp b/compiler/locomotiv/src/NodeExecution.cpp new file mode 100644 index 00000000000..e532b5af63b --- /dev/null +++ b/compiler/locomotiv/src/NodeExecution.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "NodeExecution.h" + +#include "NodeDomain.h" +#include "NodeDataImpl.h" +#include "Validation.h" + +#include +#include +#include +#include +#include + +#include +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +namespace locomotiv +{ + +float UnaryFunc::apply(float) const { throw std::runtime_error{"F32 is not supported yet"}; } +int32_t UnaryFunc::apply(int32_t) const { throw std::runtime_error{"S32 is not supported yet"}; } + +float BinaryFunc::apply(float, float) const +{ + throw std::runtime_error{"F32 is not supported yet"}; +} + +int32_t BinaryFunc::apply(int32_t, int32_t) const +{ + throw std::runtime_error{"S32 is not supported yet"}; +} + +// TODO Use visitor pattern of loco when available +void NodeExecution::run(loco::Node *node) +{ + erase_annot_data(node); + +#define NODE(Name) \ + if (as(node)) \ + { \ + execute(as(node)); \ + return; \ + } +#include "Node.lst" +#undef NODE + + throw std::runtime_error("Not supported loco::Node type"); +} + +void NodeExecution::eltwise_unary(loco::Node *node, const UnaryFunc &f) +{ + auto input_node = node->arg(0); + auto input_domain = annot_domain(input_node); + auto input_data = annot_data(input_node); + auto input_dtype = input_data->dtype(); + + validate(input_data, "Input is not ready"); + validate(input_domain != loco::Domain::Unknown, "Input domain is unknown"); + + auto output_node = node; + // Element-wise Unary Operation does not affect Domain + auto output_domain = input_domain; + // Eltwise-wise Unary Operation does not affet Data Type (ASSUMPTION) + // + // TODO Check this assumption + auto output_dtype = input_dtype; + std::unique_ptr output_data = nullptr; + + switch (output_dtype) + { + case loco::DataType::FLOAT32: + { + auto input_bufptr = input_data->as_f32_bufptr(); + auto output_buf = make_buffer(*input_data->shape()); + auto *shape = input_data->shape(); + + for (IndexEnumerator e{*shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + output_buf.at(index) = f.apply(input_bufptr->at(index)); + } + + output_data = make_data(output_buf); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(output_data != nullptr); + annot_data(output_node, std::move(output_data)); + annot_domain(output_node, output_domain); +} + +void NodeExecution::eltwise_binary(loco::Node *node, const BinaryFunc &f) +{ + auto lhs_node = node->arg(0); + auto rhs_node = node->arg(1); + auto lhs_data = annot_data(lhs_node); + auto rhs_data = annot_data(rhs_node); + + validate(lhs_data && rhs_data, "Input not ready"); + validate(annot_domain(lhs_node) == annot_domain(rhs_node), "Wrong input domain"); + validate(lhs_data->dtype() == rhs_data->dtype(), "Wrong input type"); + validate(*lhs_data->shape() == *rhs_data->shape(), "Wrong input shape"); + + auto out_node = node; + std::unique_ptr out_data = nullptr; + + switch (lhs_data->dtype()) + { + case loco::DataType::FLOAT32: + { + auto lhs_bufptr = lhs_data->as_f32_bufptr(); + auto rhs_bufptr = rhs_data->as_f32_bufptr(); + auto out_bufptr = make_buffer(*lhs_data->shape()); + + auto *shape = lhs_data->shape(); + + for (IndexEnumerator e{*shape}; e.valid(); e.advance()) + { + const auto &index = e.current(); + out_bufptr.at(index) = f.apply(lhs_bufptr->at(index), rhs_bufptr->at(index)); + } + + out_data = make_data(out_bufptr); + break; + } + default: + throw std::runtime_error("NYI for this DataType"); + } + + assert(out_data != nullptr); + annot_data(out_node, std::move(out_data)); + annot_domain(out_node, annot_domain(lhs_node)); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/NodeExecution.h b/compiler/locomotiv/src/NodeExecution.h new file mode 100644 index 00000000000..363188d38d6 --- /dev/null +++ b/compiler/locomotiv/src/NodeExecution.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_NODEEXECUTION_H_ +#define _LOCOMOTIV_NODEEXECUTION_H_ + +#include + +namespace locomotiv +{ + +struct UnaryFunc +{ + virtual ~UnaryFunc() = default; + + virtual float apply(float) const; + virtual int32_t apply(int32_t) const; +}; + +// Q. How to support mixed precision binary operators? +struct BinaryFunc +{ + virtual ~BinaryFunc() = default; + + virtual float apply(float, float) const; + virtual int32_t apply(int32_t, int32_t) const; +}; + +/** + * @brief Helper class for Session, responsible to process one node calculation. + */ +class NodeExecution +{ +public: + /// @brief Run calculation for one unspecified Node + void run(loco::Node *node); + + static NodeExecution &get() + { + static NodeExecution me; + return me; + } + +private: + NodeExecution() {} + + template Derived *as(loco::Node *node) + { + return dynamic_cast(node); + } + +// clang-format off + /** + * @brief Calculate for one specified node and update its result as NodeData. + * Abort program when its ingredients are not ready or not supported. + * + * @note Definitions of overloaded execute() are in 'Node/' directory + */ +// clang-format on +#define NODE(Name) void execute(loco::Name *); +#include "Node.lst" +#undef NODE + + void eltwise_unary(loco::Node *node, const UnaryFunc &f); + void eltwise_binary(loco::Node *node, const BinaryFunc &f); +}; + +} // namespace locomotiv + +#endif // _LOCOMOTIV_NODEEXECUTION_H_ diff --git a/compiler/locomotiv/src/Session.cpp b/compiler/locomotiv/src/Session.cpp new file mode 100644 index 00000000000..841a14a5cdf --- /dev/null +++ b/compiler/locomotiv/src/Session.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locomotiv/Session.h" +#include "locomotiv/NodeData.h" + +#include "UserData.h" +#include "NodeDataImpl.h" +#include "NodeExecution.h" +#include "NodeDomain.h" + +#include + +namespace locomotiv +{ + +Session::~Session() +{ + for (uint32_t i = 0; i < _graph->nodes()->size(); ++i) + { + auto node = _graph->nodes()->at(i); + erase_user_data(node); + erase_annot_data(node); + erase_annot_domain(node); + } +} + +void Session::set_input(uint32_t index, std::unique_ptr &&data) +{ + assert(index < input_size()); + + // Check whether already annotated + auto pull = loco::pull_node(_graph, index); + if (user_data(pull)) + { + throw std::runtime_error("Graph input already has NodeData"); + } + + // Check data type match + if (pull->dtype() != data->dtype()) + { + throw std::runtime_error("Data type mismatch"); + } + + // Check shape match + auto shape = data->shape(); + if (pull->rank() != shape->rank()) + { + throw std::runtime_error("Shape rank mismatch"); + } + for (uint32_t i = 0; i < pull->rank(); ++i) + { + if (pull->dim(i).known() && pull->dim(i).value() != shape->dim(i)) + { + throw std::runtime_error("Shape dimension mismatch"); + } + } + + user_data(pull, std::move(data)); +} + +void Session::infer() +{ + auto schedules = loco::postorder_traversal(_outputs); + + for (auto node : schedules) + { + NodeExecution::get().run(node); + } +} + +const NodeData *Session::get_output(uint32_t index) +{ + assert(index < output_size()); + + auto output_node = _outputs.at(index); + return annot_data(output_node); +} + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/Session.test.cpp b/compiler/locomotiv/src/Session.test.cpp new file mode 100644 index 00000000000..6d4a2414fbb --- /dev/null +++ b/compiler/locomotiv/src/Session.test.cpp @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locomotiv/Session.h" +#include "locomotiv/NodeData.h" + +#include "UserData.h" + +#include +#include +#include +#include + +#include + +#include + +using nncc::core::ADT::tensor::Index; +using nncc::core::ADT::tensor::Shape; +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::make_buffer; + +TEST(Session, graph_IO_size) +{ + // Make graph + auto g = loco::make_graph(); + + // inputs + const uint32_t inputs = 2; + for (uint32_t i = 0; i < inputs; ++i) + { + auto pull = g->nodes()->create(); + loco::link(g->inputs()->create(), pull); + } + + // outputs + const uint32_t outputs = 3; + for (uint32_t o = 0; o < outputs; ++o) + { + auto push = g->nodes()->create(); + loco::link(g->outputs()->create(), push); + } + + // Make session + locomotiv::Session s(g.get()); + + ASSERT_EQ(s.input_size(), inputs); + ASSERT_EQ(s.output_size(), outputs); +} + +TEST(Session, set_input) +{ + // Make graph + auto g = loco::make_graph(); + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->rank(1); + pull->dim(0) = 1; + loco::link(g->inputs()->create(), pull); + + // Make good data + auto buf = make_buffer(Shape{1}); + auto data = locomotiv::make_data(buf); + + // Make data with different data type + auto buf_not_dtype = make_buffer(Shape{1}); + auto data_not_dtype = locomotiv::make_data(buf_not_dtype); + + // Make data with different rank + auto buf_not_rank = make_buffer(Shape{1, 1}); + auto data_not_rank = locomotiv::make_data(buf_not_rank); + + // Make data with different dimension + auto buf_not_dim = make_buffer(Shape{2}); + auto data_not_dim = locomotiv::make_data(buf_not_dim); + + // Make session + locomotiv::Session s(g.get()); + + ASSERT_ANY_THROW(s.set_input(0, std::move(data_not_dtype))); + ASSERT_ANY_THROW(s.set_input(0, std::move(data_not_rank))); + ASSERT_ANY_THROW(s.set_input(0, std::move(data_not_dim))); + ASSERT_NO_THROW(s.set_input(0, std::move(data))); + ASSERT_ANY_THROW(s.set_input(0, std::move(data))); +} + +TEST(Session, inference_identity) +{ + std::vector> graphs; + + // pull-push / f32 / known shape + { + auto g = loco::make_graph(); + + // Pull node + auto pull_node = g->nodes()->create(); + pull_node->dtype(loco::DataType::FLOAT32); + pull_node->rank(1); + pull_node->dim(0) = 1; + + // Push node + auto push_node = g->nodes()->create(); + push_node->from(pull_node); + + // Input + auto graph_input = g->inputs()->create(); + loco::link(graph_input, pull_node); + + // Output + auto graph_output = g->outputs()->create(); + loco::link(graph_output, push_node); + + graphs.push_back(std::move(g)); + } + + // pull-push / f32 / unknown shape + { + auto g = loco::make_graph(); + + // Pull node + auto pull_node = g->nodes()->create(); + pull_node->dtype(loco::DataType::FLOAT32); + pull_node->rank(1); + pull_node->dim(0) = loco::make_dimension(); + + // Push node + auto push_node = g->nodes()->create(); + push_node->from(pull_node); + + // Input + auto graph_input = g->inputs()->create(); + loco::link(graph_input, pull_node); + + // Output + auto graph_output = g->outputs()->create(); + loco::link(graph_output, push_node); + + graphs.push_back(std::move(g)); + } + + for (auto it = graphs.begin(); it != graphs.end(); ++it) + { + auto g = it->get(); + locomotiv::Session s(g); + + const Shape shape{1}; + auto buf = make_buffer(shape); + buf.at(Index{0}) = 3.14f; + auto data = locomotiv::make_data(buf); + + // Input not ready + ASSERT_ANY_THROW(s.infer()); + + s.set_input(0, std::move(data)); + + // Valid run + ASSERT_NO_THROW(s.infer()); + // Multiple run is possible + ASSERT_NO_THROW(s.infer()); + + auto output_data = s.get_output(0); + ASSERT_NE(output_data, nullptr); + ASSERT_EQ(output_data->dtype(), loco::DataType::FLOAT32); + ASSERT_EQ(*(output_data->shape()), Shape{1}); + ASSERT_EQ(output_data->as_f32_bufptr()->at(Index{0}), 3.14f); + } +} + +TEST(Session, session_for_subgraph) +{ + /* + * Make following graph: + * ConstGen_1 -- + * \ + * ConstGen_2 --- TensorConcat_1 --- TensorConcat_3 --- Push + * / + * ConstGen_3 --- TensorConcat_2 -- + * / + * ConstGen_4 -- + */ + auto g = loco::make_graph(); + + auto c1 = g->nodes()->create(); + auto c2 = g->nodes()->create(); + auto c3 = g->nodes()->create(); + auto c4 = g->nodes()->create(); + + c1->dtype(loco::DataType::FLOAT32); + c2->dtype(loco::DataType::FLOAT32); + c3->dtype(loco::DataType::FLOAT32); + c4->dtype(loco::DataType::FLOAT32); + c1->shape({1}); + c2->shape({1}); + c3->shape({1}); + c4->shape({1}); + c1->size(1); + c2->size(1); + c3->size(1); + c4->size(1); + + c1->at(0) = 0.1f; + c2->at(0) = 0.2f; + c3->at(0) = 0.3f; + c4->at(0) = 0.4f; + + auto t1 = g->nodes()->create(); + auto t2 = g->nodes()->create(); + auto t3 = g->nodes()->create(); + + // Note: default concat axis is 0 + t1->lhs(c1); + t1->rhs(c2); + t2->lhs(c3); + t2->rhs(c4); + t3->lhs(t1); + t3->rhs(t2); + + auto push = g->nodes()->create(); + push->from(t3); + + { + // Session to get t1 only + locomotiv::Session s(g.get(), {t1}); + ASSERT_EQ(s.output_size(), 1); + ASSERT_EQ(s.get_output_node(0), dynamic_cast(t1)); + + s.infer(); + + auto t1_data = s.get_output(0); + ASSERT_NE(t1_data, nullptr); + ASSERT_EQ(*(t1_data->shape()), Shape{2}); + + auto t1_buf = t1_data->as_f32_bufptr(); + ASSERT_EQ(t1_buf->at({0}), 0.1f); + ASSERT_EQ(t1_buf->at({1}), 0.2f); + } + + { + // Session to get t2 only + locomotiv::Session s(g.get(), {t2}); + ASSERT_EQ(s.output_size(), 1); + ASSERT_EQ(s.get_output_node(0), dynamic_cast(t2)); + + s.infer(); + + auto t2_data = s.get_output(0); + ASSERT_NE(t2_data, nullptr); + ASSERT_EQ(*(t2_data->shape()), Shape{2}); + + auto t2_buf = t2_data->as_f32_bufptr(); + ASSERT_EQ(t2_buf->at({0}), 0.3f); + ASSERT_EQ(t2_buf->at({1}), 0.4f); + } + + { + // Session to get t2 and push + locomotiv::Session s(g.get(), {t2, push}); + ASSERT_EQ(s.output_size(), 2); + ASSERT_EQ(s.get_output_node(0), dynamic_cast(t2)); + ASSERT_EQ(s.get_output_node(1), dynamic_cast(push)); + + s.infer(); + + auto t2_data = s.get_output(0); + ASSERT_NE(t2_data, nullptr); + ASSERT_EQ(*(t2_data->shape()), Shape{2}); + + auto t2_buf = t2_data->as_f32_bufptr(); + ASSERT_EQ(t2_buf->at({0}), 0.3f); + ASSERT_EQ(t2_buf->at({1}), 0.4f); + + auto push_data = s.get_output(1); + ASSERT_NE(push_data, nullptr); + ASSERT_EQ(*(push_data->shape()), Shape{4}); + + auto push_buf = push_data->as_f32_bufptr(); + ASSERT_EQ(push_buf->at({0}), 0.1f); + ASSERT_EQ(push_buf->at({1}), 0.2f); + ASSERT_EQ(push_buf->at({2}), 0.3f); + ASSERT_EQ(push_buf->at({3}), 0.4f); + } +} + +TEST(Session, ctor_by_range) +{ + // Make graph + auto g = loco::make_graph(); + + auto constgen = g->nodes()->create(); + auto relu = g->nodes()->create(); + auto push = g->nodes()->create(); + + constgen->dtype(loco::DataType::FLOAT32); + constgen->shape({2}); + constgen->size(2); + constgen->at(0) = 0.1f; + constgen->at(1) = -0.1f; + + relu->input(constgen); + push->from(relu); + + std::array custom_outputs = {constgen, push}; + + // Make Session by range + locomotiv::Session s(g.get(), custom_outputs.begin(), custom_outputs.end()); + + s.infer(); + + auto constgen_data = s.get_output(0); + ASSERT_NE(constgen_data, nullptr); + ASSERT_EQ(*(constgen_data->shape()), Shape{2}); + + auto constgen_buf = constgen_data->as_f32_bufptr(); + ASSERT_EQ(constgen_buf->at({0}), 0.1f); + ASSERT_EQ(constgen_buf->at({1}), -0.1f); + + auto push_data = s.get_output(1); + ASSERT_NE(push_data, nullptr); + ASSERT_EQ(*(push_data->shape()), Shape{2}); + + auto push_buf = push_data->as_f32_bufptr(); + ASSERT_EQ(push_buf->at({0}), 0.1f); + ASSERT_EQ(push_buf->at({1}), 0.0f); +} + +// Below here is internal test for locomotiv, i.e. not public usage of locomotiv +#include "NodeDataImpl.h" +#include "NodeDomain.h" + +TEST(Session, dtor) +{ + auto g = loco::make_graph(); + + // Pull node + auto pull = g->nodes()->create(); + pull->dtype(loco::DataType::FLOAT32); + pull->rank(1); + pull->dim(0) = 1; + + // Input + auto input = g->inputs()->create(); + loco::link(input, pull); + + { + locomotiv::Session s(g.get()); + + auto buf = make_buffer(Shape{1}); + auto data = locomotiv::make_data(buf); + + s.set_input(0, std::move(data)); + + auto data_annotated = locomotiv::annot_data(pull); + ASSERT_EQ(data_annotated, nullptr); + auto user_data_annotated = locomotiv::user_data(pull); + ASSERT_NE(user_data_annotated, nullptr); + auto domain_annotated = locomotiv::annot_domain(pull); + ASSERT_EQ(domain_annotated, loco::Domain::Unknown); + } + + auto data_annotated = locomotiv::annot_data(pull); + ASSERT_EQ(data_annotated, nullptr); + auto user_data_annotated = locomotiv::user_data(pull); + ASSERT_EQ(user_data_annotated, nullptr); + auto domain_annotated = locomotiv::annot_domain(pull); + ASSERT_EQ(domain_annotated, loco::Domain::Unknown); +} diff --git a/compiler/locomotiv/src/UserData.cpp b/compiler/locomotiv/src/UserData.cpp new file mode 100644 index 00000000000..b658ada9b6f --- /dev/null +++ b/compiler/locomotiv/src/UserData.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "UserData.h" + +#include + +#include + +namespace +{ + +class UserDataAnnotation final : public loco::NodeAnnotation +{ +public: + UserDataAnnotation(std::unique_ptr &&data) : _data{std::move(data)} + { + // DO NOTHING + } + +public: + const locomotiv::NodeData *data(void) const { return _data.get(); } + +private: + std::unique_ptr _data; +}; + +} // namespace + +namespace locomotiv +{ + +const NodeData *user_data(const loco::Node *node) +{ + if (auto annot = node->annot()) + { + return annot->data(); + } + + return nullptr; +} + +void user_data(loco::Node *node, std::unique_ptr &&data) +{ + node->annot(stdex::make_unique(std::move(data))); +} + +void erase_user_data(loco::Node *node) { node->annot(nullptr); } + +} // namespace locomotiv diff --git a/compiler/locomotiv/src/UserData.h b/compiler/locomotiv/src/UserData.h new file mode 100644 index 00000000000..661d0214071 --- /dev/null +++ b/compiler/locomotiv/src/UserData.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_USERDATA_H_ +#define _LOCOMOTIV_USERDATA_H_ + +#include "locomotiv/NodeData.h" + +namespace locomotiv +{ + +const NodeData *user_data(const loco::Node *node); +void user_data(loco::Node *node, std::unique_ptr &&data); +void erase_user_data(loco::Node *node); + +} // namespace locomotiv + +#endif // _LOCOMOTIV_USERDATA_H_ diff --git a/compiler/locomotiv/src/Validation.h b/compiler/locomotiv/src/Validation.h new file mode 100644 index 00000000000..59b8c40c787 --- /dev/null +++ b/compiler/locomotiv/src/Validation.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _LOCOMOTIV_VALIDATION_H_ +#define _LOCOMOTIV_VALIDATION_H_ + +#include +#include + +namespace locomotiv +{ + +inline void validate(bool true_cond, const std::string &&exception_msg) +{ + if (!true_cond) + throw std::runtime_error(exception_msg); +} + +} // namespace locomotiv + +#endif // _LOCOMOTIV_VALIDATION_H_ diff --git a/compiler/locop/CMakeLists.txt b/compiler/locop/CMakeLists.txt new file mode 100644 index 00000000000..107ee8be8d8 --- /dev/null +++ b/compiler/locop/CMakeLists.txt @@ -0,0 +1,27 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(locop STATIC ${SOURCES}) +set_target_properties(locop PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(locop PUBLIC include) +target_link_libraries(locop PUBLIC loco) +# Let's apply nncc common compile options +# +# NOTE This will enable strict compilation (warnings as error). +# Please refer to the top-level CMakeLists.txt for details +target_link_libraries(locop PRIVATE nncc_common) +target_link_libraries(locop PUBLIC nncc_coverage) +target_link_libraries(locop PRIVATE pp) +target_link_libraries(locop PRIVATE stdex) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +# Google Test is mandatory for internal testing +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(locop_test ${TESTS}) +target_link_libraries(locop_test stdex) +target_link_libraries(locop_test locop) diff --git a/compiler/locop/README.md b/compiler/locop/README.md new file mode 100644 index 00000000000..81ef41ca28e --- /dev/null +++ b/compiler/locop/README.md @@ -0,0 +1,3 @@ +# locop + +_locop_ is a collection of _loco_ pretty printers. diff --git a/compiler/locop/include/locop/CanonicalNodeSummaryBuilder.h b/compiler/locop/include/locop/CanonicalNodeSummaryBuilder.h new file mode 100644 index 00000000000..e9ced3f1760 --- /dev/null +++ b/compiler/locop/include/locop/CanonicalNodeSummaryBuilder.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_CANONICAL_NODE_SUMMARY_BUILDER_H__ +#define __LOCOP_CANONICAL_NODE_SUMMARY_BUILDER_H__ + +#include "locop/NodeSummaryBuilder.h" + +namespace locop +{ + +/** + * @brief Built-in Node Summary Builder for Canonical Dialect + */ +class CanonicalNodeSummaryBuilder final : public NodeSummaryBuilder +{ +public: + CanonicalNodeSummaryBuilder(const SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *node, locop::NodeSummary &out) const final; + +private: + const SymbolTable *_tbl; +}; + +} // namespace locop + +#endif // __LOCOP_CANONICAL_NODE_SUMMARY_BUILDER_H__ diff --git a/compiler/locop/include/locop/FormattedGraph.h b/compiler/locop/include/locop/FormattedGraph.h new file mode 100644 index 00000000000..0805c0e3982 --- /dev/null +++ b/compiler/locop/include/locop/FormattedGraph.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_FORMATTED_GRAPH_H__ +#define __LOCOP_FORMATTED_GRAPH_H__ + +#include "locop/SymbolTable.h" +#include "locop/NodeSummary.h" +#include "locop/NodeSummaryBuilder.h" +// TODO Remove this redundant include +#include "locop/CanonicalNodeSummaryBuilder.h" + +#include + +#include +#include +#include +#include + +namespace locop +{ + +struct FormattedGraph +{ + virtual ~FormattedGraph() = default; + + virtual void dump(std::ostream &os) const = 0; +}; + +std::ostream &operator<<(std::ostream &, const FormattedGraph &); + +enum Formatter +{ + LinearV1, + // TO BE ADDED +}; + +template class FormattedGraphImpl; + +template <> class FormattedGraphImpl final : public FormattedGraph +{ +public: + FormattedGraphImpl(loco::Graph *graph) : _graph{graph} {} + +public: + void dump(std::ostream &os) const final; + +public: + FormattedGraphImpl &with(std::unique_ptr &&f) + { + _factory = std::move(f); + return (*this); + } + +private: + loco::Graph *_graph; + + /** + * @brief User-provided NodeSummaryBuilderFactory + */ + std::unique_ptr _factory = nullptr; +}; + +template FormattedGraphImpl fmt(loco::Graph *g) +{ + return FormattedGraphImpl{g}; +} + +template FormattedGraphImpl fmt(const std::unique_ptr &g) +{ + return fmt(g.get()); +} + +} // namespace locop + +#endif // __LOCOP_FORMATTED_GRAPH_H__ diff --git a/compiler/locop/include/locop/FormattedTensorShape.h b/compiler/locop/include/locop/FormattedTensorShape.h new file mode 100644 index 00000000000..25621d6c3ce --- /dev/null +++ b/compiler/locop/include/locop/FormattedTensorShape.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_FORMATTED_TENSOR_SHAPE_H__ +#define __LOCOP_FORMATTED_TENSOR_SHAPE_H__ + +#include "locop/Interfaces.h" + +#include + +namespace locop +{ + +enum class TensorShapeFormat +{ + // D_0 x D_1 x ... D_N + Plain, + // [ D_0 x D_1 x D_2 x ... ] + Bracket, +}; + +template class FormattedTensorShape; + +template <> +class FormattedTensorShape final : public Spec +{ +public: + FormattedTensorShape(const loco::TensorShape *ptr) : _ptr{ptr} + { + // DO NOTHING + } + +public: + void dump(std::ostream &os) const final; + +private: + const loco::TensorShape *_ptr = nullptr; +}; + +template <> +class FormattedTensorShape final : public Spec +{ +public: + FormattedTensorShape(const loco::TensorShape *ptr) : _ptr{ptr} + { + // DO NOTHING + } + +public: + void dump(std::ostream &os) const final; + +private: + const loco::TensorShape *_ptr = nullptr; +}; + +template FormattedTensorShape fmt(loco::TensorShape *ptr) +{ + return FormattedTensorShape{ptr}; +} + +} // namespace locop + +#endif // __LOCOP_FORMATTED_TENSOR_SHAPE_H__ diff --git a/compiler/locop/include/locop/GenericNodeSummaryBuilder.h b/compiler/locop/include/locop/GenericNodeSummaryBuilder.h new file mode 100644 index 00000000000..cdfe45a2ba7 --- /dev/null +++ b/compiler/locop/include/locop/GenericNodeSummaryBuilder.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_GENERIC_NODE_SUMMARY_BUILDER_H__ +#define __LOCOP_GENERIC_NODE_SUMMARY_BUILDER_H__ + +#include "locop/NodeSummaryBuilder.h" + +namespace locop +{ + +/** + * @brief Dialect-agnostic Node Summary Builder + */ +class GenericNodeSummaryBuilder final : public NodeSummaryBuilder +{ +public: + GenericNodeSummaryBuilder(const SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *node, locop::NodeSummary &out) const final; + +private: + const SymbolTable *_tbl; +}; + +} // namespace locop + +#endif // __LOCOP_GENERIC_NODE_SUMMARY_BUILDER_H__ diff --git a/compiler/locop/include/locop/Interfaces.h b/compiler/locop/include/locop/Interfaces.h new file mode 100644 index 00000000000..0b4974d0ff2 --- /dev/null +++ b/compiler/locop/include/locop/Interfaces.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_INTERFACES_H__ +#define __LOCOP_INTERFACES_H__ + +#include + +namespace locop +{ + +enum class Interface +{ + Formatted, +}; + +template struct Spec; + +template <> struct Spec +{ + virtual ~Spec() = default; + + virtual void dump(std::ostream &os) const = 0; +}; + +std::ostream &operator<<(std::ostream &, const Spec &); + +} // namespace locop + +#endif // __LOCOP_INTERFACES_H__ diff --git a/compiler/locop/include/locop/NodeSummary.h b/compiler/locop/include/locop/NodeSummary.h new file mode 100644 index 00000000000..59fe663571e --- /dev/null +++ b/compiler/locop/include/locop/NodeSummary.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCO_NODE_SUMMARY_H__ +#define __LOCO_NODE_SUMMARY_H__ + +#include +#include +#include +#include + +namespace locop +{ + +using OpName = std::string; +using ArgName = std::string; +using ArgValue = std::string; +using ArgElem = std::pair; + +class ArgDesc +{ +public: + ArgDesc() = default; + +public: + /// @brief The number of presented arguments + uint32_t count(void) const { return _args.size(); } + + const ArgElem &at(uint32_t n) const { return _args.at(n); } + void append(const ArgName &name, const ArgValue &value) { _args.emplace_back(name, value); } + +private: + std::vector _args; +}; + +struct NodeDesc +{ +public: + /** + * @brief Multi-line comments + */ + class Comments final + { + public: + Comments() = default; + + public: + uint32_t count(void) const { return _lines.size(); } + const std::string &at(uint32_t n) const { return _lines.at(n); } + void append(const std::string &s); + + private: + std::vector _lines; + }; + +public: + enum class State + { + // All the node descriptions are "Invalid" at the beginning. + // + // Any valid node description SHOULD NOT be at this state. + Invalid, + // This state means that the producer is **NOT** confident about the information that + // it generates. + // + // There may be some missing information. + PartiallyKnown, + // This state means that the producer is confident about the information that it + // generates. + Complete, + }; + +public: + NodeDesc() = default; + NodeDesc(const OpName &opname) { this->opname(opname); } + +public: + const OpName &opname(void) const; + void opname(const OpName &value); + + const ArgDesc &args(void) const { return _args; } + ArgDesc &args(void) { return _args; } + + const Comments &comments(void) const { return _comments; } + Comments &comments(void) { return _comments; } + + const State &state(void) const { return _state; } + void state(const State &s) { _state = s; } + +private: + std::unique_ptr _name = nullptr; + ArgDesc _args; + Comments _comments; + State _state = State::Invalid; +}; + +using NodeSummary = NodeDesc; + +} // namespace locop + +#endif // __LOCO_NODE_SUMMARY_H__ diff --git a/compiler/locop/include/locop/NodeSummaryBuilder.h b/compiler/locop/include/locop/NodeSummaryBuilder.h new file mode 100644 index 00000000000..b84bc71cd3c --- /dev/null +++ b/compiler/locop/include/locop/NodeSummaryBuilder.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_NODE_SUMMARY_BUILDER_H__ +#define __LOCOP_NODE_SUMMARY_BUILDER_H__ + +#include "locop/SymbolTable.h" +#include "locop/NodeSummary.h" + +#include + +namespace locop +{ + +/** + * @brief Build a summary from loco Node + */ +struct NodeSummaryBuilder +{ + virtual ~NodeSummaryBuilder() = default; + + virtual bool build(const loco::Node *, NodeSummary &) const = 0; +}; + +struct NodeSummaryBuilderFactory +{ + virtual ~NodeSummaryBuilderFactory() = default; + + virtual std::unique_ptr create(const SymbolTable *) const = 0; +}; + +} // namespace locop + +#endif // __LOCOP_NODE_SUMMARY_BUILDER_H__ diff --git a/compiler/locop/include/locop/SymbolTable.h b/compiler/locop/include/locop/SymbolTable.h new file mode 100644 index 00000000000..ee9fc78e208 --- /dev/null +++ b/compiler/locop/include/locop/SymbolTable.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOCOP_SYMBOL_TABLE_H__ +#define __LOCOP_SYMBOL_TABLE_H__ + +#include + +#include + +namespace locop +{ + +/** + * @brief Symbol Table Interface + * + * Symbol Table gives a name for each node. + */ +struct SymbolTable +{ + virtual ~SymbolTable() = default; + + virtual std::string lookup(const loco::Node *) const = 0; +}; + +} // namespace locop + +#endif // __LOCOP_SYMBOL_TABLE_H__ diff --git a/compiler/locop/src/CanonicalNodeSummaryBuilder.cpp b/compiler/locop/src/CanonicalNodeSummaryBuilder.cpp new file mode 100644 index 00000000000..b962f490b37 --- /dev/null +++ b/compiler/locop/src/CanonicalNodeSummaryBuilder.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/CanonicalNodeSummaryBuilder.h" + +#include "locop/FormattedTensorShape.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +using locop::SymbolTable; + +namespace +{ + +// TODO Move this into loco +loco::TensorShape tensor_shape(const loco::NodeMixin *m) +{ + loco::TensorShape res; + + res.rank(m->rank()); + + for (uint32_t axis = 0; axis < m->rank(); ++axis) + { + res.dim(axis) = m->dim(axis); + } + + return res; +} + +using PrettyTensorShape = locop::FormattedTensorShape; + +inline PrettyTensorShape pretty(const loco::TensorShape &shape) +{ + return PrettyTensorShape{&shape}; +} + +} // namespace + +namespace +{ + +/** + * @brief Return the opname as "." + */ +std::string opname(const loco::Node *node) +{ + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto canonical_node = dynamic_cast(node); + + assert(canonical_node != nullptr); + + switch (canonical_node->opcode()) + { +#define CANONICAL_NODE(OPCODE, CLASS) \ + case loco::CanonicalOpcode::OPCODE: \ + return "canonical." #OPCODE; +#include "loco/IR/CanonicalNodes.lst" +#undef CANONICAL_NODE + default: + break; + }; + + return "canonical." + "Invalid"; + } + + return "unknown." + "Unknown"; +} + +struct NodeDesc : public locop::NodeDesc +{ +public: + NodeDesc() = default; + NodeDesc(const locop::OpName &opname) : locop::NodeDesc{opname} + { + // DO NOTHING + } + +public: + // DEPRECATED + const locop::OpName &name(void) const { return opname(); } + + // DEPRECATED + uint32_t arg_size(void) const { return args().count(); } + // DEPRECATED + const locop::ArgElem &arg(uint32_t n) const { return args().at(n); } + // DEPRECATED + void arg(const locop::ArgName &name, const locop::ArgValue &value) { args().append(name, value); } +}; + +NodeDesc default_node_desc(const SymbolTable &tbl, const loco::Node *node) +{ + NodeDesc res{opname(node)}; + + for (uint32_t n = 0; n < node->arity(); ++n) + { + res.arg(std::string{"arg"} + std::to_string(n), tbl.lookup(node->arg(n))); + } + res.state(NodeDesc::State::PartiallyKnown); + + return res; +} + +class CanonicalNodeDescBuilder final : public loco::CanonicalNodeVisitor +{ +public: + CanonicalNodeDescBuilder(const SymbolTable *symtbl) : _symtbl{symtbl} + { + // DO NOTHING + } + +private: + std::string nodename(const loco::Node *node) const { return _symtbl->lookup(node); } + +public: + // TODO Build a node description for each canonical node + NodeDesc visit(const loco::Push *node) final + { + NodeDesc res{opname(node)}; + + res.arg("index", node->indexed() ? pp::fmt(node->index()) : pp::fmt('?')); + res.arg("from", nodename(node->from())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::Pull *node) final + { + NodeDesc res{opname(node)}; + + res.arg("index", node->indexed() ? pp::fmt(node->index()) : pp::fmt('?')); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::Forward *node) final + { + NodeDesc res{opname(node)}; + + res.arg("input", nodename(node->input())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::ConstGen *node) final + { + NodeDesc res{opname(node)}; + + // TODO Print data type + res.arg("shape", pp::fmt(pretty(tensor_shape(node)))); + res.state(NodeDesc::State::PartiallyKnown); + + return res; + } + + NodeDesc visit(const loco::TensorConcat *node) final + { + NodeDesc res{opname(node)}; + + res.arg("lhs", nodename(node->lhs())); + res.arg("rhs", nodename(node->rhs())); + res.arg("axis", pp::fmt(node->axis())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::EltwiseAdd *node) final + { + NodeDesc res{opname(node)}; + + res.arg("lhs", nodename(node->lhs())); + res.arg("rhs", nodename(node->rhs())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::EltwiseMul *node) final + { + NodeDesc res{opname(node)}; + + res.arg("lhs", nodename(node->lhs())); + res.arg("rhs", nodename(node->rhs())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::TensorReduce *node) final + { + NodeDesc res{opname(node)}; + + // TODO Print TensorAxisSet + res.arg("input", nodename(node->input())); + res.arg("func", pp::fmt((int32_t)node->func())); + + res.state(NodeDesc::State::PartiallyKnown); + + return res; + } + + NodeDesc visit(const loco::Reshape *node) final + { + NodeDesc res{opname(node)}; + + res.arg("input", nodename(node->input())); + res.arg("shape", pp::fmt(pretty(tensor_shape(node)))); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::Tanh *node) final + { + NodeDesc res{opname(node)}; + + res.arg("input", nodename(node->input())); + res.state(NodeDesc::State::Complete); + + return res; + } + + NodeDesc visit(const loco::TensorSoftmax *node) final + { + NodeDesc res{opname(node)}; + + res.arg("input", nodename(node->input())); + res.arg("axis", pp::fmt(node->axis())); + res.state(NodeDesc::State::Complete); + + return res; + } + +public: + NodeDesc visit(const loco::Node *node) final { return default_node_desc(*_symtbl, node); } + +private: + const SymbolTable *_symtbl; +}; + +NodeDesc canonical_node_desc(const SymbolTable &tbl, const loco::CanonicalNode *canonical_node) +{ + CanonicalNodeDescBuilder builder{&tbl}; + return canonical_node->accept(&builder); +} + +} // namespace + +namespace locop +{ + +bool CanonicalNodeSummaryBuilder::build(const loco::Node *node, locop::NodeSummary &out) const +{ + // Skip if a given node does not belong to loco.canonical + if (node->dialect() != loco::CanonicalDialect::get()) + { + return false; + } + + auto canonical_node = dynamic_cast(node); + assert(canonical_node != nullptr); + out = canonical_node_desc(*_tbl, canonical_node); + return true; +} + +} // namespace locop diff --git a/compiler/locop/src/ExampleGraph.h b/compiler/locop/src/ExampleGraph.h new file mode 100644 index 00000000000..76813bcd807 --- /dev/null +++ b/compiler/locop/src/ExampleGraph.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __EXAMPLE_GRAPH_H__ +#define __EXAMPLE_GRAPH_H__ + +#include + +#include + +namespace +{ + +enum GraphCode +{ + PullPush, /* Pull - Push network */ +}; + +template struct Bundle; +template std::unique_ptr> make_bundle(void); + +template <> struct Bundle +{ + std::unique_ptr g; + loco::Pull *pull; + loco::Push *push; + + loco::Graph *graph(void) { return g.get(); } +}; + +template <> std::unique_ptr> make_bundle(void) +{ + auto g = loco::make_graph(); + + auto pull = g->nodes()->create(); + + pull->rank(2); + pull->dim(0) = loco::make_dimension(); // Mark dim 0 as unknown + pull->dim(1) = 4; + + auto push = g->nodes()->create(); + + push->from(pull); + + auto res = stdex::make_unique>(); + + res->g = std::move(g); + res->pull = pull; + res->push = push; + + return std::move(res); +} + +} // namespace + +#endif // __EXAMPLE_GRAPH_H__ diff --git a/compiler/locop/src/FormattedGraph.cpp b/compiler/locop/src/FormattedGraph.cpp new file mode 100644 index 00000000000..84de1e88846 --- /dev/null +++ b/compiler/locop/src/FormattedGraph.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/FormattedGraph.h" +#include "locop/FormattedTensorShape.h" +#include "locop/GenericNodeSummaryBuilder.h" + +#include +#include + +#include + +#include + +#include +#include + +#include + +using locop::SymbolTable; + +namespace +{ + +std::string str(const loco::DataType &dtype) +{ + switch (dtype) + { + case loco::DataType::Unknown: + return "Unknown"; + + case loco::DataType::U8: + return "U8"; + case loco::DataType::U16: + return "U16"; + case loco::DataType::U32: + return "U32"; + case loco::DataType::U64: + return "U64"; + + case loco::DataType::S8: + return "S8"; + case loco::DataType::S16: + return "S16"; + case loco::DataType::S32: + return "S32"; + case loco::DataType::S64: + return "S64"; + + case loco::DataType::FLOAT16: + return "FLOAT16"; + case loco::DataType::FLOAT32: + return "FLOAT32"; + case loco::DataType::FLOAT64: + return "FLOAT64"; + + default: + break; + }; + + throw std::invalid_argument{"dtype"}; +} + +std::string str(const loco::Domain &domain) +{ + // TODO Generate! + switch (domain) + { + case loco::Domain::Unknown: + return "Unknown"; + case loco::Domain::Tensor: + return "Tensor"; + case loco::Domain::Feature: + return "Feature"; + case loco::Domain::Filter: + return "Filter"; + case loco::Domain::DepthwiseFilter: + return "DWFilter"; + case loco::Domain::Bias: + return "Bias"; + default: + break; + } + + throw std::invalid_argument{"domain"}; +} + +std::string str(const loco::NodeShape &node_shape) +{ + using namespace locop; + + switch (node_shape.domain()) + { + case loco::Domain::Tensor: + { + auto tensor_shape = node_shape.as(); + return pp::fmt(locop::fmt(&tensor_shape)); + } + // TODO Show details + case loco::Domain::Feature: + case loco::Domain::Filter: + case loco::Domain::DepthwiseFilter: + case loco::Domain::Bias: + return "..."; + + default: + break; + } + + throw std::invalid_argument{"domain"}; +} + +// TODO Use locop::fmt +locop::FormattedTensorShape +formatted_tensor_shape(const loco::TensorShape *ptr) +{ + return locop::FormattedTensorShape{ptr}; +} + +} // namespace + +namespace +{ + +struct NodeDesc : public locop::NodeDesc +{ +public: + NodeDesc() = default; + NodeDesc(const locop::OpName &opname) : locop::NodeDesc{opname} + { + // DO NOTHING + } + +public: + // DEPRECATED + const locop::OpName &name(void) const { return opname(); } + + // DEPRECATED + uint32_t arg_size(void) const { return args().count(); } + // DEPRECATED + const locop::ArgElem &arg(uint32_t n) const { return args().at(n); } + // DEPRECATED + void arg(const locop::ArgName &name, const locop::ArgValue &value) { args().append(name, value); } +}; + +} // namespace + +// TODO Remove this workaround +namespace locop +{ + +std::ostream &operator<<(std::ostream &os, const NodeDesc &d) +{ + assert(d.state() != NodeDesc::State::Invalid); + + std::vector values; + + for (uint32_t n = 0; n < d.args().count(); ++n) + { + values.emplace_back(d.args().at(n).first + ": " + d.args().at(n).second); + } + + if (d.state() == NodeDesc::State::PartiallyKnown) + { + values.emplace_back("..."); + } + + os << d.opname(); + os << "("; + if (values.size() > 0) + { + os << values.at(0); + for (uint32_t n = 1; n < values.size(); ++n) + { + os << ", " << values.at(n); + } + } + os << ")"; + + return os; +} + +} // namespace locop + +namespace locop +{ + +std::ostream &operator<<(std::ostream &os, const FormattedGraph &fmt) +{ + fmt.dump(os); + return os; +} + +} // namespace locop + +namespace locop +{ + +void FormattedGraphImpl::dump(std::ostream &os) const +{ + struct SymbolTableImpl final : public SymbolTable + { + std::string lookup(const loco::Node *node) const final + { + if (node == nullptr) + { + return "(null)"; + } + + return _content.at(node); + } + + std::map _content; + }; + + SymbolTableImpl symbols; + + auto symbol = [&symbols](const loco::Node *node) { return symbols.lookup(node); }; + + for (uint32_t n = 0; n < _graph->nodes()->size(); ++n) + { + symbols._content[_graph->nodes()->at(n)] = pp::fmt("%", n); + } + + // Find the disjoint node clusters + // + // TODO Move this implementation into loco Algorithms.h + std::map parents; + + for (auto node : loco::all_nodes(_graph)) + { + parents[node] = nullptr; + } + + for (auto node : loco::all_nodes(_graph)) + { + for (uint32_t n = 0; n < node->arity(); ++n) + { + if (auto arg = node->arg(n)) + { + parents[arg] = node; + } + } + } + + auto find = [&parents](loco::Node *node) { + loco::Node *cur = node; + + while (parents.at(cur) != nullptr) + { + cur = parents.at(cur); + } + + return cur; + }; + + std::set roots; + + for (auto node : loco::all_nodes(_graph)) + { + roots.insert(find(node)); + } + + std::map> clusters; + + // Create clusters + for (auto root : roots) + { + clusters[root] = std::set{}; + } + + for (auto node : loco::all_nodes(_graph)) + { + clusters.at(find(node)).insert(node); + } + + std::unique_ptr node_summary_builder; + + if (_factory) + { + // Use User-defined NodeSummaryBuilder if NodeSummaryBuilderFactory is present + node_summary_builder = _factory->create(&symbols); + } + else + { + // Use Built-in NodeSummaryBuilder otherwise + node_summary_builder = stdex::make_unique(&symbols); + } + + // Print Graph Input(s) + for (uint32_t n = 0; n < _graph->inputs()->size(); ++n) + { + auto input = _graph->inputs()->at(n); + + std::string name = input->name(); + + std::string shape = "?"; + if (input->shape() != nullptr) + { + shape = pp::fmt(formatted_tensor_shape(input->shape())); + } + + // TODO Print dtype + os << pp::fmt("In #", n, " { name: ", name, ", shape: ", shape, " }") << std::endl; + } + + // Print Graph Output(s) + for (uint32_t n = 0; n < _graph->outputs()->size(); ++n) + { + auto output = _graph->outputs()->at(n); + + std::string name = output->name(); + + std::string shape = "?"; + if (output->shape() != nullptr) + { + shape = pp::fmt(formatted_tensor_shape(output->shape())); + } + + // TODO Print dtype + os << pp::fmt("Out #", n, " { name: ", name, ", shape: ", shape, " }") << std::endl; + } + + if (_graph->inputs()->size() + _graph->outputs()->size() != 0) + { + os << std::endl; + } + + for (auto it = clusters.begin(); it != clusters.end(); ++it) + { + std::vector cluster_outputs; + + for (auto node : it->second) + { + // NOTE This is inefficient but anyway working :) + if (loco::succs(node).empty()) + { + cluster_outputs.emplace_back(node); + } + } + + for (auto node : loco::postorder_traversal(cluster_outputs)) + { + locop::NodeSummary node_summary; + + // Build a node summary + if (!node_summary_builder->build(node, node_summary)) + { + throw std::runtime_error{"Fail to build a node summary"}; + } + + for (uint32_t n = 0; n < node_summary.comments().count(); ++n) + { + os << "; " << node_summary.comments().at(n) << std::endl; + } + + os << symbol(node); + + if (loco::shape_known(node)) + { + auto node_shape = loco::shape_get(node); + os << " : " << str(node_shape.domain()); + os << "<"; + os << str(node_shape); + os << ", "; + // Show DataType + os << (loco::dtype_known(node) ? str(loco::dtype_get(node)) : std::string{"?"}); + os << ">"; + } + + os << " = " << node_summary << std::endl; + } + os << std::endl; + } +} + +} // namespace locop diff --git a/compiler/locop/src/FormattedGraph.test.cpp b/compiler/locop/src/FormattedGraph.test.cpp new file mode 100644 index 00000000000..c9808d3a216 --- /dev/null +++ b/compiler/locop/src/FormattedGraph.test.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/FormattedGraph.h" +#include "ExampleGraph.h" + +#include + +#include + +TEST(LinearV1FormatterTest, simple) +{ + auto bundle = make_bundle(); + auto g = bundle->graph(); + + // TODO Validate the output (when the implementation becomes stable) + std::cout << locop::fmt(g) << std::endl; +} + +TEST(LinearV1FormatterTest, user_defined_node_summary_builder) +{ + struct MyAnnotation final : public loco::NodeAnnotation + { + // DO NOTHING + }; + + auto bundle = make_bundle(); + auto g = bundle->graph(); + { + bundle->push->annot(stdex::make_unique()); + } + + struct MyBuilder final : public locop::NodeSummaryBuilder + { + bool build(const loco::Node *node, locop::NodeSummary &s) const final + { + s.opname("my.op"); + if (node->annot()) + { + s.comments().append("annotated"); + } + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; + } + }; + + struct MyFactory final : public locop::NodeSummaryBuilderFactory + { + std::unique_ptr create(const locop::SymbolTable *) const final + { + return stdex::make_unique(); + } + }; + + std::cout << locop::fmt(g).with(stdex::make_unique()) << std::endl; + + // TODO Check whether MyBuilder actually sees all the nodes in a graph + SUCCEED(); +} + +// This test shows how to compose two node summary builders. +TEST(LinearV1FormatterTest, node_summary_builder_composition) +{ + struct MyNode : public loco::FixedArity<0>::Mixin + { + uint32_t opnum(void) const final { return 0; } + const loco::Dialect *dialect(void) const final { return nullptr; }; + }; + + auto g = loco::make_graph(); + { + auto user = g->nodes()->create(); + + auto push = g->nodes()->create(); + + push->from(user); + } + + // TODO Reuse MyBuilder above + struct MyBuilder final : public locop::NodeSummaryBuilder + { + bool build(const loco::Node *node, locop::NodeSummary &s) const final + { + s.opname("my.op"); + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; + } + }; + + class CompositeBuilder final : public locop::NodeSummaryBuilder + { + public: + CompositeBuilder(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + + public: + bool build(const loco::Node *node, locop::NodeSummary &s) const final + { + if (locop::CanonicalNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + if (MyBuilder().build(node, s)) + { + return true; + } + + return false; + } + + private: + const locop::SymbolTable *_tbl; + }; + + struct MyFactory final : public locop::NodeSummaryBuilderFactory + { + std::unique_ptr create(const locop::SymbolTable *tbl) const final + { + return stdex::make_unique(tbl); + } + }; + + std::cout << locop::fmt(g).with(stdex::make_unique()) << std::endl; + + // TODO Check whether MyBuilder actually sees all the nodes in a graph + SUCCEED(); +} diff --git a/compiler/locop/src/FormattedTensorShape.cpp b/compiler/locop/src/FormattedTensorShape.cpp new file mode 100644 index 00000000000..b2b6ea074df --- /dev/null +++ b/compiler/locop/src/FormattedTensorShape.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/FormattedTensorShape.h" + +namespace loco +{ + +std::ostream &operator<<(std::ostream &os, const loco::Dimension &d) +{ + os << (d.known() ? std::to_string(d.value()) : std::string{"?"}); + return os; +} + +} // namespace + +namespace locop +{ + +void FormattedTensorShape::dump(std::ostream &os) const +{ + if (_ptr->rank() > 0) + { + os << _ptr->dim(0); + + for (uint32_t axis = 1; axis < _ptr->rank(); ++axis) + { + os << " x " << _ptr->dim(axis); + } + } +} + +} // namespace locop + +namespace locop +{ + +void FormattedTensorShape::dump(std::ostream &os) const +{ + os << "["; + + if (_ptr->rank() > 0) + { + os << " " << _ptr->dim(0); + + for (uint32_t axis = 1; axis < _ptr->rank(); ++axis) + { + os << " x " << _ptr->dim(axis); + } + } + + os << " ]"; +} + +} // namespace locop diff --git a/compiler/locop/src/FormattedTensorShape.test.cpp b/compiler/locop/src/FormattedTensorShape.test.cpp new file mode 100644 index 00000000000..0f0017ab4b2 --- /dev/null +++ b/compiler/locop/src/FormattedTensorShape.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/FormattedTensorShape.h" + +#include + +#include + +using namespace locop; + +TEST(FormattedTensorShapeTest, BracketFormat) +{ + auto tensor_shape = stdex::make_unique(); + + tensor_shape->rank(2); + tensor_shape->dim(0) = 4; + + std::cout << fmt(tensor_shape.get()) << std::endl; +} diff --git a/compiler/locop/src/GenericNodeSummaryBuilder.cpp b/compiler/locop/src/GenericNodeSummaryBuilder.cpp new file mode 100644 index 00000000000..e3bbe5aadcf --- /dev/null +++ b/compiler/locop/src/GenericNodeSummaryBuilder.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/GenericNodeSummaryBuilder.h" + +#include + +namespace locop +{ + +bool GenericNodeSummaryBuilder::build(const loco::Node *node, locop::NodeSummary &out) const +{ + out.opname(pp::fmt(node->dialect(), ".op_", node->opnum())); + + for (uint32_t n = 0; n < node->arity(); ++n) + { + out.args().append(pp::fmt("arg", n), _tbl->lookup(node->arg(n))); + } + + out.state(NodeDesc::State::PartiallyKnown); + + return true; +} + +} // namespace locop diff --git a/compiler/locop/src/GenericNodeSummaryBuilder.test.cpp b/compiler/locop/src/GenericNodeSummaryBuilder.test.cpp new file mode 100644 index 00000000000..d688b54906c --- /dev/null +++ b/compiler/locop/src/GenericNodeSummaryBuilder.test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/GenericNodeSummaryBuilder.h" +#include "locop/FormattedGraph.h" + +#include + +#include + +#include + +TEST(GenericNodeSummaryBuilderTest, simple) +{ + struct MockDialect final : public loco::Dialect + { + static Dialect *get(void) + { + static MockDialect d; + return &d; + } + }; + + struct MockNode : public loco::FixedArity<0>::Mixin + { + const loco::Dialect *dialect(void) const final { return MockDialect::get(); }; + uint32_t opnum(void) const final { return 0; } + }; + + struct MockFactory final : public locop::NodeSummaryBuilderFactory + { + std::unique_ptr create(const locop::SymbolTable *tbl) const final + { + return stdex::make_unique(tbl); + } + }; + + auto g = loco::make_graph(); + + g->nodes()->create(); + + std::cout << locop::fmt(g).with(stdex::make_unique()) << std::endl; + + SUCCEED(); +} diff --git a/compiler/locop/src/Interfaces.cpp b/compiler/locop/src/Interfaces.cpp new file mode 100644 index 00000000000..14e0211ba25 --- /dev/null +++ b/compiler/locop/src/Interfaces.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/Interfaces.h" + +namespace locop +{ + +std::ostream &operator<<(std::ostream &os, const Spec &formatted) +{ + formatted.dump(os); + return os; +} + +} // namespace locop diff --git a/compiler/locop/src/NodeSummary.cpp b/compiler/locop/src/NodeSummary.cpp new file mode 100644 index 00000000000..3f885699774 --- /dev/null +++ b/compiler/locop/src/NodeSummary.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/NodeSummary.h" + +#include + +#include + +namespace locop +{ + +void NodeDesc::Comments::append(const std::string &s) +{ + // TODO Check whether s contains any newline character + _lines.emplace_back(s); +} + +const std::string &NodeDesc::opname(void) const +{ + // _name SHOULD BE set before use + assert(_name != nullptr); + return *_name; +} + +void NodeDesc::opname(const std::string &v) { _name = stdex::make_unique(v); } + +} // namespace loco diff --git a/compiler/locop/src/NodeSummaryBuilder.cpp b/compiler/locop/src/NodeSummaryBuilder.cpp new file mode 100644 index 00000000000..6610bf71fa3 --- /dev/null +++ b/compiler/locop/src/NodeSummaryBuilder.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "locop/NodeSummaryBuilder.h" + +// This file checks whether "NodeSummaryBuilder.h" is self-complete or not. +// +// WARNING!! Do NOT remove this file. diff --git a/compiler/logo-core/CMakeLists.txt b/compiler/logo-core/CMakeLists.txt new file mode 100644 index 00000000000..3bc71dbd00f --- /dev/null +++ b/compiler/logo-core/CMakeLists.txt @@ -0,0 +1,19 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(logo_core STATIC ${SOURCES}) +set_target_properties(logo_core PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(logo_core PRIVATE src) +target_include_directories(logo_core PUBLIC include) +target_link_libraries(logo_core PUBLIC loco) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(logo_core_test ${TESTS}) +target_include_directories(logo_core_test PRIVATE src) +target_link_libraries(logo_core_test logo_core) diff --git a/compiler/logo-core/README.md b/compiler/logo-core/README.md new file mode 100644 index 00000000000..0dee3954bdd --- /dev/null +++ b/compiler/logo-core/README.md @@ -0,0 +1,3 @@ +# logo-core + +_logo-core_ provides _loco_ General Graph Pass Core for Transformation and Optimization diff --git a/compiler/logo-core/include/logo/Pass.h b/compiler/logo-core/include/logo/Pass.h new file mode 100644 index 00000000000..4f667f1567c --- /dev/null +++ b/compiler/logo-core/include/logo/Pass.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_PASS_H__ +#define __LOGO_PASS_H__ + +#include + +#include + +namespace logo +{ + +class Pass +{ +public: + virtual ~Pass() = default; + +public: + virtual const char *name(void) const { return nullptr; } + +public: + /** + * @brief Run the pass + * + * @return false if there was nothing changed + */ + virtual bool run(loco::Graph *graph) = 0; +}; + +std::string pass_name(const Pass *); + +} // namespace logo + +#endif // __LOGO_PASS_H__ diff --git a/compiler/logo-core/include/logo/Phase.h b/compiler/logo-core/include/logo/Phase.h new file mode 100644 index 00000000000..d1b7ccd5f31 --- /dev/null +++ b/compiler/logo-core/include/logo/Phase.h @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_PHASE_H__ +#define __LOGO_PHASE_H__ + +#include + +#include + +#include +#include + +namespace logo +{ + +// Phase is a collection of Pass(es) +using Phase = std::vector>; + +enum class PhaseEvent +{ + PhaseBegin, + PhaseEnd, + + PassBegin, + PassEnd, +}; + +template struct PhaseEventInfo; + +template <> class PhaseEventInfo +{ + // Empty +}; + +template <> class PhaseEventInfo +{ + // Empty +}; + +template <> class PhaseEventInfo +{ +public: + void pass(const Pass *pass) { _pass = pass; } + const Pass *pass(void) const { return _pass; } + +private: + const Pass *_pass; +}; + +template <> class PhaseEventInfo +{ +public: + void pass(const Pass *pass) { _pass = pass; } + const Pass *pass(void) const { return _pass; } + + void changed(bool changed) { _changed = changed; } + bool changed(void) const { return _changed; } + +private: + const Pass *_pass; + bool _changed; +}; + +struct PhaseEventListener +{ + virtual ~PhaseEventListener() = default; + + virtual void notify(const PhaseEventInfo *) { return; }; + virtual void notify(const PhaseEventInfo *) { return; }; + virtual void notify(const PhaseEventInfo *) { return; }; + virtual void notify(const PhaseEventInfo *) { return; }; +}; + +// TODO Will be other mix-ins for Phase Runners? +class PhaseRunnerMixinObservable +{ +public: + PhaseRunnerMixinObservable() = default; + +public: + virtual ~PhaseRunnerMixinObservable() = default; + +public: + void attach(PhaseEventListener *listener) { _listener = listener; } + +public: + void notifyPhaseBegin(void) const + { + if (_listener) + { + PhaseEventInfo info; + + _listener->notify(&info); + } + } + + void notifyPhaseEnd(void) const + { + if (_listener) + { + PhaseEventInfo info; + + _listener->notify(&info); + } + } + + void notifyPassBegin(Pass *pass) const + { + if (_listener) + { + PhaseEventInfo info; + + info.pass(pass); + + _listener->notify(&info); + } + } + + void notifyPassEnd(Pass *pass, bool changed) const + { + if (_listener) + { + PhaseEventInfo info; + + info.pass(pass); + info.changed(changed); + + _listener->notify(&info); + } + } + +private: + PhaseEventListener *_listener = nullptr; +}; + +enum class PhaseStrategy +{ + // Run all the passes until there is no pass that makes a change + Saturate, + // Same as Saturate but will restart from the first when there is a change + Restart, +}; + +template class PhaseRunner; + +template <> class PhaseRunner final : public PhaseRunnerMixinObservable +{ +public: + PhaseRunner(loco::Graph *graph) : _graph{graph} + { + // DO NOTHING + } + +public: + void run(const Phase &) const; + +private: + loco::Graph *_graph; +}; + +template <> class PhaseRunner final : public PhaseRunnerMixinObservable +{ +public: + PhaseRunner(loco::Graph *graph) : _graph{graph} + { + // DO NOTHING + } + +public: + void run(const Phase &) const; + +private: + loco::Graph *_graph; +}; + +} // namespace logo + +#endif // __LOGO_PHASE_H__ diff --git a/compiler/logo-core/requires.cmake b/compiler/logo-core/requires.cmake new file mode 100644 index 00000000000..44f6870da72 --- /dev/null +++ b/compiler/logo-core/requires.cmake @@ -0,0 +1 @@ +require("loco") diff --git a/compiler/logo-core/src/Pass.cpp b/compiler/logo-core/src/Pass.cpp new file mode 100644 index 00000000000..a44010760db --- /dev/null +++ b/compiler/logo-core/src/Pass.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +namespace logo +{ + +std::string pass_name(const Pass *t) +{ + if (t->name() == nullptr) + { + return "(unknown)"; + } + + return t->name(); +} + +} // namespace logo diff --git a/compiler/logo-core/src/Pass.test.cpp b/compiler/logo-core/src/Pass.test.cpp new file mode 100644 index 00000000000..b6bebff6214 --- /dev/null +++ b/compiler/logo-core/src/Pass.test.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +#include + +TEST(LogoPassTests, pass_name_over_unnamed_pass) +{ + struct Bumblebee final : public logo::Pass + { + bool run(loco::Graph *) final { return false; } + }; + + Bumblebee bumblebee; + + ASSERT_EQ(logo::pass_name(&bumblebee), "(unknown)"); +} + +TEST(LogoPassTests, pass_name_over_named_pass) +{ + struct Bumblebee final : public logo::Pass + { + const char *name(void) const final { return "Bee"; } + bool run(loco::Graph *) final { return false; } + }; + + Bumblebee bumblebee; + + ASSERT_EQ(logo::pass_name(&bumblebee), "Bee"); +} diff --git a/compiler/logo-core/src/Phase.cpp b/compiler/logo-core/src/Phase.cpp new file mode 100644 index 00000000000..b929a31bac6 --- /dev/null +++ b/compiler/logo-core/src/Phase.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +namespace logo +{ + +void PhaseRunner::run(const Phase &phase) const +{ + notifyPhaseBegin(); + + for (bool changed = true; changed;) + { + changed = false; + + for (auto &pass : phase) + { + notifyPassBegin(pass.get()); + + bool pass_changed = pass->run(_graph); + changed = changed || pass_changed; + + notifyPassEnd(pass.get(), pass_changed); + } + } + + notifyPhaseEnd(); +} + +void PhaseRunner::run(const Phase &phase) const +{ + notifyPhaseBegin(); + + for (bool changed = true; changed;) + { + changed = false; + + for (auto &pass : phase) + { + notifyPassBegin(pass.get()); + + bool pass_changed = pass->run(_graph); + changed = changed || pass_changed; + + notifyPassEnd(pass.get(), pass_changed); + + if (changed) + { + break; + } + } + } + + notifyPhaseEnd(); +} + +} // namespace logo diff --git a/compiler/logo/CMakeLists.txt b/compiler/logo/CMakeLists.txt new file mode 100644 index 00000000000..399cb758682 --- /dev/null +++ b/compiler/logo/CMakeLists.txt @@ -0,0 +1,23 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(logo STATIC ${SOURCES}) +set_target_properties(logo PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(logo PRIVATE src) +target_include_directories(logo PUBLIC include) +target_link_libraries(logo PUBLIC loco) +target_link_libraries(logo PUBLIC logo_core) +target_link_libraries(logo PRIVATE locomotiv) +target_link_libraries(logo PRIVATE stdex) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(logo_test ${TESTS}) +target_include_directories(logo_test PRIVATE src) +target_link_libraries(logo_test logo) +target_link_libraries(logo_test stdex) diff --git a/compiler/logo/README.md b/compiler/logo/README.md new file mode 100644 index 00000000000..0cf1ba31357 --- /dev/null +++ b/compiler/logo/README.md @@ -0,0 +1,3 @@ +# logo + +_logo_ provides _loco_ General Graph Passes for Transformation and Optimization diff --git a/compiler/logo/include/logo/ConstantFoldingPass.h b/compiler/logo/include/logo/ConstantFoldingPass.h new file mode 100644 index 00000000000..99ccdc315cf --- /dev/null +++ b/compiler/logo/include/logo/ConstantFoldingPass.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_CONSTANT_FOLDING_PASS_H__ +#define __LOGO_CONSTANT_FOLDING_PASS_H__ + +#include + +#include + +namespace logo +{ + +/** + * @brief Performs constant folding optimization + */ +class ConstantFoldingPass : public Pass +{ +public: + const char *name(void) const final { return "ConstantFoldingPass"; } + +public: + bool run(loco::Graph *graph) override; +}; + +} // namespace logo + +#endif // __LOGO_CONSTANT_FOLDING_PASS_H__ diff --git a/compiler/logo/include/logo/Passes.h b/compiler/logo/include/logo/Passes.h new file mode 100644 index 00000000000..636251e45b2 --- /dev/null +++ b/compiler/logo/include/logo/Passes.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_PASSES_H__ +#define __LOGO_PASSES_H__ + +// Please keep this in alphabetical order + +#include +#include +#include +#include +#include +#include +#include + +#endif // __LOGO_PASSES_H__ diff --git a/compiler/logo/include/logo/RemoveDeadNodePass.h b/compiler/logo/include/logo/RemoveDeadNodePass.h new file mode 100644 index 00000000000..ae1c67feba2 --- /dev/null +++ b/compiler/logo/include/logo/RemoveDeadNodePass.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_REMOVE_DEAD_NODE_PASS_H__ +#define __LOGO_REMOVE_DEAD_NODE_PASS_H__ + +#include + +namespace logo +{ + +struct RemoveDeadNodePass final : public Pass +{ + const char *name(void) const final { return "RemoveDeadNodePass"; } + + bool run(loco::Graph *g); +}; + +} // namespace logo + +#endif // __LOGO_REMOVE_DEAD_NODE_PASS_H__ diff --git a/compiler/logo/include/logo/RemoveForwardNodePass.h b/compiler/logo/include/logo/RemoveForwardNodePass.h new file mode 100644 index 00000000000..12437c43f03 --- /dev/null +++ b/compiler/logo/include/logo/RemoveForwardNodePass.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_REMOVE_FORWARD_NODE_PASS_H__ +#define __LOGO_REMOVE_FORWARD_NODE_PASS_H__ + +#include + +namespace logo +{ + +/** + * @brief Use the input of "Forward" node instead + * + * BEFORE: + * [X] -> [Forward] -> [Y] + * + * AFTER: + * [X] -> [Y] + * [Forward] + * + * NOTE This transform does not remove "Forward" node + */ +struct RemoveForwardNodePass final : public Pass +{ + const char *name(void) const final { return "RemoveForwardNodePass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace logo + +#endif // __LOGO_REMOVE_FORWARD_NODE_PASS_H__ diff --git a/compiler/logo/include/logo/ReorderDecodePass.h b/compiler/logo/include/logo/ReorderDecodePass.h new file mode 100644 index 00000000000..2f74c6afaef --- /dev/null +++ b/compiler/logo/include/logo/ReorderDecodePass.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_REORDER_DECODE_PASS_H__ +#define __LOGO_REORDER_DECODE_PASS_H__ + +#include + +#include +#include + +namespace logo +{ + +/** + * @brief Reorder XXXDecode -> ? as ? -> XXXDecode if possible + * + * This transformation increases the chance of domain conversion simplification. + */ +template struct ReorderDecodePass; + +template <> struct ReorderDecodePass final : public Pass +{ + const char *name(void) const final { return "ReorderDecodePass "; } + + bool run(loco::Graph *g); +}; + +template <> struct ReorderDecodePass final : public Pass +{ + const char *name(void) const final { return "ReorderDecodePass "; } + + bool run(loco::Graph *g); +}; + +} // namespace logo + +#endif // __LOGO_REORDER_DECODE_PASS_H__ diff --git a/compiler/logo/include/logo/ResolveDuplicateReshapePass.h b/compiler/logo/include/logo/ResolveDuplicateReshapePass.h new file mode 100644 index 00000000000..7e6c67fcd17 --- /dev/null +++ b/compiler/logo/include/logo/ResolveDuplicateReshapePass.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_RESOLVE_DUPLICATE_RESHAPE_PASS_H__ +#define __LOGO_RESOLVE_DUPLICATE_RESHAPE_PASS_H__ + +#include + +#include + +namespace logo +{ + +/** + * @brief Resolve duplicated Reshape nodes in a row + */ +class ResolveDuplicateReshapePass final : public Pass +{ +public: + const char *name(void) const final { return "ResolveDuplicateReshapePass"; } + +public: + bool run(loco::Graph *graph) override; +}; + +} // namespace logo + +#endif // __LOGO_RESOLVE_DUPLICATE_RESHAPE_PASS_H__ diff --git a/compiler/logo/include/logo/ResolveRedundantReshapePass.h b/compiler/logo/include/logo/ResolveRedundantReshapePass.h new file mode 100644 index 00000000000..3a2dc4f3df7 --- /dev/null +++ b/compiler/logo/include/logo/ResolveRedundantReshapePass.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_RESOLVE_REDUNDANT_RESHAPE_PASS_H__ +#define __LOGO_RESOLVE_REDUNDANT_RESHAPE_PASS_H__ + +#include + +#include + +namespace logo +{ + +/** + * @brief Remove redundant canonical FixedReshape + * + * @note To effectively run this transform, canonical shape inference should be + * done ahead + */ +class ResolveRedundantReshapePass final : public Pass +{ +public: + const char *name(void) const final { return "ResolveRedundantReshapePass"; } + +public: + bool run(loco::Graph *graph) override; +}; + +} // namespace logo + +#endif // __LOGO_RESOLVE_REDUNDANT_RESHAPE_PASS_H__ diff --git a/compiler/logo/include/logo/SimplifyDomainConversionPass.h b/compiler/logo/include/logo/SimplifyDomainConversionPass.h new file mode 100644 index 00000000000..551806f60db --- /dev/null +++ b/compiler/logo/include/logo/SimplifyDomainConversionPass.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LOGO_SIMPLIFY_DOMAIN_CONVERSION_H__ +#define __LOGO_SIMPLIFY_DOMAIN_CONVERSION_H__ + +#include + +namespace logo +{ + +/** + * @brief Simplify redundant domain conversion + * + * SimplifyDomainConversionPass recognizes the following patterns: + * - FeatureDecode followed by FeatureEncode (Feature -> Tensor -> Feature) + * - FeatureEncode followed by FeatureDecode (Tensor -> Feature -> Tensor) + * - FilterEncode followed by FilterDecode (Tensor -> Filter -> Tensor) + * - BiasEncode followed by BiasDecode (Tensor -> Bias -> Tensor) + * - DepthwiseFilterEncode followed by DepthwiseFilterDecode (Tensor -> DepthwiseFilter -> Tensor) + * - MatrixDecode followed by MatrixEncode (Matrix -> Tensor -> Matrix) + * - MatrixEncode followed by MatrixDecode (Tensor -> Matrix -> Tensor) + * - (TO BE ADDED) + */ +struct SimplifyDomainConversionPass final : public Pass +{ + const char *name(void) const final { return "SimplifyDomainConversionPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace logo + +#endif // __LOGO_SIMPLIFY_DOMAIN_CONVERSION_H__ diff --git a/compiler/logo/requires.cmake b/compiler/logo/requires.cmake new file mode 100644 index 00000000000..9a7d1478827 --- /dev/null +++ b/compiler/logo/requires.cmake @@ -0,0 +1,4 @@ +require("loco") +require("logo-core") +require("locomotiv") +require("stdex") diff --git a/compiler/logo/src/Passes/ConstantFoldingPass.cpp b/compiler/logo/src/Passes/ConstantFoldingPass.cpp new file mode 100644 index 00000000000..e038e71405c --- /dev/null +++ b/compiler/logo/src/Passes/ConstantFoldingPass.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include + +#include + +#include + +#include +#include + +namespace +{ + +uint64_t num_elements(const loco::NodeMixin &shape) +{ + if (shape.rank() == 0) + { + return 0; + } + + uint64_t res = 1; + + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + assert(shape.dim(axis).known()); + res *= shape.dim(axis).value(); + } + + return res; +} + +/// @brief For some op, constant folding should not be performed. This returns true if node is such +/// op. +bool skip(const loco::Node *node) +{ + static std::set skip_op = { + // TODO Current implementation works for 'Tensor' domain only. Support other domains such as + // `Feature`, `Filter`, `Bias`, etc. + static_cast(loco::CanonicalOpcode::FilterEncode), + static_cast(loco::CanonicalOpcode::FeatureEncode), + static_cast(loco::CanonicalOpcode::BiasEncode), + static_cast(loco::CanonicalOpcode::DepthwiseFilterEncode), + + // We don't perform constant folding for Push + static_cast(loco::CanonicalOpcode::Push), + + // TensorBroadcast is a good hint for optimization + // TODO Let this option be controlled by driver using logo + static_cast(loco::CanonicalOpcode::TensorBroadcast), + }; + + if (node->dialect() == loco::CanonicalDialect::get()) + { + if (skip_op.find(node->opnum()) != skip_op.end()) + return true; + } + + return false; +} + +/// @brief Checks if a node is a target of constant folding transform +bool foldable(const loco::Node *node) +{ + if (node->dialect() == loco::CanonicalDialect::get()) + { + if (skip(node)) + return false; + + if (node->arity() == 0) // e.g., when a node is e.g, ConstGen or Pull + return false; + + // When all args are ConstGen, let's do Constant Folding Transforms + for (int i = 0; i < node->arity(); i++) + { + if (node->arg(i)->opnum() != static_cast(loco::CanonicalOpcode::ConstGen)) + return false; + } + + return true; + } + else + { + return false; + } +} + +void fold(loco::Graph *graph, loco::Node *node) +{ + assert(foldable(node)); // sanity check to find a mistake when this function is reused later + + // calcluate foldable node + locomotiv::Session sess(graph, std::vector{node}); + sess.infer(); + auto data = sess.get_output(0); + + assert(data != nullptr); + + auto shape = data->shape(); + auto dtype = data->dtype(); + + // build ConstGen + auto new_const = graph->nodes()->create(); + { + new_const->dtype(dtype); + + new_const->rank(shape->rank()); + for (int d = 0; d < shape->rank(); d++) + new_const->dim(d) = shape->dim(d); + + auto count = num_elements(*new_const); + + if (dtype == loco::DataType::FLOAT32) + { + new_const->size(count); + + auto const_buf = data->as_f32_bufptr()->base(); + for (int x = 0; x < count; x++) + new_const->at(x) = const_buf[x]; + } + else if (dtype == loco::DataType::S32) + { + new_const->size(count); + + auto const_buf = data->as_s32_bufptr()->base(); + for (int x = 0; x < count; x++) + new_const->at(x) = const_buf[x]; + } + } + + // replace node with new_const + loco::replace(node).with(new_const); +} + +} // namespace + +namespace logo +{ + +bool ConstantFoldingPass::run(loco::Graph *graph) +{ + auto outputs = loco::output_nodes(graph); + + bool changed = false; + for (auto node : loco::postorder_traversal(outputs)) + { + if (foldable(node)) + { + fold(graph, node); + changed = true; + } + } + + return changed; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/ConstantFoldingPass.test.cpp b/compiler/logo/src/Passes/ConstantFoldingPass.test.cpp new file mode 100644 index 00000000000..8240277627c --- /dev/null +++ b/compiler/logo/src/Passes/ConstantFoldingPass.test.cpp @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include "TestHelper.h" + +#include + +#include + +using namespace logo::test; + +namespace +{ + +/* + test case: + ConstGen ---- Relu ---- Push + (-3.14, 3.14) (0, 3.14) + + after constant folding: + ConstGen ------Push + (0, 3.14) +*/ +void create_net_const_relu(loco::Graph *graph) +{ + assert(graph); + + auto const_node = graph->nodes()->create(); + { + const_node->dtype(loco::DataType::FLOAT32); + const_node->rank(1); + const_node->dim(0) = 2; + const_node->size(2); + const_node->at(0) = -3.14f; + const_node->at(1) = 3.14f; + } + + auto relu_node = graph->nodes()->create(); + { + relu_node->input(const_node); + } + + auto push_node = graph->nodes()->create(); + { + push_node->from(relu_node); + } + + auto graph_output = graph->outputs()->create(); + { + graph_output->name("output"); + graph_output->dtype(loco::DataType::FLOAT32); + loco::link(graph_output, push_node); + } +} + +} // namespace + +TEST(ConstantFolding, const_relu_to_const) +{ + auto graph = loco::make_graph(); + create_net_const_relu(graph.get()); + + logo::ConstantFoldingPass pass; + while (pass.run(graph.get()) == true) + { + ; + } + + auto push = logo::test::find_first_node_by_type(graph.get()); + auto const_gen = dynamic_cast(push->from()); + ASSERT_NE(const_gen, nullptr); + + ASSERT_EQ(const_gen->size(), 2); + ASSERT_EQ(const_gen->at(0), 0); // result of relu(-3.14) + ASSERT_EQ(const_gen->at(1), 3.14f); +} + +namespace +{ + +/* + test case: + ConstGen ---- Relu ---+ + (-1, 1) (0, 1) | + ConstGen ---+-- ConcatV2 ----- Push + (2, 3) | (0, 1, 2, 3) + axis(0) ---+ + + after constant folding: + ConstGen ----- Push + (0, 1, 2, 3) +*/ +void create_net_const_relu_concat(loco::Graph *graph) +{ + assert(graph); + + auto const_1_node = graph->nodes()->create(); + { + const_1_node->dtype(loco::DataType::FLOAT32); + const_1_node->rank(1); + const_1_node->dim(0) = 2; + const_1_node->size(2); + const_1_node->at(0) = -1.0f; + const_1_node->at(1) = 1.0f; + } + + auto relu_node = graph->nodes()->create(); + { + relu_node->input(const_1_node); + } + + auto const_2_node = graph->nodes()->create(); + { + const_2_node->dtype(loco::DataType::FLOAT32); + const_2_node->rank(1); + const_2_node->dim(0) = 2; + const_2_node->size(2); + const_2_node->at(0) = 2.0f; + const_2_node->at(1) = 3.0f; + } + + auto concat_node = graph->nodes()->create(); + { + concat_node->lhs(relu_node); + concat_node->rhs(const_2_node); + concat_node->axis(0); + } + + auto push_node = graph->nodes()->create(); + { + push_node->from(concat_node); + } + + auto graph_output = graph->outputs()->create(); + { + graph_output->name("output"); + graph_output->dtype(loco::DataType::FLOAT32); + loco::link(graph_output, push_node); + } +} + +} // namespace + +TEST(ConstantFolding, const_relu_to_concat) +{ + auto graph = loco::make_graph(); + create_net_const_relu_concat(graph.get()); + + logo::ConstantFoldingPass pass; + while (pass.run(graph.get()) == true) + { + ; + } + + auto push = logo::test::find_first_node_by_type(graph.get()); + auto const_gen = dynamic_cast(push->from()); + ASSERT_NE(const_gen, nullptr); + + ASSERT_EQ(const_gen->size(), 4); + ASSERT_EQ(const_gen->at(0), 0); + ASSERT_EQ(const_gen->at(1), 1); + ASSERT_EQ(const_gen->at(2), 2); + ASSERT_EQ(const_gen->at(3), 3); +} diff --git a/compiler/logo/src/Passes/RemoveDeadNodePass.cpp b/compiler/logo/src/Passes/RemoveDeadNodePass.cpp new file mode 100644 index 00000000000..9b6ed6ab0d3 --- /dev/null +++ b/compiler/logo/src/Passes/RemoveDeadNodePass.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include +#include + +#include + +namespace logo +{ + +bool RemoveDeadNodePass::run(loco::Graph *g) +{ + // Let's enumerate nodes required to compute output nodes + auto active_nodes = loco::active_nodes(loco::output_nodes(g)); + + // Find dead(= non-active) nodes + std::set candidates; + + for (auto node : loco::all_nodes(g)) + { + if (active_nodes.find(node) == active_nodes.end()) + { + candidates.insert(node); + } + } + + // Let's drop the references from each dead node first and then remove these dead nodes + // + // Why? + // + // Let us consider the following example: + // %0 = Pull(...) + // %1 = ConstGen(...) + // %2 = Forward(input: %1) + // %3 = Push(from: %0) <- OUTPUT + // + // Forward (%2) is dead as it does not contribute to the final result (%3). However, it + // refers to another dead node (%1). + // + // This example indicates that naive implementation results in dangling references. + // + // There are two possible solutions: + // 1. Destroy nodes in topological order + // 2. Drop the reference first and then destroy them + // + // The current implementation takes the latter approach for the simplicity of implementation. + for (auto node : candidates) + { + node->drop(); + } + + for (auto node : candidates) + { + g->nodes()->destroy(node); + } + + return candidates.size() > 0; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/RemoveForwardNodePass.cpp b/compiler/logo/src/Passes/RemoveForwardNodePass.cpp new file mode 100644 index 00000000000..c951cfac47c --- /dev/null +++ b/compiler/logo/src/Passes/RemoveForwardNodePass.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include + +#include + +namespace logo +{ + +bool RemoveForwardNodePass::run(loco::Graph *g) +{ + struct Collector final : public loco::CanonicalNodeMutableVisitor + { + void visit(loco::Forward *node) final + { + if (node->input() != nullptr) + { + candidates.insert(node); + } + } + + void visit(loco::Node *) final { return; } + + std::set candidates; + }; + + Collector collector; + + for (auto node : loco::all_nodes(g)) + { + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto canonical_node = dynamic_cast(node); + canonical_node->accept(&collector); + } + } + + for (auto node : collector.candidates) + { + replace(node).with(node->input()); + node->input(nullptr); + } + + return collector.candidates.size() > 0; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/ReorderDecodePass.cpp b/compiler/logo/src/Passes/ReorderDecodePass.cpp new file mode 100644 index 00000000000..724db5780af --- /dev/null +++ b/compiler/logo/src/Passes/ReorderDecodePass.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include + +#include + +#include +#include + +namespace +{ + +bool isTensorBiasAdd(const loco::Node *node) +{ + return node->opnum() == static_cast(loco::CanonicalOpcode::TensorBiasAdd); +} + +bool isReLU(const loco::Node *node) +{ + return node->opnum() == static_cast(loco::CanonicalOpcode::ReLU); +} + +} // namespace + +namespace logo +{ + +bool ReorderDecodePass::run(loco::Graph *g) +{ + std::queue q; + + // Update queue + class Collector final : public loco::CanonicalNodeMutableVisitor + { + public: + Collector(std::queue *out) : _out{out} + { + // DO NOTHING + } + + void visit(loco::FeatureDecode *node) final + { + if (node->input() != nullptr) + { + _out->push(node); + } + } + + void visit(loco::Node *) final { return; } + + private: + // TODO This definition should be revised to support other decode operations + std::queue *_out; + }; + + Collector collector{&q}; + + for (auto node : loco::all_nodes(g)) + { + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto canonical_node = dynamic_cast(node); + canonical_node->accept(&collector); + } + } + + bool changed = false; + + while (!q.empty()) + { + auto cur_decode = q.front(); + q.pop(); + + // Collector IS EXPECTED TO guarantee this property + assert(cur_decode->input() != nullptr); + + for (auto u : loco::succs(cur_decode)) + { + /** + * Let us consider the following graph: + * + * A ---> FeatureDecode(1) ---> ReLU(2) + * + * ReorderDecodeTransform rewrites this graph as follows: + * + * A -+-> FeatureDecode(1) ---> ReLU(2) + * | + * +-> ReLU(2') ---> FeatureDecode(1') + * + * Let us feed this updates graph to ReorderDecodeTransform. + * + * The naive implementation will create a new ReLU->FeatureDecode + * chain again, and results in unbounded graph blow-up. + * + * A -+-> FeatureDeocde(1) ---> ReLU(2) + * | + * +-> ReLU(2') ---> FeatureDecode(1') + * | + * +-> ReLU(2'') ---> FeatureDecode(1'') + * + * This check prevents such unbounded graph blow-up. + */ + if (loco::succs(u).empty()) + { + continue; + } + + // Q. Is it better to create an independent transform for this rewriting rule? + if (isTensorBiasAdd(u)) + { + auto old_badd = dynamic_cast(u); + + assert(old_badd != nullptr); + + /** + * Let us consider the following example: + * + * A -=-> FeatureDecode(1) -+-> TensorBiasAdd(2) -+-> B1 + * | | + * | +-> B2 + * | | + * | +-> ... + * | + * +-> ... + * + * At this point, "cur_decode" points to (1) and "u" points to (2). + * + * First rewrite the graph as follows: + * + * A -+-> FeatureBiasAdd(2') ---> FeatureDecode(1') -+-> B1 + * | | + * | +-> B2 + * | | + * | +-> ... + * | + * +-> FeatureDecode(1) -+-> TensorBiasAdd(2) ; NO USE + * | + * +-> ... + * + * Q. Is it safe to apply this transform without "decoder" check? + */ + auto new_badd = g->nodes()->create(); + auto new_decode = g->nodes()->create(); + + new_badd->value(cur_decode->input()); + new_badd->bias(old_badd->bias()); + + new_decode->input(new_badd); + new_decode->decoder(cur_decode->decoder()->clone()); + + loco::replace(u).with(new_decode); + + // Enque FeatureDeocde(1') for the further optimization. + q.push(new_decode); + + changed = true; + } + } + } + + return changed; +} + +bool ReorderDecodePass::run(loco::Graph *g) +{ + std::queue q; + + // Update queue + class Collector final : public loco::CanonicalNodeMutableVisitor + { + public: + Collector(std::queue *out) : _out{out} + { + // DO NOTHING + } + + void visit(loco::FeatureDecode *node) final + { + if (node->input() != nullptr) + { + _out->push(node); + } + } + + void visit(loco::Node *) final { return; } + + private: + // TODO This definition should be revised to support other decode operations + std::queue *_out; + }; + + Collector collector{&q}; + + for (auto node : loco::all_nodes(g)) + { + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto canonical_node = dynamic_cast(node); + canonical_node->accept(&collector); + } + } + + bool changed = false; + + while (!q.empty()) + { + auto cur_decode = q.front(); + q.pop(); + + // Collector IS EXPECTED TO guarantee this property + assert(cur_decode->input() != nullptr); + + for (auto u : loco::succs(cur_decode)) + { + /** + * Let us consider the following graph: + * + * A ---> FeatureDecode(1) ---> ReLU(2) + * + * ReorderDecodeTransform rewrites this graph as follows: + * + * A -+-> FeatureDecode(1) ---> ReLU(2) + * | + * +-> ReLU(2') ---> FeatureDecode(1') + * + * Let us feed this updates graph to ReorderDecodeTransform. + * + * The naive implementation will create a new ReLU->FeatureDecode + * chain again, and results in unbounded graph blow-up. + * + * A -+-> FeatureDeocde(1) ---> ReLU(2) + * | + * +-> ReLU(2') ---> FeatureDecode(1') + * | + * +-> ReLU(2'') ---> FeatureDecode(1'') + * + * This check prevents such unbounded graph blow-up. + */ + if (loco::succs(u).empty()) + { + continue; + } + + if (isReLU(u)) + { + /** + * Let us consider the following example: + * + * A -=-> FeatureDecode(1) -+-> ReLU(2) -+-> B1 + * | | + * | +-> B2 + * | | + * | +-> ... + * | + * +-> ... + * + * At this point, "cur_decode" points to FeatureDecode(1) and "u" points to ReLU(2). + * + * First rewrite the graph as follows: + * + * A -+-> ReLU(2') ---> FeatureDecode(1') -+-> B1 + * | | + * | +-> B2 + * | | + * | +-> ... + * | + * +-> FeatureDecode -+-> ReLU(2) ; NO USE + * | + * +-> ... + */ + auto new_relu = g->nodes()->create(); + auto new_decode = g->nodes()->create(); + + new_relu->input(cur_decode->input()); + + new_decode->input(new_relu); + new_decode->decoder(cur_decode->decoder()->clone()); + + loco::replace(u).with(new_decode); + + /** + * Enque FeatureDeocde(1') for the further optimization. + */ + q.push(new_decode); + + changed = true; + } + } + } + + return changed; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/ResolveDuplicateReshapePass.cpp b/compiler/logo/src/Passes/ResolveDuplicateReshapePass.cpp new file mode 100644 index 00000000000..d3c74cb7734 --- /dev/null +++ b/compiler/logo/src/Passes/ResolveDuplicateReshapePass.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +#include + +namespace +{ + +/// @return true when 'node' and its input node are both FixedReshapes +bool is_duplicate_reshape(loco::Node *node) +{ + auto node_as_reshape = dynamic_cast(node); + + if (!node_as_reshape) + return false; + + auto input_as_reshape = dynamic_cast(node_as_reshape->input()); + + if (!input_as_reshape) + return false; + + return true; +} + +/** + * @brief Remap reshape's input to its input's input, i.e. bypass input reshape + * + * Before: + * + * In ----- FixedReshape_1 ----- [Out_1]* + * \ + * ------- FixedReshape_2 --- [Out_2]* + * ('reshape' arg) + * + * After: + * + * In ----- FixedReshape_1 ----- [Out_1]* + * \ + * --------------------------- FixedReshape_2 --- [Out_2]* + * + * Note: In case of no Out_1, FixedReshape_1 becomes dead node. + * Out_1 can be another FixedReshape as well, which would be resolved in + * another occurance of this transform pass. + */ +void remap_input(loco::FixedReshape *reshape) +{ + auto input_reshape = dynamic_cast(reshape->input()); + + auto volume = [](loco::FixedReshape *node) { + uint32_t vol = 1; + for (uint32_t axis = 0; axis < node->rank(); ++axis) + { + assert(node->dim(axis).known()); + vol *= node->dim(axis).value(); + } + return vol; + }; + + // Volume mismatch between duplicate reshapes is pointless + assert(volume(reshape) == volume(input_reshape)); + + // Set node's input as input's input, i.e. bypass + reshape->input(input_reshape->input()); +} + +} // namespace + +namespace logo +{ + +bool ResolveDuplicateReshapePass::run(loco::Graph *graph) +{ + auto outputs = loco::output_nodes(graph); + + bool changed = false; + for (auto node : loco::postorder_traversal(outputs)) + { + if (is_duplicate_reshape(node)) + { + auto node_as_reshape = dynamic_cast(node); + + remap_input(node_as_reshape); + + changed = true; + } + } + + return changed; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/ResolveRedundantReshapePass.cpp b/compiler/logo/src/Passes/ResolveRedundantReshapePass.cpp new file mode 100644 index 00000000000..da4af15c1e4 --- /dev/null +++ b/compiler/logo/src/Passes/ResolveRedundantReshapePass.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +#include + +#include + +namespace +{ + +bool shape_inference_done(loco::FixedReshape *reshape) +{ + return loco::shape_known(reshape) && loco::shape_known(reshape->input()); +} + +bool are_same_tensor_shapes(const loco::NodeShape &lhs, const loco::NodeShape &rhs) +{ + assert(lhs.domain() == loco::Domain::Tensor); + assert(rhs.domain() == loco::Domain::Tensor); + + auto lts = lhs.as(); + auto rts = rhs.as(); + + if (lts.rank() != rts.rank()) + return false; + + for (uint32_t axis = 0; axis < lts.rank(); ++axis) + { + assert(lts.dim(axis).known()); + assert(rts.dim(axis).known()); + if (lts.dim(axis).value() != rts.dim(axis).value()) + return false; + } + return true; +} + +/// @return true when 'reshape' has same input and output shape +bool is_redundant_reshape(loco::FixedReshape *reshape) +{ + auto input_shape = loco::shape_get(reshape->input()); + auto output_shape = loco::shape_get(reshape); + + // Note that FixedReshape's input and output are always tensor + return are_same_tensor_shapes(input_shape, output_shape); +} + +} // namespace + +namespace logo +{ + +/** + * @brief Bypass redundant FixedReshape + * + * Before: + * + * In ----- FixedReshape ----- [Out]* + * + * After: + * + * In ------------------------ [Out]* + * \ + * ------ FixedReshape + */ +bool ResolveRedundantReshapePass::run(loco::Graph *graph) +{ + bool changed = false; + for (auto node : loco::postorder_traversal(loco::output_nodes(graph))) + { + if (auto reshape = dynamic_cast(node)) + { + if (shape_inference_done(reshape)) + { + if (is_redundant_reshape(reshape)) + { + replace(reshape).with(reshape->input()); + changed = true; + } + } + } + } + + return changed; +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/SimplifyDomainConversionPass.cpp b/compiler/logo/src/Passes/SimplifyDomainConversionPass.cpp new file mode 100644 index 00000000000..9b7a8d1c7bb --- /dev/null +++ b/compiler/logo/src/Passes/SimplifyDomainConversionPass.cpp @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace +{ + +using namespace loco; + +// TODO Move this helper into loco +bool equal(const Permutation *lhs, const Permutation *rhs) +{ + for (const auto &axis : + {FeatureAxis::Count, FeatureAxis::Depth, FeatureAxis::Height, FeatureAxis::Width}) + { + if (lhs->axis(axis) != rhs->axis(axis)) + { + return false; + } + } + return true; +} + +bool equal(const Permutation *lhs, const Permutation *rhs) +{ + for (const auto &axis : + {FilterAxis::Count, FilterAxis::Depth, FilterAxis::Height, FilterAxis::Width}) + { + if (lhs->axis(axis) != rhs->axis(axis)) + { + return false; + } + } + return true; +} + +bool equal(const Permutation *lhs, + const Permutation *rhs) +{ + for (const auto &axis : {DepthwiseFilterAxis::Depth, DepthwiseFilterAxis::Multiplier, + DepthwiseFilterAxis::Height, DepthwiseFilterAxis::Width}) + { + if (lhs->axis(axis) != rhs->axis(axis)) + { + return false; + } + } + return true; +} + +bool equal(const Permutation *lhs, const Permutation *rhs) +{ + for (const auto &axis : {MatrixAxis::Height, MatrixAxis::Width}) + { + if (lhs->axis(axis) != rhs->axis(axis)) + { + return false; + } + } + return true; +} + +void set_input_null(loco::Node *node) +{ + if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else if (auto casted = dynamic_cast(node)) + casted->input(nullptr); + else + assert(false && "not supported node type"); +} + +} // namespace + +namespace logo +{ + +bool SimplifyDomainConversionPass::run(loco::Graph *g) +{ + // TODO Introduce and Use "Pattern Match" + struct Collector final : public loco::CanonicalNodeMutableVisitor + { + // Let's find FeatureDecode followed by FeatureEncode + void visit(loco::FeatureEncode *encode_node) final + { + using namespace loco; + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decode_node = dynamic_cast(encode_node->input()); + if (decode_node == nullptr) + { + return; + } + assert(decode_node->input() != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({encode_node, decode_node->input()}); + } + } + + // Let's find `FeatureEncode -- FeatureDecode` pattern + void visit(loco::FeatureDecode *decode_node) final + { + using namespace loco; + + auto encode_node = dynamic_cast(decode_node->input()); + if (encode_node == nullptr) + { + return; + } + assert(encode_node->input() != nullptr); + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({decode_node, encode_node->input()}); + } + } + + // Let's find `FilterEncode -- FilterDecode` pattern + void visit(loco::FilterDecode *decode_node) final + { + using namespace loco; + + auto encode_node = dynamic_cast(decode_node->input()); + if (encode_node == nullptr) + { + return; + } + assert(encode_node->input() != nullptr); + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({decode_node, encode_node->input()}); + } + else + { + std::vector perm_vec; + perm_vec.resize(4); + + auto enc_perm = perm_encoder->perm(); + auto dec_perm = perm_decoder->perm(); + + for (const auto &axis : + {FilterAxis::Count, FilterAxis::Height, FilterAxis::Width, FilterAxis::Depth}) + { + auto from = enc_perm->axis(axis); + auto to = dec_perm->axis(axis); + perm_vec[to] = from; + } + + transposeCandidates.insert(stdex::make_unique( + encode_node, decode_node, encode_node->input(), perm_vec)); + } + } + + // Let's find `BiasEncode -- BiasDecode` pattern + void visit(loco::BiasDecode *decode_node) final + { + if (auto encode_node = dynamic_cast(decode_node->input())) + { + assert(encode_node->input() != nullptr); + forwardCandidates.insert({decode_node, encode_node->input()}); + } + } + + // Let's find `DepthwiseFilterEncode -- DepthwiseFilterDecode` pattern + void visit(loco::DepthwiseFilterDecode *decode_node) final + { + using namespace loco; + + auto encode_node = dynamic_cast(decode_node->input()); + if (encode_node == nullptr) + { + return; + } + assert(encode_node->input() != nullptr); + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({decode_node, encode_node->input()}); + } + else + { + std::vector perm_vec; + perm_vec.resize(4); + + auto enc_perm = perm_encoder->perm(); + auto dec_perm = perm_decoder->perm(); + + for (const auto &axis : {DepthwiseFilterAxis::Depth, DepthwiseFilterAxis::Height, + DepthwiseFilterAxis::Width, DepthwiseFilterAxis::Multiplier}) + { + auto from = enc_perm->axis(axis); + auto to = dec_perm->axis(axis); + perm_vec[to] = from; + } + + transposeCandidates.insert(stdex::make_unique( + encode_node, decode_node, encode_node->input(), perm_vec)); + } + } + + // Let's find MatrixDecode followed by MatrixEncode + void visit(loco::MatrixEncode *encode_node) final + { + using namespace loco; + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decode_node = dynamic_cast(encode_node->input()); + if (decode_node == nullptr) + { + return; + } + assert(decode_node->input() != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({encode_node, decode_node->input()}); + } + } + + // Let's find MatrixEncode followed by MatrixDecode + void visit(loco::MatrixDecode *decode_node) final + { + using namespace loco; + + auto encode_node = dynamic_cast(decode_node->input()); + if (encode_node == nullptr) + { + return; + } + assert(encode_node->input() != nullptr); + + auto encoder = encode_node->encoder(); + assert(encoder != nullptr); + + auto decoder = decode_node->decoder(); + assert(decoder != nullptr); + + // NOTE Work only for permuting codec + auto perm_decoder = dynamic_cast *>(decoder); + auto perm_encoder = dynamic_cast *>(encoder); + + if (perm_encoder == nullptr || perm_decoder == nullptr) + { + return; + } + + if (equal(perm_encoder->perm(), perm_decoder->perm())) + { + forwardCandidates.insert({decode_node, encode_node->input()}); + } + else + { + std::vector perm_vec; + perm_vec.resize(2); + + auto enc_perm = perm_encoder->perm(); + auto dec_perm = perm_decoder->perm(); + + for (const auto &axis : {MatrixAxis::Height, MatrixAxis::Width}) + { + auto from = enc_perm->axis(axis); + auto to = dec_perm->axis(axis); + perm_vec[to] = from; + } + + transposeCandidates.insert(stdex::make_unique( + encode_node, decode_node, encode_node->input(), perm_vec)); + } + } + + void visit(loco::Node *) final { return; } + + using SimplifyingInfo = std::pair; + std::set forwardCandidates; + + struct TransposeCtx + { + loco::Node *first_node; // starting node of subgraph that will be replaced + loco::Node *last_node; // end node of subgraph that will be replaced + loco::Node *input_node; // input of subgraph + std::vector perm_vec; // perm vector for transpose + + TransposeCtx(loco::Node *first, loco::Node *last, loco::Node *input, + std::vector perm) + : first_node(first), last_node(last), input_node(input), perm_vec(perm) + { /* empty */ + } + }; + + std::set> transposeCandidates; + }; + + Collector collector; + + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + if (node->dialect() == loco::CanonicalDialect::get()) + { + auto canonical_node = dynamic_cast(node); + canonical_node->accept(&collector); + } + } + + for (auto p : collector.forwardCandidates) + { + auto forward_node = g->nodes()->create(); + forward_node->input(p.second); + replace(p.first).with(forward_node); + set_input_null(p.first); + } + + for (auto &ctx : collector.transposeCandidates) + { + auto transpose_node = g->nodes()->create(); + { + transpose_node->perm()->size(ctx->perm_vec.size()); + + for (loco::TensorAxis axis = 0; axis < ctx->perm_vec.size(); axis++) + transpose_node->perm()->axis(axis) = ctx->perm_vec[axis]; + } + + transpose_node->input(ctx->input_node); + replace(ctx->last_node).with(transpose_node); + set_input_null(ctx->first_node); + } + + return (collector.forwardCandidates.size() > 0 or collector.transposeCandidates.size() > 0); +} + +} // namespace logo diff --git a/compiler/logo/src/Passes/SimplifyDomainConversionPass.test.cpp b/compiler/logo/src/Passes/SimplifyDomainConversionPass.test.cpp new file mode 100644 index 00000000000..6bd93c1b272 --- /dev/null +++ b/compiler/logo/src/Passes/SimplifyDomainConversionPass.test.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include "TestHelper.h" + +#include +#include + +#include + +namespace +{ + +// code borrowed from GraphBlock.h/cpp in exo-tflite +enum class FilterLayout +{ + OHWI, // a.k.a., NHWC, Tensorflow Lite uses this layout + HWIO, // Tensorflow format +}; + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + // Make NHWC permutation for encoder and decoder + loco::Permutation OHWI; // a.k.a., NHWC + + OHWI.axis(loco::FilterAxis::Count) = 0; + OHWI.axis(loco::FilterAxis::Height) = 1; + OHWI.axis(loco::FilterAxis::Width) = 2; + OHWI.axis(loco::FilterAxis::Depth) = 3; + + return OHWI; +} + +template <> loco::Permutation perm() +{ + // Make NHWC permutation for encoder and decoder + loco::Permutation HWIO; + + HWIO.axis(loco::FilterAxis::Height) = 0; + HWIO.axis(loco::FilterAxis::Width) = 1; + HWIO.axis(loco::FilterAxis::Depth) = 2; + HWIO.axis(loco::FilterAxis::Count) = 3; + + return HWIO; +} + +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode) +{ + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode) +{ + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +/* + test case: + ConstGen (2x3x4x5) ---- FeatureEncode ---- FeatureDecode --- Push + 0 H O 0 + 1 W H 1 + 2 I(depth) W 2 + 3 O(count) I 3 + + axis 0 ---------------------> H --------------> H -----------> 1 + axis 1 ---------------------> W --------------> W -----------> 2 + axis 2 ---------------------> I --------------> I -----------> 3 + axis 3 ---------------------> O --------------> O -----------> 0 + + so perm vector of Tranpose = [3, 0, 1, 2] +*/ +void create_net_FilterEncode_FilterDecode_different_perms(loco::Graph *graph) +{ + assert(graph); + + auto const_node = graph->nodes()->create(); + { + const_node->dtype(loco::DataType::FLOAT32); + const_node->rank(4); + int count = 1; + for (int i = 0; i < 4; ++i) + { + const_node->dim(i) = i + 2; + count *= i + 2; + } + const_node->size(count); + for (uint32_t i = 0; i < count; i++) + const_node->at(i) = 3.14f; // any number + } + + auto encoder = make_filter_encode(const_node); + auto decoder = make_filter_decode(encoder); + + auto push_node = graph->nodes()->create(); + { + push_node->from(decoder); + } + + auto graph_output = graph->outputs()->create(); + { + graph_output->name("output"); + graph_output->dtype(loco::DataType::FLOAT32); + loco::link(graph_output, push_node); + } +} + +/* + test case: + ConstGen (2x3x4x5) ---- FeatureEncode ---- FeatureDecode --- Push + 0 H H 0 + 1 W W 1 + 2 I(depth) I 2 + 3 O(count) O 3 + + axis 0 ---------------------> H --------------> H -----------> 0 + axis 1 ---------------------> W --------------> W -----------> 1 + axis 2 ---------------------> I --------------> I -----------> 2 + axis 3 ---------------------> O --------------> O -----------> 3 + + so perm vector of Tranpose = [0, 1, 2, 3] and transposes should be eliminated +*/ +void create_net_FilterEncode_FilterDecode_equal_perms(loco::Graph *graph) +{ + assert(graph); + + auto const_node = graph->nodes()->create(); + { + const_node->dtype(loco::DataType::FLOAT32); + const_node->rank(4); + int count = 1; + for (int i = 0; i < 4; ++i) + { + const_node->dim(i) = i + 2; + count *= i + 2; + } + const_node->size(count); + for (uint32_t i = 0; i < count; i++) + const_node->at(i) = 3.14f; // any number + } + + auto encoder = make_filter_encode(const_node); + auto decoder = make_filter_decode(encoder); + + auto push_node = graph->nodes()->create(); + { + push_node->from(decoder); + } + + auto graph_output = graph->outputs()->create(); + { + graph_output->name("output"); + graph_output->dtype(loco::DataType::FLOAT32); + loco::link(graph_output, push_node); + } +} + +} // namespace + +TEST(SimplifyDomainConversionPass, FilterEncode_FilterDecode_different_perms) +{ + auto graph = loco::make_graph(); + create_net_FilterEncode_FilterDecode_different_perms(graph.get()); + + logo::SimplifyDomainConversionPass pass; + while (pass.run(graph.get()) == true) + ; + + auto tr = logo::test::find_first_node_by_type(graph.get()); + { + ASSERT_EQ(tr->perm()->size(), 4); + ASSERT_EQ(tr->perm()->axis(0), 3); + ASSERT_EQ(tr->perm()->axis(1), 0); + ASSERT_EQ(tr->perm()->axis(2), 1); + ASSERT_EQ(tr->perm()->axis(3), 2); + } + + auto const_gen = dynamic_cast(tr->input()); + ASSERT_NE(const_gen, nullptr); +} + +TEST(SimplifyDomainConversionPass, FilterEncode_FilterDecode_equal_perms) +{ + auto graph = loco::make_graph(); + create_net_FilterEncode_FilterDecode_equal_perms(graph.get()); + + logo::SimplifyDomainConversionPass pass; + while (pass.run(graph.get()) == true) + ; + + ASSERT_EQ(loco::output_nodes(graph.get()).size(), 1); + loco::Node *output_node = loco::output_nodes(graph.get())[0]; + + auto forward = dynamic_cast(output_node->arg(0)); + ASSERT_NE(forward, nullptr); + auto const_gen = dynamic_cast(forward->arg(0)); + ASSERT_NE(const_gen, nullptr); +} diff --git a/compiler/logo/src/TestHelper.h b/compiler/logo/src/TestHelper.h new file mode 100644 index 00000000000..43631efa93d --- /dev/null +++ b/compiler/logo/src/TestHelper.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TEST_HELPER_H__ +#define __TEST_HELPER_H__ + +#include + +namespace logo +{ +namespace test +{ + +template T *find_first_node_by_type(loco::Graph *g) +{ + T *first_node = nullptr; + + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + first_node = dynamic_cast(node); + if (first_node != nullptr) + break; + } + + return first_node; +} + +} // namespace test +} // namespace logo + +#endif // __TEST_HELPER_H__ diff --git a/compiler/luci/CMakeLists.txt b/compiler/luci/CMakeLists.txt new file mode 100644 index 00000000000..387c224878b --- /dev/null +++ b/compiler/luci/CMakeLists.txt @@ -0,0 +1,10 @@ +add_subdirectory(log) +add_subdirectory(lang) +add_subdirectory(service) +add_subdirectory(pass) +add_subdirectory(logex) +add_subdirectory(import) +add_subdirectory(export) +add_subdirectory(tester) + +add_subdirectory(tests) diff --git a/compiler/luci/README.md b/compiler/luci/README.md new file mode 100644 index 00000000000..49c83312109 --- /dev/null +++ b/compiler/luci/README.md @@ -0,0 +1,3 @@ +# luci + +_luci_ provides IR for TFLite/Circle and Graph from FlatBuffer. diff --git a/compiler/luci/export/CMakeLists.txt b/compiler/luci/export/CMakeLists.txt new file mode 100644 index 00000000000..cc76ec65809 --- /dev/null +++ b/compiler/luci/export/CMakeLists.txt @@ -0,0 +1,30 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +# TODO enable tests +#file(GLOB_RECURSE TESTS "src/*.test.cpp") +#list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(luci_export SHARED ${SOURCES}) +target_include_directories(luci_export PRIVATE src) +target_include_directories(luci_export PUBLIC include) +target_link_libraries(luci_export PRIVATE luci_lang) +target_link_libraries(luci_export PRIVATE luci_service) +target_link_libraries(luci_export PRIVATE luci_pass) +target_link_libraries(luci_export PRIVATE mio_circle) +target_link_libraries(luci_export PRIVATE stdex) +target_link_libraries(luci_export PRIVATE luci_log) +target_link_libraries(luci_export PRIVATE luci_logex) +target_link_libraries(luci_export PRIVATE nncc_common) +target_link_libraries(luci_export PRIVATE locop) +target_link_libraries(luci_export PRIVATE oops) +install(TARGETS luci_export DESTINATION lib) + +#if(NOT ENABLE_TEST) +# return() +#endif(NOT ENABLE_TEST) +# +#nnas_find_package(GTest REQUIRED) +# +#GTest_AddTest(luci_export_test ${TESTS}) +#target_include_directories(luci_export_test PRIVATE src) +#target_link_libraries(luci_export_test luci_export) +#target_link_libraries(luci_export_test oops) diff --git a/compiler/luci/export/README.md b/compiler/luci/export/README.md new file mode 100644 index 00000000000..12b190a2f77 --- /dev/null +++ b/compiler/luci/export/README.md @@ -0,0 +1,3 @@ +# luci-export + +_luci-export_ provides exporting _loco_ graph of Circle IR to Circle model file diff --git a/compiler/luci/export/include/luci/CircleExporter.h b/compiler/luci/export/include/luci/CircleExporter.h new file mode 100644 index 00000000000..f260b850402 --- /dev/null +++ b/compiler/luci/export/include/luci/CircleExporter.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_CIRCLEEXPORTER_H__ +#define __LUCI_CIRCLEEXPORTER_H__ + +#include + +#include + +namespace luci +{ + +class CircleExporter +{ +public: + // This contract class describes the interaction between a exporter and its client. + struct Contract + { + public: + virtual ~Contract() = default; + + public: // Client -> Exporter + // Input Graph (to be exported) + // Exporter expects a loco graph that consists of Circle nodes + virtual loco::Graph *graph(void) const = 0; + + public: // Exporter -> Client + // Exporter calls store for export data + // Notice: Please DO NOT STORE ptr and size when implementing this in Client + virtual bool store(const char *ptr, const size_t size) const = 0; + }; + +public: + explicit CircleExporter(); + +public: + // invoke(...) returns false on failure. + bool invoke(Contract *) const; +}; + +} // namespace luci + +#endif // __LUCI_CIRCLEEXPORTER_H__ diff --git a/compiler/luci/export/src/Check.h b/compiler/luci/export/src/Check.h new file mode 100644 index 00000000000..e05ec904aec --- /dev/null +++ b/compiler/luci/export/src/Check.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CHECK_H__ +#define __CHECK_H__ + +#include +#include +#include + +// TODO Add macro for Release version + +#define LUCI_ASSERT(condition, msg) \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[assert failed] " << (msg) << ". " << std::endl; \ + assert((condition)); \ + } \ + } + +#endif // __CHECK_H__ diff --git a/compiler/luci/export/src/CircleExporter.cpp b/compiler/luci/export/src/CircleExporter.cpp new file mode 100644 index 00000000000..f137885f76d --- /dev/null +++ b/compiler/luci/export/src/CircleExporter.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/CircleExporter.h" +#include "CircleExporterImpl.h" + +#include +#include + +#include + +namespace luci +{ + +CircleExporter::CircleExporter() +{ + // NOTHING TO DO +} + +bool CircleExporter::invoke(Contract *contract) const +{ + auto graph = contract->graph(); + if (graph == nullptr) + return false; + + CircleExporterImpl impl(graph); + + const char *ptr = impl.getBufferPointer(); + const size_t size = impl.getBufferSize(); + + // we just send one time + return contract->store(ptr, size); +} + +} // namespace luci diff --git a/compiler/luci/export/src/CircleExporterImpl.cpp b/compiler/luci/export/src/CircleExporterImpl.cpp new file mode 100644 index 00000000000..179b2f4c7e6 --- /dev/null +++ b/compiler/luci/export/src/CircleExporterImpl.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleExporterImpl.h" +#include "Optimize.h" +#include "CircleTensorExporter.h" +#include "CircleOperationExporter.h" +#include "CircleExporterUtils.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + +luci::CircleInput *input_node(loco::Graph *g, const loco::GraphInputIndex &index) +{ + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + if (auto pull = dynamic_cast(g->nodes()->at(n))) + { + if (pull->indexed() && pull->index() == index) + { + return pull; + } + } + } + return nullptr; +} + +luci::CircleOutput *output_node(loco::Graph *g, const loco::GraphOutputIndex &index) +{ + for (uint32_t n = 0; n < g->nodes()->size(); ++n) + { + if (auto push = dynamic_cast(g->nodes()->at(n))) + { + if (push->indexed() && push->index() == index) + { + return push; + } + } + } + return nullptr; +} + +void registerGraphInputTensors(loco::Graph *graph, luci::SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->inputs()->size(); ++n) + { + auto node = input_node(graph, n); + assert(node != nullptr); + ctx._inputs.push_back(luci::get_tensor_index(node)); + } +} + +void registerGraphOutputTensors(loco::Graph *graph, luci::SubGraphContext &ctx) +{ + for (uint32_t n = 0; n < graph->outputs()->size(); ++n) + { + auto push = output_node(graph, n); + assert(push != nullptr); + auto node = push->from(); + assert(node != nullptr); + ctx._outputs.push_back(luci::get_tensor_index(node)); + } +} + +} // namespace + +namespace +{ + +using namespace circle; +using namespace flatbuffers; + +Offset>> +encodeOperatorCodes(FlatBufferBuilder &builder, std::unordered_map &opcodes, + std::unordered_map &custom_opcodes) +{ + std::vector> operator_codes_vec(opcodes.size()); + for (auto it : opcodes) + { + uint32_t idx = it.second; + if (it.first.opcode != BuiltinOperator_CUSTOM) + { + operator_codes_vec[idx] = CreateOperatorCode(builder, it.first.opcode); + } + else // custom op + { + auto opCode = it.first; + auto custom_code = custom_opcodes.find(opCode); + if (custom_code == custom_opcodes.end()) + INTERNAL_EXN("Cannot find code for customop even though opcode is BuiltinOperator_CUSTOM"); + + operator_codes_vec[idx] = + CreateOperatorCode(builder, it.first.opcode, builder.CreateString(custom_code->second)); + } + } + return builder.CreateVector(operator_codes_vec); +} + +} // namespace + +namespace luci +{ + +using namespace circle; +using namespace flatbuffers; + +CircleExporterImpl::CircleExporterImpl(loco::Graph *graph) { exportGraph(graph); } + +::flatbuffers::Offset<::circle::SubGraph> +CircleExporterImpl::exportSubgraph(SerializedModelData &gd) +{ + auto tensors = _builder.CreateVector(gd._tensors); + auto inputs = _builder.CreateVector(gd._inputs); + auto outputs = _builder.CreateVector(gd._outputs); + auto operators = _builder.CreateVector(gd._operators); + auto df = gd._data_format; + auto subgraph = CreateSubGraph(_builder, tensors, inputs, outputs, operators, df); + return subgraph; +} + +void CircleExporterImpl::exportGraph(loco::Graph *graph) +{ + // do graph optimization + optimize(graph); + + _builder.Clear(); + + SerializedModelData gd; + + // This version is taken from comment in fbs + constexpr uint32_t version = 0; + + registerGraphIOName(graph, gd); + + // parse graph into SerializedModelData structure + exportOpDefinedTensors(graph, _builder, gd); + + // NOTE Invoke these register functions only after each node is annotated with its tensor_index + registerGraphInputTensors(graph, gd); + registerGraphOutputTensors(graph, gd); + + exportNodes(graph, _builder, gd); + + // encode operator codes + auto operator_codes = + encodeOperatorCodes(_builder, gd._operator_codes, gd._custom_operator_codes); + + // Subgraphs + Offset subgraph = exportSubgraph(gd); + auto subgraphs = _builder.CreateVector(std::vector>{subgraph}); + + // Description + std::string description_str = "nnpackage"; + auto description = _builder.CreateString(description_str); + + // create array of buffers + auto buffers = _builder.CreateVector(gd._buffers); + + // empty metadata + std::vector metadata_buffer_vec; + auto metadata_buffer = _builder.CreateVector(metadata_buffer_vec); + + // Model + auto model_offset = CreateModel(_builder, version, operator_codes, subgraphs, description, + buffers, metadata_buffer); + FinishModelBuffer(_builder, model_offset); +} + +const char *CircleExporterImpl::getBufferPointer() const +{ + return reinterpret_cast(_builder.GetBufferPointer()); +} + +size_t CircleExporterImpl::getBufferSize() const { return _builder.GetSize(); } + +} // namespace luci diff --git a/compiler/luci/export/src/CircleExporterImpl.h b/compiler/luci/export/src/CircleExporterImpl.h new file mode 100644 index 00000000000..4d00f11468d --- /dev/null +++ b/compiler/luci/export/src/CircleExporterImpl.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_EXPORTER_IMPL_H__ +#define __CIRCLE_EXPORTER_IMPL_H__ + +#include "luci/CircleExporter.h" + +#include + +#include + +namespace luci +{ + +struct SerializedModelData; + +/** + * internal implementation of interface exporter class + */ +class CircleExporterImpl +{ +public: + CircleExporterImpl() = delete; + ~CircleExporterImpl() = default; + + explicit CircleExporterImpl(loco::Graph *graph); + + /** + * @return pointer to buffer with serialized graph + */ + const char *getBufferPointer() const; + + /** + * @return size of buffer with serialized graph + */ + size_t getBufferSize() const; + +private: + /** + * @brief create Subgraph using data stored in SerializedModelData + * @param gd information about serializer parts of model + * @return offset in buffer corresponding to serialized subgraph + */ + flatbuffers::Offset exportSubgraph(SerializedModelData &gd); + + /** + * @brief root function that writes graph into internal buffer + * @param graph + */ + void exportGraph(loco::Graph *graph); + +private: + flatbuffers::FlatBufferBuilder _builder; +}; + +} // namespace luci + +#endif // __CIRCLE_EXPORTER_IMPL_H__ diff --git a/compiler/luci/export/src/CircleExporterUtils.cpp b/compiler/luci/export/src/CircleExporterUtils.cpp new file mode 100644 index 00000000000..3ab4dc29d58 --- /dev/null +++ b/compiler/luci/export/src/CircleExporterUtils.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleExporterUtils.h" + +#include + +namespace luci +{ + +circle::ActivationFunctionType to_circle_actfunc(luci::FusedActFunc func) +{ + switch (func) + { + case luci::FusedActFunc::NONE: + return circle::ActivationFunctionType_NONE; + case luci::FusedActFunc::RELU: + return circle::ActivationFunctionType_RELU; + case luci::FusedActFunc::RELU6: + return circle::ActivationFunctionType_RELU6; + default: + INTERNAL_EXN_V("trying to convert unsupported luci::FusedActFunc", oops::to_uint32(func)); + } +} + +circle::TensorType to_circle_tensortype(loco::DataType type) +{ + switch (type) + { + case loco::DataType::U8: + return circle::TensorType_UINT8; + + case loco::DataType::S8: + return circle::TensorType_INT8; + case loco::DataType::S16: + return circle::TensorType_INT16; + case loco::DataType::S32: + return circle::TensorType_INT32; + case loco::DataType::S64: + return circle::TensorType_INT64; + + case loco::DataType::FLOAT16: + return circle::TensorType_FLOAT16; + case loco::DataType::FLOAT32: + return circle::TensorType_FLOAT32; + + default: + INTERNAL_EXN_V("failed to convert unsupported loco::DataType", oops::to_uint32(type)); + } +} + +} // namespace luci + +namespace luci +{ + +uint32_t SerializedModelData::registerBuiltinOpcode(circle::BuiltinOperator builtin_code) +{ + auto it = _operator_codes.find(OpCode{builtin_code}); + if (it != _operator_codes.end()) + { + return it->second; + } + auto idx = static_cast(_operator_codes.size()); + _operator_codes.emplace(OpCode{builtin_code}, idx); + return idx; +} + +uint32_t SerializedModelData::registerCustomOpcode(const std::string &custom_op) +{ + circle::BuiltinOperator custom_code = circle::BuiltinOperator_CUSTOM; + auto idx = registerBuiltinOpcode(custom_code); + _custom_operator_codes.emplace(OpCode{custom_code}, custom_op); + return idx; +} + +circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm) +{ + // VALID padding + if (pad->top() == 0 && pad->bottom() == 0 && pad->left() == 0 && pad->right() == 0) + return circle::Padding_VALID; + + // SAME padding + // + // For same padding, by definition, following equation should hold: + // O = floor((I - 1) / S) + 1 + // where input size I, output size O, stride S + // + // NOTE input and output 'feature' map are shape of NHWC + bool same_padding_criterion_1 = + (static_cast(ofm._dims[1]) == (ifm._dims[1] - 1) / stride->vertical() + 1) && + (static_cast(ofm._dims[2]) == (ifm._dims[2] - 1) / stride->horizontal() + 1); + + // For same padding, rear padding is same or bigger than front padding by at most 1 + bool same_padding_criterion_2 = + (pad->top() <= pad->bottom()) && (pad->bottom() <= pad->top() + 1) && + (pad->left() <= pad->right()) && (pad->right() <= pad->left() + 1); + + if (same_padding_criterion_1 && same_padding_criterion_2) + return circle::Padding_SAME; + + INTERNAL_EXN("Unsupported padding criteria"); +} + +circle::Padding getOpPadding(const luci::Padding pad) +{ + if (pad == luci::Padding::VALID) + return circle::Padding_VALID; + if (pad == luci::Padding::SAME) + return circle::Padding_SAME; + + INTERNAL_EXN_V("Unsupported luci::Padding", oops::to_uint32(pad)); +} + +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd) +{ + for (uint32_t in = 0; in < graph->inputs()->size(); ++in) + { + auto pull = loco::pull_node(graph, in); + auto name = graph->inputs()->at(in)->name(); + + gd._pull_to_name[pull] = name; + } + for (uint32_t out = 0; out < graph->outputs()->size(); ++out) + { + auto push = loco::push_node(graph, out); + auto name = graph->outputs()->at(out)->name(); + + gd._push_to_name[push] = name; + } + + // TODO set this value properly + gd._data_format = circle::DataFormat::DataFormat_CHANNELS_LAST; +} + +#include + +#include + +namespace +{ + +class CircleTensorIndexAnnotation final : public loco::NodeAnnotation +{ +public: + CircleTensorIndexAnnotation(const CircleTensorIndex &index) : _index{index} + { + // DO NOTHING + } + +public: + const CircleTensorIndex &index(void) const { return _index; } + +private: + CircleTensorIndex _index; +}; + +} // namespace + +void set_tensor_index(loco::Node *node, const CircleTensorIndex &tensor_id) +{ + assert(node->annot() == nullptr); + node->annot(stdex::make_unique(tensor_id)); +} + +CircleTensorIndex get_tensor_index(loco::Node *node) +{ + assert(node->annot() != nullptr); + return node->annot()->index(); +} + +} // namespace luci diff --git a/compiler/luci/export/src/CircleExporterUtils.h b/compiler/luci/export/src/CircleExporterUtils.h new file mode 100644 index 00000000000..e59d8c897e6 --- /dev/null +++ b/compiler/luci/export/src/CircleExporterUtils.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_EXPORTER_UTILS_H__ +#define __CIRCLE_EXPORTER_UTILS_H__ + +#include +#include + +#include + +#include + +#include + +namespace luci +{ + +struct OpCode +{ + circle::BuiltinOperator opcode; + + bool operator==(const OpCode &rhs) const { return opcode == rhs.opcode; } +}; + +circle::ActivationFunctionType to_circle_actfunc(luci::FusedActFunc func); +circle::TensorType to_circle_tensortype(loco::DataType type); + +} // namespace luci + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const luci::OpCode &x) const { return hash()(x.opcode); } +}; + +} // namespace std + +namespace luci +{ + +/** + * @breif Record the information of T/F Lite SubGraph and its mapping to loco + */ +struct SubGraphContext +{ + /// @brief SubGraph input tensor id + std::vector _inputs; + /// @brief SubGraph output tensor id + std::vector _outputs; + /// @DataFormat for SubGraph + circle::DataFormat _data_format{circle::DataFormat::DataFormat_CHANNELS_LAST}; +}; + +// Prerequisites for circle::Model object creation +struct SerializedModelData final : public SubGraphContext +{ + SerializedModelData() = default; + SerializedModelData(const SerializedModelData &) = delete; + + std::unordered_map _operator_codes; + std::unordered_map _custom_operator_codes; + std::vector> _operators; + std::vector> _tensors; + std::vector> _buffers; + + // Graph input and output names + std::unordered_map _pull_to_name; + std::unordered_map _push_to_name; + + /** + * @brief if opcode is not registered in table of opcodes add it + * @param builtin_code + * @return idx of opcode in table of opcodes (see schema) + */ + uint32_t registerBuiltinOpcode(circle::BuiltinOperator builtin_code); + uint32_t registerCustomOpcode(const std::string &custom_op); +}; + +circle::Padding getOpPadding(const loco::Padding2D *pad, const loco::Stride<2> *stride, + const ShapeDescription &ifm, const ShapeDescription &ofm); +circle::Padding getOpPadding(const luci::Padding pad); + +/// @brief Register graph input and output names to SerializedModelData +void registerGraphIOName(loco::Graph *graph, SerializedModelData &gd); + +using CircleTensorIndex = int32_t; + +void set_tensor_index(loco::Node *node, const CircleTensorIndex &tensor_id); +CircleTensorIndex get_tensor_index(loco::Node *node); + +} // namespace luci + +#endif // __CIRCLE_EXPORTER_UTILS_H__ diff --git a/compiler/luci/export/src/CircleOperationExporter.cpp b/compiler/luci/export/src/CircleOperationExporter.cpp new file mode 100644 index 00000000000..48b17659408 --- /dev/null +++ b/compiler/luci/export/src/CircleOperationExporter.cpp @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleOperationExporter.h" +#include "CircleExporterUtils.h" +#include "Check.h" + +#include +#include +#include +#include + +#include +#include + +#include + +using namespace flatbuffers; +using namespace circle; + +namespace +{ + +using namespace luci; + +class OperationExporter final : public luci::CircleNodeMutableVisitor, + public loco::CanonicalNodeMutableVisitor +{ +public: + OperationExporter(FlatBufferBuilder &fbb, SerializedModelData &ctx) : builder{fbb}, gd{ctx} + { + // DO NOTHING + } + +public: + void visit(luci::CircleAdd *) final; + void visit(luci::CircleArgMax *) final; + void visit(luci::CircleAveragePool2D *) final; + void visit(luci::CircleConcatenation *) final; + void visit(luci::CircleConst *) final{/* skip, everything is done in exportOpDefinedTensors */}; + void visit(luci::CircleConv2D *) final; + void visit(luci::CircleDepthwiseConv2D *) final; + void visit(luci::CircleDiv *) final; + void visit(luci::CircleFullyConnected *) final; + void visit(luci::CircleMaximum *) final; + void visit(luci::CircleMaxPool2D *) final; + void visit(luci::CircleMean *) final; + void visit(luci::CircleMul *) final; + void visit(luci::CirclePad *) final; + void visit(luci::CircleRelu *) final; + void visit(luci::CircleRelu6 *) final; + // TODO CircleReshape + void visit(luci::CircleRsqrt *) final; + // TODO CircleSoftmax + void visit(luci::CircleSqrt *) final; + void visit(luci::CircleSquaredDifference *) final; + void visit(luci::CircleSub *) final; + // TODO CircleTanh + void visit(luci::CircleTranspose *) final; + void visit(luci::CircleTransposeConv *) final; + // Circle only + void visit(luci::CircleInstanceNorm *) final; + // Virtual + void visit(luci::CircleInput *) final {} + void visit(luci::CircleOutput *) final {} + +private: + /** + * @brief Exports CircleMaxPool2D or CircleAveragePool2D + * + * @note CirclePool2D should be one of CircleMaxPool2D or CircleAveragePool2D + */ + template + void export_pool_2d(CirclePool2D *node, circle::BuiltinOperator builtin_op); + +private: + FlatBufferBuilder &builder; + SerializedModelData &gd; +}; + +template +void OperationExporter::export_pool_2d(CirclePool2D *node, circle::BuiltinOperator builtin_op) +{ + LUCI_ASSERT(builtin_op == circle::BuiltinOperator_MAX_POOL_2D || + builtin_op == circle::BuiltinOperator_AVERAGE_POOL_2D, + "Should be MaxPool or AvgPool"); + LUCI_ASSERT(node->padding() != luci::Padding::UNDEFINED, "Padding is not set"); + + uint32_t op_idx = gd.registerBuiltinOpcode(builtin_op); + std::vector inputs_vec{get_tensor_index(node->value())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + + circle::Padding padding = getOpPadding(node->padding()); + + auto options = CreatePool2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + node->filter()->w(), node->filter()->h(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Pool2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleAdd *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ADD); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateAddOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_AddOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleArgMax *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_ARG_MAX); + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->dimension())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateArgMaxOptions(builder, to_circle_tensortype(node->output_type())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ArgMaxOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleAveragePool2D *node) +{ + export_pool_2d(node, circle::BuiltinOperator_AVERAGE_POOL_2D); +} + +void OperationExporter::visit(luci::CircleConcatenation *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONCATENATION); + std::vector inputs_vec; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + + for (uint32_t i = 0; i < node->numValues(); ++i) + inputs_vec.push_back(get_tensor_index(node->values(i))); + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateConcatenationOptions(builder, node->axis(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ConcatenationOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = CreateConv2DOptions(builder, padding, node->stride()->w(), node->stride()->h(), + to_circle_actfunc(node->fusedActivationFunction())); + + // Make CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_Conv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleDepthwiseConv2D *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DEPTHWISE_CONV_2D); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->filter()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = CreateDepthwiseConv2DOptions(builder, padding, node->stride()->w(), + node->stride()->h(), node->depthMultiplier(), + to_circle_actfunc(node->fusedActivationFunction())); + + // Make DEPTHWISE_CONV_2D operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DepthwiseConv2DOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleDiv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_DIV); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateDivOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_DivOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleFullyConnected *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_FULLY_CONNECTED); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->weights()), + get_tensor_index(node->bias())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = + CreateFullyConnectedOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + + // Make FULLY_CONNECTED operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_FullyConnectedOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleMaximum *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MAXIMUM); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMaximumMinimumOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MaximumMinimumOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleMaxPool2D *node) +{ + export_pool_2d(node, circle::BuiltinOperator_MAX_POOL_2D); +} + +void OperationExporter::visit(luci::CircleMean *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MEAN); + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->reduction_indices())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateReducerOptions(builder, node->keep_dims()); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_ReducerOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleMul *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_MUL); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateMulOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_MulOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CirclePad *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_PAD); + std::vector inputs_vec{get_tensor_index(node->input()), + get_tensor_index(node->paddings())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreatePadOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_PadOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleRelu *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleRelu6 *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RELU6); + std::vector inputs_vec{get_tensor_index(node->features())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO CircleReshape + +void OperationExporter::visit(luci::CircleRsqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_RSQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +// TODO CircleSoftmax + +void OperationExporter::visit(luci::CircleSqrt *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQRT); + std::vector inputs_vec{get_tensor_index(node->x())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleSquaredDifference *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SQUARED_DIFFERENCE); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSquaredDifferenceOptions(builder); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SquaredDifferenceOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleSub *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_SUB); + std::vector inputs_vec{get_tensor_index(node->x()), get_tensor_index(node->y())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateSubOptions(builder, to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_SubOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +// TODO CircleTanh + +void OperationExporter::visit(luci::CircleTranspose *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE); + std::vector inputs_vec{get_tensor_index(node->arg(0)), get_tensor_index(node->arg(1))}; + std::vector outputs_vec{get_tensor_index(node)}; + + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateTransposeOptions(builder); + + auto op_offset = + CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions::BuiltinOptions_TransposeOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleTransposeConv *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_TRANSPOSE_CONV); + + // Make input, output and options for operator + std::vector inputs_vec{get_tensor_index(node->inputSizes()), + get_tensor_index(node->filter()), + get_tensor_index(node->outBackprop())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + circle::Padding padding = getOpPadding(node->padding()); + auto options = + CreateTransposeConvOptions(builder, padding, node->stride()->w(), node->stride()->h()); + + // Make TRANSPOSE_CONV operator + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_TransposeConvOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void OperationExporter::visit(luci::CircleInstanceNorm *node) +{ + uint32_t op_idx = gd.registerBuiltinOpcode(circle::BuiltinOperator_INSTANCE_NORM); + std::vector inputs_vec{get_tensor_index(node->input()), get_tensor_index(node->gamma()), + get_tensor_index(node->beta())}; + std::vector outputs_vec{get_tensor_index(static_cast(node))}; + auto inputs = builder.CreateVector(inputs_vec); + auto outputs = builder.CreateVector(outputs_vec); + auto options = CreateInstanceNormOptions(builder, node->epsilon(), + to_circle_actfunc(node->fusedActivationFunction())); + auto op_offset = CreateOperator(builder, op_idx, inputs, outputs, + circle::BuiltinOptions_InstanceNormOptions, options.Union()); + gd._operators.push_back(op_offset); +} + +void exportNode(loco::Node *node, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &data) +{ + // TODO Use explicit tagging to prevent possible mistake + auto isNoOp = [](loco::Node *node) { + // If there is only one input and the TensorIndex for the input is same + // as the TensorIndex of the output then this node is just a dummy node + if (node->arity() == 1) + { + assert(node->arg(0) != nullptr); + return get_tensor_index(node) == get_tensor_index(node->arg(0)); + } + return false; + }; + + if (isNoOp(node)) + { + // Skip if a given node is marked as NoOp (op with no effect) before + return; + } + + if (auto circle_node = dynamic_cast(node)) + { + OperationExporter exporter{builder, data}; + circle_node->accept(&exporter); + } + else + { + INTERNAL_EXN("Node with unsupported dialect found"); + } +} + +} // namespace + +namespace luci +{ + +void exportNodes(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + exportNode(node, builder, gd); + } +} + +} // namespace luci diff --git a/compiler/luci/export/src/CircleOperationExporter.h b/compiler/luci/export/src/CircleOperationExporter.h new file mode 100644 index 00000000000..558874d6285 --- /dev/null +++ b/compiler/luci/export/src/CircleOperationExporter.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_OPERATION_EXPORTER_H__ +#define __CIRCLE_OPERATION_EXPORTER_H__ + +#include "CircleExporterUtils.h" + +#include + +namespace luci +{ + +/** + * @brief create Operators corresponding to model nodes + * @param nodes container with nodes + * @param gd information about serializer parts of model + */ +void exportNodes(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, SerializedModelData &gd); + +} // namespace luci + +#endif // __CIRCLE_OPERATION_EXPORTER_H__ diff --git a/compiler/luci/export/src/CircleTensorExporter.cpp b/compiler/luci/export/src/CircleTensorExporter.cpp new file mode 100644 index 00000000000..27a190cd5ad --- /dev/null +++ b/compiler/luci/export/src/CircleTensorExporter.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CircleTensorExporter.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace circle; +using namespace flatbuffers; + +namespace +{ + +using namespace luci; + +class CircleTensoInfo +{ +public: + CircleTensoInfo() = default; + +public: + void name(const std::string &name) { _name = name; } + const std::string &name(void) const { return _name; } + +public: + const circle::TensorType &dtype(void) const { return _dtype; } + void dtype(const circle::TensorType &dtype) { _dtype = dtype; } + + const ShapeDescription &shape(void) const { return _shape; } + void shape(const ShapeDescription &shape) { _shape = shape; } + +public: + luci::CircleConst *content(void) const { return _content; } + void content(luci::CircleConst *c) { _content = c; } + + luci::CircleQuantParam *quantparam(void) const { return _quantparam; } + void quantparam(luci::CircleQuantParam *qp) { _quantparam = qp; } + +private: + std::string _name; + + circle::TensorType _dtype; + ShapeDescription _shape; + + luci::CircleConst *_content = nullptr; + luci::CircleQuantParam *_quantparam = nullptr; +}; + +using CircleTensorContext = std::vector; + +struct NoOpDetector final : public luci::CircleNodeMutableVisitor +{ + // Input is Virtual but does produce a Tensor + // Output is Virtual that does not produce any Tensor + bool visit(luci::CircleOutput *) final { return true; } + + // Return false by default + bool visit(luci::CircleNode *) final { return false; } +}; + +void allocateCircleTensor(CircleNode *node, CircleTensorContext &ctx) +{ + LOGGER(l); + + auto isNoOp = [](loco::Node *node) { + if (auto circle_node = dynamic_cast(node)) + { + NoOpDetector d; + return circle_node->accept(&d); + } + return false; + }; + + if (isNoOp(node)) + { + set_tensor_index(node, get_tensor_index(node->arg(0))); + return; + } + + auto tensor_index = static_cast(ctx.size()); + // TODO Use Graph-level metadata for Input & Output + // auto tensor_name = "t_" + std::to_string(tensor_index); + std::string tensor_name = node->name(); + if (tensor_name.empty()) + tensor_name = "t_" + std::to_string(tensor_index); + INFO(l) << "[luci] Tensor for " << tensor_name << ": " << tensor_index << std::endl; + + CircleTensoInfo tensor_info; + + tensor_info.name(tensor_name); + tensor_info.dtype(TypeInference::get(node)); + tensor_info.shape(ShapeInference::get(node)); + + tensor_info.content(dynamic_cast(node)); + tensor_info.quantparam(node->quantparam()); + + set_tensor_index(node, tensor_index); + + ctx.emplace_back(tensor_info); +} + +} // namespace + +namespace +{ + +flatbuffers::Offset> encodeShape(FlatBufferBuilder &builder, + const ShapeDescription &shape) +{ + assert(shape._rank_known && "unknown number of dimensions is not supported"); + return builder.CreateVector(shape._dims); +} + +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, NodeT *) +{ + return CreateBuffer(builder); +} + +template +flatbuffers::Offset encodeOpBufferByDType(FlatBufferBuilder &builder, + luci::CircleConst *c) +{ + using NativeType = typename loco::DataTypeImpl
::Type; + + std::vector raw_data; + const uint32_t size = c->size
(); + raw_data.reserve(size); + for (uint32_t i = 0; i < size; ++i) + { + raw_data.push_back(c->at
(i)); + } + const size_t raw_size = size * sizeof(NativeType); + auto array_offset = builder.CreateVector(reinterpret_cast(raw_data.data()), raw_size); + return CreateBuffer(builder, array_offset); +} + +template <> +flatbuffers::Offset encodeOpBuffer(FlatBufferBuilder &builder, luci::CircleConst *c) +{ + // TODO use switch + if (c->dtype() == loco::DataType::FLOAT32) + { + return encodeOpBufferByDType(builder, c); + } + else if (c->dtype() == loco::DataType::S32) + { + return encodeOpBufferByDType(builder, c); + } + else if (c->dtype() == loco::DataType::U8) + { + return encodeOpBufferByDType(builder, c); + } + + INTERNAL_EXN_V("Unsupported datatype", oops::to_uint32(c->dtype())); +} + +flatbuffers::Offset +encodeQuantizationParameters(FlatBufferBuilder &builder, luci::CircleQuantParam *quantparam) +{ + if (quantparam == nullptr) + return 0; + + flatbuffers::Offset> min; + flatbuffers::Offset> max; + flatbuffers::Offset> scale; + flatbuffers::Offset> zero_point; + if (quantparam->min.size() && quantparam->max.size()) + { + min = builder.CreateVector(quantparam->min); + max = builder.CreateVector(quantparam->max); + } + if (quantparam->scale.size() && quantparam->zerop.size()) + { + scale = builder.CreateVector(quantparam->scale); + zero_point = builder.CreateVector(quantparam->zerop); + } + return circle::CreateQuantizationParameters(builder, min, max, scale, zero_point); +} + +void exportOpDefinedTensor(const CircleTensoInfo &info, FlatBufferBuilder &builder, + SerializedModelData &gd) +{ + // Create and register output tensor shape + auto shape_offset = encodeShape(builder, info.shape()); + + // encode and register output tensor buffer + auto buffer = + info.content() == nullptr ? encodeOpBuffer(builder) : encodeOpBuffer(builder, info.content()); + + auto quantparam = encodeQuantizationParameters(builder, info.quantparam()); + + auto buffer_id = static_cast(gd._buffers.size()); + gd._buffers.push_back(buffer); + + auto name_offset = builder.CreateString(info.name()); + auto tensor_offset = CreateTensor(builder, shape_offset, info.dtype(), buffer_id, name_offset, + quantparam, /*is_variable*/ false); + gd._tensors.push_back(tensor_offset); +} + +} // namespace + +namespace luci +{ + +void exportOpDefinedTensors(loco::Graph *g, FlatBufferBuilder &builder, SerializedModelData &gd) +{ + CircleTensorContext tensor_ctx; + + for (auto node : loco::postorder_traversal(loco::output_nodes(g))) + { + CircleNode *circle_node = dynamic_cast(node); + allocateCircleTensor(circle_node, tensor_ctx); + } + + // add one empty buffer + // note: this follows TFLite + // note: there's a comment in tflite fbs file + // - Note the 0th entry of this array must be an empty buffer (sentinel). + // - This is a convention so that tensors without a buffer can provide 0 as + // - their buffer. + auto buffer = encodeOpBuffer(builder); + gd._buffers.push_back(buffer); + + for (const auto &tensor_info : tensor_ctx) + { + exportOpDefinedTensor(tensor_info, builder, gd); + } +} + +} // namespace luci diff --git a/compiler/luci/export/src/CircleTensorExporter.h b/compiler/luci/export/src/CircleTensorExporter.h new file mode 100644 index 00000000000..cb4ca28ab65 --- /dev/null +++ b/compiler/luci/export/src/CircleTensorExporter.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CIRCLE_TENSOR_EXPORTER_H__ +#define __CIRCLE_TENSOR_EXPORTER_H__ + +#include "CircleExporterUtils.h" + +#include + +#include + +namespace luci +{ + +/** + * @brief create Tensors corresponding to results of all nodes in graph + * @param computational graph + * @param gd information about serialized parts of model + */ +void exportOpDefinedTensors(loco::Graph *g, flatbuffers::FlatBufferBuilder &builder, + SerializedModelData &gd); + +} // namespace luci + +#endif // __CIRCLE_TENSOR_EXPORTER_H__ diff --git a/compiler/luci/export/src/Optimize.cpp b/compiler/luci/export/src/Optimize.cpp new file mode 100644 index 00000000000..57af61246fd --- /dev/null +++ b/compiler/luci/export/src/Optimize.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Optimize.h" +#include "ProgressReporter.h" + +#include +#include + +#include +#include + +namespace luci +{ + +void optimize(loco::Graph *g) +{ + logo::Phase phase; + { + // prepare type and shape before optimization + phase.emplace_back(stdex::make_unique()); + phase.emplace_back(stdex::make_unique()); + + // TODO add more optimization passes (with a knob) + } + + logo::PhaseRunner phase_runner{g}; + + ProgressReporter prog(g, logo::PhaseStrategy::Restart); + phase_runner.attach(&prog); + phase_runner.run(phase); +} + +} // namespace luci diff --git a/compiler/luci/export/src/Optimize.h b/compiler/luci/export/src/Optimize.h new file mode 100644 index 00000000000..c3af7a04cbc --- /dev/null +++ b/compiler/luci/export/src/Optimize.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __OPTIMIZE_H__ +#define __OPTIMIZE_H__ + +#include + +namespace luci +{ + +/** + * @brief Run passes of graph transformations + * + */ +void optimize(loco::Graph *); + +} // namespace luci + +#endif // __OPTIMIZE_H__ diff --git a/compiler/luci/export/src/ProgressReporter.cpp b/compiler/luci/export/src/ProgressReporter.cpp new file mode 100644 index 00000000000..ac9c3d9a8cf --- /dev/null +++ b/compiler/luci/export/src/ProgressReporter.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "ProgressReporter.h" + +#include "luci/Log.h" +#include "luci/LogHelper.h" + +#include +#include + +#include + +namespace +{ + +char to_char(bool b) { return b ? 'Y' : 'N'; } + +const char *to_str(logo::PhaseStrategy s) +{ + switch (s) + { + case logo::PhaseStrategy::Saturate: + return "Saturate"; + case logo::PhaseStrategy::Restart: + return "Restart"; + } + assert(false); + return ""; +} + +} // namespace + +namespace luci +{ + +void ProgressReporter::notify(const logo::PhaseEventInfo *) +{ + LOGGER(prime); + + INFO(prime) << "=============================================================="; + INFO(prime) << "luci::PhaseRunner<" << to_str(strategy()) << ">"; + INFO(prime) << "Initial graph"; + INFO(prime) << fmt(graph()); +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *) +{ + LOGGER(prime); + + INFO(prime) << "luci::PhaseRunner<" << to_str(strategy()) << "> - done"; +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *info) +{ + LOGGER(prime); + + INFO(prime) << "--------------------------------------------------------------"; + INFO(prime) << "Before " << logo::pass_name(info->pass()); +} + +void ProgressReporter::notify(const logo::PhaseEventInfo *info) +{ + LOGGER(prime); + + INFO(prime) << "After " << logo::pass_name(info->pass()) + << " (changed: " << to_char(info->changed()) << ")"; + INFO(prime) << fmt(graph()); +} + +} // namespace luci diff --git a/compiler/luci/export/src/ProgressReporter.h b/compiler/luci/export/src/ProgressReporter.h new file mode 100644 index 00000000000..e91f42592de --- /dev/null +++ b/compiler/luci/export/src/ProgressReporter.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __PROGRESSREPORTER_H__ +#define __PROGRESSREPORTER_H__ + +#include + +#include + +namespace luci +{ + +class ProgressReporter : public logo::PhaseEventListener +{ +public: + ProgressReporter(loco::Graph *graph, logo::PhaseStrategy strategy) + : _graph{graph}, _strategy{strategy} + { + // DO NOTHING + } + +public: + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + void notify(const logo::PhaseEventInfo *) override; + +public: + loco::Graph *graph(void) const { return _graph; } + logo::PhaseStrategy strategy(void) const { return _strategy; } + +private: + loco::Graph *_graph; + logo::PhaseStrategy _strategy; +}; + +} // namespace luci + +#endif // __PROGRESSREPORTER_H__ diff --git a/compiler/luci/import/CMakeLists.txt b/compiler/luci/import/CMakeLists.txt new file mode 100644 index 00000000000..4faee016bb7 --- /dev/null +++ b/compiler/luci/import/CMakeLists.txt @@ -0,0 +1,27 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(luci_import SHARED ${SOURCES}) +target_include_directories(luci_import PRIVATE src) +target_include_directories(luci_import PUBLIC include) +target_link_libraries(luci_import PUBLIC luci_lang) +target_link_libraries(luci_import PUBLIC mio_circle) +target_link_libraries(luci_import PUBLIC stdex) +target_link_libraries(luci_import PRIVATE luci_log) +target_link_libraries(luci_import PRIVATE luci_logex) +target_link_libraries(luci_import PRIVATE nncc_common) +target_link_libraries(luci_import PRIVATE locop) +target_link_libraries(luci_import PRIVATE oops) +install(TARGETS luci_import DESTINATION lib) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(luci_import_test ${TESTS}) +target_include_directories(luci_import_test PRIVATE src) +target_link_libraries(luci_import_test luci_import) +target_link_libraries(luci_import_test oops) diff --git a/compiler/luci/import/README.md b/compiler/luci/import/README.md new file mode 100644 index 00000000000..4ae81ff67fe --- /dev/null +++ b/compiler/luci/import/README.md @@ -0,0 +1,3 @@ +# luci-import + +_luci-import_ provides importing Circle model file to _loco_ graph of _luci_ Circle Dialect IR diff --git a/compiler/luci/import/include/luci/Import/CircleReader.h b/compiler/luci/import/include/luci/Import/CircleReader.h new file mode 100644 index 00000000000..fdf02b8ca29 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/CircleReader.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_GRAPHREADER_H__ +#define __LUCI_IMPORT_GRAPHREADER_H__ + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace luci +{ + +template std::vector as_index_vector(const flatbuffers::Vector *flat_array) +{ + std::vector ret(flat_array->Length()); + for (uint32_t i = 0; i < flat_array->Length(); i++) + { + ret[i] = flat_array->Get(i); + } + return ret; +} + +bool is_valid(const circle::OperatorCode *opcode); +bool is_custom(const circle::OperatorCode *opcode); +std::string opcode_name(const circle::OperatorCode *opcode); +const char *tensor_type(const circle::Tensor *tensor); +const char *tensor_name(const circle::Tensor *tensor); +const circle::QuantizationParameters *tensor_quantization(const circle::Tensor *tensor); + +loco::DataType luci_datatype(circle::TensorType type); +loco::DataType luci_datatype(const circle::Tensor *tensor); +FusedActFunc luci_actfunc(const circle::ActivationFunctionType type); +Padding luci_padding(const circle::Padding padding); +std::unique_ptr +luci_quantparam(const circle::QuantizationParameters *quantization); + +/** + * @brief Loads Circle file and provides helpers to access attributes + */ +class CircleReader +{ +private: + using CircleSubGraphs_t = flatbuffers::Vector>; + using CircleBuffers_t = flatbuffers::Vector>; + using CircleTensors_t = flatbuffers::Vector>; + using CircleOperators_t = flatbuffers::Vector>; + +public: + CircleReader() = default; + +public: + const std::vector &opcodes() const { return _op_codes; } + const CircleBuffers_t *buffers() const { return _buffers; } + const CircleTensors_t *tensors() const { return _tensors; } + const CircleOperators_t *operators() const { return _operators; } + const std::vector &inputs() const { return _inputs; } + const std::vector &outputs() const { return _outputs; } + + uint32_t num_subgraph() const { return _subgraphs->Length(); } + + size_t buffer_info(uint32_t buf_idx, const uint8_t **buff_data); + circle::BuiltinOperator builtin_code(const circle::Operator *op) const; + std::string opcode_name(const circle::Operator *op) const; + +public: + bool parse(const circle::Model *model); + bool select_subgraph(uint32_t subgraph); + +private: + const circle::Model *_model{nullptr}; + + const CircleSubGraphs_t *_subgraphs{nullptr}; + const CircleBuffers_t *_buffers{nullptr}; + const CircleTensors_t *_tensors{nullptr}; + const CircleOperators_t *_operators{nullptr}; + + std::vector _op_codes; + std::vector _inputs; + std::vector _outputs; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_GRAPHREADER_H__ diff --git a/compiler/luci/import/include/luci/Import/GraphBuilder.h b/compiler/luci/import/include/luci/Import/GraphBuilder.h new file mode 100644 index 00000000000..434ef0885e7 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/GraphBuilder.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_GRAPH_BUILDER_H__ +#define __LUCI_IMPORT_GRAPH_BUILDER_H__ + +#include "GraphBuilderContext.h" + +#include + +namespace luci +{ + +/** + * @brief Interface of convert circle:: NodeDef to loco::Node (e.g., Conv2DGraphBuilder) + */ +class GraphBuilder +{ +public: + virtual bool validate(const circle::Operator *) const = 0; + virtual void build(const circle::Operator *, GraphBuilderContext *) const = 0; + + virtual ~GraphBuilder() {} +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_GRAPH_BUILDER_H__ diff --git a/compiler/luci/import/include/luci/Import/GraphBuilderContext.h b/compiler/luci/import/include/luci/Import/GraphBuilderContext.h new file mode 100644 index 00000000000..6e7fb3f6f33 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/GraphBuilderContext.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_GRAPH_BUILDER_CONTEXT_H__ +#define __LUCI_IMPORT_GRAPH_BUILDER_CONTEXT_H__ + +#include "CircleReader.h" + +#include + +#include + +#include + +namespace luci +{ + +/* + * @brief CircleNode to circle::Operator + * To find circle::Operator from CircleNode + */ +class NodeOpFinder +{ +public: + void enroll(CircleNode *node, const circle::Operator *op); + + const circle::Operator *op(CircleNode *node) const; + +private: + using MapNodeOperator_t = std::map; + + MapNodeOperator_t _table; +}; + +/* + * @brief CircleNode to circle::Tensor + * To find circle::Tensor from CircleNode + */ +class NodeTensorFinder +{ +public: + void enroll(CircleNode *node, const circle::Tensor *tensor); + + const circle::Tensor *tensor(CircleNode *node) const; + +private: + using MapNodeTensor_t = std::map; + + MapNodeTensor_t _table; +}; + +using TensorIndex = int32_t; + +/* + * @brief Tensor Index to CircleNode + * To find CircleNode from TensorIndex + */ +class IndexNodeFinder +{ +public: + void enroll(TensorIndex idx, CircleNode *node); + + CircleNode *node(TensorIndex idx) const; + +private: + using MapIndexNode_t = std::map; + + MapIndexNode_t _table; +}; + +class GraphBuilderContext; + +/** + * @brief Interface to connect the graph + */ +class GraphUpdate +{ +public: + virtual ~GraphUpdate() = default; + +public: + /** + * @brief Do the graph input connections using the SymbolTable + */ + virtual void update(GraphBuilderContext *) = 0; +}; + +/** + * @brief Class to store GraphUpdate objects + */ +class UpdateQueue final +{ +public: + /** + * @brief Registers GraphUpdate objects + */ + void enroll(std::unique_ptr &&update); + +public: + using Queue = std::vector>; + + const Queue &queue() const { return _queue; } + +private: + Queue _queue; +}; + +/** + * @brief Class to store context to build loco graph IR from TensorFlow + */ +class GraphBuilderContext +{ +public: + GraphBuilderContext(loco::Graph *g, CircleReader *reader, NodeOpFinder *nofinder, + NodeTensorFinder *ntfinder, IndexNodeFinder *infinder, UpdateQueue *updates) + : _g(g), _reader(reader), _nodeopfinder(nofinder), _nodetensorfinder(ntfinder), + _indexnodefinder(infinder), _updates(updates) + { + // DO NOTHING + } + + GraphBuilderContext(const GraphBuilderContext &) = delete; + GraphBuilderContext(GraphBuilderContext &&) = delete; + +public: + loco::Graph *graph() { return _g; } + CircleReader *reader() { return _reader; } + + NodeOpFinder *opfinder(void) { return _nodeopfinder; } + NodeTensorFinder *tensorfinder(void) { return _nodetensorfinder; } + IndexNodeFinder *nodefinder(void) { return _indexnodefinder; } + UpdateQueue *updates() { return _updates; } + +private: + loco::Graph *_g; + CircleReader *_reader; + NodeOpFinder *_nodeopfinder; + NodeTensorFinder *_nodetensorfinder; + IndexNodeFinder *_indexnodefinder; + UpdateQueue *_updates; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_GRAPH_BUILDER_CONTEXT_H__ diff --git a/compiler/luci/import/include/luci/Import/GraphBuilderRegistry.h b/compiler/luci/import/include/luci/Import/GraphBuilderRegistry.h new file mode 100644 index 00000000000..99054e7b6ee --- /dev/null +++ b/compiler/luci/import/include/luci/Import/GraphBuilderRegistry.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_GRAPH_BUILDER_REGISTRY_H__ +#define __LUCI_IMPORT_GRAPH_BUILDER_REGISTRY_H__ + +#include "GraphBuilder.h" + +#include + +namespace luci +{ + +struct GraphBuilderSource +{ + virtual ~GraphBuilderSource() = default; + + /** + * @brief Returns registered GraphBuilder pointer for operator (nullptr if not present) + */ + virtual const GraphBuilder *lookup(const circle::BuiltinOperator &op) const = 0; +}; + +/** + * @brief Class to return graph builder for TF nodes + */ +class GraphBuilderRegistry final : public GraphBuilderSource +{ +public: + GraphBuilderRegistry(); + +public: + GraphBuilderRegistry(const GraphBuilderSource *parent) : _parent{parent} + { + // DO NOTHING + } + +public: + /** + * @brief Returns registered GraphBuilder pointer for operator or + * nullptr if not registered + */ + const GraphBuilder *lookup(const circle::BuiltinOperator &op) const final + { + if (_builder_map.find(op) == _builder_map.end()) + return (_parent == nullptr) ? nullptr : _parent->lookup(op); + + return _builder_map.at(op).get(); + } + + static GraphBuilderRegistry &get() + { + static GraphBuilderRegistry me; + return me; + } + +public: + void add(const circle::BuiltinOperator op, std::unique_ptr &&builder) + { + _builder_map[op] = std::move(builder); + } + +private: + const GraphBuilderSource *_parent = nullptr; + +private: + std::map> _builder_map; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_GRAPH_BUILDER_REGISTRY_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes.h b/compiler/luci/import/include/luci/Import/Nodes.h new file mode 100644 index 00000000000..8cfefbd9836 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_NODES_H__ +#define __LUCI_IMPORT_NODES_H__ + +#include "Nodes/CircleAdd.h" +#include "Nodes/CircleArgMax.h" +#include "Nodes/CircleConst.h" +#include "Nodes/CircleConv2D.h" +#include "Nodes/CircleMaxPool2D.h" +#include "Nodes/CircleMean.h" +#include "Nodes/CirclePad.h" +#include "Nodes/CircleReshape.h" + +#endif // __LUCI_IMPORT_NODES_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleAdd.h b/compiler/luci/import/include/luci/Import/Nodes/CircleAdd.h new file mode 100644 index 00000000000..c9683fdab89 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleAdd.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_ADD_H__ +#define __LUCI_IMPORT_OP_CIRCLE_ADD_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleAddGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_ADD_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleArgMax.h b/compiler/luci/import/include/luci/Import/Nodes/CircleArgMax.h new file mode 100644 index 00000000000..a012db49d11 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleArgMax.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_ARGMAX_H__ +#define __LUCI_IMPORT_OP_CIRCLE_ARGMAX_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleArgMaxGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_ARGMAX_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleConst.h b/compiler/luci/import/include/luci/Import/Nodes/CircleConst.h new file mode 100644 index 00000000000..7d4f10a594f --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleConst.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_CONST_H__ +#define __LUCI_IMPORT_OP_CIRCLE_CONST_H__ + +#include "luci/Import/GraphBuilderContext.h" + +#include + +/* + * @note Circle does not have Const operator. + * Methods here provide helper that creates CircleConst from + * Tensor and Buffer in circle flatbuffer file. + */ + +namespace luci +{ + +CircleConst *create_circleconst(GraphBuilderContext *context, int32_t tensor_index); + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_CONST_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleConv2D.h b/compiler/luci/import/include/luci/Import/Nodes/CircleConv2D.h new file mode 100644 index 00000000000..0fa1de914d7 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleConv2D.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_CONV_2D_H__ +#define __LUCI_IMPORT_OP_CIRCLE_CONV_2D_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleConv2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_CONV_2D_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleMaxPool2D.h b/compiler/luci/import/include/luci/Import/Nodes/CircleMaxPool2D.h new file mode 100644 index 00000000000..91ec99b4620 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleMaxPool2D.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_MAXPOOL2D_H__ +#define __LUCI_IMPORT_OP_CIRCLE_MAXPOOL2D_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleMaxPool2DGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_MAXPOOL2D_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleMean.h b/compiler/luci/import/include/luci/Import/Nodes/CircleMean.h new file mode 100644 index 00000000000..271962d44aa --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleMean.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_MEAN_H__ +#define __LUCI_IMPORT_OP_CIRCLE_MEAN_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleMeanGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_MEAN_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CirclePad.h b/compiler/luci/import/include/luci/Import/Nodes/CirclePad.h new file mode 100644 index 00000000000..146046bb786 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CirclePad.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_PAD_H__ +#define __LUCI_IMPORT_OP_CIRCLE_PAD_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CirclePadGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_PAD_H__ diff --git a/compiler/luci/import/include/luci/Import/Nodes/CircleReshape.h b/compiler/luci/import/include/luci/Import/Nodes/CircleReshape.h new file mode 100644 index 00000000000..60d36b75791 --- /dev/null +++ b/compiler/luci/import/include/luci/Import/Nodes/CircleReshape.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORT_OP_CIRCLE_RESHAPE_H__ +#define __LUCI_IMPORT_OP_CIRCLE_RESHAPE_H__ + +#include "luci/Import/GraphBuilder.h" + +namespace luci +{ + +class CircleReshapeGraphBuilder : public GraphBuilder +{ +public: + bool validate(const circle::Operator *) const final; + void build(const circle::Operator *, GraphBuilderContext *) const final; +}; + +} // namespace luci + +#endif // __LUCI_IMPORT_OP_CIRCLE_RESHAPE_H__ diff --git a/compiler/luci/import/include/luci/Importer.h b/compiler/luci/import/include/luci/Importer.h new file mode 100644 index 00000000000..889dbf3a85b --- /dev/null +++ b/compiler/luci/import/include/luci/Importer.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IMPORTER_H__ +#define __LUCI_IMPORTER_H__ + +#include "Import/GraphBuilderRegistry.h" + +#include + +#include + +#include + +namespace luci +{ + +class Importer final +{ +public: + Importer(); + +public: + explicit Importer(const GraphBuilderSource *source) : _source{source} + { + // DO NOTHING + } + +public: + std::unique_ptr import(const circle::Model *model) const; + +private: + const GraphBuilderSource *_source = nullptr; +}; + +} // namespace luci + +#endif // __MOCO_IMPORTER_H__ diff --git a/compiler/luci/import/src/CircleReader.cpp b/compiler/luci/import/src/CircleReader.cpp new file mode 100644 index 00000000000..a78fe090b99 --- /dev/null +++ b/compiler/luci/import/src/CircleReader.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/CircleReader.h" + +#include + +#include +#include + +namespace luci +{ + +bool is_valid(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (circle::BuiltinOperator_MIN <= code && code <= circle::BuiltinOperator_MAX); +} + +bool is_custom(const circle::OperatorCode *opcode) +{ + circle::BuiltinOperator code = opcode->builtin_code(); + return (code == circle::BuiltinOperator_CUSTOM); +} + +std::string opcode_name(const circle::OperatorCode *opcode) +{ + assert(opcode); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid)"; + return oss.str(); + } + + if (is_custom(opcode)) + { + if (!opcode->custom_code()) + return "(invalid custom)"; + + return opcode->custom_code()->c_str(); + } + + circle::BuiltinOperator code = opcode->builtin_code(); + return circle::EnumNameBuiltinOperator(code); +} + +const char *tensor_type(const circle::Tensor *tensor) +{ + return circle::EnumNameTensorType(tensor->type()); +} + +const char *tensor_name(const circle::Tensor *tensor) +{ + static const char *kEmptyTensorName = "(noname)"; + + auto name = tensor->name(); + if (name) + return name->c_str(); + + return kEmptyTensorName; +} + +const circle::QuantizationParameters *tensor_quantization(const circle::Tensor *tensor) +{ + return tensor->quantization(); +} + +loco::DataType luci_datatype(const circle::TensorType type) +{ + switch (type) + { + case circle::TensorType_FLOAT32: + return loco::DataType::FLOAT32; + case circle::TensorType_FLOAT16: + return loco::DataType::FLOAT16; + case circle::TensorType_INT32: + return loco::DataType::S32; + case circle::TensorType_UINT8: + return loco::DataType::U8; + case circle::TensorType_INT64: + return loco::DataType::S64; + case circle::TensorType_STRING: + break; + case circle::TensorType_BOOL: + break; + case circle::TensorType_INT16: + return loco::DataType::S16; + case circle::TensorType_COMPLEX64: + break; + case circle::TensorType_INT8: + return loco::DataType::S8; + } + assert(false); + return loco::DataType::Unknown; +} + +loco::DataType luci_datatype(const circle::Tensor *tensor) +{ + // TODO use luci_datatype(circle::TensorType type) + switch (tensor->type()) + { + case circle::TensorType_FLOAT32: + return loco::DataType::FLOAT32; + case circle::TensorType_FLOAT16: + return loco::DataType::FLOAT16; + case circle::TensorType_INT32: + return loco::DataType::S32; + case circle::TensorType_UINT8: + return loco::DataType::U8; + case circle::TensorType_INT64: + return loco::DataType::S64; + case circle::TensorType_STRING: + break; + case circle::TensorType_BOOL: + break; + case circle::TensorType_INT16: + return loco::DataType::S16; + case circle::TensorType_COMPLEX64: + break; + case circle::TensorType_INT8: + return loco::DataType::S8; + } + assert(false); + return loco::DataType::Unknown; +} + +FusedActFunc luci_actfunc(const circle::ActivationFunctionType type) +{ + switch (type) + { + case circle::ActivationFunctionType::ActivationFunctionType_NONE: + return luci::FusedActFunc::NONE; + case circle::ActivationFunctionType::ActivationFunctionType_RELU: + return luci::FusedActFunc::RELU; + case circle::ActivationFunctionType::ActivationFunctionType_RELU_N1_TO_1: + break; + case circle::ActivationFunctionType::ActivationFunctionType_RELU6: + return luci::FusedActFunc::RELU6; + case circle::ActivationFunctionType::ActivationFunctionType_TANH: + break; + default: + break; + } + assert(false); + return luci::FusedActFunc::UNDEFINED; +} + +Padding luci_padding(const circle::Padding padding) +{ + switch (padding) + { + case circle::Padding::Padding_SAME: + return Padding::SAME; + case circle::Padding::Padding_VALID: + return Padding::VALID; + } + assert(false); + return Padding::UNDEFINED; +} + +std::unique_ptr +luci_quantparam(const circle::QuantizationParameters *quantization) +{ + if ((quantization->min() && quantization->max()) || + (quantization->scale() && quantization->zero_point())) + { + auto quantparam = stdex::make_unique(); + + if (quantization->min()) + quantparam->min = as_index_vector(quantization->min()); + if (quantization->max()) + quantparam->max = as_index_vector(quantization->max()); + + if (quantization->scale()) + quantparam->scale = as_index_vector(quantization->scale()); + if (quantization->zero_point()) + quantparam->zerop = as_index_vector(quantization->zero_point()); + + return std::move(quantparam); + } + + return nullptr; +} + +size_t CircleReader::buffer_info(uint32_t buf_idx, const uint8_t **buff_data) +{ + *buff_data = nullptr; + + if (buf_idx == 0) + return 0; + + if (auto *buffer = (*_buffers)[buf_idx]) + { + if (auto *array = buffer->data()) + { + if (size_t size = array->size()) + { + *buff_data = reinterpret_cast(array->data()); + return size; + } + } + } + + return 0; +} + +circle::BuiltinOperator CircleReader::builtin_code(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + return opcode->builtin_code(); +} + +std::string CircleReader::opcode_name(const circle::Operator *op) const +{ + uint32_t index = op->opcode_index(); + assert(index < _op_codes.size()); + const circle::OperatorCode *opcode = _op_codes.at(index); + + if (!is_valid(opcode)) + { + std::ostringstream oss; + oss << "(invalid: " << index << ")"; + return oss.str(); + } + + return ::luci::opcode_name(opcode); +} + +bool CircleReader::parse(const circle::Model *model) +{ + assert(model != nullptr); + + _model = model; + + _subgraphs = _model->subgraphs(); + _buffers = _model->buffers(); + + auto opcodes = _model->operator_codes(); + for (const ::circle::OperatorCode *opcode : *opcodes) + { + _op_codes.push_back(opcode); + } + + return true; +} + +bool CircleReader::select_subgraph(uint32_t sgindex) +{ + _tensors = nullptr; + _operators = nullptr; + + _inputs.clear(); + _outputs.clear(); + + if (_subgraphs->Length() <= sgindex) + { + assert(false); + return false; + } + + const circle::SubGraph *subgraph = (*_subgraphs)[sgindex]; + + _tensors = subgraph->tensors(); + _operators = subgraph->operators(); + + _inputs = as_index_vector(subgraph->inputs()); + _outputs = as_index_vector(subgraph->outputs()); + + return true; +} + +} // namespace luci diff --git a/compiler/luci/import/src/GraphBuilderContext.cpp b/compiler/luci/import/src/GraphBuilderContext.cpp new file mode 100644 index 00000000000..d9520aad6f8 --- /dev/null +++ b/compiler/luci/import/src/GraphBuilderContext.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/GraphBuilderContext.h" + +#include + +#include + +namespace luci +{ + +void NodeOpFinder::enroll(CircleNode *node, const circle::Operator *op) +{ + assert(_table.find(node) == _table.end()); + + _table[node] = op; +} + +const circle::Operator *NodeOpFinder::op(CircleNode *node) const +{ + MapNodeOperator_t::const_iterator iter = _table.find(node); + + assert(iter != _table.end()); + + return iter->second; +} + +void NodeTensorFinder::enroll(CircleNode *node, const circle::Tensor *tensor) +{ + assert(_table.find(node) == _table.end()); + + _table[node] = tensor; +} + +const circle::Tensor *NodeTensorFinder::tensor(CircleNode *node) const +{ + MapNodeTensor_t::const_iterator iter = _table.find(node); + + assert(iter != _table.end()); + + return iter->second; +} + +void IndexNodeFinder::enroll(TensorIndex idx, CircleNode *node) +{ + if (_table.find(idx) != _table.end()) + { + LOGGER(l); + INFO(l) << "[luci] NodeFinder SKIP (" << idx << ") " << node << std::endl; + return; + } + + _table[idx] = node; +} + +CircleNode *IndexNodeFinder::node(TensorIndex idx) const +{ + MapIndexNode_t::const_iterator iter = _table.find(idx); + + assert(iter != _table.end()); + + return iter->second; +} + +void UpdateQueue::enroll(std::unique_ptr &&update) +{ + _queue.push_back(std::move(update)); +} + +} // namespace luci diff --git a/compiler/luci/import/src/GraphBuilderRegistry.cpp b/compiler/luci/import/src/GraphBuilderRegistry.cpp new file mode 100644 index 00000000000..fc6de6209d4 --- /dev/null +++ b/compiler/luci/import/src/GraphBuilderRegistry.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/GraphBuilderRegistry.h" + +#include "luci/Import/Nodes.h" + +#include + +namespace luci +{ + +GraphBuilderRegistry::GraphBuilderRegistry() +{ + add(circle::BuiltinOperator_ADD, stdex::make_unique()); // 0 + add(circle::BuiltinOperator_ARG_MAX, stdex::make_unique()); // 56 + add(circle::BuiltinOperator_CONV_2D, stdex::make_unique()); // 3 + add(circle::BuiltinOperator_MAX_POOL_2D, stdex::make_unique()); // 17 + add(circle::BuiltinOperator_MEAN, stdex::make_unique()); // 40 + add(circle::BuiltinOperator_PAD, stdex::make_unique()); // 34 + add(circle::BuiltinOperator_RESHAPE, stdex::make_unique()); // 22 + + // BuiltinOperator_AVERAGE_POOL_2D = 1, + // BuiltinOperator_CONCATENATION = 2, + // BuiltinOperator_DEPTHWISE_CONV_2D = 4, + // BuiltinOperator_DEQUANTIZE = 6, + // BuiltinOperator_EMBEDDING_LOOKUP = 7, + // BuiltinOperator_FLOOR = 8, + // BuiltinOperator_FULLY_CONNECTED = 9, + // BuiltinOperator_HASHTABLE_LOOKUP = 10, + // BuiltinOperator_L2_NORMALIZATION = 11, + // BuiltinOperator_L2_POOL_2D = 12, + // BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION = 13, + // BuiltinOperator_LOGISTIC = 14, + // BuiltinOperator_LSH_PROJECTION = 15, + // BuiltinOperator_LSTM = 16, + // BuiltinOperator_MUL = 18, + // BuiltinOperator_RELU = 19, + // BuiltinOperator_RELU_N1_TO_1 = 20, + // BuiltinOperator_RELU6 = 21, + // BuiltinOperator_RESIZE_BILINEAR = 23, + // BuiltinOperator_RNN = 24, + // BuiltinOperator_SOFTMAX = 25, + // BuiltinOperator_SPACE_TO_DEPTH = 26, + // BuiltinOperator_SVDF = 27, + // BuiltinOperator_TANH = 28, + // BuiltinOperator_CONCAT_EMBEDDINGS = 29, + // BuiltinOperator_SKIP_GRAM = 30, + // BuiltinOperator_CALL = 31, + // BuiltinOperator_CUSTOM = 32, + // BuiltinOperator_EMBEDDING_LOOKUP_SPARSE = 33, + // BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_RNN = 35, + // BuiltinOperator_GATHER = 36, + // BuiltinOperator_BATCH_TO_SPACE_ND = 37, + // BuiltinOperator_SPACE_TO_BATCH_ND = 38, + // BuiltinOperator_TRANSPOSE = 39, + // BuiltinOperator_SUB = 41, + // BuiltinOperator_DIV = 42, + // BuiltinOperator_SQUEEZE = 43, + // BuiltinOperator_UNIDIRECTIONAL_SEQUENCE_LSTM = 44, + // BuiltinOperator_STRIDED_SLICE = 45, + // BuiltinOperator_BIDIRECTIONAL_SEQUENCE_RNN = 46, + // BuiltinOperator_EXP = 47, + // BuiltinOperator_TOPK_V2 = 48, + // BuiltinOperator_SPLIT = 49, + // BuiltinOperator_LOG_SOFTMAX = 50, + // BuiltinOperator_DELEGATE = 51, + // BuiltinOperator_BIDIRECTIONAL_SEQUENCE_LSTM = 52, + // BuiltinOperator_CAST = 53, + // BuiltinOperator_PRELU = 54, + // BuiltinOperator_MAXIMUM = 55, + // BuiltinOperator_ARG_MAX = 56, + // BuiltinOperator_MINIMUM = 57, + // BuiltinOperator_LESS = 58, + // BuiltinOperator_NEG = 59, + // BuiltinOperator_PADV2 = 60, + // BuiltinOperator_GREATER = 61, + // BuiltinOperator_GREATER_EQUAL = 62, + // BuiltinOperator_LESS_EQUAL = 63, + // BuiltinOperator_SELECT = 64, + // BuiltinOperator_SLICE = 65, + // BuiltinOperator_SIN = 66, + // BuiltinOperator_TRANSPOSE_CONV = 67, + // BuiltinOperator_SPARSE_TO_DENSE = 68, + // BuiltinOperator_TILE = 69, + // BuiltinOperator_EXPAND_DIMS = 70, + // BuiltinOperator_EQUAL = 71, + // BuiltinOperator_NOT_EQUAL = 72, + // BuiltinOperator_LOG = 73, + // BuiltinOperator_SUM = 74, + // BuiltinOperator_SQRT = 75, + // BuiltinOperator_RSQRT = 76, + // BuiltinOperator_SHAPE = 77, + // BuiltinOperator_POW = 78, + // BuiltinOperator_ARG_MIN = 79, + // BuiltinOperator_FAKE_QUANT = 80, + // BuiltinOperator_REDUCE_PROD = 81, + // BuiltinOperator_REDUCE_MAX = 82, + // BuiltinOperator_PACK = 83, + // BuiltinOperator_LOGICAL_OR = 84, + // BuiltinOperator_ONE_HOT = 85, + // BuiltinOperator_LOGICAL_AND = 86, + // BuiltinOperator_LOGICAL_NOT = 87, + // BuiltinOperator_UNPACK = 88, + // BuiltinOperator_REDUCE_MIN = 89, + // BuiltinOperator_FLOOR_DIV = 90, + // BuiltinOperator_REDUCE_ANY = 91, + // BuiltinOperator_SQUARE = 92, + // BuiltinOperator_ZEROS_LIKE = 93, + // BuiltinOperator_FILL = 94, + // BuiltinOperator_FLOOR_MOD = 95, + // BuiltinOperator_RANGE = 96, + // BuiltinOperator_RESIZE_NEAREST_NEIGHBOR = 97, + // BuiltinOperator_LEAKY_RELU = 98, + // BuiltinOperator_SQUARED_DIFFERENCE = 99, + // BuiltinOperator_MIRROR_PAD = 100, + // BuiltinOperator_ABS = 101, + // BuiltinOperator_SPLIT_V = 102, + // BuiltinOperator_INSTANCE_NORM = 254, +} + +} // namespace luci diff --git a/compiler/luci/import/src/Importer.cpp b/compiler/luci/import/src/Importer.cpp new file mode 100644 index 00000000000..ee88bd2e443 --- /dev/null +++ b/compiler/luci/import/src/Importer.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Importer.h" + +#include "luci/Import/GraphBuilder.h" +#include "luci/Import/GraphBuilderContext.h" +#include "luci/Import/GraphBuilderRegistry.h" +#include "luci/Import/CircleReader.h" + +#include +#include +#include + +#include +#include + +namespace +{ + +void convert_graph(const luci::GraphBuilderSource &source, luci::CircleReader &reader, + loco::Graph *graph) +{ + LOGGER(l); + + auto opfinder = stdex::make_unique(); + auto tensorfinder = stdex::make_unique(); + auto nodefinder = stdex::make_unique(); + auto updates = stdex::make_unique(); + + luci::GraphBuilderContext gb_context(graph, &reader, opfinder.get(), tensorfinder.get(), + nodefinder.get(), updates.get()); + + auto operators = reader.operators(); + auto tensors = reader.tensors(); + + // graph inputs; there are no input nodes in TFlite but just Tensors + // creating virtual input nodes will make possible to connect nodes that uses them + // all attributes of tensor should be copied to CircleInput node + for (const auto input : reader.inputs()) + { + auto input_node = graph->nodes()->create(); + assert(input_node != nullptr); + opfinder->enroll(input_node, nullptr); // there is no Op for graph output + auto tensor = tensors->Get(input); + tensorfinder->enroll(input_node, tensor); + + auto tname = luci::tensor_name(tensor); + input_node->name(tname); + auto quantization = luci::tensor_quantization(tensor); + if (quantization) + { + auto quantparam = luci::luci_quantparam(quantization); + if (quantparam.get()) + input_node->quantparam(std::move(quantparam)); + } + + INFO(l) << "[luci] NodeFinder INPUT(" << input << ") = " << input_node << std::endl; + nodefinder->enroll(input, input_node); + + // Shape of Input + assert(tensor->shape()); + std::vector input_dims = luci::as_index_vector(tensor->shape()); // in NHWC + input_node->rank(input_dims.size()); + for (uint32_t r = 0; r < input_dims.size(); ++r) + input_node->dim(r) = loco::Dimension(input_dims[r]); + + // Data type of Input + auto dtype = luci::luci_datatype(tensor); + input_node->dtype(dtype); + + // Name + auto graph_input = graph->inputs()->create(); + graph_input->name(tname); + + // Set GraphInputOutputIndex for graph + input_node->index(graph_input->index()); + + // Data type + graph_input->dtype(dtype); + } + + for (uint32_t i = 0; i < operators->Length(); ++i) + { + const auto op = operators->Get(i); + circle::BuiltinOperator builtincode = reader.builtin_code(op); + + if (const auto *builder = source.lookup(builtincode)) + { + if (!builder->validate(op)) + { + throw oops::UserExn("Invalid operator", reader.opcode_name(op)); + } + + builder->build(op, &gb_context); + } + else + { + throw oops::UserExn("Not supported", reader.opcode_name(op)); + } + } + + // connect nodes + for (auto &update : updates->queue()) + { + update->update(&gb_context); + } + + // graph outputs + for (auto output : reader.outputs()) + { + auto output_node = graph->nodes()->create(); + assert(output_node != nullptr); + auto node = nodefinder->node(output); + assert(node != nullptr); + output_node->from(node); + + INFO(l) << "[luci] NodeFinder OUTPUT(" << output << ") = " << output_node << std::endl; + + // set the graph output name and node object + auto tensor = tensors->Get(output); + auto graph_output = graph->outputs()->create(); + std::string tname = luci::tensor_name(tensor); + graph_output->name("output_" + tname); + + // Set GraphInputOutputIndex for graph + output_node->index(graph_output->index()); + + // Shape of Output + assert(tensor->shape()); + auto output_shape = stdex::make_unique(); + std::vector output_dims = luci::as_index_vector(tensor->shape()); // in NHWC + output_shape->rank(output_dims.size()); + for (uint32_t r = 0; r < output_dims.size(); ++r) + output_shape->dim(r) = loco::Dimension(output_dims[r]); + graph_output->shape(std::move(output_shape)); + + // Data type + auto dtype = luci::luci_datatype(tensor); + graph_output->dtype(dtype); + } +} + +class ValidateCollector final : public loco::ErrorListener +{ +public: + void notify(const loco::ErrorDetail &d) override + { + LOGGER(l); + INFO(l) << "[luci] GraphValidate error " << d.node() << "(" << d.index() << ")" << std::endl; + } +}; + +} // namespace + +namespace luci +{ + +Importer::Importer() +{ + // DO NOTHING +} + +std::unique_ptr Importer::import(const circle::Model *model) const +{ + auto graph = loco::make_graph(); + + const GraphBuilderSource *source_ptr = &GraphBuilderRegistry::get(); + + if (_source != nullptr) + { + // Use user-defined GraphBuilderSource + source_ptr = _source; + } + + CircleReader reader; + if (!reader.parse(model)) + return nullptr; + + // TODO support multiple subgraph when Circle supports + assert(reader.num_subgraph() == 1); + if (!reader.select_subgraph(0)) + return nullptr; + + // Convert circle::Model to loco::Graph + convert_graph(*source_ptr, reader, graph.get()); + + LOGGER(l); + INFO(l) << fmt(graph.get()); + + assert(loco::valid(graph.get(), stdex::make_unique())); + + return std::move(graph); +} + +} // namespace luci diff --git a/compiler/luci/import/src/Importer.test.cpp b/compiler/luci/import/src/Importer.test.cpp new file mode 100644 index 00000000000..4426e15fdc7 --- /dev/null +++ b/compiler/luci/import/src/Importer.test.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Importer.h" + +#include + +#include + +TEST(TensorFlowLiteImport, Dummy) { luci::Importer import; } diff --git a/compiler/luci/import/src/Nodes/CircleAdd.cpp b/compiler/luci/import/src/Nodes/CircleAdd.cpp new file mode 100644 index 00000000000..dc4b91f19f3 --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleAdd.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleAdd.h" +#include "luci/Import/GraphBuilderContext.h" + +#include +#include + +#include +#include + +#include + +namespace +{ + +using namespace luci; + +class CircleAddGraphUpdate final : public GraphUpdate +{ +public: + CircleAddGraphUpdate(CircleAdd *node) : _node(node) {} + + void update(GraphBuilderContext *) override; + +private: + CircleAdd *_node; +}; + +} // namespace + +namespace luci +{ + +bool CircleAddGraphBuilder::validate(const circle::Operator *op) const +{ + const std::vector &inputs = as_index_vector(op->inputs()); + if (inputs.size() != 2) + return false; + + return true; +} + +void CircleAddGraphBuilder::build(const circle::Operator *op, GraphBuilderContext *context) const +{ + LOGGER(l); + + assert(context != nullptr); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto updates = context->updates(); + + // FlatBuffer contents + auto tensors = reader->tensors(); + + const std::vector &inputs = as_index_vector(op->inputs()); + const std::vector &outputs = as_index_vector(op->outputs()); + + // Add node itself + auto add_node = graph->nodes()->create(); + assert(outputs.size() > 0); + uint32_t output_ti = static_cast(outputs[0]); + auto output_tensor = tensors->Get(output_ti); + auto tname = tensor_name(output_tensor); + add_node->name(tname); + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam.get()) + add_node->quantparam(std::move(quantparam)); + } + opfinder->enroll(add_node, op); + tensorfinder->enroll(add_node, output_tensor); + for (auto output : outputs) + { + INFO(l) << "[luci] NodeFinder add_node(" << output << ") -> " << add_node << std::endl; + nodefinder->enroll(output, add_node); + } + const auto *options = op->builtin_options_as_AddOptions(); + + // Activation + auto actfunctype = luci_actfunc(options->fused_activation_function()); + add_node->fusedActivationFunction(actfunctype); + + // Create GraphUpdate for graph connection for Add node + auto update = stdex::make_unique(add_node); + updates->enroll(std::move(update)); +} + +} // namespace luci + +namespace +{ + +void CircleAddGraphUpdate::update(GraphBuilderContext *context) +{ + auto opfinder = context->opfinder(); + auto nodefinder = context->nodefinder(); + + auto op = opfinder->op(_node); + + // set input 'x, y' + const std::vector &inputs = luci::as_index_vector(op->inputs()); + uint32_t idx_x = static_cast(inputs[0]); + uint32_t idx_y = static_cast(inputs[1]); + auto node_x = nodefinder->node(idx_x); + assert(node_x != nullptr); + auto node_y = nodefinder->node(idx_y); + _node->x(node_x); + _node->y(node_y); +} + +} // namespace diff --git a/compiler/luci/import/src/Nodes/CircleArgMax.cpp b/compiler/luci/import/src/Nodes/CircleArgMax.cpp new file mode 100644 index 00000000000..054a5918c44 --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleArgMax.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleArgMax.h" +#include "luci/Import/Nodes/CircleConst.h" +#include "luci/Import/GraphBuilderContext.h" + +#include +#include + +#include +#include + +#include + +namespace +{ + +using namespace luci; + +class CircleArgMaxGraphUpdate final : public GraphUpdate +{ +public: + CircleArgMaxGraphUpdate(CircleArgMax *node) : _node(node) {} + + void update(GraphBuilderContext *) override; + +private: + CircleArgMax *_node; +}; + +} // namespace + +namespace luci +{ + +bool CircleArgMaxGraphBuilder::validate(const circle::Operator *op) const +{ + const std::vector &inputs = as_index_vector(op->inputs()); + if (inputs.size() != 2) + return false; + + return true; +} + +void CircleArgMaxGraphBuilder::build(const circle::Operator *op, GraphBuilderContext *context) const +{ + LOGGER(l); + + assert(context != nullptr); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto updates = context->updates(); + + // FlatBuffer contents + auto tensors = reader->tensors(); + + const std::vector &inputs = as_index_vector(op->inputs()); + const std::vector &outputs = as_index_vector(op->outputs()); + assert(outputs.size() > 0); + + // ArgMax node itself + auto argmax_node = graph->nodes()->create(); + uint32_t output_ti = static_cast(outputs[0]); + auto output_tensor = tensors->Get(output_ti); + auto tname = tensor_name(output_tensor); + argmax_node->name(tname); + opfinder->enroll(argmax_node, op); + tensorfinder->enroll(argmax_node, output_tensor); + for (auto output : outputs) + { + INFO(l) << "[luci] NodeFinder argmax_node(" << output << ") -> " << argmax_node << std::endl; + nodefinder->enroll(output, argmax_node); + } + const auto *options = op->builtin_options_as_ArgMaxOptions(); + if (options != nullptr) + { + // output_type + auto output_type = luci_datatype(options->output_type()); + argmax_node->output_type(output_type); + } + + // ArgMax dimension tensor + buffer to CircleConst node + uint32_t dimension_ti = static_cast(inputs[1]); + auto dimension_const = create_circleconst(context, dimension_ti); + argmax_node->dimension(dimension_const); + + // Create GraphUpdate for graph connection for Add node + auto update = stdex::make_unique(argmax_node); + updates->enroll(std::move(update)); +} + +} // namespace luci + +namespace +{ + +void CircleArgMaxGraphUpdate::update(GraphBuilderContext *context) +{ + LOGGER(l); + + auto opfinder = context->opfinder(); + auto nodefinder = context->nodefinder(); + + auto op = opfinder->op(_node); + + // set input + const std::vector &inputs = luci::as_index_vector(op->inputs()); + uint32_t idx_0 = static_cast(inputs[0]); + uint32_t idx_1 = static_cast(inputs[1]); + INFO(l) << "[luci] ArgMax update " << idx_0 << ", " << idx_1 << std::endl; + auto node_0 = nodefinder->node(idx_0); + assert(node_0 != nullptr); + auto node_1 = nodefinder->node(idx_1); + (void)node_1; // unused error for release build + assert(node_1 != nullptr); + _node->input(node_0); + assert(_node->dimension() == node_1); +} + +} // namespace diff --git a/compiler/luci/import/src/Nodes/CircleConst.cpp b/compiler/luci/import/src/Nodes/CircleConst.cpp new file mode 100644 index 00000000000..6718e89befe --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleConst.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleConst.h" + +#include +#include + +#include +#include +#include + +#include + +namespace luci +{ + +// +// circleconst_from_tensor() ? +// +CircleConst *create_circleconst(GraphBuilderContext *context, int32_t tensor_index) +{ + LOGGER(l); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto tensors = reader->tensors(); + + // (1) create CircleConst + auto const_node = graph->nodes()->create(); + auto const_tensor = tensors->Get(tensor_index); + opfinder->enroll(const_node, nullptr); + tensorfinder->enroll(const_node, const_tensor); + nodefinder->enroll(tensor_index, const_node); + + INFO(l) << "[luci] NodeFinder const_node(" << tensor_index << ") -> " << const_node << std::endl; + + // (2) set data_type to CircleConst + const_node->dtype(luci_datatype(const_tensor)); + + // (3) set shape to CicleConst + assert(const_tensor->shape()); + std::vector const_dims = as_index_vector(const_tensor->shape()); // in NHWC + const_node->rank(const_dims.size()); + uint32_t num_elements = 1; + for (uint32_t r = 0; r < const_dims.size(); ++r) + { + const_node->dim(r) = loco::Dimension(const_dims[r]); + num_elements = num_elements * const_dims[r]; + } + + // (4) constant values from circle buffer + uint32_t const_buff_idx = const_tensor->buffer(); + const uint8_t *const_buff_data = nullptr; + size_t const_buff_size = reader->buffer_info(const_buff_idx, &const_buff_data); + switch (luci_datatype(const_tensor)) + { + case loco::DataType::FLOAT32: + { + // NOTE assert(const_buff_size == num_elements * sizeof(float)) will drop + // unused variables compilation error in release build. + if (const_buff_size != num_elements * sizeof(float)) + throw oops::UserExn("Invalid Buffer size", "FLOAT32"); + const float *float_cb = reinterpret_cast(const_buff_data); + const_node->size(num_elements); + for (uint32_t ele = 0; ele < num_elements; ++ele) + const_node->at(ele) = float_cb[ele]; + break; + } + + case loco::DataType::U8: + { + if (const_buff_size != num_elements * sizeof(uint8_t)) + throw oops::UserExn("Invalid Buffer size", "UINT8"); + const uint8_t *uint8_cb = reinterpret_cast(const_buff_data); + const_node->size(num_elements); + for (uint32_t ele = 0; ele < num_elements; ++ele) + const_node->at(ele) = uint8_cb[ele]; + break; + } + + case loco::DataType::S32: + { + if (const_buff_size != num_elements * sizeof(int32_t)) + throw oops::UserExn("Invalid Buffer size", "INT32"); + const int32_t *int32_cb = reinterpret_cast(const_buff_data); + const_node->size(num_elements); + for (uint32_t ele = 0; ele < num_elements; ++ele) + const_node->at(ele) = int32_cb[ele]; + break; + } + + default: + assert(false); + } + + return const_node; +} + +} // namespace luci diff --git a/compiler/luci/import/src/Nodes/CircleConv2D.cpp b/compiler/luci/import/src/Nodes/CircleConv2D.cpp new file mode 100644 index 00000000000..05c7f7fa502 --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleConv2D.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleConv2D.h" +#include "luci/Import/Nodes/CircleConst.h" +#include "luci/Import/GraphBuilderContext.h" + +#include +#include +#include + +#include +#include + +#include + +namespace +{ + +using namespace luci; + +class CircleConv2DGraphUpdate final : public GraphUpdate +{ +public: + CircleConv2DGraphUpdate(CircleConv2D *node) : _node(node) {} + + void update(GraphBuilderContext *) override; + +private: + CircleConv2D *_node; +}; + +} // namespace + +namespace luci +{ + +bool CircleConv2DGraphBuilder::validate(const circle::Operator *op) const +{ + // Circle Conv2D may not have a bias but we won't support this + const std::vector &inputs = as_index_vector(op->inputs()); + if (inputs.size() != 3) + return false; + + return true; +} + +void CircleConv2DGraphBuilder::build(const circle::Operator *op, GraphBuilderContext *context) const +{ + LOGGER(l); + + assert(context != nullptr); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto updates = context->updates(); + + // FlatBuffer contents + auto tensors = reader->tensors(); + + const std::vector &inputs = as_index_vector(op->inputs()); + const std::vector &outputs = as_index_vector(op->outputs()); + + // Conv2D node itself + auto conv2d_node = graph->nodes()->create(); + assert(outputs.size() > 0); + uint32_t output_ti = static_cast(outputs[0]); + auto output_tensor = tensors->Get(output_ti); + auto tname = tensor_name(output_tensor); + conv2d_node->name(tname); + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam.get()) + conv2d_node->quantparam(std::move(quantparam)); + } + opfinder->enroll(conv2d_node, op); + tensorfinder->enroll(conv2d_node, output_tensor); + for (auto output : outputs) + { + INFO(l) << "[luci] NodeFinder conv2d_node(" << output << ") -> " << conv2d_node << std::endl; + nodefinder->enroll(output, conv2d_node); + } + // TODO Output Shape ? + + const auto *options = op->builtin_options_as_Conv2DOptions(); + + // Padding + auto padding = luci_padding(options->padding()); + conv2d_node->padding(padding); + + // Stride + conv2d_node->stride()->w(options->stride_w()); + conv2d_node->stride()->h(options->stride_h()); + + // Activation + auto actfunctype = luci_actfunc(options->fused_activation_function()); + conv2d_node->fusedActivationFunction(actfunctype); + + // TODO extract function that returns CircleConst from tensor_index + // Conv2D kernel tensor + buffer to CircleConst node + uint32_t kernel_ti = static_cast(inputs[1]); + auto kernel_const = create_circleconst(context, kernel_ti); + conv2d_node->filter(kernel_const); + + // Conv2D bias tensor + buffer to CircleConst node, if exist + if (inputs.size() == 3) + { + uint32_t bias_ti = static_cast(inputs[2]); + auto bias_const = create_circleconst(context, bias_ti); + conv2d_node->bias(bias_const); + } + else + { + // TODO if we should support without bias, let's implement here + } + + // Create GraphUpdate for graph connection for Conv2D node + auto update = stdex::make_unique(conv2d_node); + updates->enroll(std::move(update)); +} + +} // namespace luci + +namespace +{ + +void CircleConv2DGraphUpdate::update(GraphBuilderContext *context) +{ + auto opfinder = context->opfinder(); + auto nodefinder = context->nodefinder(); + + auto op = opfinder->op(_node); + + // set input 'input' + const std::vector &inputs = luci::as_index_vector(op->inputs()); + uint32_t idx_input = static_cast(inputs[0]); + auto node_input = nodefinder->node(idx_input); + assert(node_input != nullptr); + _node->input(node_input); +} + +} // namespace diff --git a/compiler/luci/import/src/Nodes/CircleMaxPool2D.cpp b/compiler/luci/import/src/Nodes/CircleMaxPool2D.cpp new file mode 100644 index 00000000000..1d361eeb465 --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleMaxPool2D.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleMaxPool2D.h" +#include "luci/Import/GraphBuilderContext.h" + +#include +#include + +#include +#include + +#include + +namespace +{ + +using namespace luci; + +class CircleMaxPool2DGraphUpdate final : public GraphUpdate +{ +public: + CircleMaxPool2DGraphUpdate(CircleMaxPool2D *node) : _node(node) {} + + void update(GraphBuilderContext *) override; + +private: + CircleMaxPool2D *_node; +}; + +} // namespace + +namespace luci +{ + +bool CircleMaxPool2DGraphBuilder::validate(const circle::Operator *op) const +{ + const std::vector &inputs = as_index_vector(op->inputs()); + if (inputs.size() != 1) + return false; + + return true; +} + +void CircleMaxPool2DGraphBuilder::build(const circle::Operator *op, + GraphBuilderContext *context) const +{ + LOGGER(l); + + assert(context != nullptr); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto updates = context->updates(); + + // FlatBuffer contents + auto tensors = reader->tensors(); + + const std::vector &inputs = as_index_vector(op->inputs()); + const std::vector &outputs = as_index_vector(op->outputs()); + + // MaxPool2D node itself + auto maxpool2d_node = graph->nodes()->create(); + assert(outputs.size() > 0); + uint32_t output_ti = static_cast(outputs[0]); + auto output_tensor = tensors->Get(output_ti); + + auto tname = tensor_name(output_tensor); + maxpool2d_node->name(tname); + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam.get()) + maxpool2d_node->quantparam(std::move(quantparam)); + } + + opfinder->enroll(maxpool2d_node, op); + tensorfinder->enroll(maxpool2d_node, output_tensor); + for (auto output : outputs) + { + INFO(l) << "[luci] NodeFinder maxpool2d_node(" << output << ") -> " << maxpool2d_node + << std::endl; + nodefinder->enroll(output, maxpool2d_node); + } + const auto *options = op->builtin_options_as_Pool2DOptions(); + + // Filter + maxpool2d_node->filter()->w(options->filter_width()); + maxpool2d_node->filter()->h(options->filter_height()); + + // Padding + auto padding = luci_padding(options->padding()); + maxpool2d_node->padding(padding); + + // Stride + maxpool2d_node->stride()->w(options->stride_w()); + maxpool2d_node->stride()->h(options->stride_h()); + + // Activation + auto actfunctype = luci_actfunc(options->fused_activation_function()); + maxpool2d_node->fusedActivationFunction(actfunctype); + + // Create GraphUpdate for graph connection for MaxPool2D node + auto update = stdex::make_unique(maxpool2d_node); + updates->enroll(std::move(update)); +} + +} // namespace luci + +namespace +{ + +void CircleMaxPool2DGraphUpdate::update(GraphBuilderContext *context) +{ + auto opfinder = context->opfinder(); + auto nodefinder = context->nodefinder(); + + auto op = opfinder->op(_node); + + // set input 'value' + const std::vector &inputs = luci::as_index_vector(op->inputs()); + uint32_t idx_value = static_cast(inputs[0]); + auto node_value = nodefinder->node(idx_value); + assert(node_value != nullptr); + _node->value(node_value); +} + +} // namespace diff --git a/compiler/luci/import/src/Nodes/CircleMean.cpp b/compiler/luci/import/src/Nodes/CircleMean.cpp new file mode 100644 index 00000000000..6749e0f162a --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleMean.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleMean.h" +#include "luci/Import/GraphBuilderContext.h" + +#include + +#include +#include + +#include + +namespace luci +{ + +bool CircleMeanGraphBuilder::validate(const circle::Operator *op) const +{ + const auto &inputs = *op->inputs(); + + if (inputs.size() != 2) + return false; + + return true; +} + +void CircleMeanGraphBuilder::build(const circle::Operator *op, GraphBuilderContext *context) const +{ + auto graph = context->graph(); + auto reader = context->reader(); + auto nodefinder = context->nodefinder(); + + auto tensors = reader->tensors(); + const auto &inputs = *op->inputs(); + const auto &outputs = *op->outputs(); + + assert(outputs.size() == 1); + const circle::Tensor *output_tensor = tensors->Get(outputs[0]); + + // Create the node. + auto mean_node = graph->nodes()->create(); + mean_node->name(tensor_name(output_tensor)); + + // Set node's quantization parameters, if any. + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam) + mean_node->quantparam(std::move(quantparam)); + } + + // input + CircleNode *input_node = nodefinder->node(inputs[0]); + assert(input_node != nullptr); + mean_node->input(input_node); + + // reduction indices + CircleNode *reduction_insices_node = nodefinder->node(inputs[1]); + assert(reduction_insices_node != nullptr); + mean_node->reduction_indices(reduction_insices_node); + + // Configure options. + const auto *options = op->builtin_options_as_ReducerOptions(); + mean_node->keep_dims(options->keep_dims()); + + // Register node's only output. + nodefinder->enroll(outputs[0], mean_node); +} + +} // namespace luci diff --git a/compiler/luci/import/src/Nodes/CirclePad.cpp b/compiler/luci/import/src/Nodes/CirclePad.cpp new file mode 100644 index 00000000000..eb6baf9bb3a --- /dev/null +++ b/compiler/luci/import/src/Nodes/CirclePad.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CirclePad.h" +#include "luci/Import/Nodes/CircleConst.h" +#include "luci/Import/GraphBuilderContext.h" + +#include +#include + +#include +#include + +#include + +namespace +{ + +using namespace luci; + +class CirclePadGraphUpdate final : public GraphUpdate +{ +public: + CirclePadGraphUpdate(CirclePad *node) : _node(node) {} + + void update(GraphBuilderContext *) override; + +private: + CirclePad *_node; +}; + +} // namespace + +namespace luci +{ + +bool CirclePadGraphBuilder::validate(const circle::Operator *op) const +{ + const std::vector &inputs = as_index_vector(op->inputs()); + if (inputs.size() != 2) + return false; + + // TODO do attribute checks + + return true; +} + +void CirclePadGraphBuilder::build(const circle::Operator *op, GraphBuilderContext *context) const +{ + LOGGER(l); + + assert(context != nullptr); + + auto graph = context->graph(); + auto reader = context->reader(); + auto opfinder = context->opfinder(); + auto tensorfinder = context->tensorfinder(); + auto nodefinder = context->nodefinder(); + auto updates = context->updates(); + + // FlatBuffer contents + auto tensors = reader->tensors(); + + const std::vector &inputs = as_index_vector(op->inputs()); + const std::vector &outputs = as_index_vector(op->outputs()); + + // Pad node itself + auto pad_node = graph->nodes()->create(); + assert(outputs.size() > 0); + uint32_t output_ti = static_cast(outputs[0]); + auto output_tensor = tensors->Get(output_ti); + + // name + auto tname = tensor_name(output_tensor); + pad_node->name(tname); + + // quantization + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam.get()) + pad_node->quantparam(std::move(quantparam)); + } + + opfinder->enroll(pad_node, op); + tensorfinder->enroll(pad_node, output_tensor); + for (auto output : outputs) + { + INFO(l) << "[luci] NodeFinder pad_node(" << output << ") -> " << pad_node << std::endl; + nodefinder->enroll(output, pad_node); + } + + // There's no options to read for Pad + + // paddings Const + uint32_t paddings_ti = static_cast(inputs[1]); + auto paddings_const = create_circleconst(context, paddings_ti); + pad_node->paddings(paddings_const); + + // Create GraphUpdate for graph connection for Pad node + auto update = stdex::make_unique(pad_node); + updates->enroll(std::move(update)); +} + +} // namespace luci + +namespace +{ + +void CirclePadGraphUpdate::update(GraphBuilderContext *context) +{ + auto opfinder = context->opfinder(); + auto nodefinder = context->nodefinder(); + + auto op = opfinder->op(_node); + + // set input 'input, paddings' + const std::vector &inputs = luci::as_index_vector(op->inputs()); + uint32_t idx_input = static_cast(inputs[0]); + auto node_input = nodefinder->node(idx_input); + assert(node_input != nullptr); + _node->input(node_input); + // paddings CircleConst is created in build() and should not be null + assert(_node->paddings() != nullptr); +} + +} // namespace diff --git a/compiler/luci/import/src/Nodes/CircleReshape.cpp b/compiler/luci/import/src/Nodes/CircleReshape.cpp new file mode 100644 index 00000000000..e7e05f81399 --- /dev/null +++ b/compiler/luci/import/src/Nodes/CircleReshape.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Import/Nodes/CircleReshape.h" +#include "luci/Import/GraphBuilderContext.h" + +#include + +#include +#include + +#include + +namespace luci +{ + +bool CircleReshapeGraphBuilder::validate(const circle::Operator *op) const +{ + const auto &inputs = *op->inputs(); + const auto &outputs = *op->outputs(); + + if (inputs.size() != 1 && inputs.size() != 2) + return false; + + if (outputs.size() != 1) + return false; + + return true; +} + +void CircleReshapeGraphBuilder::build(const circle::Operator *op, + GraphBuilderContext *context) const +{ + auto graph = context->graph(); + auto reader = context->reader(); + auto nodefinder = context->nodefinder(); + + auto tensors = reader->tensors(); + const auto &inputs = *op->inputs(); + const auto &outputs = *op->outputs(); + + assert(outputs.size() == 1); + const circle::Tensor *output_tensor = tensors->Get(outputs[0]); + + // Create the node. + auto reshape_node = graph->nodes()->create(); + reshape_node->name(tensor_name(output_tensor)); + + // Set node's quantization parameters, if any. + auto quantization = tensor_quantization(output_tensor); + if (quantization) + { + auto quantparam = luci_quantparam(quantization); + if (quantparam) + reshape_node->quantparam(std::move(quantparam)); + } + + // Set node's inputs. There may be one or two, but the IR requires 2 atm. + assert(inputs.size() == 1 || inputs.size() == 2); + if (inputs.size() != 2) + throw oops::UserExn("Unsupported number of inputs", inputs.size()); + + CircleNode *tensor_node = nodefinder->node(inputs[0]); + assert(tensor_node != nullptr); + reshape_node->tensor(tensor_node); + + CircleNode *shape_node = nodefinder->node(inputs[1]); + assert(shape_node != nullptr); + reshape_node->shape(shape_node); + + // Configure options. + const circle::ReshapeOptions *options = op->builtin_options_as_ReshapeOptions(); + const auto &new_shape = *options->new_shape(); + reshape_node->newShape()->rank(new_shape.size()); + for (uint32_t i = 0; i < new_shape.size(); ++i) + reshape_node->newShape()->dim(i) = new_shape[i]; + + // Register node's only output. + nodefinder->enroll(outputs[0], reshape_node); +} + +} // namespace luci diff --git a/compiler/luci/lang/CMakeLists.txt b/compiler/luci/lang/CMakeLists.txt new file mode 100644 index 00000000000..3c96a40bc10 --- /dev/null +++ b/compiler/luci/lang/CMakeLists.txt @@ -0,0 +1,23 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(luci_lang SHARED ${SOURCES}) +target_include_directories(luci_lang PRIVATE src) +target_include_directories(luci_lang PUBLIC include) +target_link_libraries(luci_lang PUBLIC loco) +target_link_libraries(luci_lang PRIVATE nncc_common) +target_link_libraries(luci_lang PRIVATE stdex) +target_link_libraries(luci_lang PRIVATE oops) + +install(TARGETS luci_lang DESTINATION lib) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(luci_lang_test ${TESTS}) +target_include_directories(luci_lang_test PRIVATE src) +target_link_libraries(luci_lang_test luci_lang) diff --git a/compiler/luci/lang/README.md b/compiler/luci/lang/README.md new file mode 100644 index 00000000000..ea0e3d5da24 --- /dev/null +++ b/compiler/luci/lang/README.md @@ -0,0 +1,3 @@ +# luci-lang + +`luci-lang` provides TensorFlow Lite and Circle Dialect IR diff --git a/compiler/luci/lang/include/luci/IR/AttrFilter.h b/compiler/luci/lang/include/luci/IR/AttrFilter.h new file mode 100644 index 00000000000..7909fa523d7 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/AttrFilter.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_ATTRFILTER_H__ +#define __LUCI_IR_ATTRFILTER_H__ + +#include + +namespace luci +{ + +class Filter final +{ +public: + Filter() : _w(1), _h(1) {} + + int32_t w() const { return _w; } + void w(int32_t w) { _w = w; } + + int32_t h() const { return _h; } + void h(int32_t h) { _h = h; } + +private: + int32_t _w; + int32_t _h; +}; + +} // namespace luci + +#endif // __LUCI_IR_ATTRFILTER_H__ diff --git a/compiler/luci/lang/include/luci/IR/AttrFusedActFunc.h b/compiler/luci/lang/include/luci/IR/AttrFusedActFunc.h new file mode 100644 index 00000000000..ab93b7fc527 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/AttrFusedActFunc.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_ATTRFUSEDACTFUNC_H__ +#define __LUCI_IR_ATTRFUSEDACTFUNC_H__ + +namespace luci +{ + +// TODO Divide into TFL version and Circle version when they go different approach +enum class FusedActFunc +{ + UNDEFINED, // This is not defined by TFLite or Circle. This was added to + // prevent programming error. + NONE, + RELU, + RELU6 +}; + +} // namespace luci + +#endif // __LUCI_IR_ATTRFUSEDACTFUNC_H__ diff --git a/compiler/luci/lang/include/luci/IR/AttrPadding.h b/compiler/luci/lang/include/luci/IR/AttrPadding.h new file mode 100644 index 00000000000..5c295e0cdb8 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/AttrPadding.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_ATTRPADDING_H__ +#define __LUCI_IR_ATTRPADDING_H__ + +namespace luci +{ + +enum class Padding +{ + UNDEFINED, // This is not defined by TFLite. This was added to prevent programming error. + + SAME, + VALID, +}; + +} // namespace luci + +#endif // __LUCI_IR_ATTRPADDING_H__ diff --git a/compiler/luci/lang/include/luci/IR/AttrStride.h b/compiler/luci/lang/include/luci/IR/AttrStride.h new file mode 100644 index 00000000000..654967d73ec --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/AttrStride.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_ATTRSTRIDE_H__ +#define __LUCI_IR_ATTRSTRIDE_H__ + +#include + +namespace luci +{ + +class Stride final +{ +public: + Stride() : _w(1), _h(1) {} + + int32_t w() const { return _w; } + void w(int32_t w) { _w = w; } + + int32_t h() const { return _h; } + void h(int32_t h) { _h = h; } + +private: + int32_t _w; + int32_t _h; +}; + +} // namespace luci + +#endif // __LUCI_IR_ATTRSTRIDE_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleDialect.h b/compiler/luci/lang/include/luci/IR/CircleDialect.h new file mode 100644 index 00000000000..1b25dc9c20b --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleDialect.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEDIALECT_H__ +#define __LUCI_IR_CIRCLEDIALECT_H__ + +#include + +namespace luci +{ + +/** + * @brief A singleton for Circle Dialect + */ +class CircleDialect final : public loco::Dialect +{ +private: + CircleDialect(); + +public: + CircleDialect(const CircleDialect &) = delete; + CircleDialect(CircleDialect &&) = delete; + +public: + static loco::Dialect *get(void); +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEDIALECT_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNode.h b/compiler/luci/lang/include/luci/IR/CircleNode.h new file mode 100644 index 00000000000..92816ef04f8 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNode.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODE_H__ +#define __LUCI_IR_CIRCLENODE_H__ + +#include "CircleNodeDecl.h" +#include "CircleNodeImpl.h" + +#endif // __LUCI_IR_CIRCLENODE_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodeDecl.h b/compiler/luci/lang/include/luci/IR/CircleNodeDecl.h new file mode 100644 index 00000000000..305c888d760 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodeDecl.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODEDECL_H__ +#define __LUCI_IR_CIRCLENODEDECL_H__ + +#include +#include + +#include "CircleOpcode.h" +#include "CircleNodeVisitor.forward.h" +#include "CircleQuantParam.h" + +#include + +namespace luci +{ + +using NodeName = std::string; + +struct CircleNode : public loco::Node +{ + virtual ~CircleNode() = default; + + const loco::Dialect *dialect(void) const final; + virtual CircleOpcode opcode(void) const = 0; + + template T accept(CircleNodeVisitorBase *) const; + template T accept(CircleNodeMutableVisitorBase *); + + NodeName name(void) const { return _name; } + void name(const NodeName &name) { _name = name; } + + CircleQuantParam *quantparam(void) { return _quantparam.get(); } + void quantparam(std::unique_ptr &&quantparam) + { + _quantparam = std::move(quantparam); + } + +private: + NodeName _name; + std::unique_ptr _quantparam; +}; + +template struct CircleNodeImpl : public CircleNode +{ + virtual ~CircleNodeImpl() = default; + + uint32_t opnum(void) const final { return static_cast(Code); } + CircleOpcode opcode(void) const final { return Code; } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLENODEDECL_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodeImpl.h b/compiler/luci/lang/include/luci/IR/CircleNodeImpl.h new file mode 100644 index 00000000000..bdcfc9c9d51 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodeImpl.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODEIMPL_H__ +#define __LUCI_IR_CIRCLENODEIMPL_H__ + +#include "CircleNodes.h" +#include "CircleNodeVisitor.h" + +#include + +#include + +namespace luci +{ + +template T CircleNode::accept(CircleNodeVisitorBase *v) const +{ + switch (this->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + \ + case CircleOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + default: + break; + } + + INTERNAL_EXN("CircleNode::accept(CircleNodeVisitorBase) not handled"); +} + +template T CircleNode::accept(CircleNodeMutableVisitorBase *v) +{ + switch (this->opcode()) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + \ + case CircleOpcode::OPCODE: \ + return v->visit(dynamic_cast(this)); + +#include "CircleNodes.lst" +#undef CIRCLE_NODE + + default: + break; + } + + INTERNAL_EXN("CircleNode::accept(CircleNodeMutableVisitorBase) not handled"); +} + +} // namespace luci + +#endif // __LUCI_IR_CIRCLENODEIMPL_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.forward.h b/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.forward.h new file mode 100644 index 00000000000..70901ca8728 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.forward.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODE_VISITOR_FORWARD_H__ +#define __LUCI_IR_CIRCLENODE_VISITOR_FORWARD_H__ + +namespace luci +{ + +// NOTE These forward declarations SHOULD BE aligned with Node delcarations in +// "CircleNodeVisitor.h" +template struct CircleNodeVisitorBase; +template struct CircleNodeMutableVisitorBase; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLENODE_VISITOR_FORWARD_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.h b/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.h new file mode 100644 index 00000000000..43339fe84bd --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodeVisitor.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODE_VISITOR_H__ +#define __LUCI_IR_CIRCLENODE_VISITOR_H__ + +#include "CircleNode.h" +#include "CircleNodes.h" + +#include + +namespace luci +{ + +/** + * DO NOT use this class. Use CircleNodeVisitor instead. + */ +template struct CircleNodeVisitorBase +{ + virtual ~CircleNodeVisitorBase() = default; + +#define CIRCLE_NODE(OPCODE, CIRCLE_CLASS) virtual T visit(const CIRCLE_CLASS *) = 0; + +#include "CircleNodes.lst" +#undef CIRCLE_NODE +}; + +template struct CircleNodeVisitor : public CircleNodeVisitorBase +{ + virtual ~CircleNodeVisitor() = default; + +#define CIRCLE_NODE(OPCODE, CIRCLE_CLASS) \ + virtual T visit(const CIRCLE_CLASS *node) { return visit(static_cast(node)); } + +#include "CircleNodes.lst" + +#undef CIRCLE_NODE + + /// @brief Default fallback + virtual T visit(const CircleNode *) { INTERNAL_EXN("CircleNodeVisitor: NYI node"); } +}; + +/** + * DO NOT use this class. Use CircleNodeMutableVisitor instead. + */ +template struct CircleNodeMutableVisitorBase +{ + virtual ~CircleNodeMutableVisitorBase() = default; + +#define CIRCLE_NODE(OPCODE, CIRCLE_CLASS) virtual T visit(CIRCLE_CLASS *) = 0; + +#include "CircleNodes.lst" + +#undef CIRCLE_NODE +}; + +template struct CircleNodeMutableVisitor : public CircleNodeMutableVisitorBase +{ + virtual ~CircleNodeMutableVisitor() = default; + +#define CIRCLE_NODE(OPCODE, CIRCLE_CLASS) \ + virtual T visit(CIRCLE_CLASS *node) { return visit(static_cast(node)); } + +#include "CircleNodes.lst" + +#undef CIRCLE_NODE + + /// @brief Default fallback + virtual T visit(CircleNode *) { INTERNAL_EXN("CircleNodeMutableVisitor: NYI node"); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CircleNode_VISITOR_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodes.h b/compiler/luci/lang/include/luci/IR/CircleNodes.h new file mode 100644 index 00000000000..b0996a37d2a --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodes.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLENODES_H__ +#define __LUCI_IR_CIRCLENODES_H__ + +#include "Nodes/CircleAdd.h" +#include "Nodes/CircleArgMax.h" +#include "Nodes/CircleAveragePool2D.h" +#include "Nodes/CircleConcatenation.h" +#include "Nodes/CircleConst.h" +#include "Nodes/CircleConv2D.h" +#include "Nodes/CircleDepthwiseConv2D.h" +#include "Nodes/CircleDiv.h" +#include "Nodes/CircleFullyConnected.h" +#include "Nodes/CircleMaximum.h" +#include "Nodes/CircleMaxPool2D.h" +#include "Nodes/CircleMean.h" +#include "Nodes/CircleMul.h" +#include "Nodes/CirclePad.h" +#include "Nodes/CircleRelu6.h" +#include "Nodes/CircleRelu.h" +#include "Nodes/CircleReshape.h" +#include "Nodes/CircleRsqrt.h" +#include "Nodes/CircleSqrt.h" +#include "Nodes/CircleSquaredDifference.h" +#include "Nodes/CircleSub.h" +#include "Nodes/CircleTransposeConv.h" +#include "Nodes/CircleTranspose.h" +// Circle only +#include "Nodes/CircleInstanceNorm.h" +// Virtual nodes +#include "Nodes/CircleInput.h" +#include "Nodes/CircleOutput.h" + +namespace luci +{ + +/** + * @brief Set both CircleReshape's 2nd input as CircleConst, and newShape attribute + * with same value + * @note Shape inference for TFLReshape forces them to be same + * + * TODO find better place for this helper + */ +void set_new_shape(CircleReshape *node, int32_t *base, uint32_t size); + +} // namespace luci + +#endif // __LUCI_IR_CIRCLENODES_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleNodes.lst b/compiler/luci/lang/include/luci/IR/CircleNodes.lst new file mode 100644 index 00000000000..6ccf0890ce6 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleNodes.lst @@ -0,0 +1,37 @@ +#ifndef CIRCLE_NODE +#error "Define CIRCLE_NODE" +#endif // CIRCLE_NODE + +// +// PLEASE SORT NODE DECLS IN ALPHABETICAL ORDER +// +CIRCLE_NODE(ADD, luci::CircleAdd) +CIRCLE_NODE(ARG_MAX, luci::CircleArgMax) +CIRCLE_NODE(AVERAGE_POOL_2D, luci::CircleAveragePool2D) +CIRCLE_NODE(CONCATENATION, luci::CircleConcatenation) +CIRCLE_NODE(CONST, luci::CircleConst) +CIRCLE_NODE(CONV_2D, luci::CircleConv2D) +CIRCLE_NODE(DEPTHWISE_CONV_2D, luci::CircleDepthwiseConv2D) +CIRCLE_NODE(DIV, luci::CircleDiv) +CIRCLE_NODE(FULLY_CONNECTED, luci::CircleFullyConnected) +CIRCLE_NODE(MAXIMUM, luci::CircleMaximum) +CIRCLE_NODE(MAX_POOL_2D, luci::CircleMaxPool2D) +CIRCLE_NODE(MEAN, luci::CircleMean) +CIRCLE_NODE(MUL, luci::CircleMul) +CIRCLE_NODE(PAD, luci::CirclePad) +CIRCLE_NODE(RELU, luci::CircleRelu) +CIRCLE_NODE(RELU6, luci::CircleRelu6) +CIRCLE_NODE(RESHAPE, luci::CircleReshape) +CIRCLE_NODE(RSQRT, luci::CircleRsqrt) +// TODO TFLSoftmax +CIRCLE_NODE(SQRT, luci::CircleSqrt) +CIRCLE_NODE(SQUARED_DIFFERENCE, luci::CircleSquaredDifference) +CIRCLE_NODE(SUB, luci::CircleSub) +// TODO TFLTanh +CIRCLE_NODE(TRANSPOSE, luci::CircleTranspose) +CIRCLE_NODE(TRANSPOSE_CONV, luci::CircleTransposeConv) +// Circle Only +CIRCLE_NODE(INSTANCE_NORM, luci::CircleInstanceNorm) +// Virtual node(s) +CIRCLE_NODE(CIRCLEINPUT, luci::CircleInput) +CIRCLE_NODE(CIRCLEOUTPUT, luci::CircleOutput) diff --git a/compiler/luci/lang/include/luci/IR/CircleOpcode.h b/compiler/luci/lang/include/luci/IR/CircleOpcode.h new file mode 100644 index 00000000000..703b70da216 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleOpcode.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEOPCODE_H__ +#define __LUCI_IR_CIRCLEOPCODE_H__ + +namespace luci +{ + +enum class CircleOpcode +{ +#define CIRCLE_NODE(OPCODE, CLASS) OPCODE, +#include "CircleNodes.lst" +#undef CIRCLE_NODE +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEOPCODE_H__ diff --git a/compiler/luci/lang/include/luci/IR/CircleQuantParam.h b/compiler/luci/lang/include/luci/IR/CircleQuantParam.h new file mode 100644 index 00000000000..be688e42e3f --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/CircleQuantParam.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEQUANTPARAM_H__ +#define __LUCI_IR_CIRCLEQUANTPARAM_H__ + +#include + +namespace luci +{ + +struct CircleQuantParam +{ + std::vector min; + std::vector max; + std::vector scale; + std::vector zerop; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEQUANTPARAM_H__ diff --git a/compiler/luci/lang/include/luci/IR/LuciNodeMixins.h b/compiler/luci/lang/include/luci/IR/LuciNodeMixins.h new file mode 100644 index 00000000000..656bba8e30a --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/LuciNodeMixins.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_LUCINODEMIXINS_H__ +#define __LUCI_IR_LUCINODEMIXINS_H__ + +#include "luci/IR/AttrFusedActFunc.h" + +#include +#include + +namespace luci +{ + +/// @brief enumeration of mixin class +enum class LuciNodeTrait +{ + FusedActFunc, + Bias +}; + +template class LuciNodeMixin; + +template <> class LuciNodeMixin +{ +public: + LuciNodeMixin() = default; + +public: + FusedActFunc fusedActivationFunction() const { return _fused_act_fun; } + void fusedActivationFunction(FusedActFunc fused_act_fun) { _fused_act_fun = fused_act_fun; } + +private: + FusedActFunc _fused_act_fun = FusedActFunc::UNDEFINED; +}; + +/** + * @brief Mixin class for nodes that has a bias input + */ +template <> class LuciNodeMixin +{ +public: + LuciNodeMixin() = default; + +public: + virtual loco::Node *bias(void) const = 0; /// @brief get the input for bias. + virtual void bias(loco::Node *node) = 0; /// @brief set the input for bias. +}; + +/** + * @brief Nodes with the fixed number of inputs + * + * TODO Deprecated this class, and use loco::FixedArity instead + */ +template class FixedArityNode : public Base +{ +public: + FixedArityNode() + { + for (uint32_t n = 0; n < N; ++n) + { + _args[n] = std::unique_ptr(new loco::Use{this}); + } + } + + virtual ~FixedArityNode() = default; + +public: + unsigned arity(void) const final { return N; } + + loco::Node *arg(uint32_t n) const final { return _args.at(n)->node(); } + + void drop(void) final + { + for (uint32_t n = 0; n < N; ++n) + { + _args.at(n)->node(nullptr); + } + } + +protected: + // This API allows inherited classes to access "_args" field. + loco::Use *at(unsigned n) const { return _args.at(n).get(); } + +private: + std::array, N> _args; +}; + +} // namespace luci + +#endif // __LUCI_IR_LUCINODEMIXINS_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleAdd.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleAdd.h new file mode 100644 index 00000000000..f26eccd1a96 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleAdd.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCELADD_H__ +#define __LUCI_IR_CIRCELADD_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief ADD in Circle + */ +class CircleAdd final : public FixedArityNode<2, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCELADD_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleArgMax.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleArgMax.h new file mode 100644 index 00000000000..dbc4b2b3a52 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleArgMax.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCELARGMAX_H__ +#define __LUCI_IR_CIRCELARGMAX_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief ARG_MAX in Circle + */ +class CircleArgMax final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *dimension(void) const { return at(1)->node(); } + void dimension(loco::Node *node) { at(1)->node(node); } + +public: + loco::DataType output_type(void) const { return _output_type; } + void output_type(loco::DataType ot) { _output_type = ot; } + +private: + loco::DataType _output_type{loco::DataType::S64}; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCELARGMAX_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleAveragePool2D.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleAveragePool2D.h new file mode 100644 index 00000000000..0b43b40c843 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleAveragePool2D.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEAVERAGEPOOL2D_H__ +#define __LUCI_IR_CIRCLEAVERAGEPOOL2D_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFilter.h" +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief AVERAGE_POOL_2D in Circle + */ +class CircleAveragePool2D final + : public FixedArityNode<1, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + CircleAveragePool2D() : _padding(Padding::UNDEFINED) { /* empty */} + +public: + loco::Node *value(void) const { return at(0)->node(); } + void value(loco::Node *node) { at(0)->node(node); } + + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Filter *filter(void) const { return &_filter; } + Filter *filter(void) { return &_filter; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; + Filter _filter; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEAVERAGEPOOL2D_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleConcatenation.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleConcatenation.h new file mode 100644 index 00000000000..55beaf67ce9 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleConcatenation.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLECONCATENATION_H__ +#define __LUCI_IR_CIRCLECONCATENATION_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" +#include "luci/IR/VariadicArityNode.h" + +#include + +namespace luci +{ + +/** + * @brief CONCATENATION in Circle + */ +class CircleConcatenation final + : public VariadicArityNode>, + public LuciNodeMixin +{ +public: + CircleConcatenation(uint32_t arity) + : VariadicArityNode>(arity) + { + // TODO Support when arity is 0 + assert(arity >= 1); + } + +public: + uint32_t numValues(void) const { return arity(); } + +public: + Node *values(uint32_t index) const + { + assert(index < numValues()); + return at(index)->node(); + } + void values(uint32_t index, Node *node) + { + assert(index < numValues()); + at(index)->node(node); + } + +public: + uint32_t axis(void) const { return _axis; } + void axis(uint32_t axis) { _axis = axis; } + +private: + uint32_t _axis; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLECONCATENATION_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleConst.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleConst.h new file mode 100644 index 00000000000..089836eb91d --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleConst.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLECONST_H__ +#define __LUCI_IR_CIRCLECONST_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +#include + +namespace luci +{ + +/** + * @brief Class to build tensor data + * @note This will not be exported as a specific op + */ +class CircleConst final : public FixedArityNode<0, CircleNodeImpl>, + public loco::NodeMixin, + public loco::NodeMixin +{ +public: + CircleConst() = default; + +public: + template uint32_t size(void) const; + template void size(uint32_t size); + template const typename loco::DataTypeImpl
::Type &at(uint32_t n) const; + template typename loco::DataTypeImpl
::Type &at(uint32_t n); + + template const typename loco::DataTypeImpl
::Type &scalar(void) const; + template typename loco::DataTypeImpl
::Type &scalar(void); + +private: + std::vector _data; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLECONST_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleConv2D.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleConv2D.h new file mode 100644 index 00000000000..54318e65c53 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleConv2D.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLECONV2D_H__ +#define __LUCI_IR_CIRCLECONV2D_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief CONV_2D in Circle + */ +class CircleConv2D final : public FixedArityNode<3, CircleNodeImpl>, + public LuciNodeMixin, + public LuciNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } + +public: + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding = Padding::UNDEFINED; + Stride _stride; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLECONV2D_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleDepthwiseConv2D.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleDepthwiseConv2D.h new file mode 100644 index 00000000000..15ee62ba7c3 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleDepthwiseConv2D.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEDEPTHWISECONV2D_H__ +#define __LUCI_IR_CIRCLEDEPTHWISECONV2D_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFilter.h" +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief DEPTHWISE_CONV_2D in Circle + */ +class CircleDepthwiseConv2D final + : public FixedArityNode<3, CircleNodeImpl>, + public LuciNodeMixin, + public LuciNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } + +public: + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + + int32_t depthMultiplier(void) const { return _depth_multiplier; } + void depthMultiplier(int32_t arg) { _depth_multiplier = arg; } + +private: + Padding _padding = Padding::UNDEFINED; + Stride _stride; + int32_t _depth_multiplier = 0; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEDEPTHWISECONV2D_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleDiv.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleDiv.h new file mode 100644 index 00000000000..1d4d3a23950 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleDiv.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEDIV_H__ +#define __LUCI_IR_CIRCLEDIV_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFilter.h" +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief DIV in Circle + */ +class CircleDiv final : public FixedArityNode<2, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + CircleDiv() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEDIV_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleFullyConnected.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleFullyConnected.h new file mode 100644 index 00000000000..d78f3949446 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleFullyConnected.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEFULLYCONNECTED_H__ +#define __LUCI_IR_CIRCLEFULLYCONNECTED_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief FULLY_CONNECTED in Circle + */ +class CircleFullyConnected final + : public FixedArityNode<3, CircleNodeImpl>, + public LuciNodeMixin, + public LuciNodeMixin +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *weights(void) const { return at(1)->node(); } + void weights(loco::Node *node) { at(1)->node(node); } + + loco::Node *bias(void) const override { return at(2)->node(); } + void bias(loco::Node *node) override { at(2)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEFULLYCONNECTED_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleInput.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleInput.h new file mode 100644 index 00000000000..c5ae0fc4991 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleInput.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEINPUT_H__ +#define __LUCI_IR_CIRCLEINPUT_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +#include +#include + +namespace luci +{ + +/** + * @brief CircleNode used for Input of the Graph + * @note This will not be exported as a specific op + */ +class CircleInput final : public FixedArityNode<0, CircleNodeImpl>, + public loco::NodeMixin, + public loco::NodeMixin +{ +public: + CircleInput() = default; + +public: + void index(const loco::GraphInputIndex &index); + loco::GraphInputIndex index(void) const; + + bool indexed(void) const { return _index != -1; } + +public: + template uint32_t size(void) const; + template void size(uint32_t size); + +private: + int64_t _index = -1; // Uninitialized +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEINPUT_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleInstanceNorm.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleInstanceNorm.h new file mode 100644 index 00000000000..db0faa05ea0 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleInstanceNorm.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEINSTANCENORM_H__ +#define __LUCI_IR_CIRCLEINSTANCENORM_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief INSTANCE_NORM in Circle + */ +class CircleInstanceNorm final + : public FixedArityNode<3, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + /// @note Currently only support FLOAT32 as input node + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *gamma(void) const { return at(1)->node(); } + void gamma(loco::Node *node) { at(1)->node(node); } + + loco::Node *beta(void) const { return at(2)->node(); } + void beta(loco::Node *node) { at(2)->node(node); } + + float epsilon() const { return _epsilon; } + void epsilon(float epsilon) { _epsilon = epsilon; } + +private: + float _epsilon = 1e-05; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEINSTANCENORM_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleMaxPool2D.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleMaxPool2D.h new file mode 100644 index 00000000000..1eb6532ffca --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleMaxPool2D.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEMAXPOOL2D_H__ +#define __LUCI_IR_CIRCLEMAXPOOL2D_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFilter.h" +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief MAX_POOL_2D in Circle + */ +class CircleMaxPool2D final : public FixedArityNode<1, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + CircleMaxPool2D() : _padding(Padding::UNDEFINED) { /* empty */} + +public: + loco::Node *value(void) const { return at(0)->node(); } + void value(loco::Node *node) { at(0)->node(node); } + + Padding padding() const { return _padding; } + void padding(Padding padding) { _padding = padding; } + + const Filter *filter(void) const { return &_filter; } + Filter *filter(void) { return &_filter; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; + Filter _filter; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEMAXPOOL2D_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleMaximum.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleMaximum.h new file mode 100644 index 00000000000..cf7305e3a5b --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleMaximum.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEMAXIMUM_H__ +#define __LUCI_IR_CIRCLEMAXIMUM_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief MAXIMUM in Circle + */ +class CircleMaximum final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEMAXIMUM_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleMean.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleMean.h new file mode 100644 index 00000000000..6fd7914506c --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleMean.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEMEAN_H__ +#define __LUCI_IR_CIRCLEMEAN_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief MEAN in Circle + */ +class CircleMean final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *reduction_indices(void) const { return at(1)->node(); } + void reduction_indices(loco::Node *node) { at(1)->node(node); } + +public: + bool keep_dims(void) const { return _keep_dims; } + void keep_dims(bool keep_dims) { _keep_dims = keep_dims; } + +private: + bool _keep_dims = false; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEMEAN_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleMul.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleMul.h new file mode 100644 index 00000000000..67e8971706e --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleMul.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEMUL_H__ +#define __LUCI_IR_CIRCLEMUL_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief MUL in Circle + */ +class CircleMul final : public FixedArityNode<2, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEMUL_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleOutput.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleOutput.h new file mode 100644 index 00000000000..c65317ad1c4 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleOutput.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEOUTPUT_H__ +#define __LUCI_IR_CIRCLEOUTPUT_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +#include + +namespace luci +{ + +/** + * @brief CircleNode for Output of the Graph + * @note This will not be exported as a specific op + */ +class CircleOutput final : public FixedArityNode<1, CircleNodeImpl> +{ +public: + CircleOutput() = default; + + void index(const loco::GraphOutputIndex &index); + loco::GraphOutputIndex index(void) const; + + bool indexed(void) const { return _index != -1; } + +public: + loco::Node *from(void) const { return at(0)->node(); } + void from(loco::Node *node) { at(0)->node(node); } + +private: + int64_t _index = -1; // Uninitialized +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEOUTPUT_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CirclePad.h b/compiler/luci/lang/include/luci/IR/Nodes/CirclePad.h new file mode 100644 index 00000000000..31599bda0f3 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CirclePad.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLEPAD_H__ +#define __LUCI_IR_CIRCLEPAD_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief PAD in Circle + */ +class CirclePad final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + CirclePad() = default; + +public: + loco::Node *input(void) const { return at(0)->node(); } + void input(loco::Node *node) { at(0)->node(node); } + + loco::Node *paddings(void) const { return at(1)->node(); } + void paddings(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLEPAD_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu.h new file mode 100644 index 00000000000..afb2c667a85 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLERELU_H__ +#define __LUCI_IR_CIRCLERELU_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief RELU in Circle + */ +class CircleRelu final : public FixedArityNode<1, CircleNodeImpl> +{ +public: + CircleRelu() = default; + +public: + loco::Node *features(void) const { return at(0)->node(); } + void features(loco::Node *node) { at(0)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLERELU_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu6.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu6.h new file mode 100644 index 00000000000..b313a555794 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleRelu6.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLERELU6_H__ +#define __LUCI_IR_CIRCLERELU6_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief RELU6 in Circle + */ +class CircleRelu6 final : public FixedArityNode<1, CircleNodeImpl> +{ +public: + CircleRelu6() = default; + +public: + loco::Node *features(void) const { return at(0)->node(); } + void features(loco::Node *node) { at(0)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLERELU6_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleReshape.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleReshape.h new file mode 100644 index 00000000000..a3a2a3f31cf --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleReshape.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLERESHAPE_H__ +#define __LUCI_IR_CIRCLERESHAPE_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief RESHAPE in Circle + */ +class CircleReshape final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + CircleReshape() = default; + +public: + loco::Node *tensor(void) const { return at(0)->node(); } + void tensor(loco::Node *node) { at(0)->node(node); } + + // TODO Make this input optional. That is, loco system does not emit error + // with this input being null + loco::Node *shape(void) const { return at(1)->node(); } + void shape(loco::Node *node) { at(1)->node(node); } + +public: + class Shape + { + public: + uint32_t rank(void) const { return _shape.size(); } + void rank(uint32_t rank) { _shape.resize(rank); } + + int32_t dim(uint32_t n) const { return _shape.at(n); } + int32_t &dim(uint32_t n) { return _shape.at(n); } + + private: + std::vector _shape; + }; + + const Shape *newShape(void) const { return &_new_shape; } + Shape *newShape(void) { return &_new_shape; } + +private: + Shape _new_shape; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLERESHAPE_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleRsqrt.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleRsqrt.h new file mode 100644 index 00000000000..44d22ef2204 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleRsqrt.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLERSQRT_H__ +#define __LUCI_IR_CIRCLERSQRT_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief RSQRT in Circle + */ +class CircleRsqrt final : public FixedArityNode<1, CircleNodeImpl> +{ +public: + CircleRsqrt() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLERSQRT_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleSqrt.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleSqrt.h new file mode 100644 index 00000000000..bc1f39d90cc --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleSqrt.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLESQRT_H__ +#define __LUCI_IR_CIRCLESQRT_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief SQRT in Circle + */ +class CircleSqrt final : public FixedArityNode<1, CircleNodeImpl> +{ +public: + CircleSqrt() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLESQRT_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleSquaredDifference.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleSquaredDifference.h new file mode 100644 index 00000000000..ff337dfbe91 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleSquaredDifference.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLESQUAREDIFFERENCE_H__ +#define __LUCI_IR_CIRCLESQUAREDIFFERENCE_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief SQUARED_DIFFERENCE in Circle + */ +class CircleSquaredDifference final + : public FixedArityNode<2, CircleNodeImpl> +{ +public: + CircleSquaredDifference() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLESQUAREDIFFERENCE_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleSub.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleSub.h new file mode 100644 index 00000000000..08208f942db --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleSub.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLESUB_H__ +#define __LUCI_IR_CIRCLESUB_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief SUB in Circle + */ +class CircleSub final : public FixedArityNode<2, CircleNodeImpl>, + public LuciNodeMixin +{ +public: + CircleSub() = default; + +public: + loco::Node *x(void) const { return at(0)->node(); } + void x(loco::Node *node) { at(0)->node(node); } + + loco::Node *y(void) const { return at(1)->node(); } + void y(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLESUB_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleTranspose.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleTranspose.h new file mode 100644 index 00000000000..198b56afdf3 --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleTranspose.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLETRANSPOSE_H__ +#define __LUCI_IR_CIRCLETRANSPOSE_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief TRANSPOSE in Circle + */ +class CircleTranspose final : public FixedArityNode<2, CircleNodeImpl> +{ +public: + CircleTranspose() = default; + +public: + /// @brief Get the input node to transpose + loco::Node *a(void) const { return at(0)->node(); } + + /// @brief Set the input node to transpose + void a(loco::Node *node) { at(0)->node(node); } + + loco::Node *perm(void) const { return at(1)->node(); } + void perm(loco::Node *node) { at(1)->node(node); } +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLETRANSPOSE_H__ diff --git a/compiler/luci/lang/include/luci/IR/Nodes/CircleTransposeConv.h b/compiler/luci/lang/include/luci/IR/Nodes/CircleTransposeConv.h new file mode 100644 index 00000000000..54a0d010c8c --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/Nodes/CircleTransposeConv.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_CIRCLETRANSPOSECONV_H__ +#define __LUCI_IR_CIRCLETRANSPOSECONV_H__ + +#include "luci/IR/CircleNodeDecl.h" +#include "luci/IR/CircleOpcode.h" + +#include "luci/IR/AttrPadding.h" +#include "luci/IR/AttrStride.h" +#include "luci/IR/AttrFusedActFunc.h" +#include "luci/IR/LuciNodeMixins.h" + +namespace luci +{ + +/** + * @brief TRANSPOSE_CONV in Circle + * + * @note Argument node function names are from TensorFlow. So referring 'in' and + * 'out' acutally means 'out' and 'in' of the this node. + */ +class CircleTransposeConv final + : public FixedArityNode<3, CircleNodeImpl> +{ +public: + loco::Node *inputSizes(void) const { return at(0)->node(); } + void inputSizes(Node *node) { at(0)->node(node); } + + loco::Node *filter(void) const { return at(1)->node(); } + void filter(Node *node) { at(1)->node(node); } + + loco::Node *outBackprop(void) const { return at(2)->node(); } + void outBackprop(Node *node) { at(2)->node(node); } + +public: + const Padding &padding(void) const { return _padding; } + void padding(const Padding &padding) { _padding = padding; } + + const Stride *stride(void) const { return &_stride; } + Stride *stride(void) { return &_stride; } + +private: + Padding _padding; + Stride _stride; +}; + +} // namespace luci + +#endif // __LUCI_IR_CIRCLETRANSPOSECONV_H__ diff --git a/compiler/luci/lang/include/luci/IR/VariadicArityNode.h b/compiler/luci/lang/include/luci/IR/VariadicArityNode.h new file mode 100644 index 00000000000..474c188ad6b --- /dev/null +++ b/compiler/luci/lang/include/luci/IR/VariadicArityNode.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_IR_VARIADICARITYNODES_H__ +#define __LUCI_IR_VARIADICARITYNODES_H__ + +#include +#include + +#include +#include +#include + +namespace luci +{ + +/** + * @brief Nodes with the variadic inputs + */ +template class VariadicArityNode : public Base +{ +public: + VariadicArityNode(uint32_t arity) + { + for (uint32_t n = 0; n < arity; ++n) + { + _args.emplace_back(std::move(std::unique_ptr{new loco::Use{this}})); + } + }; + + virtual ~VariadicArityNode() = default; + +public: + uint32_t arity(void) const final { return _args.size(); } + + loco::Node *arg(uint32_t n) const final + { + assert(n < _args.size()); + return _args.at(n)->node(); + } + + void drop(void) final + { + for (uint32_t n = 0; n < _args.size(); ++n) + { + _args.at(n)->node(nullptr); + } + } + +protected: + // This API allows inherited classes to access "_args" field. + loco::Use *at(uint32_t n) const + { + assert(n < _args.size()); + return _args.at(n).get(); + } + +private: + std::vector> _args; +}; + +} // namespace luci + +#endif // __LUCI_IR_VARIADICARITYNODES_H__ diff --git a/compiler/luci/lang/src/Check.h b/compiler/luci/lang/src/Check.h new file mode 100644 index 00000000000..e05ec904aec --- /dev/null +++ b/compiler/luci/lang/src/Check.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CHECK_H__ +#define __CHECK_H__ + +#include +#include +#include + +// TODO Add macro for Release version + +#define LUCI_ASSERT(condition, msg) \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[assert failed] " << (msg) << ". " << std::endl; \ + assert((condition)); \ + } \ + } + +#endif // __CHECK_H__ diff --git a/compiler/luci/lang/src/CircleDialect.cpp b/compiler/luci/lang/src/CircleDialect.cpp new file mode 100644 index 00000000000..a90a48689a2 --- /dev/null +++ b/compiler/luci/lang/src/CircleDialect.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/CircleDialect.h" +#include "luci/IR/Nodes/CircleInput.h" +#include "luci/IR/Nodes/CircleOutput.h" + +#include +#include +#include + +#include + +#include + +namespace +{ + +struct GiiQueryServiceImpl final : public loco::GraphInputIndexQueryService +{ + bool associated(const loco::Node *node) const final + { + if (auto circleinput = dynamic_cast(node)) + { + return circleinput->indexed(); + } + return false; + } + + loco::GraphOutputIndex index(const loco::Node *node) const final + { + assert(associated(node)); + auto circleinput = dynamic_cast(node); + assert(circleinput != nullptr); + return circleinput->index(); + } +}; + +struct GoiQueryServiceImpl final : public loco::GraphOutputIndexQueryService +{ + bool associated(const loco::Node *node) const final + { + if (auto circleoutput = dynamic_cast(node)) + { + return circleoutput->indexed(); + } + return false; + } + + loco::GraphOutputIndex index(const loco::Node *node) const final + { + assert(associated(node)); + auto circleoutput = dynamic_cast(node); + assert(circleoutput != nullptr); + return circleoutput->index(); + } +}; + +} // namespace + +namespace luci +{ + +CircleDialect::CircleDialect() +{ + service(stdex::make_unique()); + service(stdex::make_unique()); +} + +loco::Dialect *CircleDialect::get(void) +{ + static CircleDialect d; + return &d; +} + +} // namespace luci diff --git a/compiler/luci/lang/src/CircleDialect.test.cpp b/compiler/luci/lang/src/CircleDialect.test.cpp new file mode 100644 index 00000000000..78221f19950 --- /dev/null +++ b/compiler/luci/lang/src/CircleDialect.test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleDialectTest, get_P) +{ + auto d = luci::CircleDialect::get(); + + // get() SHOULD return a valid(non-null) pointer + ASSERT_NE(d, nullptr); + // The return value SHOULD be stable across multiple invocations + ASSERT_EQ(d, luci::CircleDialect::get()); +} + +TEST(CircleDialectTest, get_N) +{ + // TBD +} diff --git a/compiler/luci/lang/src/CircleNode.cpp b/compiler/luci/lang/src/CircleNode.cpp new file mode 100644 index 00000000000..cc273ba915f --- /dev/null +++ b/compiler/luci/lang/src/CircleNode.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/CircleNode.h" +#include "luci/IR/CircleDialect.h" + +namespace luci +{ + +const loco::Dialect *CircleNode::dialect(void) const { return CircleDialect::get(); } + +} // namespace luci diff --git a/compiler/luci/lang/src/CircleNodes.cpp b/compiler/luci/lang/src/CircleNodes.cpp new file mode 100644 index 00000000000..76ff7ec5a41 --- /dev/null +++ b/compiler/luci/lang/src/CircleNodes.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/CircleNodes.h" + +#include "Check.h" + +#include + +namespace luci +{ + +void set_new_shape(CircleReshape *node, int32_t *base, uint32_t size) +{ + // Check node does not have both of new shape infos + LUCI_ASSERT(node->shape() == nullptr, "node already has shape input"); + LUCI_ASSERT(node->newShape()->rank() == 0, "node already has newShape attribute"); + + const loco::DataType S32 = loco::DataType::S32; + + // Set 2nd input as CircleConst + auto const_shape_node = node->graph()->nodes()->create(); + const_shape_node->rank(1); + const_shape_node->dim(0) = size; + const_shape_node->dtype(S32); + const_shape_node->size(size); + for (uint32_t axis = 0; axis < size; ++axis) + const_shape_node->at(axis) = base[axis]; + node->shape(const_shape_node); + + // Set newShape attribute + node->newShape()->rank(size); + for (uint32_t axis = 0; axis < size; ++axis) + node->newShape()->dim(axis) = base[axis]; +} + +} // namespace luci diff --git a/compiler/luci/lang/src/LuciNodeMixins.cpp b/compiler/luci/lang/src/LuciNodeMixins.cpp new file mode 100644 index 00000000000..660cbe1a5e7 --- /dev/null +++ b/compiler/luci/lang/src/LuciNodeMixins.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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 is to validate LuciNodeMixins.h +#include "luci/IR/LuciNodeMixins.h" diff --git a/compiler/luci/lang/src/Nodes/CircleAdd.test.cpp b/compiler/luci/lang/src/Nodes/CircleAdd.test.cpp new file mode 100644 index 00000000000..a7701963d8f --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleAdd.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleAdd.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleAddTest, constructor_P) +{ + luci::CircleAdd add_node; + + ASSERT_EQ(add_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(add_node.opcode(), luci::CircleOpcode::ADD); + + ASSERT_EQ(add_node.x(), nullptr); + ASSERT_EQ(add_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleArgMax.test.cpp b/compiler/luci/lang/src/Nodes/CircleArgMax.test.cpp new file mode 100644 index 00000000000..6b2cff11c5f --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleArgMax.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleArgMax.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleArgMaxTest, constructor_P) +{ + luci::CircleArgMax add_node; + + ASSERT_EQ(add_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(add_node.opcode(), luci::CircleOpcode::ARG_MAX); + + ASSERT_EQ(add_node.input(), nullptr); + ASSERT_EQ(add_node.dimension(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleConcatenation.test.cpp b/compiler/luci/lang/src/Nodes/CircleConcatenation.test.cpp new file mode 100644 index 00000000000..7167682b2dd --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleConcatenation.test.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleConcatenation.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleConcatenationTest, constructor_P) +{ + luci::CircleConcatenation concat_node(3); + + ASSERT_EQ(concat_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(concat_node.opcode(), luci::CircleOpcode::CONCATENATION); + + ASSERT_EQ(concat_node.numValues(), 3); + ASSERT_EQ(concat_node.values(0), nullptr); + ASSERT_EQ(concat_node.values(1), nullptr); + ASSERT_EQ(concat_node.values(2), nullptr); + ASSERT_EQ(concat_node.fusedActivationFunction(), luci::FusedActFunc::UNDEFINED); +} diff --git a/compiler/luci/lang/src/Nodes/CircleConst.cpp b/compiler/luci/lang/src/Nodes/CircleConst.cpp new file mode 100644 index 00000000000..1c46884d880 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleConst.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleConst.h" + +#include + +namespace luci +{ + +template uint32_t CircleConst::size(void) const +{ + assert(dtype() == DT); + assert(_data.size() % sizeof(typename loco::DataTypeImpl
::Type) == 0); + return _data.size() / sizeof(typename loco::DataTypeImpl
::Type); +} + +template void CircleConst::size(uint32_t l) +{ + assert(dtype() == DT); + _data.resize(l * sizeof(typename loco::DataTypeImpl
::Type)); +} + +template +const typename loco::DataTypeImpl
::Type &CircleConst::at(uint32_t n) const +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +template typename loco::DataTypeImpl
::Type &CircleConst::at(uint32_t n) +{ + assert(dtype() == DT); + assert(n < size
()); + return *(reinterpret_cast::Type *>(_data.data()) + n); +} + +template +const typename loco::DataTypeImpl
::Type &CircleConst::scalar(void) const +{ + assert(dtype() == DT); + return *(reinterpret_cast::Type *>(_data.data())); +} + +template typename loco::DataTypeImpl
::Type &CircleConst::scalar(void) +{ + assert(dtype() == DT); + return *(reinterpret_cast::Type *>(_data.data())); +} + +#define INSTANTIATE(DT) \ + template uint32_t CircleConst::size
(void) const; \ + template void CircleConst::size
(uint32_t); \ + template const typename loco::DataTypeImpl
::Type &CircleConst::at
(uint32_t) const; \ + template typename loco::DataTypeImpl
::Type &CircleConst::at
(uint32_t); \ + template const typename loco::DataTypeImpl
::Type &CircleConst::scalar
(void) const; \ + template typename loco::DataTypeImpl
::Type &CircleConst::scalar
(void); + +INSTANTIATE(loco::DataType::S32); +INSTANTIATE(loco::DataType::FLOAT32); +INSTANTIATE(loco::DataType::U8); + +#undef INSTANTIATE + +} // namespace luci diff --git a/compiler/luci/lang/src/Nodes/CircleConv2D.test.cpp b/compiler/luci/lang/src/Nodes/CircleConv2D.test.cpp new file mode 100644 index 00000000000..7931c7eba23 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleConv2D.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleConv2D.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleConv2Dest, constructor_P) +{ + luci::CircleConv2D conv2d_node; + + ASSERT_EQ(conv2d_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(conv2d_node.opcode(), luci::CircleOpcode::CONV_2D); + + ASSERT_EQ(conv2d_node.input(), nullptr); + ASSERT_EQ(conv2d_node.filter(), nullptr); + ASSERT_EQ(conv2d_node.bias(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleDepthwiseConv2D.test.cpp b/compiler/luci/lang/src/Nodes/CircleDepthwiseConv2D.test.cpp new file mode 100644 index 00000000000..bbc1ea54348 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleDepthwiseConv2D.test.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleDepthwiseConv2D.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleDepthwiseConv2DTest, constructor_P) +{ + luci::CircleDepthwiseConv2D dw_conv2d_node; + + ASSERT_EQ(dw_conv2d_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(dw_conv2d_node.opcode(), luci::CircleOpcode::DEPTHWISE_CONV_2D); + + ASSERT_EQ(dw_conv2d_node.input(), nullptr); + ASSERT_EQ(dw_conv2d_node.filter(), nullptr); + ASSERT_EQ(dw_conv2d_node.bias(), nullptr); + ASSERT_EQ(dw_conv2d_node.padding(), luci::Padding::UNDEFINED); + ASSERT_EQ(dw_conv2d_node.stride()->h(), 1); + ASSERT_EQ(dw_conv2d_node.stride()->w(), 1); + ASSERT_EQ(dw_conv2d_node.depthMultiplier(), 0); + ASSERT_EQ(dw_conv2d_node.fusedActivationFunction(), luci::FusedActFunc::UNDEFINED); +} diff --git a/compiler/luci/lang/src/Nodes/CircleDiv.test.cpp b/compiler/luci/lang/src/Nodes/CircleDiv.test.cpp new file mode 100644 index 00000000000..e950cc6be30 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleDiv.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleDiv.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleDivTest, constructor_P) +{ + luci::CircleDiv div_node; + + ASSERT_EQ(div_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(div_node.opcode(), luci::CircleOpcode::DIV); + + ASSERT_EQ(div_node.x(), nullptr); + ASSERT_EQ(div_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleInput.cpp b/compiler/luci/lang/src/Nodes/CircleInput.cpp new file mode 100644 index 00000000000..dcf54f3b051 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleInput.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleInput.h" + +#include +#include + +namespace luci +{ + +void CircleInput::index(const loco::GraphInputIndex &index) +{ + // CircleInput internally stores "GraphInputIndex" as int64_t + _index = static_cast(index); +} + +loco::GraphInputIndex CircleInput::index(void) const +{ + assert(_index >= std::numeric_limits::min()); + assert(_index <= std::numeric_limits::max()); + return static_cast(_index); +} + +} // namespace luci diff --git a/compiler/luci/lang/src/Nodes/CircleInstanceNorm.test.cpp b/compiler/luci/lang/src/Nodes/CircleInstanceNorm.test.cpp new file mode 100644 index 00000000000..b87e81791c7 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleInstanceNorm.test.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleInstanceNorm.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleInstanceNormTest, constructor) +{ + luci::CircleInstanceNorm instance_norm; + + ASSERT_EQ(instance_norm.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(instance_norm.opcode(), luci::CircleOpcode::INSTANCE_NORM); + + ASSERT_EQ(instance_norm.input(), nullptr); + ASSERT_EQ(instance_norm.gamma(), nullptr); + ASSERT_EQ(instance_norm.beta(), nullptr); + ASSERT_FLOAT_EQ(instance_norm.epsilon(), 1e-05); + ASSERT_EQ(instance_norm.fusedActivationFunction(), luci::FusedActFunc::UNDEFINED); +} diff --git a/compiler/luci/lang/src/Nodes/CircleMaxPool2D.test.cpp b/compiler/luci/lang/src/Nodes/CircleMaxPool2D.test.cpp new file mode 100644 index 00000000000..874ecec0ee3 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleMaxPool2D.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleMaxPool2D.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleMaxPool2DTest, constructor_P) +{ + luci::CircleMaxPool2D maxpool2d_node; + + ASSERT_EQ(maxpool2d_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(maxpool2d_node.opcode(), luci::CircleOpcode::MAX_POOL_2D); + + ASSERT_EQ(maxpool2d_node.value(), nullptr); + ASSERT_NE(maxpool2d_node.filter(), nullptr); + ASSERT_NE(maxpool2d_node.stride(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleMaximum.test.cpp b/compiler/luci/lang/src/Nodes/CircleMaximum.test.cpp new file mode 100644 index 00000000000..efe62f11a30 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleMaximum.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleMaximum.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleMaximumTest, constructor_P) +{ + luci::CircleMaximum max_node; + + ASSERT_EQ(max_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(max_node.opcode(), luci::CircleOpcode::MAXIMUM); + + ASSERT_EQ(max_node.x(), nullptr); + ASSERT_EQ(max_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleMul.test.cpp b/compiler/luci/lang/src/Nodes/CircleMul.test.cpp new file mode 100644 index 00000000000..f9eca42f9af --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleMul.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleMul.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleMulTest, constructor_P) +{ + luci::CircleMul mul_node; + + ASSERT_EQ(mul_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(mul_node.opcode(), luci::CircleOpcode::MUL); + + ASSERT_EQ(mul_node.x(), nullptr); + ASSERT_EQ(mul_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleOutput.cpp b/compiler/luci/lang/src/Nodes/CircleOutput.cpp new file mode 100644 index 00000000000..31380456f19 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleOutput.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleOutput.h" + +#include +#include + +namespace luci +{ + +void CircleOutput::index(const loco::GraphOutputIndex &index) +{ + // CircleOutput internally stores "GraphOutputIndex" as int64_t + _index = static_cast(index); +} + +loco::GraphOutputIndex CircleOutput::index(void) const +{ + assert(_index >= std::numeric_limits::min()); + assert(_index <= std::numeric_limits::max()); + return static_cast(_index); +} + +} // namespace luci diff --git a/compiler/luci/lang/src/Nodes/CirclePad.test.cpp b/compiler/luci/lang/src/Nodes/CirclePad.test.cpp new file mode 100644 index 00000000000..3a23fa0f0bb --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CirclePad.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CirclePad.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CirclePadTest, constructor_P) +{ + luci::CirclePad pad_node; + + ASSERT_EQ(pad_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(pad_node.opcode(), luci::CircleOpcode::PAD); + + ASSERT_EQ(pad_node.input(), nullptr); + ASSERT_EQ(pad_node.paddings(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleRelu.test.cpp b/compiler/luci/lang/src/Nodes/CircleRelu.test.cpp new file mode 100644 index 00000000000..19ea88aa61d --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleRelu.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleRelu.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleReluTest, constructor_P) +{ + luci::CircleRelu relu_node; + + ASSERT_EQ(relu_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(relu_node.opcode(), luci::CircleOpcode::RELU); + + ASSERT_EQ(relu_node.features(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleRelu6.test.cpp b/compiler/luci/lang/src/Nodes/CircleRelu6.test.cpp new file mode 100644 index 00000000000..74bf2e86a0e --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleRelu6.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleRelu6.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleRelu6Test, constructor_P) +{ + luci::CircleRelu6 relu6_node; + + ASSERT_EQ(relu6_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(relu6_node.opcode(), luci::CircleOpcode::RELU6); + + ASSERT_EQ(relu6_node.features(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleReshape.test.cpp b/compiler/luci/lang/src/Nodes/CircleReshape.test.cpp new file mode 100644 index 00000000000..7bc2d32a4dd --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleReshape.test.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleReshape.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleReshapeTest, constructor_P) +{ + luci::CircleReshape reshape; + + ASSERT_EQ(reshape.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(reshape.opcode(), luci::CircleOpcode::RESHAPE); + + ASSERT_EQ(reshape.tensor(), nullptr); + ASSERT_EQ(reshape.shape(), nullptr); + ASSERT_EQ(reshape.newShape()->rank(), 0); +} + +TEST(CircleReshapeTest, alloc_new_shape_P) +{ + luci::CircleReshape reshape; + + reshape.newShape()->rank(2); + ASSERT_EQ(reshape.newShape()->rank(), 2); + + reshape.newShape()->dim(0) = 0; + reshape.newShape()->dim(1) = 1; + + auto &const_reshape = const_cast(reshape); + ASSERT_EQ(const_reshape.newShape()->dim(0), 0); + ASSERT_EQ(const_reshape.newShape()->dim(1), 1); +} diff --git a/compiler/luci/lang/src/Nodes/CircleSqrt.test.cpp b/compiler/luci/lang/src/Nodes/CircleSqrt.test.cpp new file mode 100644 index 00000000000..6cfb3bc9450 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleSqrt.test.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleSqrt.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleSqrtTest, constructor_P) +{ + luci::CircleSqrt sqrt_node; + + ASSERT_EQ(sqrt_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(sqrt_node.opcode(), luci::CircleOpcode::SQRT); + + ASSERT_EQ(sqrt_node.x(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleSquaredDifference.test.cpp b/compiler/luci/lang/src/Nodes/CircleSquaredDifference.test.cpp new file mode 100644 index 00000000000..71df189b94c --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleSquaredDifference.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleSquaredDifference.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleSquaredDifferenceTest, constructor_P) +{ + luci::CircleSquaredDifference sd_node; + + ASSERT_EQ(sd_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(sd_node.opcode(), luci::CircleOpcode::SQUARED_DIFFERENCE); + + ASSERT_EQ(sd_node.x(), nullptr); + ASSERT_EQ(sd_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleSub.test.cpp b/compiler/luci/lang/src/Nodes/CircleSub.test.cpp new file mode 100644 index 00000000000..ebb29446aab --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleSub.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleSub.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleSubTest, constructor_P) +{ + luci::CircleSub sub_node; + + ASSERT_EQ(sub_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(sub_node.opcode(), luci::CircleOpcode::SUB); + + ASSERT_EQ(sub_node.x(), nullptr); + ASSERT_EQ(sub_node.y(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleTranspose.test.cpp b/compiler/luci/lang/src/Nodes/CircleTranspose.test.cpp new file mode 100644 index 00000000000..7233869e69a --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleTranspose.test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleTranspose.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleTransposeTest, constructor_P) +{ + luci::CircleTranspose tr_node; + + ASSERT_EQ(tr_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(tr_node.opcode(), luci::CircleOpcode::TRANSPOSE); + + ASSERT_EQ(tr_node.a(), nullptr); + ASSERT_EQ(tr_node.perm(), nullptr); +} diff --git a/compiler/luci/lang/src/Nodes/CircleTransposeConv.test.cpp b/compiler/luci/lang/src/Nodes/CircleTransposeConv.test.cpp new file mode 100644 index 00000000000..9615082d957 --- /dev/null +++ b/compiler/luci/lang/src/Nodes/CircleTransposeConv.test.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/IR/Nodes/CircleTransposeConv.h" + +#include "luci/IR/CircleDialect.h" + +#include + +TEST(CircleTransposeConvTest, constructor_P) +{ + luci::CircleTransposeConv trc_node; + + ASSERT_EQ(trc_node.dialect(), luci::CircleDialect::get()); + ASSERT_EQ(trc_node.opcode(), luci::CircleOpcode::TRANSPOSE_CONV); + + ASSERT_EQ(trc_node.inputSizes(), nullptr); + ASSERT_EQ(trc_node.filter(), nullptr); + ASSERT_EQ(trc_node.outBackprop(), nullptr); +} diff --git a/compiler/luci/log/CMakeLists.txt b/compiler/luci/log/CMakeLists.txt new file mode 100644 index 00000000000..23fed1cf198 --- /dev/null +++ b/compiler/luci/log/CMakeLists.txt @@ -0,0 +1,10 @@ +# TODO Find how to test logging framework +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_library(luci_log SHARED ${SOURCES}) +target_include_directories(luci_log PUBLIC include) +target_link_libraries(luci_log PUBLIC hermes) +target_link_libraries(luci_log PRIVATE hermes_std) +target_link_libraries(luci_log PRIVATE stdex) +target_link_libraries(luci_log PRIVATE nncc_common) +install(TARGETS luci_log DESTINATION lib) diff --git a/compiler/luci/log/README.md b/compiler/luci/log/README.md new file mode 100644 index 00000000000..512bc96d241 --- /dev/null +++ b/compiler/luci/log/README.md @@ -0,0 +1,3 @@ +# luci-log + +_luci-log_ is a logging framework for _luci_ compiler framework. diff --git a/compiler/luci/log/include/luci/Log.h b/compiler/luci/log/include/luci/Log.h new file mode 100644 index 00000000000..51299a08210 --- /dev/null +++ b/compiler/luci/log/include/luci/Log.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_LOG_H__ +#define __LUCI_LOG_H__ + +#include + +namespace luci +{ + +/** + * @brief Logger Implementation + */ +class Logger final : public hermes::Source +{ +public: + Logger(hermes::Context *ctx); + ~Logger(); +}; + +/** + * @brief Logger Configuration + * + * Users are able to turn logging on/off via MOCO_LOG environment variable. + */ +class LoggerConfig final : public hermes::Config +{ +public: + LoggerConfig(); + +public: + void configure(const hermes::Source *, hermes::Source::Setting &) const final; + void configure(const Logger *, hermes::Source::Setting &) const; + +private: + bool _enabled; +}; + +} // namespace luci + +#include "luci/LoggingContext.h" + +/** + * HOW TO USE: + * + * LOGGER(l); + * + * INFO(l) << "Hello, World" << std::endl; + * + */ +#define LOGGER(name) ::luci::Logger name{::luci::LoggingContext::get()}; + +// TODO Support FATAL, ERROR, WARN, and VERBOSE +#define INFO(name) HERMES_INFO(name) + +// WARNING! +// +// THE CURRENT IMPLEMENTATION IS NOT THREAD SAFE. +// + +#endif // __LUCI_LOG_H__ diff --git a/compiler/luci/log/include/luci/LoggingContext.h b/compiler/luci/log/include/luci/LoggingContext.h new file mode 100644 index 00000000000..f5091099f21 --- /dev/null +++ b/compiler/luci/log/include/luci/LoggingContext.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_LOGGING_CONTEXT_H__ +#define __LUCI_LOGGING_CONTEXT_H__ + +#include + +namespace luci +{ + +/** + * @brief Global logging context + */ +struct LoggingContext +{ + static hermes::Context *get(void); +}; + +} // namespace luci + +#endif // __LUCI_LOGGING_CONTEXT_H__ diff --git a/compiler/luci/log/src/Log.cpp b/compiler/luci/log/src/Log.cpp new file mode 100644 index 00000000000..7e1634009b1 --- /dev/null +++ b/compiler/luci/log/src/Log.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Log.h" + +#include +#include +#include + +// TODO Extract these lexical conversion routines as a library +namespace +{ + +/** + * @brief Convert C-string as a value of type T + * + * safecast(s, v) returns v if s is nullptr. + */ +template T safecast(const char *, const T &); + +template <> bool safecast(const char *s, const bool &value) +{ + return (s == nullptr) ? value : (std::stoi(s) != 0); +} + +} // namespace + +// +// Logger +// +namespace luci +{ + +Logger::Logger(hermes::Context *ctx) { activate(ctx->sources(), ctx->bus()); } +Logger::~Logger() { deactivate(); } + +} // namespace luci + +// +// LoggerConfig +// +namespace luci +{ + +LoggerConfig::LoggerConfig() +{ + // Turn on logging if LUCI_LOG is set as non-zero value + _enabled = safecast(std::getenv("LUCI_LOG"), false); +} + +void LoggerConfig::configure(const hermes::Source *source, hermes::Source::Setting &setting) const +{ + // Let's ignore hermes::Sources if that is not a moco logger + if (auto logger = dynamic_cast(source)) + { + configure(logger, setting); + } +} + +void LoggerConfig::configure(const Logger *, hermes::Source::Setting &setting) const +{ + if (_enabled) + { + // Enable all catagories + setting.accept_all(); + } + else + { + // Disable all catagories + setting.reject_all(); + } +} + +} // namespace luci diff --git a/compiler/luci/log/src/LoggingContext.cpp b/compiler/luci/log/src/LoggingContext.cpp new file mode 100644 index 00000000000..ed091195d19 --- /dev/null +++ b/compiler/luci/log/src/LoggingContext.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/LoggingContext.h" +#include "luci/Log.h" + +#include +#include + +namespace luci +{ + +hermes::Context *LoggingContext::get(void) +{ + static hermes::Context *ctx = nullptr; + + if (ctx == nullptr) + { + ctx = new hermes::Context; + ctx->sinks()->append(stdex::make_unique()); + ctx->config(stdex::make_unique()); + } + + return ctx; +} + +} // namespace luci diff --git a/compiler/luci/logex/CMakeLists.txt b/compiler/luci/logex/CMakeLists.txt new file mode 100644 index 00000000000..a5f7e771f34 --- /dev/null +++ b/compiler/luci/logex/CMakeLists.txt @@ -0,0 +1,14 @@ +# TODO Find how to test logging-ex utility +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_library(luci_logex SHARED ${SOURCES}) +target_include_directories(luci_logex PUBLIC include) +target_link_libraries(luci_logex PUBLIC loco) +target_link_libraries(luci_logex PUBLIC locop) +target_link_libraries(luci_logex PRIVATE luci_log) +target_link_libraries(luci_logex PRIVATE luci_lang) +target_link_libraries(luci_logex PRIVATE hermes_std) +target_link_libraries(luci_logex PRIVATE stdex) +target_link_libraries(luci_logex PRIVATE nncc_common) +target_link_libraries(luci_logex PRIVATE pepper_str) +install(TARGETS luci_logex DESTINATION lib) diff --git a/compiler/luci/logex/README.md b/compiler/luci/logex/README.md new file mode 100644 index 00000000000..03b6baf35d6 --- /dev/null +++ b/compiler/luci/logex/README.md @@ -0,0 +1,3 @@ +# luci-logex + +_luci-logex_ is a extended logging utility for _luci_ compiler framework. diff --git a/compiler/luci/logex/include/luci/LogHelper.h b/compiler/luci/logex/include/luci/LogHelper.h new file mode 100644 index 00000000000..37cdd735bf9 --- /dev/null +++ b/compiler/luci/logex/include/luci/LogHelper.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_LOG_HELPER_H__ +#define __LUCI_LOG_HELPER_H__ + +#include +#include + +#include + +namespace luci +{ + +using FormattedGraph = locop::FormattedGraphImpl; + +FormattedGraph fmt(loco::Graph *g); + +static inline FormattedGraph fmt(const std::unique_ptr &g) { return fmt(g.get()); } + +} // namespace luci + +#endif // __LUCI_LOG_HELPER_H__ diff --git a/compiler/luci/logex/src/FormattedGraph.cpp b/compiler/luci/logex/src/FormattedGraph.cpp new file mode 100644 index 00000000000..d954438e4de --- /dev/null +++ b/compiler/luci/logex/src/FormattedGraph.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "FormattedGraph.h" + +#include +#include + +#include + +#include +#include +#include + +/** + * @brief dump std::vector values to stream + */ +std::ostream &operator<<(std::ostream &os, const std::vector &vi64) +{ + for (auto vi : vi64) + { + os << vi << " "; + } + return os; +} + +// For TF lite +namespace +{ + +const char *to_str(loco::DataType type) +{ + switch (type) + { + case loco::DataType::U8: + return "UINT8"; + case loco::DataType::U16: + return "UINT16"; + case loco::DataType::U32: + return "UINT32"; + case loco::DataType::U64: + return "UINT64"; + + case loco::DataType::S8: + return "INT8"; + case loco::DataType::S16: + return "INT16"; + case loco::DataType::S32: + return "INT32"; + case loco::DataType::S64: + return "INT64"; + + case loco::DataType::FLOAT16: + return "FLOAT16"; + case loco::DataType::FLOAT32: + return "FLOAT32"; + case loco::DataType::FLOAT64: + return "FLOAT64"; + + default: + return "Error"; + } +} + +const char *to_str(luci::FusedActFunc fused) +{ + switch (fused) + { + case luci::FusedActFunc::NONE: + return "NONE"; + case luci::FusedActFunc::RELU: + return "RELU"; + case luci::FusedActFunc::RELU6: + return "RELU6"; + default: + return "Error"; + } +} + +const char *to_str(luci::Padding padding) +{ + switch (padding) + { + case luci::Padding::SAME: + return "SAME"; + case luci::Padding::VALID: + return "VALID"; + default: + return "Error"; + } +} + +std::string to_str(const luci::Stride *stride) +{ + return pepper::str(stride->h(), ",", stride->w()); +} + +std::string to_str(const luci::Filter *filter) +{ + return pepper::str(filter->h(), ",", filter->w()); +} + +std::string circle_opname(uint32_t opnum) +{ + static const std::string prefix{"circle."}; + + switch (static_cast(opnum)) + { +#define CIRCLE_NODE(OPCODE, CLASS) \ + case luci::CircleOpcode::OPCODE: \ + return prefix + #OPCODE; +#include +#undef CIRCLE_NODE + default: + break; + }; + + return prefix + "Invalid"; +} + +// CircleNodeSummaryBuilder with default implementation +class CircleNodeSummaryBuilderBase : public locop::NodeSummaryBuilder +{ +public: + CircleNodeSummaryBuilderBase(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *, locop::NodeSummary &s) const final; + +protected: +#define CIRCLE_NODE(OPCODE, CLASS) \ + virtual bool summary(const CLASS *, locop::NodeSummary &s) const \ + { \ + s.comments().append("Emitted by Default CircleNodeSummaryBuilder"); \ + s.state(locop::NodeSummary::State::PartiallyKnown); \ + return true; \ + } +#include +#undef CIRCLE_NODE + +protected: + const locop::SymbolTable *tbl(void) const { return _tbl; } + + // Please do not use _tbl directly and use tbl(). + // This will be changed to private in near future. +protected: + const locop::SymbolTable *_tbl; +}; + +class CircleNodeSummaryBuilder final : public CircleNodeSummaryBuilderBase +{ +public: + CircleNodeSummaryBuilder(const locop::SymbolTable *tbl) : CircleNodeSummaryBuilderBase(tbl) + { + // DO NOTHING + } + +private: +#define IMPLEMENT(CLASS) bool summary(const CLASS *, locop::NodeSummary &) const final; + IMPLEMENT(luci::CircleAdd) + IMPLEMENT(luci::CircleArgMax) + IMPLEMENT(luci::CircleAveragePool2D) + IMPLEMENT(luci::CircleConcatenation) + IMPLEMENT(luci::CircleConst) + IMPLEMENT(luci::CircleConv2D) + IMPLEMENT(luci::CircleDepthwiseConv2D) + IMPLEMENT(luci::CircleDiv) + IMPLEMENT(luci::CircleMaximum) + IMPLEMENT(luci::CircleMaxPool2D) + IMPLEMENT(luci::CircleMean) + IMPLEMENT(luci::CircleMul) + IMPLEMENT(luci::CirclePad) + IMPLEMENT(luci::CircleRelu) + IMPLEMENT(luci::CircleRelu6) + IMPLEMENT(luci::CircleReshape) + IMPLEMENT(luci::CircleRsqrt) + IMPLEMENT(luci::CircleSqrt) + IMPLEMENT(luci::CircleSquaredDifference) + IMPLEMENT(luci::CircleSub) + IMPLEMENT(luci::CircleTranspose) + IMPLEMENT(luci::CircleTransposeConv) + // Circle Only + IMPLEMENT(luci::CircleInstanceNorm) + // Virtual nodes + IMPLEMENT(luci::CircleInput) + IMPLEMENT(luci::CircleOutput) +#undef IMPLEMENT +}; + +bool CircleNodeSummaryBuilderBase::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (node->dialect() != luci::CircleDialect::get()) + return false; + +#define CIRCLE_NODE(OPCODE, CLASS) \ + if (dynamic_cast(node)) \ + { \ + s.opname(circle_opname(node->opnum())); \ + return summary(dynamic_cast(node), s); \ + } +#include +#undef CIRCLE_NODE + + return false; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleAdd *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.args().append("fused_activation_function", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleArgMax *node, locop::NodeSummary &s) const +{ + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("dimension", tbl()->lookup(node->dimension())); + s.args().append("output_type", to_str(node->output_type())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleAveragePool2D *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + + s.args().append("value", tbl()->lookup(node->value())); + s.args().append("filter(h,w)", to_str(node->filter())); + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleConcatenation *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + + for (uint32_t i = 0; i < node->numValues(); ++i) + s.args().append("values", tbl()->lookup(node->values(i))); + s.args().append("axis", pepper::str(node->axis())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleConst *, locop::NodeSummary &s) const +{ + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleConv2D *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + assert(node->padding() != luci::Padding::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("bias", tbl()->lookup(node->bias())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleDepthwiseConv2D *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + assert(node->padding() != luci::Padding::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("bias", tbl()->lookup(node->bias())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("depthMultiplier", std::to_string(node->depthMultiplier())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleDiv *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleMaximum *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleMaxPool2D *node, + locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + + s.args().append("value", tbl()->lookup(node->value())); + s.args().append("filter(h,w)", to_str(node->filter())); + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + s.args().append("fused", to_str(node->fusedActivationFunction())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleMean *node, locop::NodeSummary &s) const +{ + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("reduction_indices", tbl()->lookup(node->reduction_indices())); + s.args().append("keep_dims", node->keep_dims() ? "true" : "false"); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleMul *node, locop::NodeSummary &s) const +{ + assert(node->fusedActivationFunction() != luci::FusedActFunc::UNDEFINED); + + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.args().append("fused_activation_function", to_str(node->fusedActivationFunction())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CirclePad *node, locop::NodeSummary &s) const +{ + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("paddings", tbl()->lookup(node->paddings())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleRelu *node, locop::NodeSummary &s) const +{ + s.args().append("features", tbl()->lookup(node->features())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleRelu6 *node, locop::NodeSummary &s) const +{ + s.args().append("features", tbl()->lookup(node->features())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleReshape *node, locop::NodeSummary &s) const +{ + s.args().append("tensor", tbl()->lookup(node->tensor())); + s.args().append("shape", tbl()->lookup(node->shape())); + // TODO Show newShape info + s.state(locop::NodeSummary::State::PartiallyKnown); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleRsqrt *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +// TODO TFLSoftmax + +bool CircleNodeSummaryBuilder::summary(const luci::CircleSqrt *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleSquaredDifference *node, + locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleSub *node, locop::NodeSummary &s) const +{ + s.args().append("x", tbl()->lookup(node->x())); + s.args().append("y", tbl()->lookup(node->y())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +// TODO TFLTanh + +bool CircleNodeSummaryBuilder::summary(const luci::CircleTranspose *node, + locop::NodeSummary &s) const +{ + s.args().append("a", tbl()->lookup(node->a())); + s.args().append("perm", tbl()->lookup(node->perm())); + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleTransposeConv *node, + locop::NodeSummary &s) const +{ + assert(node->padding() != luci::Padding::UNDEFINED); + + s.args().append("inputSizes", tbl()->lookup(node->inputSizes())); + s.args().append("filter", tbl()->lookup(node->filter())); + s.args().append("outBackprop", tbl()->lookup(node->outBackprop())); + + s.args().append("stride(h,w)", to_str(node->stride())); + s.args().append("padding", to_str(node->padding())); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleInput *, locop::NodeSummary &s) const +{ + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleOutput *node, locop::NodeSummary &s) const +{ + s.args().append("from", tbl()->lookup(node->from())); + + s.state(locop::NodeSummary::State::Complete); + return true; +} + +bool CircleNodeSummaryBuilder::summary(const luci::CircleInstanceNorm *node, + locop::NodeSummary &s) const +{ + auto fused = node->fusedActivationFunction(); + assert(fused != luci::FusedActFunc::UNDEFINED); + + s.args().append("input", tbl()->lookup(node->input())); + s.args().append("gamma", tbl()->lookup(node->gamma())); + s.args().append("beta", tbl()->lookup(node->beta())); + s.args().append("epsilon", pepper::str(node->epsilon())); + s.args().append("fused_activation_function", to_str(fused)); + + s.state(locop::NodeSummary::State::Complete); + + return true; +} + +} // namespace + +namespace luci +{ + +bool NodeSummaryBuilder::build(const loco::Node *node, locop::NodeSummary &s) const +{ + if (locop::CanonicalNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + if (CircleNodeSummaryBuilder(_tbl).build(node, s)) + { + return true; + } + + return false; +} + +} // namespace exo diff --git a/compiler/luci/logex/src/FormattedGraph.h b/compiler/luci/logex/src/FormattedGraph.h new file mode 100644 index 00000000000..c340f86403a --- /dev/null +++ b/compiler/luci/logex/src/FormattedGraph.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_FORMATTED_GRAPH_H__ +#define __LUCI_FORMATTED_GRAPH_H__ + +#include + +#include + +namespace luci +{ + +class NodeSummaryBuilder final : public locop::NodeSummaryBuilder +{ +public: + NodeSummaryBuilder(const locop::SymbolTable *tbl) : _tbl{tbl} + { + // DO NOTHING + } + +public: + bool build(const loco::Node *node, locop::NodeSummary &s) const final; + +private: + const locop::SymbolTable *_tbl; +}; + +class NodeSummaryBuilderFactory final : public locop::NodeSummaryBuilderFactory +{ +public: + NodeSummaryBuilderFactory() = default; + +public: + std::unique_ptr create(const locop::SymbolTable *tlb) const final + { + return stdex::make_unique(tlb); + } +}; + +} // namespace luci + +#endif // __LUCI_FORMATTED_GRAPH_H__ diff --git a/compiler/luci/logex/src/LogHelper.cpp b/compiler/luci/logex/src/LogHelper.cpp new file mode 100644 index 00000000000..fe7970a1d4d --- /dev/null +++ b/compiler/luci/logex/src/LogHelper.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/LogHelper.h" +#include "FormattedGraph.h" + +namespace luci +{ + +FormattedGraph fmt(loco::Graph *g) +{ + auto node_summary_builder = stdex::make_unique(); + return std::move(locop::fmt(g).with(std::move(node_summary_builder))); +} + +} // namespace luci diff --git a/compiler/luci/pass/CMakeLists.txt b/compiler/luci/pass/CMakeLists.txt new file mode 100644 index 00000000000..c349a17839c --- /dev/null +++ b/compiler/luci/pass/CMakeLists.txt @@ -0,0 +1,28 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +#file(GLOB_RECURSE TESTS "src/*.test.cpp") +#list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(luci_pass SHARED ${SOURCES}) +target_include_directories(luci_pass PRIVATE src) +target_include_directories(luci_pass PUBLIC include) +target_link_libraries(luci_pass PUBLIC luci_lang) +target_link_libraries(luci_pass PUBLIC luci_service) +target_link_libraries(luci_pass PUBLIC mio_tflite) +target_link_libraries(luci_pass PUBLIC stdex) +target_link_libraries(luci_pass PUBLIC logo_core) +target_link_libraries(luci_pass PRIVATE luci_log) +target_link_libraries(luci_pass PRIVATE nncc_common) +target_link_libraries(luci_pass PRIVATE oops) +install(TARGETS luci_pass DESTINATION lib) + +# TODO enable for tests +#if(NOT ENABLE_TEST) +# return() +#endif(NOT ENABLE_TEST) +# +#nnas_find_package(GTest REQUIRED) +# +#GTest_AddTest(luci_pass_test ${TESTS}) +#target_include_directories(luci_pass_test PRIVATE src) +#target_link_libraries(luci_pass_test luci_pass) +#target_link_libraries(luci_pass_test oops) diff --git a/compiler/luci/pass/README.md b/compiler/luci/pass/README.md new file mode 100644 index 00000000000..9b6cdebd3d5 --- /dev/null +++ b/compiler/luci/pass/README.md @@ -0,0 +1,3 @@ +# luci-pass + +_luci-pass_ provides Circle Dialect transformation passes diff --git a/compiler/luci/pass/include/luci/Pass/FuseInstanceNormPass.h b/compiler/luci/pass/include/luci/Pass/FuseInstanceNormPass.h new file mode 100644 index 00000000000..800a5f789e2 --- /dev/null +++ b/compiler/luci/pass/include/luci/Pass/FuseInstanceNormPass.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_FUSE_INSTANCE_NORM_PASS_H__ +#define __LUCI_FUSE_INSTANCE_NORM_PASS_H__ + +#include + +namespace luci +{ + +/** + * @brief Class to fuse certain pattern of subgraph into CircleInstanceNorm + * with auxiliary nodes + * + * For detailed subgraph pattern to be fused, please check its implementation. + */ +struct FuseInstanceNormPass final : public logo::Pass +{ + const char *name(void) const final { return "luci::FuseInstanceNormPass"; } + + bool run(loco::Graph *g) final; +}; + +} // namespace luci + +#endif // __LUCI_FUSE_INSTANCE_NORM_PASS_H__ diff --git a/compiler/luci/pass/include/luci/Pass/ShapeInferencePass.h b/compiler/luci/pass/include/luci/Pass/ShapeInferencePass.h new file mode 100644 index 00000000000..86bb2ab4206 --- /dev/null +++ b/compiler/luci/pass/include/luci/Pass/ShapeInferencePass.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_SHAPE_INFERENCE_PASS_H__ +#define __LUCI_SHAPE_INFERENCE_PASS_H__ + +#include + +#include + +namespace luci +{ + +/** + * @brief Pass to infer shape of nodes + */ +class ShapeInferencePass : public logo::Pass +{ +public: + virtual const char *name(void) const { return "luci::ShapeInferencePass"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace luci + +#endif //__LUCI_SHAPE_INFERENCE_PASS_H__ diff --git a/compiler/luci/pass/include/luci/Pass/TypeInferencePass.h b/compiler/luci/pass/include/luci/Pass/TypeInferencePass.h new file mode 100644 index 00000000000..c607ac63f0f --- /dev/null +++ b/compiler/luci/pass/include/luci/Pass/TypeInferencePass.h @@ -0,0 +1,42 @@ + +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_TYPE_INFERENCE_PASS_H__ +#define __LUCI_TYPE_INFERENCE_PASS_H__ + +#include + +#include + +namespace luci +{ + +/** + * @brief Pass to infer type of nodes + */ +class TypeInferencePass : public logo::Pass +{ +public: + virtual const char *name(void) const { return "luci::TypeInferencePass"; } + +public: + bool run(loco::Graph *graph); +}; + +} // namespace luci + +#endif //__LUCI_TYPE_INFERENCE_PASS_H__ diff --git a/compiler/luci/pass/src/FuseInstanceNormPass.cpp b/compiler/luci/pass/src/FuseInstanceNormPass.cpp new file mode 100644 index 00000000000..180b5bbef30 --- /dev/null +++ b/compiler/luci/pass/src/FuseInstanceNormPass.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Pass/FuseInstanceNormPass.h" + +#include + +#include + +#include +#include + +// Helper to find commutative node's arguments +namespace +{ + +/** + * INTRODUCTION + * Binary operation f(x,y) is 'commutative' when + * f(x,y) == f(y,x) holds for all x, y. + * For examples, ADD, MUL and SQUARED_DIFFERENCE are commutative. + * These helpers make it easy to find commutative arguemnts of commtative node. + * + * HOW TO USE + * COMM_NODE *node; + * ARG_TYPE_1 *arg1; + * ARG_TYPE_2 *arg2; + * + * bool ok = fill(&arg1, &arg2).with_commutative_args_of(node); + * + * Result + * If 'node's commutative argument types are actually {ARG_TYPE_1, ARG_TYPE_2} + * (as a set), 'arg1' and 'arg2' set as actual 'node's arguemnts with matching + * type, and return value 'ok' is true. + * Otherwise, 'arg1' and 'arg2' not changed, 'ok' is false. + */ + +template class NodeFiller final +{ +public: + NodeFiller(ARG_TYPE_1 **arg_1, ARG_TYPE_2 **arg_2) : _arg_1(arg_1), _arg_2(arg_2) + { + // DO NOTHING + } + + /** + * @return true When 'node's argument types are 'ARG_TYPE_1' and 'ARG_TYPE_2' + * In such case, it assign '_arg_1' and '_arg_2' to actual arguments + * + * @return false When 'node's argument types are NOT matched with 'ARG_TYPE_*' + * In such case, it does not amend '_arg_1' and '_arg_2' + * + * @require COMM_NODE has member x() and y() + */ + template bool with_commutative_args_of(const COMM_NODE *node); + +private: + ARG_TYPE_1 **_arg_1; + ARG_TYPE_2 **_arg_2; +}; + +template +inline NodeFiller fill(ARG_TYPE_1 **arg_1, ARG_TYPE_2 **arg_2) +{ + return NodeFiller{arg_1, arg_2}; +} + +template +template +bool NodeFiller::with_commutative_args_of(const COMM_NODE *node) +{ + // Case 1) X == ARG_TYPE_1 / Y == ARG_TYPE_2 + { + auto x = dynamic_cast(node->x()); + auto y = dynamic_cast(node->y()); + + if (x && y) + { + *_arg_1 = x; + *_arg_2 = y; + return true; + } + } + + // Case 2) X == ARG_TYPE_2 / Y == ARG_TYPE_1 + { + auto x = dynamic_cast(node->x()); + auto y = dynamic_cast(node->y()); + + if (x && y) + { + *_arg_1 = y; + *_arg_2 = x; + return true; + } + } + + return false; +} + +} // namespace + +// Helper to check detail +namespace +{ + +/// @return true When node has shape of '1 x .. x 1 x depth' +bool is_1D_with_dummy_dim(luci::CircleConst *node, uint32_t depth) +{ + auto rank = node->rank(); + uint32_t axis; + for (axis = 0; axis < rank - 1; ++axis) + { + if (node->dim(axis).value() != 1) + return false; + } + return node->dim(axis).value() == depth; +} + +bool is_instance_mean(luci::CircleMean *mean) +{ + // + // CHECK 1) input is rank 4 + // + auto input = mean->input(); + if (not loco::shape_known(input)) + return false; + auto input_shape = loco::shape_get(input).as(); + if (input_shape.rank() != 4) + return false; + + // + // CHECK 2) 'reduction indices' is CircleConst of value [1,2], that is HW of NHWC + // + // TODO Support equivalent case, like [-3,-2] + // TODO Support non-Const case? + // TODO What if input is NCHW format in Circle? + auto red_indices = dynamic_cast(mean->reduction_indices()); + if (not red_indices) + return false; + if (red_indices->rank() != 1) + return false; + std::set red_indices_set; + { + // TODO Currently only support S32, support other types + assert(red_indices->dtype() == loco::DataType::S32); + for (uint32_t i = 0; i < red_indices->dim(0).value(); ++i) + red_indices_set.insert(red_indices->at(i)); + } + if (red_indices_set.size() != 2) + return false; + if (red_indices_set.find(1) == red_indices_set.end()) + return false; + if (red_indices_set.find(2) == red_indices_set.end()) + return false; + + // + // CHECK 3) keep_dims == true (?) + // + // We only have case of 'keep_dims == true' so far, but it might be okay with 'keep_dims == false' + // TODO Check this fact, and if true, return true regardless of keep_dims + return mean->keep_dims(); +} + +} // namespace + +// Helper to fuse Instance Norm +namespace +{ + +/** + * SUBGRAPH PATTERN + * + * - Below diagram shows Instance Norm pattern to fuse. + * - Execution dependency order is top to the bottom. + * - Node name is matched with variable name of InstanceNormPattern class. + * - Usually, first word of node name (variable name) is node type. For e.g. + * variable 'mean_as_variance' is pointer to TFLMean. + * - (Item in parenthesis) means actually exist, but not having a name and + * not a variable of InstanceNormPattern class. + * + * TODO support other semantically same patterns for instance norm + * + * [In] + * | + * V + * +----------- ifm -----+ (reduction indicies) + * | | | | + * | | V V + * | | mean_of_ifm ----------------+ + * | V | | + * | sqdiff <--+ (reduction indicies) | + * | | | | + * | V | | + * | mean_as_variance <---+ const_as_epsilon | + * | | | | + * | V | | + * | add_as_variance <--------+ | + * | | | + * | V | + * | rsqrt const_as_gamma | + * | | | | + * | V | | + * | mul_gamma <--+ | + * | | | | + * V V V | + * mul_as_scaled_ifm mul_as_scaled_mean <-------------+ + * | | + * | const_as_beta | + * | | V + * | +------> sub + * V | + * add_as_terminal <----------+ + * | + * V + * [Out] + */ +class InstanceNormPattern final +{ +public: + InstanceNormPattern(luci::CircleAdd *candidate) + { + assert(candidate); + add_as_terminal = candidate; + } + +public: + bool matched(); + bool matched() const { return _matched; } + +public: + // Context + loco::Node *ifm = nullptr; + luci::CircleMean *mean_of_ifm = nullptr; + luci::CircleSquaredDifference *sqdiff = nullptr; + luci::CircleMean *mean_as_variance = nullptr; + luci::CircleConst *const_as_epsilon = nullptr; + luci::CircleAdd *add_as_variance = nullptr; + luci::CircleRsqrt *rsqrt = nullptr; + luci::CircleConst *const_as_gamma = nullptr; + luci::CircleMul *mul_gamma = nullptr; + luci::CircleMul *mul_as_scaled_ifm = nullptr; + luci::CircleMul *mul_as_scaled_mean = nullptr; + luci::CircleConst *const_as_beta = nullptr; + luci::CircleSub *sub = nullptr; + luci::CircleAdd *add_as_terminal = nullptr; + +private: + bool _matched = false; +}; + +bool InstanceNormPattern::matched() +{ + if (_matched) + return true; + +#define CHECK_OR_FALSE(condition) \ + if (not(condition)) \ + return false; + + // Check order is DFS + + CHECK_OR_FALSE(fill(&mul_as_scaled_ifm, &sub).with_commutative_args_of(add_as_terminal)); + CHECK_OR_FALSE(fill(&ifm, &mul_gamma).with_commutative_args_of(mul_as_scaled_ifm)); + + CHECK_OR_FALSE(loco::shape_known(ifm)); + auto ifm_shape = loco::shape_get(ifm); + CHECK_OR_FALSE(ifm_shape.domain() == loco::Domain::Tensor); + auto ifm_tensor_shape = ifm_shape.as(); + CHECK_OR_FALSE(ifm_tensor_shape.rank() == 4); + uint32_t ifm_channel_depth = ifm_tensor_shape.dim(3).value(); + + CHECK_OR_FALSE(fill(&rsqrt, &const_as_gamma).with_commutative_args_of(mul_gamma)); + CHECK_OR_FALSE(is_1D_with_dummy_dim(const_as_gamma, ifm_channel_depth)); + + add_as_variance = dynamic_cast(rsqrt->x()); + CHECK_OR_FALSE(add_as_variance); + + CHECK_OR_FALSE( + fill(&mean_as_variance, &const_as_epsilon).with_commutative_args_of(add_as_variance)); + + CHECK_OR_FALSE(const_as_epsilon->dtype() == loco::DataType::FLOAT32); + // TODO Support regarding broadcast + CHECK_OR_FALSE(const_as_epsilon->size() == 1); + + CHECK_OR_FALSE(is_instance_mean(mean_as_variance)); + sqdiff = dynamic_cast(mean_as_variance->input()); + CHECK_OR_FALSE(sqdiff); + + loco::Node *ifm_should_be = nullptr; + CHECK_OR_FALSE(fill(&ifm_should_be, &mean_of_ifm).with_commutative_args_of(sqdiff)); + CHECK_OR_FALSE(ifm == ifm_should_be); + CHECK_OR_FALSE(is_instance_mean(mean_of_ifm)); + CHECK_OR_FALSE(ifm == mean_of_ifm->input()); + + const_as_beta = dynamic_cast(sub->x()); + CHECK_OR_FALSE(const_as_beta); + CHECK_OR_FALSE(is_1D_with_dummy_dim(const_as_beta, ifm_channel_depth)); + + mul_as_scaled_mean = dynamic_cast(sub->y()); + CHECK_OR_FALSE(mul_as_scaled_mean); + + luci::CircleMul *mul_gamma_should_be = nullptr; + luci::CircleMean *mean_of_ifm_should_be = nullptr; + CHECK_OR_FALSE(fill(&mul_gamma_should_be, &mean_of_ifm_should_be) + .with_commutative_args_of(mul_as_scaled_mean)); + CHECK_OR_FALSE(mul_gamma == mul_gamma_should_be); + CHECK_OR_FALSE(mean_of_ifm == mean_of_ifm_should_be); +#undef CHECK_OR_FALSE + _matched = true; + return true; +} + +/** + * Instance norm pattern would be fused like following diagram: + * + * [In] --------------------------- CircleInstanceNorm --- [Out] + * / / + * const_as_gamma --- TFLReshape --- / + * / + * const_as_beta ---- TFLReshape --- + * + * Note + * - 'const_as_gamma' and 'const_as_beta' are from original graph + * - Value of 'const_as_epsilon' would be copied to CircleInstanceNorm's attribute + * - TFLReshape is added as CircleInstanceNorm only accept 1D tensor + * - 'CircleConst --- TFLReshape' is expected to be fused in constant folding for Reshape + */ +void fuse_instance_norm(const InstanceNormPattern &p) +{ + assert(p.matched()); + + auto graph = p.add_as_terminal->graph(); + + // Make reshape for gamma & beta + auto reshape_gamma = graph->nodes()->create(); + auto reshape_beta = graph->nodes()->create(); + { + auto ifm_shape = loco::shape_get(p.ifm).as(); + uint32_t ifm_channel_depth = ifm_shape.dim(3).value(); + + int32_t new_shape[1] = {static_cast(ifm_channel_depth)}; + + reshape_gamma->tensor(p.const_as_gamma); + reshape_beta->tensor(p.const_as_beta); + + luci::set_new_shape(reshape_gamma, new_shape, 1); + luci::set_new_shape(reshape_beta, new_shape, 1); + } + + // Make Instance Norm to replace + auto instance_norm = graph->nodes()->create(); + instance_norm->input(p.ifm); + instance_norm->gamma(reshape_gamma); + instance_norm->beta(reshape_beta); + float epsilon = p.const_as_epsilon->at(0); + instance_norm->epsilon(epsilon); + instance_norm->fusedActivationFunction(p.add_as_terminal->fusedActivationFunction()); + + replace(p.add_as_terminal).with(instance_norm); +} + +} // namespace + +namespace luci +{ + +bool FuseInstanceNormPass::run(loco::Graph *g) +{ + bool changed = false; + for (auto node : loco::active_nodes(loco::output_nodes(g))) + { + auto add = dynamic_cast(node); + if (not add) + continue; + + InstanceNormPattern pattern(add); + if (not pattern.matched()) + continue; + + fuse_instance_norm(pattern); + changed = true; + } + + return changed; +} + +} // namespace luci diff --git a/compiler/luci/pass/src/ShapeInferencePass.cpp b/compiler/luci/pass/src/ShapeInferencePass.cpp new file mode 100644 index 00000000000..f681b3d5fbb --- /dev/null +++ b/compiler/luci/pass/src/ShapeInferencePass.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Pass/ShapeInferencePass.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace luci +{ + +bool ShapeInferencePass::run(loco::Graph *g) +{ + loco::CanonicalShapeInferenceRule canonical_rule; + luci::CircleShapeInferenceRule circle_rule; + + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(luci::CircleDialect::get(), &circle_rule); + + return loco::apply(&rules).to(g); +} + +} // namespace luci diff --git a/compiler/luci/pass/src/TypeInferencePass.cpp b/compiler/luci/pass/src/TypeInferencePass.cpp new file mode 100644 index 00000000000..2c7b3a89712 --- /dev/null +++ b/compiler/luci/pass/src/TypeInferencePass.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Pass/TypeInferencePass.h" + +#include +#include + +#include +#include +#include + +namespace luci +{ + +bool TypeInferencePass::run(loco::Graph *g) +{ + loco::CanonicalTypeInferenceRule canonical_rule; + luci::CircleTypeInferenceRule circle_rule; + + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(luci::CircleDialect::get(), &circle_rule); + + return loco::apply(&rules).to(g); +} + +} // namespace luci diff --git a/compiler/luci/requires.cmake b/compiler/luci/requires.cmake new file mode 100644 index 00000000000..e878c8036a8 --- /dev/null +++ b/compiler/luci/requires.cmake @@ -0,0 +1,10 @@ +require("loco") +require("locop") +require("logo-core") +require("stdex") +require("mio-circle") +require("oops") +require("hermes") +require("hermes-std") +require("tflchef") +require("tflite2circle") diff --git a/compiler/luci/service/CMakeLists.txt b/compiler/luci/service/CMakeLists.txt new file mode 100644 index 00000000000..2c7b9f358d9 --- /dev/null +++ b/compiler/luci/service/CMakeLists.txt @@ -0,0 +1,26 @@ +file(GLOB_RECURSE SOURCES "src/*.cpp") +file(GLOB_RECURSE TESTS "src/*.test.cpp") +list(REMOVE_ITEM SOURCES ${TESTS}) + +add_library(luci_service SHARED ${SOURCES}) +target_include_directories(luci_service PRIVATE src) +target_include_directories(luci_service PUBLIC include) +target_link_libraries(luci_service PUBLIC luci_lang) +target_link_libraries(luci_service PUBLIC mio_circle) +target_link_libraries(luci_service PUBLIC stdex) +target_link_libraries(luci_service PUBLIC logo_core) +target_link_libraries(luci_service PRIVATE luci_log) +target_link_libraries(luci_service PRIVATE nncc_common) +target_link_libraries(luci_service PRIVATE oops) +install(TARGETS luci_service DESTINATION lib) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +GTest_AddTest(luci_service_test ${TESTS}) +target_include_directories(luci_service_test PRIVATE src) +target_link_libraries(luci_service_test luci_service) +target_link_libraries(luci_service_test oops) diff --git a/compiler/luci/service/README.md b/compiler/luci/service/README.md new file mode 100644 index 00000000000..ac358314513 --- /dev/null +++ b/compiler/luci/service/README.md @@ -0,0 +1,3 @@ +# luci-service + +_luci-service_ provides Circle Dialect Services diff --git a/compiler/luci/service/include/luci/Service/CircleShapeInference.h b/compiler/luci/service/include/luci/Service/CircleShapeInference.h new file mode 100644 index 00000000000..fb934c2cfa0 --- /dev/null +++ b/compiler/luci/service/include/luci/Service/CircleShapeInference.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_CIRCLE_SHAPE_INFERENCE_H__ +#define __LUCI_CIRCLE_SHAPE_INFERENCE_H__ + +#include "ShapeDescription.h" + +#include + +namespace luci +{ + +/** + * @brief Get the shape of each node as a node annotation + * + * HOW TO USE + * + * ShapeInference::get(g->nodes()->at(..)); + */ +struct ShapeInference +{ + static ShapeDescription get(loco::Node *node); +}; + +} // namespace luci + +#endif // __LUCI_CIRCLE_SHAPE_INFERENCE_H__ diff --git a/compiler/luci/service/include/luci/Service/CircleShapeInferenceRule.h b/compiler/luci/service/include/luci/Service/CircleShapeInferenceRule.h new file mode 100644 index 00000000000..3f63c9633ba --- /dev/null +++ b/compiler/luci/service/include/luci/Service/CircleShapeInferenceRule.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_CIRCLE_SHAPE_INFERENCE_RULE_H__ +#define __LUCI_CIRCLE_SHAPE_INFERENCE_RULE_H__ + +#include + +namespace luci +{ + +struct CircleShapeInferenceRule final : public loco::ShapeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::NodeShape &) const final; +}; + +} // namespace luci + +#endif // __LUCI_CIRCLE_SHAPE_INFERENCE_RULE_H__ diff --git a/compiler/luci/service/include/luci/Service/CircleTypeInference.h b/compiler/luci/service/include/luci/Service/CircleTypeInference.h new file mode 100644 index 00000000000..ea7a3c5edef --- /dev/null +++ b/compiler/luci/service/include/luci/Service/CircleTypeInference.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_CIRCLE_TYPE_INFERENCE_H__ +#define __LUCI_CIRCLE_TYPE_INFERENCE_H__ + +#include + +#include + +namespace luci +{ + +/** + * @brief Get the type of each node as NodeAnnotation + * + * HOW TO USE + * + * TypeInference::get(g->nodes()->at(0)); + * TypeInference::get(g->nodes()->at(...)); + */ +struct TypeInference +{ + static circle::TensorType get(loco::Node *node); +}; + +} // namespace luci + +#endif // __LUCI_CIRCLE_TYPE_INFERENCE_H__ diff --git a/compiler/luci/service/include/luci/Service/CircleTypeInferenceRule.h b/compiler/luci/service/include/luci/Service/CircleTypeInferenceRule.h new file mode 100644 index 00000000000..3b21081ef26 --- /dev/null +++ b/compiler/luci/service/include/luci/Service/CircleTypeInferenceRule.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_CIRCLE_TYPE_INFERENCE_RULE_H__ +#define __LUCI_CIRCLE_TYPE_INFERENCE_RULE_H__ + +#include + +namespace luci +{ + +/** + * @brief Type Inference Rule for CircleDialect + */ +struct CircleTypeInferenceRule final : public loco::TypeInferenceRule +{ + bool recognize(const loco::Dialect *) const final; + bool infer(const loco::Node *, loco::DataType &) const final; +}; + +} // namespace luci + +#endif // __LUCI_CIRCLE_TYPE_INFERENCE_RULE_H__ diff --git a/compiler/luci/service/include/luci/Service/ShapeDescription.h b/compiler/luci/service/include/luci/Service/ShapeDescription.h new file mode 100644 index 00000000000..949cce5353d --- /dev/null +++ b/compiler/luci/service/include/luci/Service/ShapeDescription.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_SHAPE_DESCRIPTION_H__ +#define __LUCI_SHAPE_DESCRIPTION_H__ + +#include +#include + +#include +#include + +namespace luci +{ + +struct ShapeDescription +{ + std::vector _dims; + bool _rank_known; +}; + +// TODO remove these when CircleDialect is fully functioal +ShapeDescription to_shape_description(const loco::TensorShape &shape); +ShapeDescription to_shape_description(const loco::FeatureShape &shape); +ShapeDescription to_shape_description(const loco::FilterShape &shape); +ShapeDescription to_shape_description(const loco::BiasShape &shape); +ShapeDescription to_shape_description(const loco::MatrixShape &shape); +ShapeDescription to_shape_description(const loco::NodeShape &shape); + +template inline bool isNHWC(Permutation *perm); + +template <> inline bool isNHWC(loco::Permutation *perm) +{ + return perm->axis(loco::FeatureAxis::Count) == 0 && perm->axis(loco::FeatureAxis::Height) == 1 && + perm->axis(loco::FeatureAxis::Width) == 2 && perm->axis(loco::FeatureAxis::Depth) == 3; +} + +template <> inline bool isNHWC(loco::Permutation *perm) +{ + return perm->axis(loco::FilterAxis::Count) == 0 && perm->axis(loco::FilterAxis::Height) == 1 && + perm->axis(loco::FilterAxis::Width) == 2 && perm->axis(loco::FilterAxis::Depth) == 3; +} + +} // namespace luci + +#endif // __LUCI_SHAPE_DESCRIPTION_H__ diff --git a/compiler/luci/service/include/luci/Service/Validate.h b/compiler/luci/service/include/luci/Service/Validate.h new file mode 100644 index 00000000000..4b80d1d16f4 --- /dev/null +++ b/compiler/luci/service/include/luci/Service/Validate.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __LUCI_SERVICE_VALIDATE_H__ +#define __LUCI_SERVICE_VALIDATE_H__ + +#include + +namespace luci +{ + +bool validate(loco::Graph *); + +} // namespace luci + +#endif // __LUCI_SERVICE_VALIDATE_H__ diff --git a/compiler/luci/service/src/Check.h b/compiler/luci/service/src/Check.h new file mode 100644 index 00000000000..e05ec904aec --- /dev/null +++ b/compiler/luci/service/src/Check.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __CHECK_H__ +#define __CHECK_H__ + +#include +#include +#include + +// TODO Add macro for Release version + +#define LUCI_ASSERT(condition, msg) \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[assert failed] " << (msg) << ". " << std::endl; \ + assert((condition)); \ + } \ + } + +#endif // __CHECK_H__ diff --git a/compiler/luci/service/src/CircleShapeInference.cpp b/compiler/luci/service/src/CircleShapeInference.cpp new file mode 100644 index 00000000000..fdcfa76bc0f --- /dev/null +++ b/compiler/luci/service/src/CircleShapeInference.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/CircleShapeInference.h" +#include "luci/Service/ShapeDescription.h" + +#include +#include + +#include + +namespace luci +{ + +ShapeDescription ShapeInference::get(loco::Node *node) +{ + // TODO Adjust indentation level + { + assert(loco::shape_known(node)); + return to_shape_description(loco::shape_get(node)); + } +} + +} // namespace luci diff --git a/compiler/luci/service/src/CircleShapeInferenceRule.cpp b/compiler/luci/service/src/CircleShapeInferenceRule.cpp new file mode 100644 index 00000000000..4742771e3ed --- /dev/null +++ b/compiler/luci/service/src/CircleShapeInferenceRule.cpp @@ -0,0 +1,736 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/CircleShapeInferenceRule.h" +#include "Check.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace +{ + +// Call this for CircleAvgPool2D and CircleMaxPool2D only +template loco::NodeShape infer_pool_2d_shape(const Pool2DType *node) +{ + LUCI_ASSERT(loco::shape_known(node->value()), "Shape must be known"); + + auto ifm_shape = loco::shape_get(node->value()).template as(); + assert(ifm_shape.rank() == 4); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t window_height = node->filter()->h(); + uint32_t window_width = node->filter()->w(); + uint32_t dilation_height = 1; // dilation for CircleAvgPool2D and CircleMaxPool2D is 1 + uint32_t dilation_width = 1; + uint32_t effective_window_height = dilation_height * (window_height - 1) + 1; + uint32_t effective_window_width = dilation_width * (window_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == luci::Padding::VALID) + { + output_height = (input_height + stride_height - effective_window_height) / stride_height; + output_width = (input_width + stride_width - effective_window_width) / stride_width; + } + else if (node->padding() == luci::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + LUCI_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ifm_shape.dim(3); + + return loco::NodeShape{ofm_shape}; +} + +/** + * @brief Create a higher-rank TensorShape following NumPy broadcasting semantics + * + * HOW TO USE: + * + * auto expanded_tensor_shape = expand(tensor_shape).to(N); + */ +class TensorShapeExpander +{ +public: + TensorShapeExpander(const loco::TensorShape &shape) : _shape{shape} + { + // DO NOTHING + } + +public: + loco::TensorShape to(uint32_t output_rank) + { + auto const &input_shape = _shape; + uint32_t const input_rank = input_shape.rank(); + + assert(input_rank <= output_rank && "Cannot shrink rank"); + uint32_t const axis_shift = output_rank - input_rank; + + loco::TensorShape output_shape; + + output_shape.rank(output_rank); + for (uint32_t axis = 0; axis < output_rank; ++axis) + { + output_shape.dim(axis) = (axis < axis_shift) ? 1 : input_shape.dim(axis - axis_shift); + } + + return output_shape; + } + +private: + const loco::TensorShape _shape; +}; + +/** + * @breif Expand shape x and y to same rank by align right and filling with 1 + */ +void expand_rank(loco::TensorShape &x, loco::TensorShape &y) +{ + auto x_rank = x.rank(); + auto y_rank = y.rank(); + + if (x_rank == y_rank) + return; + + TensorShapeExpander x_exp(x); + TensorShapeExpander y_exp(y); + + auto xy_rank = std::max(x_rank, y_rank); + + x = x_rank > y_rank ? x : x_exp.to(xy_rank); + y = y_rank > x_rank ? y : y_exp.to(xy_rank); +} + +/** + * @breif Returns shape of expanded dimension of input x and y having same rank + */ +loco::TensorShape expand_dimension(const loco::TensorShape &x, const loco::TensorShape &y) +{ + assert(x.rank() == y.rank()); + + auto rank = x.rank(); + + loco::TensorShape output_shape; + + output_shape.rank(rank); + for (uint32_t axis = 0; axis < rank; ++axis) + { + assert(x.dim(axis).known() && y.dim(axis).known()); + + auto x_dim = x.dim(axis).value(); + auto y_dim = y.dim(axis).value(); + + // each dimension of x and y should be same or one must be 1 if different + if (!((x_dim == y_dim) || (x_dim == 1 || y_dim == 1))) + INTERNAL_EXN("Cannot produce expand_dimension of two shapes"); + + output_shape.dim(axis) = std::max(x_dim, y_dim); + } + + return output_shape; +} + +loco::TensorShape broadcast_shape(const loco::TensorShape &x, const loco::TensorShape &y) +{ + auto x_match = x; + auto y_match = y; + + expand_rank(x_match, y_match); + + auto output_shape = expand_dimension(x_match, y_match); + + return output_shape; +} + +/** + * @brief Class to infer the shape of CircleNode + * + * @note All CircleNode's inputs and outputs are always loco::Domain::Tensor + */ +class ShapeInferenceAlgorithm final : public luci::CircleNodeVisitor +{ +public: + loco::NodeShape visit(const luci::CircleAdd *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleArgMax *node) final + { + auto input_shape = loco::shape_get(node->input()).as(); + auto dimension_shape = loco::shape_get(node->dimension()).as(); + + int64_t select_axis = 0; + { + LUCI_ASSERT(node->dimension(), "2nd input dimension() should not be nullptr"); + + // Only support node's shape() is CircleConst with S32/S64 + // Support S32 for now. + auto const_shape_node = dynamic_cast(node->dimension()); + LUCI_ASSERT(const_shape_node, "Only support CircleConst for shape of CircleArgMax"); + LUCI_ASSERT(const_shape_node->dtype() == loco::DataType::S32, + "Only support int32 CircleConst for CircleArgMax"); + + if (const_shape_node->rank() > 1) + INTERNAL_EXN_V("Only support rank 0/1 CircleConst", + oops::to_uint32(const_shape_node->rank())); + + select_axis = const_shape_node->scalar(); + } + assert(select_axis < input_shape.rank()); + assert(select_axis >= 0); // TODO support minus of this breaks + + // NOTE select_axis is removed + loco::TensorShape shape_output; + uint32_t rank = input_shape.rank(); + uint32_t shrink = static_cast(select_axis); + assert(rank > 0); + shape_output.rank(rank - 1); + for (uint32_t r = 0, d = 0; r < rank; ++r) + { + if (r == shrink) + continue; + shape_output.dim(d++) = input_shape.dim(r); + } + return loco::NodeShape{shape_output}; + } + + loco::NodeShape visit(const luci::CircleAveragePool2D *node) final + { + return infer_pool_2d_shape(node); + } + + loco::NodeShape visit(const luci::CircleConcatenation *node) final + { + // TODO Support when CircleConcatenation has 0 input + assert(node->numValues() > 0); + + auto axis = node->axis(); + auto first_shape = loco::shape_get(node->values(0)).as(); + + loco::TensorShape output_shape; + + output_shape.rank(first_shape.rank()); + for (uint32_t i = 0; i < output_shape.rank(); ++i) + output_shape.dim(i) = first_shape.dim(i); + + for (uint32_t i = 1; i < node->numValues(); ++i) + { + auto input_shape = loco::shape_get(node->values(i)).as(); + + for (uint32_t j = 0; j < output_shape.rank(); ++j) + { + if (j == axis) + output_shape.dim(j) = output_shape.dim(j).value() + input_shape.dim(j).value(); + else + assert(output_shape.dim(j) == input_shape.dim(j)); + } + } + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleConst *node) final + { + loco::TensorShape shape; + + shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); axis++) + shape.dim(axis) = node->dim(axis); + + return loco::NodeShape{shape}; + } + + loco::NodeShape visit(const luci::CircleConv2D *node) final + { + LOGGER(l); + + auto ifm_shape = loco::shape_get(node->input()).as(); // in NHWC + auto ker_shape = loco::shape_get(node->filter()).as(); // in OHWI + + INFO(l) << "[luci] CircleConv2D ShapeInf ifm(" << ifm_shape.rank() << ") ker(" + << ker_shape.rank() << ")" << std::endl; + + assert(ifm_shape.rank() == 4); + assert(ker_shape.rank() == 4); + assert(ifm_shape.dim(3) == ker_shape.dim(3)); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t ker_height = ker_shape.dim(1).value(); + uint32_t ker_width = ker_shape.dim(2).value(); + uint32_t dilation_height = 1; + uint32_t dilation_width = 1; + uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1; + uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == luci::Padding::VALID) + { + output_height = (input_height + stride_height - effective_ker_height) / stride_height; + output_width = (input_width + stride_width - effective_ker_width) / stride_width; + } + else if (node->padding() == luci::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + LUCI_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ker_shape.dim(0); + + return loco::NodeShape{ofm_shape}; + } + + loco::NodeShape visit(const luci::CircleDepthwiseConv2D *node) final + { + auto ifm_shape = loco::shape_get(node->input()).as(); // in NHWC + auto ker_shape = loco::shape_get(node->filter()).as(); // in 1 H W CM + + assert(ifm_shape.rank() == 4); + assert(ker_shape.rank() == 4); + assert(ker_shape.dim(0).value() == 1); + + uint32_t input_height = ifm_shape.dim(1).value(); + uint32_t input_width = ifm_shape.dim(2).value(); + uint32_t stride_height = node->stride()->h(); + uint32_t stride_width = node->stride()->w(); + uint32_t ker_height = ker_shape.dim(1).value(); + uint32_t ker_width = ker_shape.dim(2).value(); + uint32_t dilation_height = 1; + uint32_t dilation_width = 1; + uint32_t effective_ker_height = dilation_height * (ker_height - 1) + 1; + uint32_t effective_ker_width = dilation_width * (ker_width - 1) + 1; + + uint32_t output_height = 0; + uint32_t output_width = 0; + + if (node->padding() == luci::Padding::VALID) + { + output_height = (input_height + stride_height - effective_ker_height) / stride_height; + output_width = (input_width + stride_width - effective_ker_width) / stride_width; + } + else if (node->padding() == luci::Padding::SAME) + { + output_height = (input_height + stride_height - 1) / stride_height; + output_width = (input_width + stride_width - 1) / stride_width; + } + else + LUCI_ASSERT(false, "Wrong padding type"); + + loco::TensorShape ofm_shape; + ofm_shape.rank(4); + ofm_shape.dim(0) = ifm_shape.dim(0); + ofm_shape.dim(1) = output_height; + ofm_shape.dim(2) = output_width; + ofm_shape.dim(3) = ker_shape.dim(3); + + return loco::NodeShape{ofm_shape}; + } + + loco::NodeShape visit(const luci::CircleDiv *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleFullyConnected *node) final + { + auto input_shape = loco::shape_get(node->input()).as(); + auto weights_shape = loco::shape_get(node->weights()).as(); + + // Checking shape capability for multiplication + LUCI_ASSERT(input_shape.rank() == 2, "NYI for input shape rank > 2"); + LUCI_ASSERT(weights_shape.rank() == 2, "Incompatible weights rank for fully connected"); + LUCI_ASSERT(input_shape.dim(1) == weights_shape.dim(1), + "Incompatible shapes for fully connected"); + + loco::TensorShape out_shape; + out_shape.rank(2); + + out_shape.dim(0) = input_shape.dim(0); + out_shape.dim(1) = weights_shape.dim(0); + + return loco::NodeShape{out_shape}; + } + + loco::NodeShape visit(const luci::CircleMaximum *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleMaxPool2D *node) final + { + return infer_pool_2d_shape(node); + } + + loco::NodeShape visit(const luci::CircleMean *node) final + { + const loco::DataType S32 = loco::DataType::S32; + + auto input_shape = loco::shape_get(node->input()).as(); + auto reduction_indices = dynamic_cast(node->reduction_indices()); + + { // Exceptions + // TODO support non-const case + LUCI_ASSERT(reduction_indices, "Only support constant reduction_indices"); + // TODO support other data type + LUCI_ASSERT(reduction_indices->dtype() == S32, "Only support int 32"); + } + + std::vector reduction_values; + + for (uint32_t i = 0; i < reduction_indices->size(); ++i) + { + int32_t axis = reduction_indices->at(i); + if (axis < 0) + axis += input_shape.rank(); + if (not(0 <= axis and axis < static_cast(input_shape.rank()))) + INTERNAL_EXN_V("Invalid reduction axis for MEAN", oops::to_uint32(axis)); + reduction_values.push_back(axis); + } + + loco::TensorShape output_shape; + + if (node->keep_dims()) + { + output_shape.rank(input_shape.rank()); + for (uint32_t i = 0; i < input_shape.rank(); ++i) + output_shape.dim(i) = input_shape.dim(i); + for (uint32_t i = 0; i < reduction_values.size(); ++i) + output_shape.dim(reduction_values.at(i)) = 1; + } + else + { + std::vector check_reduce(input_shape.rank(), false); + for (uint32_t i = 0; i < reduction_values.size(); ++i) + check_reduce.at(reduction_values.at(i)) = true; + + uint32_t reduce_cnt = 0; + for (uint32_t i = 0; i < check_reduce.size(); ++i) + if (check_reduce.at(i)) + ++reduce_cnt; + + output_shape.rank(input_shape.rank() - reduce_cnt); + for (uint32_t i = 0, j = 0; i < check_reduce.size(); ++i) + if (check_reduce.at(i) == false) + output_shape.dim(j++) = i; + } + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleMul *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CirclePad *node) final + { + const loco::DataType S32 = loco::DataType::S32; + + auto input_shape = loco::shape_get(node->input()).as(); + auto paddings = dynamic_cast(node->paddings()); + + // TODO support non-const case + LUCI_ASSERT(paddings, "Only support constant reduction_indices"); + // TODO support other data type + LUCI_ASSERT(paddings->dtype() == S32, "Only support int 32 for now"); + LUCI_ASSERT(paddings->rank() == 2, "paddings should be rank 2") + + int32_t n = paddings->dim(0).value(); + int32_t v = paddings->dim(1).value(); + + LUCI_ASSERT(v == 2, "paddings should be [n, 2]"); + LUCI_ASSERT(n == int32_t(input_shape.rank()), + "paddings [n, 2] should have same value of input rank"); + + loco::TensorShape output_shape; + + output_shape.rank(input_shape.rank()); + for (int32_t ni = 0; ni < n; ++ni) + { + int32_t idx = ni * 2; + int value = input_shape.dim(ni).value(); + value += paddings->at(idx + 0); // left + value += paddings->at(idx + 1); // right + output_shape.dim(ni) = value; + } + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleRelu *node) final + { + auto input_shape = loco::shape_get(node->features()).as(); + + return loco::NodeShape{input_shape}; + } + + loco::NodeShape visit(const luci::CircleRelu6 *node) final + { + auto input_shape = loco::shape_get(node->features()).as(); + + return loco::NodeShape{input_shape}; + } + + /** + * @note CircleReshape has new shape info in two places: 2nd input and attribute. + * This shape inference forces both to exist, and match each other. + * When this condition satisfied, it return the inferred shape + * + * TODO Change this policy when not appropriate + */ + loco::NodeShape visit(const luci::CircleReshape *node) final + { + const loco::DataType S32 = loco::DataType::S32; + + loco::TensorShape shape_by_input; + { + LUCI_ASSERT(node->shape(), "2nd input shape() should not be nullptr"); + + // Only support node's shape() is CircleConst with S32 + // TODO support other node with other types + auto const_shape_node = dynamic_cast(node->shape()); + LUCI_ASSERT(const_shape_node, "Only support CircleConst for shape of CircleReshape"); + LUCI_ASSERT(const_shape_node->dtype() == S32, "Only support int32 CircleConst"); + + if (const_shape_node->rank() != 1) + INTERNAL_EXN_V("Only support rank 1 CircleConst", + oops::to_uint32(const_shape_node->rank())); + + shape_by_input.rank(const_shape_node->dim(0).value()); + + for (uint32_t axis = 0; axis < shape_by_input.rank(); ++axis) + { + LUCI_ASSERT(const_shape_node->at(axis) > 0, "Dimension should be > 0") + shape_by_input.dim(axis) = const_shape_node->at(axis); + } + } + + loco::TensorShape shape_by_attr; + { + shape_by_attr.rank(node->newShape()->rank()); + + for (uint32_t axis = 0; axis < shape_by_attr.rank(); ++axis) + { + LUCI_ASSERT(node->newShape()->dim(axis) > 0, "Dimension should be > 0") + shape_by_attr.dim(axis) = node->newShape()->dim(axis); + } + } + + LUCI_ASSERT(shape_by_input == shape_by_attr, + "Warning: Two new shape information mismatched for CircleReshape"); + + return loco::NodeShape{shape_by_input}; + } + + loco::NodeShape visit(const luci::CircleRsqrt *node) final + { + auto input_shape = loco::shape_get(node->x()).as(); + + return loco::NodeShape{input_shape}; + } + + // TODO CircleSoftmax + + loco::NodeShape visit(const luci::CircleSqrt *node) final + { + auto input_shape = loco::shape_get(node->x()).as(); + + return loco::NodeShape{input_shape}; + } + + loco::NodeShape visit(const luci::CircleSquaredDifference *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + loco::NodeShape visit(const luci::CircleSub *node) final + { + auto x_shape = loco::shape_get(node->x()).as(); + auto y_shape = loco::shape_get(node->y()).as(); + + auto output_shape = broadcast_shape(x_shape, y_shape); + + return loco::NodeShape{output_shape}; + } + + // TODO CircleTanh + + /// @brief Returns output shape of transpose. Use loco::ConstGen and luci::CircleConst for ConstT. + template + loco::TensorShape output_shape_of_transpose(loco::TensorShape input_shape, + const ConstT *perm_node) + { + loco::TensorShape output_shape; + output_shape.rank(input_shape.rank()); + + assert(perm_node->dtype() == loco::DataType::S32); + assert(input_shape.rank() == perm_node->template size()); + + for (uint32_t out_axis = 0; out_axis < output_shape.rank(); out_axis++) + { + auto new_dim = perm_node->template at(out_axis); + output_shape.dim(new_dim) = input_shape.dim(out_axis); + } + + return output_shape; + } + + loco::NodeShape visit(const luci::CircleTranspose *node) final + { + auto input_shape = loco::shape_get(node->a()).as(); + + auto canon_perm = dynamic_cast(node->perm()); + auto tfl_perm = dynamic_cast(node->perm()); + + if (canon_perm) + { + return loco::NodeShape{output_shape_of_transpose(input_shape, canon_perm)}; + } + else if (tfl_perm) + { + return loco::NodeShape{output_shape_of_transpose(input_shape, tfl_perm)}; + } + else + INTERNAL_EXN("perm of CircleTranspose should be either ConstGen or CircleConst"); + } + + loco::NodeShape visit(const luci::CircleTransposeConv *node) final + { + // TransposeConv's output shape is written in its 'inputSizes' argument + auto input_sizes_const = dynamic_cast(node->inputSizes()); + LUCI_ASSERT(input_sizes_const, + "Only support when CircleTransposeConv's inputSizes is CircleConst") + LUCI_ASSERT(input_sizes_const->dtype() == loco::DataType::S32, "Only support S32 dtype") + LUCI_ASSERT(input_sizes_const->rank() == 1 && input_sizes_const->dim(0).value() == 4, + "Only support rank 1 with 4 entries") + + loco::TensorShape shape; + + shape.rank(4); + for (uint32_t axis = 0; axis < 4; ++axis) + shape.dim(axis) = input_sizes_const->at(axis); + + return loco::NodeShape{shape}; + } + + // Circle Only + loco::NodeShape visit(const luci::CircleInstanceNorm *node) final + { + auto input_shape = loco::shape_get(node->input()).as(); + + return loco::NodeShape{input_shape}; + } + + // Virtual + loco::NodeShape visit(const luci::CircleInput *node) final + { + loco::TensorShape shape; + + shape.rank(node->rank()); + for (uint32_t axis = 0; axis < node->rank(); axis++) + shape.dim(axis) = node->dim(axis); + + return loco::NodeShape{shape}; + } + + loco::NodeShape visit(const luci::CircleOutput *node) final + { + auto from_shape = loco::shape_get(node->from()).as(); + + return loco::NodeShape{from_shape}; + } +}; + +} // namespace + +namespace luci +{ + +bool CircleShapeInferenceRule::recognize(const loco::Dialect *d) const +{ + return CircleDialect::get() == d; +} + +bool CircleShapeInferenceRule::infer(const loco::Node *node, loco::NodeShape &shape) const +{ + assert(node->dialect() == CircleDialect::get()); + assert(dynamic_cast(node) != nullptr); + + ShapeInferenceAlgorithm alg; + shape = dynamic_cast(node)->accept(&alg); + + return true; +} + +} // namespace luci diff --git a/compiler/luci/service/src/CircleShapeInferenceRule.test.cpp b/compiler/luci/service/src/CircleShapeInferenceRule.test.cpp new file mode 100644 index 00000000000..dcef9f19884 --- /dev/null +++ b/compiler/luci/service/src/CircleShapeInferenceRule.test.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TestGraph.h" +#include "luci/Service/CircleShapeInferenceRule.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + +bool shape_pass(loco::Graph *g) +{ + loco::CanonicalShapeInferenceRule canonical_rule; + luci::CircleShapeInferenceRule circle_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(luci::CircleDialect::get(), &circle_rule); + + return loco::apply(&rules).to(g); +} + +} // namespace + +TEST(CircleShapeInferenceRuleTest, minimal_with_CircleRelu) +{ + // Create a simple network + luci::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(tfl_node); + + // set shape + { + graph.pull->rank(2); + graph.pull->dim(0) = 3; + graph.pull->dim(1) = 4; + } + + // pre-check + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + luci::CircleShapeInferenceRule tfl_rule; + loco::CanonicalShapeInferenceRule canonical_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(luci::CircleDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 2); + ASSERT_EQ(shape.dim(0), 3); + ASSERT_EQ(shape.dim(1), 4); + } +} + +// based on the case shown in +// https://www.corvil.com/kb/what-is-the-difference-between-same-and-valid-padding-in-tf-nn-max-pool-of-tensorflow +TEST(CircleShapeInferenceRuleTest, avgpool2d_valid) +{ + luci::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(); + + auto pull = graph.pull; + { + pull->shape({1, 4, 3, 1}); + } + // setting CircleAveragePool2D + { + tfl_node->filter()->h(2); + tfl_node->filter()->w(2); + tfl_node->stride()->h(2); + tfl_node->stride()->w(2); + tfl_node->fusedActivationFunction(luci::FusedActFunc::NONE); + tfl_node->padding(luci::Padding::VALID); + } + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + luci::CircleShapeInferenceRule tfl_rule; + loco::CanonicalShapeInferenceRule canonical_rule; + loco::MultiDialectShapeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canonical_rule) + .bind(luci::CircleDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0).value(), 1); + ASSERT_EQ(shape.dim(1).value(), 2); + ASSERT_EQ(shape.dim(2).value(), 1); + ASSERT_EQ(shape.dim(3).value(), 1); + } +} + +TEST(CircleShapeInferenceRuleTest, avgpool2d_same) +{ + luci::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(); + + auto pull = graph.pull; + { + pull->shape({1, 4, 3, 1}); + } + + // setting CircleAveragePool2D + { + tfl_node->filter()->h(2); + tfl_node->filter()->w(2); + tfl_node->stride()->h(2); + tfl_node->stride()->w(2); + tfl_node->fusedActivationFunction(luci::FusedActFunc::NONE); + tfl_node->padding(luci::Padding::SAME); + } + + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + shape_pass(graph.g.get()); + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0).value(), 1); + ASSERT_EQ(shape.dim(1).value(), 2); + ASSERT_EQ(shape.dim(2).value(), 2); + ASSERT_EQ(shape.dim(3).value(), 1); + } +} + +/** + * @note Function to test: Shape inference of two different input shapes + * + * Rank expansion to higher input side + * x(2,1,5) + y(3,5) --> x(2,1,5) + y(1,3,5) + * Do output shape inference like numpy + * x(2,1,5) + y(1,3,5) --> output(2,3,5) + * For each axis, dim value should be same OR one of them should be 1 + */ +TEST(CircleShapeInferenceRuleTest, TFAdd_shapeinf_different) +{ + auto g = loco::make_graph(); + + auto x_node = g->nodes()->create(); + { + x_node->rank(3); + x_node->dim(0) = 2; + x_node->dim(1) = 1; + x_node->dim(2) = 5; + } + auto y_node = g->nodes()->create(); + { + y_node->rank(2); + y_node->dim(0) = 3; + y_node->dim(1) = 5; + } + auto tfl_node = g->nodes()->create(); + { + tfl_node->x(x_node); + tfl_node->y(y_node); + } + auto push_node = g->nodes()->create(); + { + push_node->from(tfl_node); + } + + auto x_input = g->inputs()->create(); + { + x_input->name("x"); + loco::link(x_input, x_node); + } + auto y_input = g->inputs()->create(); + { + y_input->name("y"); + loco::link(y_input, y_node); + } + auto output = g->outputs()->create(); + { + output->name("output"); + loco::link(output, push_node); + } + + // pre-check + ASSERT_FALSE(loco::shape_known(tfl_node)); + + // shape inference + while (shape_pass(g.get()) == true) + ; + + // Verify + { + ASSERT_TRUE(loco::shape_known(tfl_node)); + ASSERT_EQ(loco::shape_get(tfl_node).domain(), loco::Domain::Tensor); + + auto shape = loco::shape_get(tfl_node).as(); + ASSERT_EQ(shape.rank(), 3); + ASSERT_EQ(shape.dim(0), 2); + ASSERT_EQ(shape.dim(1), 3); + ASSERT_EQ(shape.dim(2), 5); + } +} + +TEST(CircleShapeInferenceRuleTest, CircleTranspose_simple) +{ + luci::test::ExampleGraph g; + + g.pull->rank(4); + g.pull->dim(0) = 10; + g.pull->dim(1) = 20; + g.pull->dim(2) = 30; + g.pull->dim(3) = 40; + + g.const_perm->dtype(loco::DataType::S32); + g.const_perm->rank(1); + g.const_perm->dim(0) = 4; + g.const_perm->size(4); + g.const_perm->at(0) = 2; + g.const_perm->at(1) = 3; + g.const_perm->at(2) = 0; + g.const_perm->at(3) = 1; + + // pre-check + ASSERT_FALSE(loco::shape_known(g.transpose_node)); + + // shape inference + while (shape_pass(g.graph()) == true) + ; + + // Verify + { + ASSERT_TRUE(loco::shape_known(g.transpose_node)); + + auto shape = loco::shape_get(g.transpose_node).as(); + ASSERT_EQ(shape.rank(), 4); + ASSERT_EQ(shape.dim(0), 30); + ASSERT_EQ(shape.dim(1), 40); + ASSERT_EQ(shape.dim(2), 10); + ASSERT_EQ(shape.dim(3), 20); + } +} diff --git a/compiler/luci/service/src/CircleTypeInference.cpp b/compiler/luci/service/src/CircleTypeInference.cpp new file mode 100644 index 00000000000..f6a4e8aa3b3 --- /dev/null +++ b/compiler/luci/service/src/CircleTypeInference.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/CircleTypeInference.h" +#include "luci/Service/CircleTypeInferenceRule.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + +circle::TensorType translateLocoTypeToCircle(loco::DataType dtype) +{ + switch (dtype) + { + case loco::DataType::U8: + return circle::TensorType_UINT8; + // case loco::DataType::U16: unsupported + // case loco::DataType::U32: unsupported + // case loco::DataType::U64: unsupported + case loco::DataType::S8: + return circle::TensorType_INT8; + case loco::DataType::S16: + return circle::TensorType_INT16; + case loco::DataType::S32: + return circle::TensorType_INT32; + case loco::DataType::S64: + return circle::TensorType_INT64; + case loco::DataType::FLOAT16: + return circle::TensorType_FLOAT16; + case loco::DataType::FLOAT32: + return circle::TensorType_FLOAT32; + // case loco::DataType::FLOAT64: unsupported + default: + break; + } + + INTERNAL_EXN_V("Invalid loco dtype", oops::to_uint32(dtype)); +} + +} // namespace + +namespace luci +{ + +circle::TensorType TypeInference::get(loco::Node *node) +{ + assert(loco::dtype_known(node)); + return translateLocoTypeToCircle(loco::dtype_get(node)); +} + +} // namespace luci diff --git a/compiler/luci/service/src/CircleTypeInferenceRule.cpp b/compiler/luci/service/src/CircleTypeInferenceRule.cpp new file mode 100644 index 00000000000..37ab9b52524 --- /dev/null +++ b/compiler/luci/service/src/CircleTypeInferenceRule.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/CircleTypeInferenceRule.h" + +#include +#include +#include + +#include + +namespace +{ + +struct TypeInferenceAlgorithm final : public luci::CircleNodeVisitor +{ + loco::DataType visit(const luci::CircleAdd *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const luci::CircleArgMax *node) final { return node->output_type(); } + + loco::DataType visit(const luci::CircleAveragePool2D *node) final + { + return loco::dtype_get(node->value()); + } + + loco::DataType visit(const luci::CircleConcatenation *node) final + { + // TODO Support when CircleConcatenation has 0 input + assert(node->numValues() > 0); + + for (uint32_t i = 1; i < node->numValues(); ++i) + assert(loco::dtype_get(node->values(i - 1)) == loco::dtype_get(node->values(i))); + + return loco::dtype_get(node->values(0)); + } + + loco::DataType visit(const luci::CircleConst *node) final { return node->dtype(); } + + loco::DataType visit(const luci::CircleConv2D *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const luci::CircleDepthwiseConv2D *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const luci::CircleDiv *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const luci::CircleFullyConnected *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const luci::CircleMaximum *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const luci::CircleMaxPool2D *node) final + { + return loco::dtype_get(node->value()); + } + + loco::DataType visit(const luci::CircleMean *node) final + { + return loco::dtype_get(node->input()); + } + + loco::DataType visit(const luci::CirclePad *node) final { return loco::dtype_get(node->input()); } + + loco::DataType visit(const luci::CircleMul *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const luci::CircleRelu *node) final + { + return loco::dtype_get(node->features()); + } + + loco::DataType visit(const luci::CircleRelu6 *node) final + { + return loco::dtype_get(node->features()); + } + + loco::DataType visit(const luci::CircleReshape *node) final + { + return loco::dtype_get(node->tensor()); + } + + loco::DataType visit(const luci::CircleRsqrt *node) final { return loco::dtype_get(node->x()); } + + // TODO CircleSoftmax + + loco::DataType visit(const luci::CircleSqrt *node) final { return loco::dtype_get(node->x()); } + + loco::DataType visit(const luci::CircleSquaredDifference *node) final + { + return loco::dtype_get(node->x()); + } + + loco::DataType visit(const luci::CircleSub *node) final { return loco::dtype_get(node->x()); } + + // TODO CircleTanh + + loco::DataType visit(const luci::CircleTranspose *node) final + { + return loco::dtype_get(node->a()); + } + + loco::DataType visit(const luci::CircleTransposeConv *node) final + { + return loco::dtype_get(node->outBackprop()); + } + + // Circle Only + loco::DataType visit(const luci::CircleInstanceNorm *node) final + { + return loco::dtype_get(node->input()); + } + + // Virtual + loco::DataType visit(const luci::CircleInput *node) final { return node->dtype(); } + + loco::DataType visit(const luci::CircleOutput *node) final + { + return loco::dtype_get(node->from()); + } +}; + +} // namespace + +namespace luci +{ + +bool CircleTypeInferenceRule::recognize(const loco::Dialect *d) const +{ + return CircleDialect::get() == d; +} + +bool CircleTypeInferenceRule::infer(const loco::Node *node, loco::DataType &dtype) const +{ + assert(node->dialect() == CircleDialect::get()); + + TypeInferenceAlgorithm alg; + + dtype = dynamic_cast(node)->accept(&alg); + assert(dtype != loco::DataType::Unknown); + + return true; +} + +} // namespace luci diff --git a/compiler/luci/service/src/CircleTypeInferenceRule.test.cpp b/compiler/luci/service/src/CircleTypeInferenceRule.test.cpp new file mode 100644 index 00000000000..67960df5059 --- /dev/null +++ b/compiler/luci/service/src/CircleTypeInferenceRule.test.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "TestGraph.h" +#include + +#include +#include + +#include +#include +#include + +#include + +#include + +TEST(CircleTypeInferenceRuleTest, minimal_with_CircleRelu) +{ + // Create a simple network + luci::test::TestGraph graph; + auto tfl_node = graph.append(graph.pull); + graph.complete(tfl_node); + + graph.pull->dtype(loco::DataType::S32); + + // pre-check + ASSERT_FALSE(loco::dtype_known(tfl_node)); + + // type inference + luci::CircleTypeInferenceRule tfl_rule; + loco::CanonicalTypeInferenceRule canon_rule; + loco::MultiDialectTypeInferenceRule rules; + + rules.bind(loco::CanonicalDialect::get(), &canon_rule); + rules.bind(luci::CircleDialect::get(), &tfl_rule); + + loco::apply(&rules).to(graph.g.get()); + + // Verify + ASSERT_TRUE(loco::dtype_known(tfl_node)); + auto type = loco::dtype_get(tfl_node); + ASSERT_EQ(type, loco::DataType::S32); +} diff --git a/compiler/luci/service/src/GraphBlock.h b/compiler/luci/service/src/GraphBlock.h new file mode 100644 index 00000000000..2a455888af3 --- /dev/null +++ b/compiler/luci/service/src/GraphBlock.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __GRAPH_BLOCK_H__ +#define __GRAPH_BLOCK_H__ + +#include +#include + +#include + +#include + +// TODO Change all Canonical nodes to Circle nodes + +namespace luci +{ + +/// @brief feature layout of TFlite/Circle file +enum class FeatureLayout +{ + NHWC, +}; + +/// @brief Creates a loco::FeatureEncode with T layout (NHWC for tflite) and add it to graph. +template loco::FeatureEncode *make_feature_encode(loco::Node *input_for_encode); + +/// @brief Creates a loco::FeatureDecode with T layout (NHWC for tflite) and add it to graph. +template loco::FeatureDecode *make_feature_decode(loco::Node *input_for_decode); + +enum class FilterLayout +{ + OHWI, // a.k.a., NHWC, Tensorflow Lite uses this layout for filter + HWIO, // a.k.a., HWCN, Tensorflow uses this layout for filter +}; + +/// @brief Create a loco::FilterEncode of given layout +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode); + +/// @brief Create a loco::FilterDecode of given layout +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode); + +enum class DepthwiseFilterLayout +{ + HWCM, +}; + +/// @brief Create a loco::DepthwiseFilterDecode of given layout +template +loco::DepthwiseFilterDecode *make_dw_filter_decode(loco::Node *input_for_decode); + +enum class MatrixLayout +{ + HW, + WH +}; + +/// @brief Create a loco::MatrixEncode of given layout +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); + +/// @brief Create a loco::MatrixDecode of given layout +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); + +} // luci + +// +// DomainConverter +// + +/** + * Some canonical nodes can have input of various loco::Domain, e.g., loco::Domain::Tensor, + * loco::Domain::Feature, etc. However, TFL node accepts only loco::Domain::Tensor. + * So, When converting such canonical node to TFL node and input(s) of a canonical node are not + * loco::Domain::Tensor, additional nodes need to be inserted. + * + * The following two classes helps this insertion. + * + * For example, in case of loco::Relu conversion, + * + * Before: + * + * A (output: feature) -- loco::ReLU --- B (input:feature) + * + * After: + * + * A -- loco::FeatureDecode -- locoex::TFLRelu -- loco::FeatureEncode --- B + * + * loco::ReLU (dead node) + */ + +namespace luci +{ + +/** + * @brief Handles input(s) while converting a canonical node to TFL node(s). + * This class informs DomainConverter how to handle inputs of a specific canonical node. + */ +template class InputHandler +{ +public: + /** + * @brief Assign origin's inputs to replacer's inputs. + * (This is called when origin belongs in Tensor domain.) + */ + virtual void handover(CanonicalT *origin, TFLT *replacer) = 0; + + /** + * @brief Returns the list of inputs that needs to have FeatureDecode as its input. + * (This is called when origin belongs in Feature domain.) + */ + virtual std::vector getInputsToConvert(CanonicalT *origin) = 0; + + /// @brief Set the inputs of replacer to new_inputs + virtual void set(TFLT *replacer, std::vector &new_inputs) = 0; + + /// @brief Set the inputs to nullptr + virtual void nullify(CanonicalT *origin) = 0; +}; + +/** + * @brief Class to handle domain conversion while converting a canonical node to TFL node(s) + */ +template class DomainConverter +{ +public: + template + TFLT *convert(CanonicalT *origin, InputHandler &input_handler); +}; + +/** + * @brief Performs domain conversion + * + * 1. if origin belong to loco::Domain::Tensor, and replace origin to a TFL node. + * 2. if origin belong to loco::Domain::Feature, insert loco::FeatureDecode for input(s) and + * insert loco::FeatureEncode for output. Then replace origin to a TFL node. + * + * @return new TFL node; nullptr if shape of origin cannot be known + */ +template +template +TFLT *DomainConverter::convert(CanonicalT *origin, + InputHandler &input_handler) +{ + static_assert(FeatureLayoutT == FeatureLayout::NHWC, "Feature layout should be NHWC"); + + if (!loco::shape_known(origin)) + { + return nullptr; + } + + auto tfl_node = origin->graph()->nodes()->template create(); + + // when the input is Tensor, just replace canonical node to TFL node. + if (loco::shape_get(origin).domain() == loco::Domain::Tensor) + { + input_handler.handover(origin, tfl_node); + + loco::replace(origin).with(tfl_node); + input_handler.nullify(origin); + + return tfl_node; + } + else if (loco::shape_get(origin).domain() == loco::Domain::Feature) + { + std::vector feature_decodes; + + for (auto input : input_handler.getInputsToConvert(origin)) + { + auto dec = make_feature_decode(input); + feature_decodes.emplace_back(dec); + } + + input_handler.set(tfl_node, feature_decodes); + + auto enc = make_feature_encode(tfl_node); + + loco::replace(origin).with(enc); + input_handler.nullify(origin); + + return tfl_node; + } + else + INTERNAL_EXN_V("Unsupported loco::Domain", oops::to_uint32(loco::shape_get(origin).domain())); +} + +} // namespace luci + +#endif //__GRAPH_BLOCK_H__ diff --git a/compiler/luci/service/src/GraphBlock.test.cpp b/compiler/luci/service/src/GraphBlock.test.cpp new file mode 100644 index 00000000000..361c35f3ea8 --- /dev/null +++ b/compiler/luci/service/src/GraphBlock.test.cpp @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "GraphBlock.h" + +#include "Check.h" + +#include +#include + +// TODO Change all Canonical nodes to Circle nodes + +namespace +{ + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + // Make NHWC permutation for encoder and decoder + loco::Permutation NHWC; + + NHWC.axis(loco::FeatureAxis::Count) = 0; + NHWC.axis(loco::FeatureAxis::Height) = 1; + NHWC.axis(loco::FeatureAxis::Width) = 2; + NHWC.axis(loco::FeatureAxis::Depth) = 3; + + return NHWC; +} + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + loco::Permutation HWIO; // a.k.a., HWCN + + HWIO.axis(loco::FilterAxis::Height) = 0; + HWIO.axis(loco::FilterAxis::Width) = 1; + HWIO.axis(loco::FilterAxis::Depth) = 2; + HWIO.axis(loco::FilterAxis::Count) = 3; + + return HWIO; +} + +template <> loco::Permutation perm() +{ + + // Make NHWC permutation for encoder and decoder + loco::Permutation OHWI; // a.k.a., NHWC + + OHWI.axis(loco::FilterAxis::Count) = 0; + OHWI.axis(loco::FilterAxis::Height) = 1; + OHWI.axis(loco::FilterAxis::Width) = 2; + OHWI.axis(loco::FilterAxis::Depth) = 3; + + return OHWI; +} + +template loco::Permutation perm(); + +template <> +loco::Permutation perm() +{ + loco::Permutation HWCM; + + HWCM.axis(loco::DepthwiseFilterAxis::Height) = 0; + HWCM.axis(loco::DepthwiseFilterAxis::Width) = 1; + HWCM.axis(loco::DepthwiseFilterAxis::Depth) = 2; + HWCM.axis(loco::DepthwiseFilterAxis::Multiplier) = 3; + + return HWCM; +} + +template loco::Permutation perm(); + +template <> loco::Permutation perm() +{ + loco::Permutation HW; + + HW.axis(loco::MatrixAxis::Height) = 0; + HW.axis(loco::MatrixAxis::Width) = 1; + + return HW; +} + +template <> loco::Permutation perm() +{ + loco::Permutation WH; + + WH.axis(loco::MatrixAxis::Height) = 1; + WH.axis(loco::MatrixAxis::Width) = 0; + + return WH; +} + +} // namespace + +namespace luci +{ + +template loco::FeatureEncode *make_feature_encode(loco::Node *input_for_encode) +{ + LUCI_ASSERT(input_for_encode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::FeatureDecode *make_feature_decode(loco::Node *input_for_decode) +{ + LUCI_ASSERT(input_for_decode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode) +{ + LUCI_ASSERT(input_for_encode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode) +{ + LUCI_ASSERT(input_for_decode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template +loco::DepthwiseFilterDecode *make_dw_filter_decode(loco::Node *input_for_decode) +{ + LUCI_ASSERT(input_for_decode != nullptr, "filter should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode) +{ + LUCI_ASSERT(input_for_encode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_encode->graph(); + + auto encoder = stdex::make_unique>(); + + encoder->perm(perm()); + + auto enc = g->nodes()->create(); + enc->input(input_for_encode); + enc->encoder(std::move(encoder)); + + return enc; +} + +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode) +{ + LUCI_ASSERT(input_for_decode != nullptr, "input should not be nullptr"); + loco::Graph *g = input_for_decode->graph(); + + auto decoder = stdex::make_unique>(); + + decoder->perm(perm()); + + auto dec = g->nodes()->create(); + dec->input(input_for_decode); + dec->decoder(std::move(decoder)); + + return dec; +} + +// template instantiation +template loco::FeatureEncode * +make_feature_encode(loco::Node *input_for_encode); + +template loco::FeatureDecode * +make_feature_decode(loco::Node *input_for_encode); + +template loco::FilterEncode *make_filter_encode(loco::Node *input_for_encode); +template loco::FilterDecode *make_filter_decode(loco::Node *input_for_decode); + +template loco::DepthwiseFilterDecode * +make_dw_filter_decode(loco::Node *input_for_decode); + +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); +template loco::MatrixEncode *make_matrix_encode(loco::Node *input_for_encode); +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); +template loco::MatrixDecode *make_matrix_decode(loco::Node *input_for_decode); + +} // namespace luci diff --git a/compiler/luci/service/src/ShapeDescription.cpp b/compiler/luci/service/src/ShapeDescription.cpp new file mode 100644 index 00000000000..cbc302f7040 --- /dev/null +++ b/compiler/luci/service/src/ShapeDescription.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/ShapeDescription.h" + +#include + +#include + +namespace luci +{ + +ShapeDescription to_shape_description(const loco::TensorShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(shape.rank()); + for (uint32_t axis = 0; axis < shape.rank(); ++axis) + { + // All the dimensions SHOULD be known + assert(shape.dim(axis).known()); + res._dims.at(axis) = shape.dim(axis).value(); + } + + return res; +} + +ShapeDescription to_shape_description(const loco::FeatureShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a feature map as a NHWC tensor + res._dims.resize(4); + res._dims.at(0) = shape.count().value(); + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::FilterShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a convolution filter as a NHWC tensor + res._dims.resize(4); + res._dims.at(0) = shape.count().value(); + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::DepthwiseFilterShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + // T/F Lite encodes a depthwise convolution filter as a [1, H, W, C*M] tensor + res._dims.resize(4); + res._dims.at(0) = 1; + res._dims.at(1) = shape.height().value(); + res._dims.at(2) = shape.width().value(); + res._dims.at(3) = shape.depth().value() * shape.multiplier().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::BiasShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(1); + res._dims.at(0) = shape.length().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::MatrixShape &shape) +{ + ShapeDescription res; + + res._rank_known = true; + + res._dims.resize(2); + res._dims.at(0) = shape.height().value(); + res._dims.at(1) = shape.width().value(); + + return res; +} + +ShapeDescription to_shape_description(const loco::NodeShape &shape) +{ + switch (shape.domain()) + { + case loco::Domain::Tensor: + return to_shape_description(shape.as()); + case loco::Domain::Feature: + return to_shape_description(shape.as()); + case loco::Domain::Filter: + return to_shape_description(shape.as()); + case loco::Domain::DepthwiseFilter: + return to_shape_description(shape.as()); + case loco::Domain::Bias: + return to_shape_description(shape.as()); + case loco::Domain::Matrix: + return to_shape_description(shape.as()); + default: + break; + } + + INTERNAL_EXN_V("Unsupported loco domain", oops::to_uint32(shape.domain())); +} + +} // namespace luci diff --git a/compiler/luci/service/src/TestGraph.h b/compiler/luci/service/src/TestGraph.h new file mode 100644 index 00000000000..9068fa7109b --- /dev/null +++ b/compiler/luci/service/src/TestGraph.h @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef __TEST_GRAPH_H__ +#define __TEST_GRAPH_H__ + +#include +#include "GraphBlock.h" + +#include +#include + +#include + +// TODO Change all Canonical nodes to Circle nodes + +namespace luci +{ +namespace test +{ + +class TestGraph +{ +public: + std::unique_ptr g; + loco::Pull *pull; + loco::Push *push; + + TestGraph() // creates Pull and Push + { + g = loco::make_graph(); + + pull = g->nodes()->create(); + + push = g->nodes()->create(); + + auto input = g->inputs()->create(); + { + input->name("input"); + loco::link(input, pull); + } + auto output = g->outputs()->create(); + { + output->name("output"); + loco::link(output, push); + } + + _next_input = pull; + } + + loco::Graph *graph() { return g.get(); } + + /// @brief Creates node with NO arg and appends it to graph + template T *append() + { + auto node = g->nodes()->create(); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=1) with arg1 as an input and appends it to graph + template T *append(loco::Node *arg1) + { + auto node = g->nodes()->create(); + setInput(node, arg1); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=2) with arg1, arg2 as inputs and appends it to graph + template T *append(loco::Node *arg1, loco::Node *arg2) + { + auto node = g->nodes()->create(); + setInput(node, arg1, arg2); + _next_input = node; + + return node; + } + + /// @brief Creates op T (arity=3) with arg1, arg2, arg3 as inputs and appends it to graph + template T *append(loco::Node *arg1, loco::Node *arg2, loco::Node *arg3) + { + auto node = g->nodes()->create(); + setInput(node, arg1, arg2, arg3); + _next_input = node; + + return node; + } + + // push will get the last appended node + void complete() { push->from(_next_input); } + + void complete(loco::Node *last_node) { push->from(last_node); } + +private: + // arity 1 + void setInput(loco::Node *node, loco::Node *) { assert(false && "NYI"); }; + + void setInput(loco::AvgPool2D *node, loco::Node *input) { node->ifm(input); } + void setInput(loco::BiasDecode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::BiasEncode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::FeatureDecode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::FeatureEncode *node, loco::Node *input) { node->input(input); }; + void setInput(loco::MaxPool2D *node, loco::Node *input) { node->ifm(input); } + void setInput(loco::Push *node, loco::Node *input) { node->from(input); }; + void setInput(loco::ReLU *node, loco::Node *input) { node->input(input); }; + void setInput(loco::ReLU6 *node, loco::Node *input) { node->input(input); }; + void setInput(loco::Tanh *node, loco::Node *input) { node->input(input); }; + void setInput(loco::TensorTranspose *node, loco::Node *input) { node->input(input); }; + + void setInput(luci::CircleAveragePool2D *node, loco::Node *input) { node->value(input); }; + void setInput(luci::CircleMaxPool2D *node, loco::Node *input) { node->value(input); }; + void setInput(luci::CircleRelu *node, loco::Node *input) { node->features(input); }; + void setInput(luci::CircleRelu6 *node, loco::Node *input) { node->features(input); }; + + // arity 2 + void setInput(loco::Node *node, loco::Node *, loco::Node *) { assert(false && "NYI"); }; + + void setInput(loco::Conv2D *node, loco::Node *input, loco::Node *filter) + { + node->ifm(input); + node->ker(filter); + } + + void setInput(loco::EltwiseAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->lhs(arg1); + node->rhs(arg2); + }; + + void setInput(loco::FeatureBiasAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->value(arg1); + node->bias(arg2); + }; + + void setInput(luci::CircleAdd *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(luci::CircleMul *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(luci::CircleSub *node, loco::Node *arg1, loco::Node *arg2) + { + node->x(arg1); + node->y(arg2); + }; + + void setInput(luci::CircleTranspose *node, loco::Node *arg1, loco::Node *arg2) + { + node->a(arg1); + node->perm(arg2); + }; + + // arity 3 + void setInput(loco::Node *node, loco::Node *, loco::Node *, loco::Node *) + { + assert(false && "NYI"); + }; + + void setInput(luci::CircleConv2D *node, loco::Node *input, loco::Node *filter, loco::Node *bias) + { + node->input(input); + node->filter(filter); + node->bias(bias); + } + +private: + loco::Node *_next_input; +}; + +enum class ExampleGraphType +{ + FeatureBiasAdd, + ConstGen_ReLU, + FilterEncode_FilterDecode, + Transpose, + + CircleTranspose, +}; + +template class ExampleGraph; + +/** + * @brief Class to create the following: + * + * Pull - FeatureEncoder - FeatureBiasAdd - FeatureDecode - Push + * | + * ConstGen - BiasEncode --+ + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::FeatureEncode *fea_enc = nullptr; + loco::ConstGen *constgen = nullptr; + loco::BiasEncode *bias_enc = nullptr; + loco::FeatureBiasAdd *fea_bias_add = nullptr; + loco::FeatureDecode *fea_dec = nullptr; + +public: + ExampleGraph() + { + fea_enc = luci::make_feature_encode(pull); + constgen = append(); + bias_enc = append(constgen); + fea_bias_add = append(fea_enc, bias_enc); + fea_dec = luci::make_feature_decode(fea_bias_add); + complete(fea_dec); + } +}; + +/** + * @brief Class to creates the following: + * + * ConstGen -- ReLU -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::ConstGen *constgen = nullptr; + loco::ReLU *relu = nullptr; + +public: + ExampleGraph() + { + constgen = append(); + relu = append(constgen); + complete(relu); + } +}; + +/** + * @brief Class to creates the following: + * + * Pull -- Transpose -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::TensorTranspose *transpose = nullptr; + +public: + ExampleGraph() + { + transpose = append(pull); + complete(transpose); + } +}; + +/** + * @brief Class to creates the following: + * + * Pull -- FilterEncode -- FilterDecode -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::FilterEncode *filterEncode = nullptr; + loco::FilterDecode *filterDecode = nullptr; + +public: + ExampleGraph() + { + filterEncode = luci::make_filter_encode(pull); // from Tensorflow + filterDecode = + luci::make_filter_decode(filterEncode); // to Tensorflow Lite + complete(filterDecode); + } +}; + +/** + * @brief Class to create the following: + * + * Pull -- CircleTranspose -- Push + */ +template <> class ExampleGraph : public TestGraph +{ +public: + loco::ConstGen *const_perm = nullptr; + luci::CircleTranspose *transpose_node = nullptr; + +public: + ExampleGraph() + { + const_perm = append(); + transpose_node = append(pull, const_perm); + complete(transpose_node); + } +}; + +} // namespace test +} // namespace luci + +#endif // __TEST_GRAPH_H__ diff --git a/compiler/luci/service/src/Validate.cpp b/compiler/luci/service/src/Validate.cpp new file mode 100644 index 00000000000..65b82c2b4f0 --- /dev/null +++ b/compiler/luci/service/src/Validate.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "luci/Service/Validate.h" + +#include +#include + +#include +#include +#include + +#include +#include + +namespace +{ + +/** + * @brief returns a node that is CircleOutput with index is out_index in nodes + */ +luci::CircleOutput *find_node(std::vector nodes, loco::GraphOutputIndex out_index) +{ + for (auto node : nodes) + { + auto circle_output = dynamic_cast(node); + if (circle_output != nullptr) + { + if (circle_output->indexed() && circle_output->index() == out_index) + return circle_output; + } + } + return nullptr; +} + +bool validate_shape_type(loco::Graph *g) +{ + LOGGER(l); + + auto output_nodes = loco::output_nodes(g); + + auto count = g->outputs()->size(); + for (uint32_t out = 0; out < count; ++out) + { + auto graph_out = g->outputs()->at(out); + auto out_index = graph_out->index(); + + auto circle_output = find_node(output_nodes, out_index); + assert(circle_output != nullptr); + assert(circle_output->from() != nullptr); + auto circle_node = dynamic_cast(circle_output->from()); + assert(circle_node != nullptr); + assert(loco::shape_known(circle_node)); + + // check if output node shape is same as graph output shape + auto co_shape = loco::shape_get(circle_node); + auto go_tensor_shape = graph_out->shape(); + assert(go_tensor_shape); + auto go_shape = loco::NodeShape(*go_tensor_shape); + if (!(co_shape == go_shape)) + { + INFO(l) << "Shape for #" << out_index << " not same " << std::endl; + return false; + } + + // check if data type match + assert(loco::dtype_known(circle_node)); + if (graph_out->dtype() != loco::dtype_get(circle_node)) + { + INFO(l) << "Type for #" << out_index << " not same " << std::endl; + return false; + } + } + + return true; +} + +} // namespace + +namespace luci +{ + +bool validate(loco::Graph *g) +{ + if (!loco::valid(g)) + return false; + + if (!validate_shape_type(g)) + return false; + + // TODO add more validation + + return true; +} + +} // namespace luci diff --git a/compiler/luci/tester/CMakeLists.txt b/compiler/luci/tester/CMakeLists.txt new file mode 100644 index 00000000000..0f6d1f0ae41 --- /dev/null +++ b/compiler/luci/tester/CMakeLists.txt @@ -0,0 +1,22 @@ +set(SRCS_READ_TESTER + src/ReadTester.cpp + src/Model.cpp + ) + +add_executable(luci_readtester "${SRCS_READ_TESTER}") +target_link_libraries(luci_readtester PRIVATE luci_import) +target_link_libraries(luci_readtester PRIVATE luci_service) +target_link_libraries(luci_readtester PRIVATE luci_pass) +target_link_libraries(luci_readtester PRIVATE oops) + +set(SRCS_WRITE_TESTER + src/WriteTester.cpp + src/Model.cpp + ) + +add_executable(luci_writetester "${SRCS_WRITE_TESTER}") +target_link_libraries(luci_writetester PRIVATE luci_import) +target_link_libraries(luci_readtester PRIVATE luci_service) +target_link_libraries(luci_writetester PRIVATE luci_pass) +target_link_libraries(luci_writetester PRIVATE luci_export) +target_link_libraries(luci_writetester PRIVATE oops) diff --git a/compiler/luci/tester/src/Model.cpp b/compiler/luci/tester/src/Model.cpp new file mode 100644 index 00000000000..453be74f77b --- /dev/null +++ b/compiler/luci/tester/src/Model.cpp @@ -0,0 +1,62 @@ +#include "Model.h" + +#include +#include + +#include +#include +#include + +namespace +{ + +class FileModel final : public luci::Model +{ +public: + explicit FileModel(const std::string &filename) : _filename(filename) {} + +public: + FileModel(const FileModel &) = delete; + FileModel(FileModel &&) = delete; + +public: + const ::circle::Model *model(void) override + { + std::ifstream file(_filename, std::ios::binary | std::ios::in); + if (!file.good()) + return nullptr; + + file.unsetf(std::ios::skipws); + + std::streampos fileSize; + file.seekg(0, std::ios::end); + fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // reserve capacity + _data.reserve(fileSize); + + // read the data + file.read(_data.data(), fileSize); + if (file.fail()) + return nullptr; + + return ::circle::GetModel(_data.data()); + } + +private: + const std::string _filename; + std::vector _data; +}; + +} // namespace + +namespace luci +{ + +std::unique_ptr load_model(const std::string &path) +{ + return std::unique_ptr{new FileModel(path)}; +} + +} // namespace luci diff --git a/compiler/luci/tester/src/Model.h b/compiler/luci/tester/src/Model.h new file mode 100644 index 00000000000..e40faf33ee9 --- /dev/null +++ b/compiler/luci/tester/src/Model.h @@ -0,0 +1,27 @@ +#ifndef __TESTER_MODEL_H__ +#define __TESTER_MODEL_H__ + +#include + +#include + +namespace luci +{ + +struct Model +{ + virtual ~Model() = default; + + virtual const ::circle::Model *model(void) = 0; +}; + +/** + * @brief Load Circle model (as a raw Model) from a given path + * + * @note May return a nullptr + */ +std::unique_ptr load_model(const std::string &path); + +} // namespace luci + +#endif // __TESTER_MODEL_H__ diff --git a/compiler/luci/tester/src/ReadTester.cpp b/compiler/luci/tester/src/ReadTester.cpp new file mode 100644 index 00000000000..692d726841d --- /dev/null +++ b/compiler/luci/tester/src/ReadTester.cpp @@ -0,0 +1,85 @@ +#include "Model.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + +void show_help_message(const char *progname, std::ostream &os) +{ + os << "USAGE: " << progname << " circlefile" << std::endl << std::endl; +} + +void show_error_message(const char *progname, std::ostream &os, const std::string &msg) +{ + os << "ERROR: " << msg << std::endl; + os << std::endl; + + show_help_message(progname, os); +} + +} // namespace + +/* + * @brief ReadTest main + * + * Give one Circle file as an argument + * + * This will use luci_import to read the file and get loco graph + * In luci_import, LUCI_LOG environment will be checked and will + * dump graph to console if set. + * i.e. "LUCI_LOG=1 luci_readtester mymodel.circle" + */ +int main(int argc, char **argv) +{ + if (argc != 2) + { + show_error_message(argv[0], std::cerr, "Circle file is not specified"); + return 255; + } + + std::string input_path = argv[1]; + + std::cout << "[INFO] Circle is '" << input_path << "'" << std::endl; + + // Load model from the file + std::unique_ptr model = luci::load_model(input_path); + if (model == nullptr) + { + std::cerr << "ERROR: Failed to load '" << input_path << "'" << std::endl; + return 255; + } + + const circle::Model *input_model = model->model(); + if (input_model == nullptr) + { + std::cerr << "ERROR: Failed to read '" << input_path << "'" << std::endl; + return 255; + } + + luci::Importer importer; + auto graph = importer.import(input_model); + + { + luci::ShapeInferencePass pass; + while (pass.run(graph.get()) == true) + ; + } + { + luci::TypeInferencePass pass; + while (pass.run(graph.get()) == true) + ; + } + + if (!luci::validate(graph.get())) + return 255; + + return graph.get() != nullptr ? 0 : 255; +} diff --git a/compiler/luci/tester/src/WriteTester.cpp b/compiler/luci/tester/src/WriteTester.cpp new file mode 100644 index 00000000000..421ad75d827 --- /dev/null +++ b/compiler/luci/tester/src/WriteTester.cpp @@ -0,0 +1,129 @@ +#include "Model.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + +void show_help_message(const char *progname, std::ostream &os) +{ + os << "USAGE: " << progname << " circlefile_in circlefile_out" << std::endl << std::endl; +} + +void show_error_message(const char *progname, std::ostream &os, const std::string &msg) +{ + os << "ERROR: " << msg << std::endl; + os << std::endl; + + show_help_message(progname, os); +} + +struct CircleExpContract : public luci::CircleExporter::Contract +{ +public: + CircleExpContract(loco::Graph *graph, const std::string &filename) + : _graph(graph), _filepath(filename) + { + // NOTHING TO DO + } + virtual ~CircleExpContract() = default; + +public: + loco::Graph *graph(void) const final { return _graph; } + +public: + bool store(const char *ptr, const size_t size) const final; + +private: + loco::Graph *_graph; + const std::string _filepath; +}; + +bool CircleExpContract::store(const char *ptr, const size_t size) const +{ + if (!ptr) + INTERNAL_EXN("Graph was not serialized by FlatBuffer for some reason"); + + std::ofstream fs(_filepath.c_str(), std::ofstream::binary); + fs.write(ptr, size); + + return fs.good(); +} + +} // namespace + +/* + * @brief WriteTester main + * + * Give two Circle file as an argument + * + * This will use luci_import to read the first file and get loco graph + * With the graph, this will use luci_export to write to the second file + * Like ReadTester, LUCI_LOG=1 environment variable is available to dump the graph + */ +int main(int argc, char **argv) +{ + if (argc != 3) + { + show_error_message(argv[0], std::cerr, "In/Out Circle file path is not specified"); + return 255; + } + + std::string input_path = argv[1]; + std::string output_path = argv[2]; + + std::cout << "[INFO] Circle from '" << input_path << "' to '" << output_path << "'" << std::endl; + + // Load model from the file + std::unique_ptr model = luci::load_model(input_path); + if (model == nullptr) + { + std::cerr << "ERROR: Failed to load '" << input_path << "'" << std::endl; + return 255; + } + + const circle::Model *input_model = model->model(); + if (input_model == nullptr) + { + std::cerr << "ERROR: Failed to read '" << input_path << "'" << std::endl; + return 255; + } + + // Import from input Circle file + luci::Importer importer; + auto graph = importer.import(input_model); + + if (graph.get() == nullptr) + return 255; + + { + luci::ShapeInferencePass pass; + while (pass.run(graph.get()) == true) + ; + } + { + luci::TypeInferencePass pass; + while (pass.run(graph.get()) == true) + ; + } + + if (!luci::validate(graph.get())) + return 255; + + // Export to output Circle file + luci::CircleExporter exporter; + + CircleExpContract contract(graph.get(), output_path); + + return exporter.invoke(&contract) ? 0 : 255; +} diff --git a/compiler/luci/tests/.gitignore b/compiler/luci/tests/.gitignore new file mode 100644 index 00000000000..8dbfa901227 --- /dev/null +++ b/compiler/luci/tests/.gitignore @@ -0,0 +1 @@ +/test.local.lst diff --git a/compiler/luci/tests/CMakeLists.txt b/compiler/luci/tests/CMakeLists.txt new file mode 100644 index 00000000000..e6bfaf950f4 --- /dev/null +++ b/compiler/luci/tests/CMakeLists.txt @@ -0,0 +1,101 @@ +# TODO use local test.recipe files for small networks +file(GLOB RECIPES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*/test.recipe") + +foreach(RECIPE IN ITEMS ${RECIPES}) + get_filename_component(RECIPE_PREFIX ${RECIPE} DIRECTORY) + + set(RECIPE_SOURCE_FILE "${RECIPE_PREFIX}.recipe") + set(RECIPE_SOURCE_TARGET lucitests_${RECIPE_PREFIX}_recipe) + + set(RECIPE_OUTPUT_FILE "${RECIPE_PREFIX}.tflite") + set(RECIPE_OUTPUT_TARGET lucitests_${RECIPE_PREFIX}_tflite) + + set(CIRCLE_OUTPUT_FILE "${RECIPE_PREFIX}.circle") + set(CIRCLE_OUTPUT_TARGET lucitests_${RECIPE_PREFIX}_circle) + + # Copy .recipe + add_custom_target(${RECIPE_SOURCE_TARGET} + ALL ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${RECIPE}" + "${CMAKE_CURRENT_BINARY_DIR}/${RECIPE_SOURCE_FILE}" + COMMENT "Generate ${RECIPE_PREFIX}.recipe") + + # Generate .tflite + add_custom_target(${RECIPE_OUTPUT_TARGET} + ALL $ ${RECIPE_SOURCE_FILE} ${RECIPE_OUTPUT_FILE} + DEPENDS ${RECIPE_SOURCE_TARGET} + COMMENT "Generate ${RECIPE_PREFIX}.tflite") + + # Generate .circle + add_custom_target(${CIRCLE_OUTPUT_TARGET} + ALL $ ${RECIPE_OUTPUT_FILE} ${CIRCLE_OUTPUT_FILE} + DEPENDS ${RECIPE_OUTPUT_TARGET} + COMMENT "Generate ${RECIPE_PREFIX}.circle") + + list(APPEND TESTS ${RECIPE_PREFIX}) +endforeach(RECIPE) + +# Generate from res/TensorFlowLiteRecipes +nncc_find_resource(TensorFlowLiteRecipes) +set(TENSORFLOWLITERECIPES_DIR "${TensorFlowLiteRecipes_DIR}") + +file(GLOB RECIPES RELATIVE ${TENSORFLOWLITERECIPES_DIR} "${TENSORFLOWLITERECIPES_DIR}/*/test.recipe") + +foreach(RECIPE IN ITEMS ${RECIPES}) + get_filename_component(RECIPE_PREFIX ${RECIPE} DIRECTORY) + + set(RECIPE_SOURCE_FILE "${RECIPE_PREFIX}.recipe") + set(RECIPE_SOURCE_TARGET lucitests_res_${RECIPE_PREFIX}_recipe) + + set(RECIPE_OUTPUT_FILE "${RECIPE_PREFIX}.tflite") + set(RECIPE_OUTPUT_TARGET lucitests_res_${RECIPE_PREFIX}_tflite) + + set(CIRCLE_OUTPUT_FILE "${RECIPE_PREFIX}.circle") + set(CIRCLE_OUTPUT_TARGET lucitests_res_${RECIPE_PREFIX}_circle) + + # Copy .recipe + add_custom_target(${RECIPE_SOURCE_TARGET} + ALL ${CMAKE_COMMAND} -E copy "${TENSORFLOWLITERECIPES_DIR}/${RECIPE}" + "${CMAKE_CURRENT_BINARY_DIR}/${RECIPE_SOURCE_FILE}" + COMMENT "Generate ${RECIPE_PREFIX}.recipe") + + # Generate .tflite + add_custom_target(${RECIPE_OUTPUT_TARGET} + ALL $ ${RECIPE_SOURCE_FILE} ${RECIPE_OUTPUT_FILE} + DEPENDS ${RECIPE_SOURCE_TARGET} + COMMENT "Generate ${RECIPE_PREFIX}.tflite") + + # Generate .circle + add_custom_target(${CIRCLE_OUTPUT_TARGET} + ALL $ ${RECIPE_OUTPUT_FILE} ${CIRCLE_OUTPUT_FILE} + DEPENDS ${RECIPE_OUTPUT_TARGET} + COMMENT "Generate ${RECIPE_PREFIX}.circle") + + list(APPEND TESTS ${RECIPE_PREFIX}) +endforeach(RECIPE) + +macro(addread NAME) + list(APPEND DAILY_READ_TESTS ${NAME}) +endmacro(addread) + +macro(addwrite NAME) + list(APPEND DAILY_WRITE_TESTS ${NAME}) +endmacro(addwrite) + +# Read "test.lst" +include("test.lst") +# Read "test.local.lst" if exists +include("test.local.lst" OPTIONAL) + +add_test(NAME luci_unit_readtest + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/readverify.sh" + "${CMAKE_CURRENT_BINARY_DIR}" + "$" + ${DAILY_READ_TESTS} +) + +add_test(NAME luci_unit_writetest + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/writeverify.sh" + "${CMAKE_CURRENT_BINARY_DIR}" + "$" + ${DAILY_WRITE_TESTS} +) diff --git a/compiler/luci/tests/readverify.sh b/compiler/luci/tests/readverify.sh new file mode 100755 index 00000000000..3403e9c19f4 --- /dev/null +++ b/compiler/luci/tests/readverify.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# This script verifies the basic behavior of luci frontend +# +# HOW TO USE +# +# ./readverify.sh ... +VERIFY_SOURCE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +WORKDIR="$1"; shift +VERIFY_BINARY_PATH="$1"; shift + +TESTED=() +PASSED=() +FAILED=() + +for TESTCASE in "$@"; do + TESTED+=("${TESTCASE}") + + TESTCASE_FILE="${WORKDIR}/${TESTCASE}" + + PASSED_TAG="${TESTCASE_FILE}.passed" + rm -f "${PASSED_TAG}" + + cat > "${TESTCASE_FILE}.log" <( + exec 2>&1 + set -ex + + "${VERIFY_BINARY_PATH}" "${TESTCASE_FILE}.circle" + + if [[ $? -eq 0 ]]; then + touch "${PASSED_TAG}" + fi + ) + + if [[ -f "${PASSED_TAG}" ]]; then + PASSED+=("${TESTCASE}") + else + FAILED+=("${TESTCASE}") + fi +done + +if [[ ${#TESTED[@]} -ne ${#PASSED[@]} ]]; then + echo "FAILED" + for TEST in "${FAILED[@]}" + do + echo "- ${TEST}" + done + exit 255 +fi + +echo "PASSED" +exit 0 diff --git a/compiler/luci/tests/test.lst b/compiler/luci/tests/test.lst new file mode 100644 index 00000000000..fa3d10fba62 --- /dev/null +++ b/compiler/luci/tests/test.lst @@ -0,0 +1,15 @@ +addread(Add_000) +addread(Add_U8_000) +addread(Conv2D_000) +addread(Conv2D_U8_000) +addread(Conv2D_002) +addread(MaxPool2D_000) +addread(MaxPool2D_U8_000) + +addwrite(Add_000) +addwrite(Add_U8_000) +addwrite(Conv2D_000) +addwrite(Conv2D_U8_000) +addwrite(Conv2D_002) +addwrite(MaxPool2D_000) +addwrite(MaxPool2D_U8_000) diff --git a/compiler/luci/tests/writeverify.sh b/compiler/luci/tests/writeverify.sh new file mode 100755 index 00000000000..6980bac4494 --- /dev/null +++ b/compiler/luci/tests/writeverify.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# This script verifies the basic behavior of luci frontend +# +# HOW TO USE +# +# ./writeverify.sh ... +VERIFY_SOURCE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +WORKDIR="$1"; shift +VERIFY_BINARY_PATH="$1"; shift + +TESTED=() +PASSED=() +FAILED=() + +for TESTCASE in "$@"; do + TESTED+=("${TESTCASE}") + + TESTCASE_FILE="${WORKDIR}/${TESTCASE}" + + PASSED_TAG="${TESTCASE_FILE}_w.passed" + rm -f "${PASSED_TAG}" + + cat > "${TESTCASE_FILE}_w.log" <( + exec 2>&1 + set -ex + + "${VERIFY_BINARY_PATH}" "${TESTCASE_FILE}.circle" "${TESTCASE_FILE}_w.circle" + + if [[ $? -eq 0 ]]; then + touch "${PASSED_TAG}" + fi + ) + + if [[ -f "${PASSED_TAG}" ]]; then + PASSED+=("${TESTCASE}") + else + FAILED+=("${TESTCASE}") + fi +done + +if [[ ${#TESTED[@]} -ne ${#PASSED[@]} ]]; then + echo "FAILED" + for TEST in "${FAILED[@]}" + do + echo "- ${TEST}" + done + exit 255 +fi + +echo "PASSED" +exit 0 diff --git a/compiler/mio-circle/CMakeLists.txt b/compiler/mio-circle/CMakeLists.txt new file mode 100644 index 00000000000..f97ec2b990b --- /dev/null +++ b/compiler/mio-circle/CMakeLists.txt @@ -0,0 +1,28 @@ +nnas_find_package(FlatBuffers QUIET) + +if(NOT FlatBuffers_FOUND) + return() +endif(NOT FlatBuffers_FOUND) + +message(STATUS "Build mio-circle: TRUE") + +# TODO Find a better way +set(SCHEMA_FILE "${NNAS_PROJECT_SOURCE_DIR}/nnpackage/schema/circle_schema.fbs") + +# NOTE Copy circle_schema.fbs as schema.fbs to generate "schema_generated.fbs" instead of "circle_schema_generated.fbs" +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/schema.fbs" + COMMAND ${CMAKE_COMMAND} -E copy "${SCHEMA_FILE}" schema.fbs + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS "${NNAS_PROJECT_SOURCE_DIR}/nnpackage/schema/circle_schema.fbs" +) + +FlatBuffers_Target(mio_circle + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen/mio/circle" + INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen" + SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}" + SCHEMA_FILES "schema.fbs" +) + +# This example shows how to use "mio-circle" library +add_executable(mio_circle_example example.cpp) +target_link_libraries(mio_circle_example mio_circle) diff --git a/compiler/mio-circle/README.md b/compiler/mio-circle/README.md new file mode 100644 index 00000000000..e90ec513f8e --- /dev/null +++ b/compiler/mio-circle/README.md @@ -0,0 +1,3 @@ +# mio-circle + +Let's make it easy to read and write Circle models. diff --git a/compiler/mio-circle/example.cpp b/compiler/mio-circle/example.cpp new file mode 100644 index 00000000000..6418e0411d4 --- /dev/null +++ b/compiler/mio-circle/example.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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 example shows how to include and use "mio-circle" +// +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + std::ifstream ifs(argv[1], std::ios_base::binary); + std::vector buf(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); + + flatbuffers::Verifier verifier{reinterpret_cast(buf.data()), buf.size()}; + + if (!circle::VerifyModelBuffer(verifier)) + { + std::cout << "Fail" << std::endl; + return 255; + } + + std::cout << "Pass" << std::endl; + return 0; +} diff --git a/compiler/mio-tf/CMakeLists.txt b/compiler/mio-tf/CMakeLists.txt new file mode 100644 index 00000000000..d670f6bab54 --- /dev/null +++ b/compiler/mio-tf/CMakeLists.txt @@ -0,0 +1,48 @@ +nnas_find_package(Protobuf QUIET) +# TensorFlowSource package is used to use ~.proto files +nnas_find_package(TensorFlowSource EXACT 1.12 QUIET) + +if(NOT Protobuf_FOUND) + return() +endif(NOT Protobuf_FOUND) + +if(NOT TensorFlowSource_FOUND) + return() +endif(NOT TensorFlowSource_FOUND) + +message(STATUS "Build mio-tf: TRUE") + +# Minimal Protocol Buffer specification for GraphDef file (.pb) encoding/decoding +unset(PROTO_FILES) +list(APPEND PROTO_FILES tensorflow/core/framework/versions.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/resource_handle.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/types.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/tensor.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/tensor_shape.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/attr_value.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/op_def.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/node_def.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/function.proto) +list(APPEND PROTO_FILES tensorflow/core/framework/graph.proto) + +Protobuf_Generate(GRAPHDEF_PROTO + "${CMAKE_CURRENT_BINARY_DIR}/generated" + "${TensorFlowSource_DIR}" + ${PROTO_FILES}) + +add_library(mio_tf STATIC ${GRAPHDEF_PROTO_SOURCES}) +set_target_properties(mio_tf PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(mio_tf PUBLIC ${GRAPHDEF_PROTO_INCLUDE_DIRS}) +target_link_libraries(mio_tf PUBLIC ${GRAPHDEF_PROTO_LIBRARIES}) + +if(NOT ENABLE_TEST) + return() +endif(NOT ENABLE_TEST) + +nnas_find_package(GTest REQUIRED) + +file(GLOB_RECURSE TESTS "src/*.test.cpp") + +GTest_AddTest(mio_tf_test ${TESTS}) +target_include_directories(mio_tf_test PRIVATE src) +target_link_libraries(mio_tf_test mio_tf) diff --git a/compiler/mio-tf/README.md b/compiler/mio-tf/README.md new file mode 100644 index 00000000000..18f475a85d1 --- /dev/null +++ b/compiler/mio-tf/README.md @@ -0,0 +1,3 @@ +# mio-tf + +_mio-tf_ provides a library to access TensorFlow model files diff --git a/compiler/mio-tf/src/mio_tf.test.cpp b/compiler/mio-tf/src/mio_tf.test.cpp new file mode 100644 index 00000000000..013dc2d54ae --- /dev/null +++ b/compiler/mio-tf/src/mio_tf.test.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include + +TEST(MIO_TF_Test, instance) +{ + tensorflow::GraphDef gd; + tensorflow::NodeDef nd; + + SUCCEED(); +} diff --git a/compiler/mio-tflite/CMakeLists.txt b/compiler/mio-tflite/CMakeLists.txt new file mode 100644 index 00000000000..bc2bdf395e3 --- /dev/null +++ b/compiler/mio-tflite/CMakeLists.txt @@ -0,0 +1,32 @@ +nnas_find_package(FlatBuffers QUIET) + +if(NOT FlatBuffers_FOUND) + return() +endif(NOT FlatBuffers_FOUND) + +nnas_find_package(TensorFlowSource EXACT 1.13.1 QUIET) + +if(NOT TensorFlowSource_FOUND) + return() +endif(NOT TensorFlowSource_FOUND) + +message(STATUS "Build mio-tflite: TRUE") + +set(SCHEMA_FILE "${TensorFlowSource_DIR}/tensorflow/lite/schema/schema.fbs") + +# NOTE Use copy of schema.fbs as to provide unified way for circle also +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/schema.fbs" + COMMAND ${CMAKE_COMMAND} -E copy "${SCHEMA_FILE}" schema.fbs + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + DEPENDS "${SCHEMA_FILE}" +) + +FlatBuffers_Target(mio_tflite + OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen/mio/tflite" + INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/gen" + SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}" + SCHEMA_FILES "schema.fbs" +) + +add_executable(mio_tflite_example example.cpp) +target_link_libraries(mio_tflite_example mio_tflite) diff --git a/compiler/mio-tflite/README.md b/compiler/mio-tflite/README.md new file mode 100644 index 00000000000..187b1a5c64c --- /dev/null +++ b/compiler/mio-tflite/README.md @@ -0,0 +1,3 @@ +# mio-tflite + +_mio-tflite_ provides a library to access TensorFlow lite model files diff --git a/compiler/mio-tflite/example.cpp b/compiler/mio-tflite/example.cpp new file mode 100644 index 00000000000..54d15103c05 --- /dev/null +++ b/compiler/mio-tflite/example.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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 example shows how to include and use "mio-tflite" +// +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + std::ifstream ifs(argv[1], std::ios_base::binary); + std::vector buf(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); + + flatbuffers::Verifier verifier{reinterpret_cast(buf.data()), buf.size()}; + + if (!tflite::VerifyModelBuffer(verifier)) + { + std::cout << "Fail" << std::endl; + return 255; + } + + std::cout << "Pass" << std::endl; + return 0; +} diff --git a/compiler/mir-caffe-importer/CMakeLists.txt b/compiler/mir-caffe-importer/CMakeLists.txt new file mode 100644 index 00000000000..83176510e47 --- /dev/null +++ b/compiler/mir-caffe-importer/CMakeLists.txt @@ -0,0 +1,17 @@ +nnas_find_package(CaffeProto QUIET) + +if (NOT CaffeProto_FOUND) + return() +endif () + +set(MIR_CAFFE_IMPORTER_SOURCES + caffe_importer.cpp + caffe_importer.h + caffe_op_creator.cpp + caffe_op_creator.h + caffe_op_types.h) + +add_library(mir_caffe_importer STATIC ${MIR_CAFFE_IMPORTER_SOURCES}) +set_target_properties(mir_caffe_importer PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(mir_caffe_importer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(mir_caffe_importer PUBLIC mir caffeproto PRIVATE stdex) diff --git a/compiler/mir-caffe-importer/caffe_importer.cpp b/compiler/mir-caffe-importer/caffe_importer.cpp new file mode 100644 index 00000000000..8e5ebda15e5 --- /dev/null +++ b/compiler/mir-caffe-importer/caffe_importer.cpp @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "caffe_importer.h" +#include "caffe/proto/caffe.pb.h" +#include "caffe_op_creator.h" +#include "caffe_op_types.h" + +#include "mir/ops/OutputOp.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mir_caffe +{ + +namespace +{ + +class CaffeImporter +{ +public: + /// @brief Load the model and convert it into a MIR Graph. + std::unique_ptr importModelFromBinaryFile(const std::string &filename); + std::unique_ptr importModelFromTextFile(const std::string &filename); + +private: + std::unique_ptr importModel(); + + std::unique_ptr _net; + std::unique_ptr _opCreator; + + // Maps Caffe blob names to corresponding MIR operation outputs. + std::map _blobNameToOpOutput; + + static const std::map _operatorTypes; + + /** + * @brief Mark output MIR nodes + */ + void setGraphOutputs(mir::Graph *graph); + + /** + * @brief Pass through caffe graph and collect unsupported by NNC layers + * @throw PassException with message, containing detected problems + */ + void collectUnsupportedLayers(); + + /** + * @brief Create MIR node from single caffe layer + */ + void createMIRNodesFromLayer(const caffe::LayerParameter &layer); + + mir::Operation::Output *getOutputForBlob(const std::string &blob_name) const; + void setOutputForBlob(const std::string &blob_name, mir::Operation::Output *output); + + /** + * @brief Collect unsupported parts of caffe layer + */ + void collectUnsupportedOp(const caffe::LayerParameter &layer, std::set &problems); + + /** + * @brief Returns MIR operation outputs corresponding to the inputs of the given layer. + */ + std::vector getMIRInputsForLayer(const caffe::LayerParameter &layer); + + void processDeprecatedInput(); +}; + +void loadModelFromBinaryFile(const std::string &filename, caffe::NetParameter *net) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + int file_handle = open(filename.c_str(), O_RDONLY); + + if (file_handle == -1) + throw std::runtime_error("Couldn't open file \"" + filename + "\": " + std::strerror(errno) + + "."); + + google::protobuf::io::FileInputStream file_stream(file_handle); + file_stream.SetCloseOnDelete(true); + + google::protobuf::io::CodedInputStream coded_stream(&file_stream); + coded_stream.SetTotalBytesLimit(INT_MAX, INT_MAX); + + if (!net->ParseFromCodedStream(&coded_stream)) + throw std::runtime_error("Couldn't parse file \"" + filename + "\"."); + + // If the file has not been consumed entirely, assume that the file is in the wrong format. + if (!coded_stream.ConsumedEntireMessage()) + throw std::runtime_error("File \"" + filename + "\" has not been consumed entirely."); +} + +void loadModelFromTextFile(const std::string &filename, caffe::NetParameter *net) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + int file_handle = open(filename.c_str(), O_RDONLY); + + if (file_handle == -1) + throw std::runtime_error("Couldn't open file \"" + filename + "\": " + std::strerror(errno) + + "."); + + google::protobuf::io::FileInputStream file_stream(file_handle); + file_stream.SetCloseOnDelete(true); + + if (!google::protobuf::TextFormat::Parse(&file_stream, net)) + throw std::runtime_error("Couldn't parse file \"" + filename + "\"."); +} + +std::unique_ptr CaffeImporter::importModel() +{ + auto graph = stdex::make_unique(); + _opCreator = stdex::make_unique(graph.get()); + + collectUnsupportedLayers(); + + for (int i = 0; i < _net->layer_size(); ++i) + createMIRNodesFromLayer(_net->layer(i)); + + setGraphOutputs(graph.get()); + + return std::move(graph); +} + +std::unique_ptr CaffeImporter::importModelFromBinaryFile(const std::string &filename) +{ + _net = stdex::make_unique(); + loadModelFromBinaryFile(filename, _net.get()); + + return importModel(); +} + +std::unique_ptr CaffeImporter::importModelFromTextFile(const std::string &filename) +{ + _net = stdex::make_unique(); + loadModelFromTextFile(filename, _net.get()); + + return importModel(); +} + +void CaffeImporter::collectUnsupportedLayers() +{ + processDeprecatedInput(); + + std::set problems; + + for (const caffe::LayerParameter &layer : _net->layer()) + collectUnsupportedOp(layer, problems); + + if (!problems.empty()) + { + std::string msg("NNC can't load model. Detected problems:"); + for (const auto &problemStr : problems) + msg.append("\n * " + problemStr); + throw std::runtime_error(msg); + } +} + +void CaffeImporter::createMIRNodesFromLayer(const caffe::LayerParameter &layer) +{ + std::vector inputs = getMIRInputsForLayer(layer); + std::vector outputs; + + switch (_operatorTypes.at(layer.type())) + { + case CaffeOpType::input: + outputs = _opCreator->convertInput(layer); + break; + case CaffeOpType::convolution: + outputs = _opCreator->convertConvolution(layer, inputs); + break; + case CaffeOpType::innerProduct: + outputs = _opCreator->convertInnerProduct(layer, inputs); + break; + case CaffeOpType::pooling: + outputs = _opCreator->convertPooling(layer, inputs); + break; + case CaffeOpType::concat: + outputs = _opCreator->convertConcat(layer, inputs); + break; + case CaffeOpType::reshape: + outputs = _opCreator->convertReshape(layer, inputs); + break; + case CaffeOpType::ReLU: + outputs = _opCreator->convertReLU(layer, inputs); + break; + case CaffeOpType::softmax: + outputs = _opCreator->convertSoftmax(layer, inputs); + break; + case CaffeOpType::scale: + outputs = _opCreator->convertScale(layer, inputs); + break; + case CaffeOpType::batchNorm: + outputs = _opCreator->convertBatchNorm(layer, inputs); + break; + case CaffeOpType::dropout: + outputs = _opCreator->convertDropout(layer, inputs); + break; + case CaffeOpType::tanh: + outputs = _opCreator->convertTanH(layer, inputs); + break; + case CaffeOpType::ELU: + outputs = _opCreator->convertELU(layer, inputs); + break; + case CaffeOpType::eltwise: + outputs = _opCreator->convertEltwise(layer, inputs); + break; + case CaffeOpType::embed: + outputs = _opCreator->convertEmbed(layer, inputs); + break; + case CaffeOpType::deconvolution: + outputs = _opCreator->convertDeconvolution(layer, inputs); + break; + case CaffeOpType::split: + outputs = _opCreator->convertSplit(layer, inputs); + break; + case CaffeOpType::sigmoid: + outputs = _opCreator->convertSigmoid(layer, inputs); + break; + case CaffeOpType::LSTM: + outputs = _opCreator->convertLSTM(layer, inputs); + break; + default: + assert(false && "All unsupported types should have been found before this pass."); + } + + assert(static_cast(outputs.size()) == layer.top_size() && "Number of outputs differs."); + for (int i = 0; i < layer.top_size(); ++i) + setOutputForBlob(layer.top(i), outputs[i]); +} + +void CaffeImporter::collectUnsupportedOp(const caffe::LayerParameter &layer, + std::set &problems) +{ + auto it = _operatorTypes.find(layer.type()); + if (it == _operatorTypes.end()) + { + problems.insert(layer.type() + ": unknown layer"); + return; + } + + CaffeOpType op_type = it->second; + + switch (op_type) + { + case CaffeOpType::concat: + case CaffeOpType::input: + case CaffeOpType::softmax: + case CaffeOpType::scale: + case CaffeOpType::dropout: + case CaffeOpType::split: + case CaffeOpType::eltwise: + case CaffeOpType::ELU: + case CaffeOpType::ReLU: + case CaffeOpType::embed: + case CaffeOpType::sigmoid: + case CaffeOpType::tanh: + case CaffeOpType::innerProduct: + // No checks + break; + case CaffeOpType::deconvolution: + case CaffeOpType::convolution: + _opCreator->checkConvolution(layer, problems); + break; + case CaffeOpType::pooling: + _opCreator->checkPooling(layer, problems); + break; + case CaffeOpType::reshape: + _opCreator->checkReshape(layer, problems); + break; + case CaffeOpType::batchNorm: + _opCreator->checkBatchNorm(layer, problems); + break; + case CaffeOpType::LSTM: + _opCreator->checkLSTM(layer, problems); + break; + default: + problems.insert(layer.type() + ": unsupported layer"); + break; + } +} + +void CaffeImporter::processDeprecatedInput() +{ + if (_net->input_dim_size() != 0 || _net->input_shape_size() != 0) + throw std::runtime_error("Deprecated Caffe input types are not supported"); +} + +std::vector +CaffeImporter::getMIRInputsForLayer(const caffe::LayerParameter &layer) +{ + std::vector inputs; + + for (const auto &input_name : layer.bottom()) + inputs.push_back(getOutputForBlob(input_name)); + + return inputs; +} + +mir::Operation::Output *CaffeImporter::getOutputForBlob(const std::string &blob_name) const +{ + return _blobNameToOpOutput.at(blob_name); +} + +void CaffeImporter::setOutputForBlob(const std::string &blob_name, mir::Operation::Output *output) +{ + const auto it = _blobNameToOpOutput.find(blob_name); + if (it != _blobNameToOpOutput.cend()) + { + // caffe input blob name could be same as output blob name, and next line will overwrite + // '_blobNameToOpOutput' element, but in all networks that I saw it was not a problem + it->second->setName(""); + } + + // Do not overwrite the name in case of fall-through layers (ex. Dropout, Split). + // TODO Find a way to handle it properly. + if (output->getName().empty()) + output->setName(blob_name); + + _blobNameToOpOutput[blob_name] = output; +} + +void CaffeImporter::setGraphOutputs(mir::Graph *graph) +{ + // TODO For now, we assume that: + // - there is exactly one output; + // - the output is from the last layer. + const auto &last_layer = *_net->layer().rbegin(); + auto output = getOutputForBlob(last_layer.top(0)); + graph->create(output); +} + +const std::map CaffeImporter::_operatorTypes = { + {"AbsVal", CaffeOpType::absVal}, + {"Accuracy", CaffeOpType::accuracy}, + {"ArgMax", CaffeOpType::argMax}, + {"BatchNorm", CaffeOpType::batchNorm}, + {"BatchReindex", CaffeOpType::batchReindex}, + {"Bias", CaffeOpType::bias}, + {"BNLL", CaffeOpType::BNLL}, + {"Clip", CaffeOpType::clip}, + {"Concat", CaffeOpType::concat}, + {"ContrastiveLoss", CaffeOpType::contrastiveLoss}, + {"Convolution", CaffeOpType::convolution}, + {"Crop", CaffeOpType::crop}, + {"Data", CaffeOpType::data}, + {"Deconvolution", CaffeOpType::deconvolution}, + {"Dropout", CaffeOpType::dropout}, + {"DummyData", CaffeOpType::dummyData}, + {"Eltwise", CaffeOpType::eltwise}, + {"ELU", CaffeOpType::ELU}, + {"Embed", CaffeOpType::embed}, + {"EuclidianLoss", CaffeOpType::euclidianLoss}, + {"Exp", CaffeOpType::exp}, + {"Filter", CaffeOpType::filter}, + {"Flatten", CaffeOpType::flatten}, + {"HDF5Data", CaffeOpType::HDF5Data}, + {"HDF5Output", CaffeOpType::HDF5Output}, + {"HingeLoss", CaffeOpType::hingeLoss}, + {"Im2Col", CaffeOpType::im2Col}, + {"ImageData", CaffeOpType::imageData}, + {"InfogainLoss", CaffeOpType::infogainLoss}, + {"InnerProduct", CaffeOpType::innerProduct}, + {"Input", CaffeOpType::input}, + {"Log", CaffeOpType::log}, + {"LRN", CaffeOpType::LRN}, + {"LSTM", CaffeOpType::LSTM}, + {"MemoryData", CaffeOpType::memoryData}, + {"MultinomialLogisticLoss", CaffeOpType::multinomialLogisticLoss}, + {"MVN", CaffeOpType::MVN}, + {"Parameter", CaffeOpType::parameter}, + {"Pooling", CaffeOpType::pooling}, + {"Power", CaffeOpType::power}, + {"PReLU", CaffeOpType::PReLU}, + {"Python", CaffeOpType::python}, + {"Recurrent", CaffeOpType::recurrent}, + {"Reduction", CaffeOpType::reduction}, + {"ReLU", CaffeOpType::ReLU}, + {"Reshape", CaffeOpType::reshape}, + {"RNN", CaffeOpType::RNN}, + {"Scale", CaffeOpType::scale}, + {"SigmoidCrossEntropyLoss", CaffeOpType::sigmoidCrossEntropyLoss}, + {"Sigmoid", CaffeOpType::sigmoid}, + {"Silence", CaffeOpType::silence}, + {"Softmax", CaffeOpType::softmax}, + {"SoftmaxWithLoss", CaffeOpType::softmaxWithLoss}, + {"SPP", CaffeOpType::SPP}, + {"Split", CaffeOpType::split}, + {"Slice", CaffeOpType::slice}, + {"TanH", CaffeOpType::tanh}, + {"Threshold", CaffeOpType::threshold}, + {"Tile", CaffeOpType::tile}, + {"WindowData", CaffeOpType::windowData}}; +} // namespace + +std::unique_ptr importModelFromBinaryFile(const std::string &filename) +{ + CaffeImporter importer; + return importer.importModelFromBinaryFile(filename); +} + +std::unique_ptr importModelFromTextFile(const std::string &filename) +{ + CaffeImporter importer; + return importer.importModelFromTextFile(filename); +} + +std::unique_ptr loadModel(const std::string &filename) +{ + return importModelFromBinaryFile(filename); +} + +} // namespace mir_caffe diff --git a/compiler/mir-caffe-importer/caffe_importer.h b/compiler/mir-caffe-importer/caffe_importer.h new file mode 100644 index 00000000000..cf2c055bcf6 --- /dev/null +++ b/compiler/mir-caffe-importer/caffe_importer.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE_IMPORTER_H +#define MIR_CAFFE_IMPORTER_H + +#include +#include + +#include "mir/Graph.h" + +namespace mir_caffe +{ + +std::unique_ptr importModelFromBinaryFile(const std::string &filename); +std::unique_ptr importModelFromTextFile(const std::string &filename); +// TODO Remove after changing all uses. +std::unique_ptr loadModel(const std::string &filename); + +} // namespace mir_caffe + +#endif // MIR_CAFFE_IMPORTER_H diff --git a/compiler/mir-caffe-importer/caffe_op_creator.cpp b/compiler/mir-caffe-importer/caffe_op_creator.cpp new file mode 100644 index 00000000000..5d43d248eb7 --- /dev/null +++ b/compiler/mir-caffe-importer/caffe_op_creator.cpp @@ -0,0 +1,834 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "caffe_op_creator.h" + +#include "mir/ops/AddOp.h" +#include "mir/ops/AvgPool2DOp.h" +#include "mir/ops/ConcatOp.h" +#include "mir/ops/ConstantOp.h" +#include "mir/ops/Conv2DOp.h" +#include "mir/ops/Deconv2DOp.h" +#include "mir/ops/EluOp.h" +#include "mir/ops/FullyConnectedOp.h" +#include "mir/ops/GatherOp.h" +#include "mir/ops/LeakyReluOp.h" +#include "mir/ops/MaxOp.h" +#include "mir/ops/MaxPool2DOp.h" +#include "mir/ops/MulOp.h" +#include "mir/ops/ReluOp.h" +#include "mir/ops/ReshapeOp.h" +#include "mir/ops/SigmoidOp.h" +#include "mir/ops/SliceOp.h" +#include "mir/ops/SoftmaxOp.h" +#include "mir/ops/TanhOp.h" +#include "mir/ops/TransposeOp.h" +#include "mir/Index.h" +#include "mir/ShapeRange.h" +#include "mir/Tensor.h" + +#include +#include +#include + +namespace mir_caffe +{ + +static mir::Shape convertBlobShape(const caffe::BlobShape &shape) +{ + mir::Shape mir_shape(shape.dim_size()); + + for (int i = 0; i < shape.dim_size(); ++i) + { + mir_shape.dim(i) = shape.dim(i); + } + + return mir_shape; +} + +using namespace mir; + +/// @brief Split arg into @p num_parts equal parts along @p axis axis. +std::vector CaffeOpCreator::createSplit(mir::Operation::Output *arg, + int32_t num_parts, int32_t axis) +{ + const auto &arg_shape = arg->getShape(); + + assert(axis >= 0 && axis < arg_shape.rank()); + int32_t part_size = arg_shape.dim(axis) / num_parts; + assert(part_size * num_parts == arg_shape.dim(axis)); + + Shape starts(arg_shape.rank()); + Shape sizes(arg_shape); + sizes.dim(axis) = part_size; + + std::vector outputs(num_parts); + for (int32_t i = 0; i < num_parts; ++i) + { + outputs[i] = createOp(arg, starts, sizes)->getOutput(0); + starts.dim(axis) += part_size; + } + + return outputs; +} + +/// @brief Helper function for creating FullyConnected operation with non-square input. +mir::Operation::Output *CaffeOpCreator::createFullyConnected(mir::Operation::Output *input, + mir::Operation::Output *weights, + int32_t axis) +{ + const auto &input_shape = input->getShape(); + const auto &weights_shape = weights->getShape(); + + assert(axis >= 0 && axis < input_shape.rank()); + assert(weights_shape.rank() == 2); + + // Result shape is: input.shape[0:axis] + weights.shape[1]. + Shape result_shape = input_shape; + result_shape.resize(axis + 1); + result_shape.dim(axis) = weights_shape.dim(1); + + // Flatten input to 2-D shape. + int32_t outer_size = 1; + for (int32_t i = 0; i < axis; ++i) + outer_size *= input_shape.dim(i); + int32_t inner_size = 1; + for (int32_t i = axis; i < input_shape.rank(); ++i) + inner_size *= input_shape.dim(i); + + auto flatten = createOp(input, Shape{outer_size, inner_size})->getOutput(0); + auto fc = createOp(flatten, weights)->getOutput(0); + return createOp(fc, result_shape)->getOutput(0); +} + +TensorVariant CaffeOpCreator::convertBlob(const caffe::BlobProto &blob) +{ + const void *src_data; + + mir::DataType dtype; + if (blob.data_size() != 0) + { + assert(blob.double_data_size() == 0); + dtype = mir::DataType::FLOAT32; + src_data = blob.data().data(); + } + else if (blob.double_data_size() != 0) + { + dtype = mir::DataType::FLOAT64; + src_data = blob.double_data().data(); + } + else + { + throw std::runtime_error("No data in Caffe BlobProto, investigate"); + } + + const mir::Shape shape = convertBlobShape(blob.shape()); + return TensorVariant({dtype, shape}, src_data); +} + +std::vector +CaffeOpCreator::convertInput(const caffe::LayerParameter &layer) +{ + const auto ¶ms = layer.input_param(); + const auto num_inputs = layer.top_size(); + const auto num_shapes = params.shape_size(); + std::vector outputs; + + assert((num_shapes == 1 || num_shapes == num_inputs) && "Unsupported number of shapes."); + + for (int i = 0; i < num_inputs; ++i) + { + const auto &blob_shape = params.shape(num_shapes == 1 ? 0 : i); + mir::TensorType input_type(DataType::FLOAT32, convertBlobShape(blob_shape)); + auto input = createOp(input_type)->getOutput(0); + outputs.push_back(input); + } + + return outputs; +} + +template +static void convertConvolutionParam(const caffe::ConvolutionParameter &conv_param, + OperationAttributes &attributes) +{ + std::int32_t stride_h, stride_w; + if (conv_param.has_stride_h() || conv_param.has_stride_w()) + { + // If stride_h or stride_w are set, they take precedence. + stride_h = conv_param.stride_h(); + stride_w = conv_param.stride_w(); + } + else if (conv_param.stride_size() == 0) + { + // If no strides specified, they defaults to 1. + stride_h = stride_w = 1; + } + else if (conv_param.stride_size() == 1) + { + // If only one stride specified, all strides take the same value. + stride_h = stride_w = conv_param.stride(0); + } + else + { + // Otherwise, there must be a stride for each dimension. + assert(conv_param.stride_size() == 2); + stride_h = conv_param.stride(0); + stride_w = conv_param.stride(1); + } + attributes.strides = {stride_h, stride_w}; + + std::int32_t pad_h, pad_w; + if (conv_param.has_pad_h() || conv_param.has_pad_w()) + { + // If pad_h or pad_w are set, they take precedence. + pad_h = conv_param.pad_h(); + pad_w = conv_param.pad_w(); + } + else if (conv_param.pad_size() == 0) + { + // If no pads specified, they defaults to 0. + pad_h = pad_w = 0; + } + else if (conv_param.pad_size() == 1) + { + // If only one pad specified, all pads take the same value. + pad_h = pad_w = conv_param.pad(0); + } + else + { + // Otherwise, there must be a pad for each dimension. + assert(conv_param.pad_size() == 2); + pad_h = conv_param.pad(0); + pad_w = conv_param.pad(1); + } + attributes.padding_after = attributes.padding_before = {pad_h, pad_w}; +} + +void CaffeOpCreator::checkConvolution(const caffe::LayerParameter &layer, + std::set &problems_ops_set) +{ + const caffe::ConvolutionParameter ¶ms = layer.convolution_param(); + + assert(params.stride_size() <= 2); + + if (params.axis() != 1) + problems_ops_set.insert("Conv2D: Unsupported axis"); + + if (params.pad_size() != 0 && (params.has_pad_h() || params.has_pad_w())) + problems_ops_set.insert("Conv2D: Conflicting padding properties"); + + if (params.pad_size() > 2) + problems_ops_set.insert("Conv2D: Unsupported number of pads"); +} + +std::vector +CaffeOpCreator::convertConvolution(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.convolution_param(); + Conv2DOpAttributes attributes; + + convertConvolutionParam(params, attributes); + attributes.num_groups = params.group(); + attributes.data_format = DataFormat::NCHW; + + assert(layer.blobs(0).shape().dim_size() == 4); + auto kernel = createOp(convertBlob(layer.blobs(0)))->getOutput(0); + std::vector perm{0, 2, 3, 1}; // OIHW -> OHWI + kernel = createOp(kernel, perm)->getOutput(0); + auto result = createOp(inputs[0], kernel, attributes)->getOutput(0); + + // Add the bias, if any. + if (params.bias_term()) + { + auto bias = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + bias = createOp(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +std::vector +CaffeOpCreator::convertDeconvolution(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const caffe::ConvolutionParameter ¶ms = layer.convolution_param(); + Deconv2DOpAttributes attributes; + + convertConvolutionParam(params, attributes); + attributes.data_format = DataFormat::NCHW; + + if (params.group() != 1) + { + throw std::runtime_error("Deconvolution: 'group' != 1 is not supported."); + } + + auto kernel = createOp(convertBlob(layer.blobs(0)))->getOutput(0); + std::vector perm{2, 3, 1, 0}; // IOHW -> HWOI + kernel = createOp(kernel, perm)->getOutput(0); + auto result = createOp(inputs[0], kernel, attributes)->getOutput(0); + + // bias_term is optional (so might not be present) and defaults to true + if (params.bias_term()) + { + auto bias = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + bias = createOp(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +std::vector +CaffeOpCreator::convertInnerProduct(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.inner_product_param(); + auto weights = createOp(convertBlob(layer.blobs(0)))->getOutput(0); + + if (!params.transpose()) + weights = createOp(weights, std::vector{1, 0})->getOutput(0); + + auto result = createFullyConnected(inputs[0], weights, params.axis()); + + // Add the bias, if any. + if (params.bias_term()) + { + auto bias = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +std::vector +CaffeOpCreator::convertConcat(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.concat_param(); + auto concat = createOp(inputs, params.axis()); + return {concat->getOutput(0)}; +} + +template +static void convertPoolingParam(const caffe::PoolingParameter ¶ms, + const mir::Shape &input_shape, PoolingAttributes &attributes) +{ + std::int32_t kernel_h, kernel_w; + assert(!params.global_pooling()); + if (params.has_kernel_size()) + { + kernel_h = kernel_w = params.kernel_size(); + } + else + { + kernel_h = params.kernel_h(); + kernel_w = params.kernel_w(); + } + attributes.window = {kernel_h, kernel_w}; + + std::int32_t stride_h, stride_w; + if (params.has_stride_h() || params.has_stride_w()) + { + stride_h = params.stride_h(); + stride_w = params.stride_w(); + } + else + { + stride_h = stride_w = params.stride(); + } + attributes.strides = {stride_h, stride_w}; + + std::int32_t pad_h, pad_w; + if (params.has_pad_h() || params.has_pad_w()) + { + pad_h = params.pad_h(); + pad_w = params.pad_w(); + } + else + { + pad_h = pad_w = params.pad(); + } + + attributes.padding_before = attributes.padding_after = {pad_h, pad_w}; + + // Caffe uses different formula for computing output shape than MIR. Adjust padding so that + // the output shape stays the same. + constexpr int num_spatial_dims = 2; + for (int i = 0; i < num_spatial_dims; ++i) + { + // Assuming NCHW format. + const std::int32_t padded_input = + input_shape.dim(2 + i) + attributes.padding_before[i] + attributes.padding_after[i]; + if ((padded_input - attributes.window[i]) % attributes.strides[i] != 0) + ++attributes.padding_after[i]; + } +} + +void CaffeOpCreator::checkPooling(const caffe::LayerParameter &layer, + std::set &problems_ops_set) +{ + const caffe::PoolingParameter ¶ms = layer.pooling_param(); + + if (params.has_global_pooling() && params.global_pooling()) + problems_ops_set.insert("Pooling: pooling layer global_pooling param is not supported yet"); + + if (params.pool() != caffe::PoolingParameter::AVE && + params.pool() != caffe::PoolingParameter::MAX) + problems_ops_set.insert("Pooling: unsupported pooling type"); + + if (params.has_pad() && (params.has_pad_h() || params.has_pad_w())) + problems_ops_set.insert("Pooling: conflicting padding properties in pooling"); +} + +std::vector +CaffeOpCreator::convertPooling(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.pooling_param(); + + assert(inputs.size() == 1); + auto input = inputs[0]; + + mir::Operation::Output *result; + + switch (params.pool()) + { + case caffe::PoolingParameter::AVE: + { + AvgPool2DOpAttributes attributes_avg; + attributes_avg.data_format = DataFormat::NCHW; + convertPoolingParam(params, input->getShape(), attributes_avg); + result = createOp(input, attributes_avg)->getOutput(0); + break; + } + case caffe::PoolingParameter::MAX: + { + MaxPool2DOpAttributes attributes_max; + attributes_max.data_format = DataFormat::NCHW; + convertPoolingParam(params, input->getShape(), attributes_max); + result = createOp(input, attributes_max)->getOutput(0); + break; + } + default: + assert(false); + } + + return {result}; +} + +std::vector +CaffeOpCreator::convertSoftmax(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.softmax_param(); + + // CPP and ACL backends are able to perform Softmax only along the last axis. + // FIXME Do it in backends. + if (inputs[0]->getShape().rank() == 4) + { + // For now, we only account for the most common case. + if (params.axis() != 1) + throw std::runtime_error("Softmax: unsupported axis"); + int32_t axis = 3; + auto input = createOp(inputs[0], std::vector{0, 2, 3, 1}); + auto softmax = createOp(input->getOutput(0), axis); + auto result = + createOp(softmax->getOutput(0), std::vector{0, 3, 1, 2}); + return {result->getOutput(0)}; + } + + auto softmax = createOp(inputs[0], params.axis()); + return {softmax->getOutput(0)}; +} + +void CaffeOpCreator::checkReshape(const caffe::LayerParameter &layer, + std::set &problems_ops_set) +{ + const caffe::ReshapeParameter ¶ms = layer.reshape_param(); + + if (params.has_axis() || params.has_num_axes()) + problems_ops_set.insert("Reshape layer axis and num_axes params are not supported yet"); + + if (!params.has_shape()) + problems_ops_set.insert("Reshape layer doesn't have shape parameter"); + + const mir::Shape newShape = convertBlobShape(params.shape()); + + for (int32_t i = 0; i < newShape.rank(); ++i) + if (newShape.dim(i) == 0) + problems_ops_set.insert("Reshape layer zero shape values are not supported yet"); +} + +/** + * @brief Converts Caffe Reshape layer to Model IR Reshape operation. + * @todo Support "axis" and "num_axes" parameters as needed. + * @todo Decide how to react to the absence of "shape" parameter. + * @todo Support zero values in "shape" parameter. + */ +std::vector +CaffeOpCreator::convertReshape(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const caffe::ReshapeParameter ¶ms = layer.reshape_param(); + + const mir::Shape new_shape = convertBlobShape(params.shape()); + auto reshape = createOp(inputs[0], new_shape); + return {reshape->getOutput(0)}; +} + +std::vector +CaffeOpCreator::convertReLU(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + mir::Operation *relu; + if (layer.relu_param().has_negative_slope()) + { + float alpha = layer.relu_param().negative_slope(); + relu = createOp(inputs[0], alpha); + } + else + { + relu = createOp(inputs[0]); + } + + return {relu->getOutput(0)}; +} + +std::vector +CaffeOpCreator::convertScale(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.scale_param(); + auto scale = createOp(convertBlob(layer.blobs(0)))->getOutput(0); + scale = createOp(scale, Shape{1, scale->getShape().dim(0), 1, 1})->getOutput(0); + auto result = createOp(inputs[0], scale)->getOutput(0); + + // Add the bias, if any. + if (params.bias_term()) + { + auto bias = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + bias = createOp(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +void CaffeOpCreator::checkBatchNorm(const caffe::LayerParameter &layer, + std::set &problems_ops_set) +{ + const auto &scale_shape = layer.blobs(2).shape(); + + // Check that last blob(with scaleFactor) containing only one number + if (scale_shape.dim_size() != 1 || scale_shape.dim(0) != 1) + problems_ops_set.insert("Unexpected shape of scale parameter in batch norm"); +} + +std::vector +CaffeOpCreator::convertBatchNorm(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const caffe::BatchNormParameter ¶ms = layer.batch_norm_param(); + + auto input = inputs[0]; + auto mean_tensor = convertBlob(layer.blobs(0)); + auto var_tensor = convertBlob(layer.blobs(1)); + auto scale_tensor = convertBlob(layer.blobs(2)); + const float eps = params.eps(); + + float scale_factor = *reinterpret_cast(scale_tensor.at(mir::Index{0})); + + // See https://github.com/BVLC/caffe/blob/master/src/caffe/layers/batch_norm_layer.cpp#L100 + // Y = (X - mean / scale_factor) / sqrt(var / scale_factor + epsilon) = + // = (X + C1) * C2 + if (scale_factor != 0.0f) + scale_factor = 1.0f / scale_factor; + + // C1 = -mean / scale_factor + Tensor mean_accessor(mean_tensor); + for (const auto &idx : ShapeRange(mean_accessor.getShape())) + mean_accessor.at(idx) *= -scale_factor; + auto c1 = createOp(mean_tensor)->getOutput(0); + + // C2 = 1 / sqrt(var / scale_factor + epsilon) + Tensor var_accessor(var_tensor); + for (const auto &idx : ShapeRange(var_accessor.getShape())) + var_accessor.at(idx) = 1.0f / std::sqrt(var_accessor.at(idx) * scale_factor + eps); + auto c2 = createOp(var_tensor)->getOutput(0); + + c1 = createOp(c1, Shape{1, c1->getShape().dim(0), 1, 1})->getOutput(0); + c2 = createOp(c2, Shape{1, c2->getShape().dim(0), 1, 1})->getOutput(0); + + // Y = (X + C1) * C2 + auto result = createOp(input, c1)->getOutput(0); + result = createOp(result, c2)->getOutput(0); + + return {result}; +} + +std::vector +CaffeOpCreator::convertDropout(const caffe::LayerParameter &, + const std::vector &inputs) +{ + // This is a no-op in inference mode. + return {inputs[0]}; +} + +std::vector +CaffeOpCreator::convertELU(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const caffe::ELUParameter ¶ms = layer.elu_param(); + + auto elu = createOp(inputs[0], params.alpha()); + return {elu->getOutput(0)}; +} + +std::vector +CaffeOpCreator::convertEmbed(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.embed_param(); + auto data = createOp(convertBlob(layer.blobs(0))); + auto result = createOp(data->getOutput(0), inputs[0], 0)->getOutput(0); + + // Add the bias, if any. + if (params.bias_term()) + { + auto bias = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +std::vector +CaffeOpCreator::convertSigmoid(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + auto result = createOp(inputs[0]); + return {result->getOutput(0)}; +} + +std::vector +CaffeOpCreator::convertTanH(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + auto tanh = createOp(inputs[0]); + return {tanh->getOutput(0)}; +} + +std::vector +CaffeOpCreator::convertEltwise(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + auto ¶ms = layer.eltwise_param(); + + mir::Operation::Output *result; + switch (params.operation()) + { + case caffe::EltwiseParameter::PROD: + { + result = createOp(inputs[0], inputs[1])->getOutput(0); + for (int i = 2; i < layer.bottom_size(); ++i) + { + result = createOp(result, inputs[i])->getOutput(0); + } + break; + } + case caffe::EltwiseParameter::SUM: + { + std::vector scaled_inputs = inputs; + if (params.coeff_size() > 0) + { + assert(params.coeff_size() == layer.bottom_size()); + for (int i = 0; i < layer.bottom_size(); i++) + { + if (params.coeff(i) != 1.0f) + { + const float coeff_val = params.coeff(i); + TensorVariant coeff_tensor({DataType::FLOAT32, {}}, &coeff_val); + auto coeff_const = createOp(coeff_tensor)->getOutput(0); + scaled_inputs[i] = createOp(coeff_const, inputs[i])->getOutput(0); + } + } + } + result = createOp(scaled_inputs[0], scaled_inputs[1])->getOutput(0); + for (int i = 2; i < layer.bottom_size(); ++i) + { + result = createOp(result, scaled_inputs[i])->getOutput(0); + } + break; + } + case caffe::EltwiseParameter::MAX: + { + result = createOp(inputs[0], inputs[1])->getOutput(0); + for (int i = 2; i < layer.bottom_size(); ++i) + { + result = createOp(result, inputs[i])->getOutput(0); + } + break; + } + default: + throw std::runtime_error("Unknown element-wise operation."); + } + return {result}; +} + +std::vector +CaffeOpCreator::convertSplit(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + std::vector outputs(layer.top_size(), inputs.at(0)); + return outputs; +} + +void CaffeOpCreator::checkLSTM(const caffe::LayerParameter &layer, + std::set &problems_ops_set) +{ + const auto ¶ms = layer.recurrent_param(); + if (params.expose_hidden()) + problems_ops_set.insert("LSTM: parameter 'expose_hidden' has unsupported value: " + + std::to_string(params.expose_hidden())); +} + +static TensorVariant createZeroedTensor(const mir::Shape &shape) +{ + // TODO For now it is hardcoded float32. + auto elem_type = mir::DataType::FLOAT32; + std::vector zeros(static_cast(shape.numElements()), 0.0f); + return TensorVariant({elem_type, shape}, zeros.data()); +} + +/* See the following links for details on implementation: + * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/recurrent_layer.cpp + * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/lstm_layer.cpp + * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/lstm_unit_layer.cpp + * + * Inputs: + * x -- The time-varying input. Shape: [T, N, d0, d1, ..., dn]. + * cont -- The sequence continuation indicators. Shape: [T, N]. + * x_static -- The static (non-time-varying) input. Shape: [N, ...]. + * This parameter is optional and not currently supported. + * + * Additional inputs when parameter "expose_hidden" is true (not currently supported): + * h_0 -- The initial value of the hidden state. Shape: [1, N, D]. + * c_0 -- The initial value of the cell state. Shape: [1, N, D]. + * + * Learned parameters: + * xw -- x weights for input, output, forget and cell gates concatenated. + * Shape: [4 * D, d0 * d1 * ... * dn]. + * xb -- x biases for input, output, forget and cell gates concatenated. Shape: [4 * D]. + * hw -- h weights for input, output, forget and cell gates concatenated. Shape: [4 * D, D]. + * + * Outputs: + * h -- The time-varying output. Shape: [T, N, D]. + * + * Additional outputs when parameter "expose_hidden" is true (not currently supported): + * h_T -- The value of the hidden state at the last timestep. Shape: [1, N, D]. + * c_T -- The value of the cell state at the last timestep. Shape: [1, N, D]. + * + * Here: + * T - the number of timesteps, + * N - the number of independent streams. + * D - the number of hidden parameters. + * + * Formulas: + * c_cont = c[t-1] * cont[t] + * h_cont = h[t-1] * cont[t] + * i[t] = Sigmoid(x[t] . xw_i + xb_i + h_cont . hw_i) + * f[t] = Sigmoid(x[t] . xw_f + xb_f + h_cont . hw_f) + * o[t] = Sigmoid(x[t] . xw_o + xb_o + h_cont . hw_o) + * g[t] = Tanh(x[t] . xw_g + xb_g + h_cont . hw_g) + * c[t] = c_cont * f[t] + i[t] * g[t] + * h[t] = o[t] * Tanh(c[t]) + * + * Here: + * t -- the timestep (ranges from 1 to T), + * * -- the inner product, + * . -- the Hadamard product (elementwise product). + * + * In this implementation the inner products for all gates are performed as single inner product for + * efficiency. + */ +std::vector +CaffeOpCreator::convertLSTM(const caffe::LayerParameter &layer, + const std::vector &inputs) +{ + const auto ¶ms = layer.recurrent_param(); + + // Inputs to the layer. + auto x = inputs[0]; + auto cont = inputs[1]; + assert(inputs.size() == 2); + + const auto &x_shape = x->getShape(); + const int32_t seq_length = x_shape.dim(0); + const int32_t batch_size = x_shape.dim(1); + const int32_t hidden_size = params.num_output(); + + // Learned parameters of the layer. Tensors are transposed to match the ModelIR. + auto xw = createOp(convertBlob(layer.blobs(0)))->getOutput(0); + auto xb = createOp(convertBlob(layer.blobs(1)))->getOutput(0); + auto hw = createOp(convertBlob(layer.blobs(2)))->getOutput(0); + xw = createOp(xw, std::vector{1, 0})->getOutput(0); + hw = createOp(hw, std::vector{1, 0})->getOutput(0); + + // Add a dummy dimension so that element-wise operations perform properly. + cont = createOp(cont, Shape{seq_length, batch_size, 1})->getOutput(0); + + // Initialize cell and hidden states with zeros. + auto zero_tensor = createZeroedTensor(Shape{1, batch_size, hidden_size}); + auto c_t = createOp(zero_tensor)->getOutput(0); + auto h_t = createOp(zero_tensor)->getOutput(0); + + auto x_xw = createFullyConnected(x, xw, 2); + auto x_xw_b = createOp(x_xw, xb)->getOutput(0); + + // Split input and continuation tensors into seq_length slices. + std::vector x_xw_b_slices = createSplit(x_xw_b, seq_length, 0); + std::vector cont_slices = createSplit(cont, seq_length, 0); + std::vector h_slices(seq_length); + + for (int32_t t = 0; t < seq_length; t++) + { + auto c_cont_t = createOp(c_t, cont_slices[t])->getOutput(0); + auto h_cont_t = createOp(h_t, cont_slices[t])->getOutput(0); + + auto x_xw_b_t = x_xw_b_slices[t]; + auto h_hw_t = createFullyConnected(h_cont_t, hw, 2); + auto activation_inputs_concat = createOp(x_xw_b_t, h_hw_t)->getOutput(0); + auto activation_inputs = createSplit(activation_inputs_concat, 4, 2); + + auto i_t = createOp(activation_inputs[0])->getOutput(0); + auto f_t = createOp(activation_inputs[1])->getOutput(0); + auto o_t = createOp(activation_inputs[2])->getOutput(0); + auto g_t = createOp(activation_inputs[3])->getOutput(0); + + c_t = createOp(createOp(c_cont_t, f_t)->getOutput(0), + createOp(i_t, g_t)->getOutput(0)) + ->getOutput(0); + h_t = createOp(createOp(c_t)->getOutput(0), o_t)->getOutput(0); + + h_slices[t] = h_t; + } + + return {createOp(h_slices, 0)->getOutput(0)}; +} + +} // namespace mir_caffe diff --git a/compiler/mir-caffe-importer/caffe_op_creator.h b/compiler/mir-caffe-importer/caffe_op_creator.h new file mode 100644 index 00000000000..721bb90b8a0 --- /dev/null +++ b/compiler/mir-caffe-importer/caffe_op_creator.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE_OP_CREATOR_H +#define MIR_CAFFE_OP_CREATOR_H + +#include +#include +#include +#include + +#include "mir/Graph.h" +#include "mir/TensorVariant.h" +#include "mir/Shape.h" + +#include "caffe/proto/caffe.pb.h" + +namespace mir_caffe +{ + +class CaffeOpCreator +{ +public: + explicit CaffeOpCreator(mir::Graph *g) : _graph(g){}; + + std::vector convertInput(const caffe::LayerParameter &layer); + + std::vector + convertConvolution(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertInnerProduct(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertConcat(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertPooling(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertSoftmax(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertReshape(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertReLU(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertScale(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertBatchNorm(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertDropout(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertDeconvolution(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertELU(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertEmbed(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertSigmoid(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertTanH(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertEltwise(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertSplit(const caffe::LayerParameter &layer, + const std::vector &inputs); + + std::vector + convertLSTM(const caffe::LayerParameter &layer, + const std::vector &inputs); + + void checkConvolution(const caffe::LayerParameter &layer, + std::set &problems_ops_set); + + void checkPooling(const caffe::LayerParameter &layer, std::set &problems_ops_set); + + void checkReshape(const caffe::LayerParameter &layer, std::set &problems_ops_set); + + void checkBatchNorm(const caffe::LayerParameter &layer, std::set &problems_ops_set); + + void checkLSTM(const caffe::LayerParameter &layer, std::set &problems_ops_set); + +private: + mir::Graph *_graph = nullptr; + + std::vector createSplit(mir::Operation::Output *arg, int32_t num_parts, + int32_t axis); + + mir::Operation::Output *createFullyConnected(mir::Operation::Output *input, + mir::Operation::Output *weights, int32_t axis); + + mir::TensorVariant convertBlob(const caffe::BlobProto &blob); + + template mir::Operation *createOp(Types &&... args); +}; + +template +mir::Operation *CaffeOpCreator::createOp(Types &&... args) +{ + return _graph->create(std::forward(args)...); +} + +} // namespace mir_caffe + +#endif // MIR_CAFFE_OP_CREATOR_H diff --git a/compiler/mir-caffe-importer/caffe_op_types.h b/compiler/mir-caffe-importer/caffe_op_types.h new file mode 100644 index 00000000000..30fce7d5ff7 --- /dev/null +++ b/compiler/mir-caffe-importer/caffe_op_types.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE_OP_TYPES_H +#define MIR_CAFFE_OP_TYPES_H + +namespace mir_caffe +{ + +enum class CaffeOpType +{ + absVal, + accuracy, + argMax, + batchNorm, + batchReindex, + bias, + BNLL, + clip, + concat, + contrastiveLoss, + convolution, + crop, + data, + deconvolution, + dropout, + dummyData, + eltwise, + ELU, + embed, + euclidianLoss, + exp, + filter, + flatten, + HDF5Data, + HDF5Output, + hingeLoss, + im2Col, + imageData, + infogainLoss, + innerProduct, + input, + log, + LRN, + LSTM, + memoryData, + multinomialLogisticLoss, + MVN, + parameter, + pooling, + power, + PReLU, + python, + recurrent, + reduction, + ReLU, + reshape, + RNN, + scale, + sigmoidCrossEntropyLoss, + sigmoid, + silence, + slice, + softmax, + softmaxWithLoss, + split, + SPP, + tanh, + threshold, + tile, + windowData +}; + +} // namespace mir_caffe + +#endif // MIR_CAFFE_OP_TYPES_H diff --git a/compiler/mir-caffe-importer/requires.cmake b/compiler/mir-caffe-importer/requires.cmake new file mode 100644 index 00000000000..1059c50d360 --- /dev/null +++ b/compiler/mir-caffe-importer/requires.cmake @@ -0,0 +1 @@ +require("mir") diff --git a/compiler/mir-caffe2-importer/CMakeLists.txt b/compiler/mir-caffe2-importer/CMakeLists.txt new file mode 100644 index 00000000000..da55839a750 --- /dev/null +++ b/compiler/mir-caffe2-importer/CMakeLists.txt @@ -0,0 +1,29 @@ +nnas_find_package(PytorchSource QUIET) +nnas_find_package(Protobuf QUIET) + +if (NOT PytorchSource_FOUND OR NOT Protobuf_FOUND) + return() +endif() + +Protobuf_Generate(CAFFE2_PROTO "${CMAKE_CURRENT_BINARY_DIR}/generated/caffe2" + "${PytorchSource_DIR}" "caffe2/proto/caffe2.proto") + +add_library(caffe2proto STATIC ${CAFFE2_PROTO_SOURCES}) +set_target_properties(caffe2proto PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(caffe2proto PUBLIC ${CAFFE2_PROTO_INCLUDE_DIRS}) +target_link_libraries(caffe2proto PUBLIC libprotobuf) + + +set(MIR_CAFFE2_IMPORTER_SOURCES + caffe2_importer.cpp + caffe2_importer.h + caffe2_op_creator.cpp + caffe2_op_creator.h + caffe2_op_types.h + caffe2_proto_helper.cpp + caffe2_proto_helper.h) + +add_library(mir_caffe2_importer STATIC ${MIR_CAFFE2_IMPORTER_SOURCES}) +set_target_properties(mir_caffe2_importer PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_include_directories(mir_caffe2_importer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(mir_caffe2_importer PUBLIC mir caffe2proto PRIVATE stdex) diff --git a/compiler/mir-caffe2-importer/caffe2_importer.cpp b/compiler/mir-caffe2-importer/caffe2_importer.cpp new file mode 100644 index 00000000000..5a6eef0aa28 --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_importer.cpp @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "caffe2_importer.h" +#include "caffe2/proto/caffe2.pb.h" +#include "caffe2_op_types.h" +#include "caffe2_op_creator.h" +#include "caffe2_proto_helper.h" + +#include "mir/ops/InputOp.h" +#include "mir/ops/OutputOp.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +using namespace mir_caffe2; + +class Caffe2Importer +{ +public: + explicit Caffe2Importer(std::string predict_net, std::string init_net, + const std::vector> &input_shapes); + + /// @brief Load the model and convert it into a MIR Graph. + std::unique_ptr importModel(); + + ~Caffe2Importer(); + +private: + std::string _predictNet; + std::string _initNet; + std::unique_ptr _graph; + std::unique_ptr _predict_net; + std::unique_ptr _init_net; + std::unique_ptr _opCreator; + std::vector _inputShapes; + + static const std::map _operatorTypes; + + // Maps Caffe2 operator input names to corresponding MIR operation outputs. + std::unordered_map _blobNameToOutput; + + void import(); + std::unique_ptr createIR(); + + /** + * @brief Pass through caffe2 graph and collect ops unsupported by NNC + * @throw PassException with message, containing detected problems + */ + void collectUnsupportedOps(); + + /** + * @brief Creating MIR node from single caffe2 operator + */ + void createMIRNodesFromOp(const ::caffe2::OperatorDef &op); + + /** + * @brief Returns MIR operation outputs corresponding to the inputs of the given operator. + */ + std::vector getInputMIROps(const ::caffe2::OperatorDef &op); + + void setOutputForTensor(const std::string &tensor_name, Operation::Output *output); + mir::Operation::Output *getOutputForTensor(const std::string &name) const; + + /** + * @brief Mark output MIR nodes + */ + void setGraphOutputs(); +}; + +using namespace ::caffe2; +using mir::Shape; + +Caffe2Importer::Caffe2Importer(std::string predict_net, std::string init_net, + const std::vector> &input_shapes) + : _predictNet(std::move(predict_net)), _initNet(std::move(init_net)) +{ + for (auto &shape : input_shapes) + _inputShapes.emplace_back(shape); + + _graph = stdex::make_unique(); + _opCreator = stdex::make_unique(_graph.get()); +} + +Caffe2Importer::~Caffe2Importer() = default; + +static void loadModelFile(const std::string &filename, caffe2::NetDef *net) +{ + GOOGLE_PROTOBUF_VERIFY_VERSION; + + int file_handle = open(filename.c_str(), O_RDONLY); + + if (file_handle == -1) + throw std::runtime_error("Couldn't open file \"" + filename + "\": " + std::strerror(errno) + + "."); + + google::protobuf::io::FileInputStream file_stream(file_handle); + file_stream.SetCloseOnDelete(true); + + google::protobuf::io::CodedInputStream coded_stream(&file_stream); + coded_stream.SetTotalBytesLimit(INT_MAX, INT_MAX); + + if (!net->ParseFromCodedStream(&coded_stream)) + throw std::runtime_error("Couldn't parse file \"" + filename + "\"."); + + // If the file has not been consumed entirely, assume that the file is in the wrong format. + if (!coded_stream.ConsumedEntireMessage()) + throw std::runtime_error("File \"" + filename + "\" has not been consumed entirely."); +} + +void Caffe2Importer::import() +{ + _predict_net = stdex::make_unique(); + loadModelFile(_predictNet, _predict_net.get()); + + _init_net = stdex::make_unique(); + loadModelFile(_initNet, _init_net.get()); + + collectUnsupportedOps(); +} + +std::unique_ptr Caffe2Importer::createIR() +{ + // Load initializers. + for (const auto &op : _init_net->op()) + createMIRNodesFromOp(op); + + // Create inputs. This has to be done after processing initializers, because they may contain + // fake inputs. + // TODO Caffe2 does not provide a way to detect model inputs and outputs. For now assume that: + // - there is exactly one input; + // - the input is for the first layer; + // - the input has 'float' element type. + const auto &input_name = _predict_net->op(0).input(0); + mir::TensorType input_type(mir::DataType::FLOAT32, _inputShapes[0]); + auto input = _graph->create(input_type)->getOutput(0); + setOutputForTensor(input_name, input); + + for (const auto &op : _predict_net->op()) + createMIRNodesFromOp(op); + + setGraphOutputs(); + + return std::move(_graph); +} + +std::unique_ptr Caffe2Importer::importModel() +{ + import(); + return createIR(); +} + +void Caffe2Importer::collectUnsupportedOps() +{ + std::set unsupportedOps; + for (const auto &op : _predict_net->op()) + { + if (_operatorTypes.find(op.type()) == _operatorTypes.end()) + unsupportedOps.insert(op.type()); + } + + if (!unsupportedOps.empty()) + { + std::string exceptionMsg("Can't load model, unsupported operators:"); + for (const auto &op : unsupportedOps) + exceptionMsg.append("\n * " + op); + throw std::runtime_error(exceptionMsg); + } +} + +void Caffe2Importer::createMIRNodesFromOp(const OperatorDef &op) +{ + std::vector outputs; + + auto inputs = getInputMIROps(op); + + SupportedCaffe2OpType opType = _operatorTypes.at(op.type()); + switch (opType) + { + case SupportedCaffe2OpType::constantFill: + case SupportedCaffe2OpType::givenTensorFill: + case SupportedCaffe2OpType::givenTensorInt64Fill: + outputs = _opCreator->convertConstant(inputs, op); + break; + case SupportedCaffe2OpType::add: + outputs = _opCreator->convertAdd(inputs, op); + break; + case SupportedCaffe2OpType::averagePool: + outputs = _opCreator->convertAveragePool(inputs, op); + break; + case SupportedCaffe2OpType::conv: + outputs = _opCreator->convertConv(inputs, op); + break; + case SupportedCaffe2OpType::concat: + outputs = _opCreator->convertConcat(inputs, op); + break; + case SupportedCaffe2OpType::dropout: + outputs = _opCreator->convertDropout(inputs, op); + break; + case SupportedCaffe2OpType::FC: + outputs = _opCreator->convertFC(inputs, op); + break; + case SupportedCaffe2OpType::maxPool: + outputs = _opCreator->convertMaxPool(inputs, op); + break; + case SupportedCaffe2OpType::mul: + outputs = _opCreator->convertMul(inputs, op); + break; + case SupportedCaffe2OpType::relu: + outputs = _opCreator->convertRelu(inputs); + break; + case SupportedCaffe2OpType::resizeNearest: + outputs = _opCreator->convertResizeNearest(inputs, op); + break; + case SupportedCaffe2OpType::sigmoid: + outputs = _opCreator->convertSigmoid(inputs); + break; + case SupportedCaffe2OpType::softmax: + outputs = _opCreator->convertSoftmax(inputs, op); + break; + case SupportedCaffe2OpType::spatialBN: + outputs = _opCreator->convertSpatialBN(inputs, op); + break; + case SupportedCaffe2OpType::sum: + outputs = _opCreator->convertSum(inputs); + break; + case SupportedCaffe2OpType::clip: + outputs = _opCreator->convertClip(inputs, op); + break; + case SupportedCaffe2OpType::reshape: + outputs = _opCreator->convertReshape(inputs, op); + break; + default: + assert(false && "All unsupported types should have been found before this pass."); + } + + for (size_t i = 0; i < outputs.size(); ++i) + { + setOutputForTensor(op.output(i), outputs[i]); + } +} + +std::vector Caffe2Importer::getInputMIROps(const OperatorDef &op) +{ + std::vector inputs; + + for (const auto &input_name : op.input()) + { + inputs.push_back(getOutputForTensor(input_name)); + } + + return inputs; +} + +void Caffe2Importer::setOutputForTensor(const std::string &tensor_name, Operation::Output *output) +{ + auto it = _blobNameToOutput.find(tensor_name); + if (it != _blobNameToOutput.cend()) + { + // caffe2 input blob name could be same as output blob name, and next line will overwrite + // '_blobNameToOpOutput' element, but in all networks that I saw it was not a problem + it->second->setName(""); + } + output->setName(tensor_name); + _blobNameToOutput[tensor_name] = output; +} + +mir::Operation::Output *Caffe2Importer::getOutputForTensor(const std::string &name) const +{ + return _blobNameToOutput.at(name); +} + +void Caffe2Importer::setGraphOutputs() +{ + // Create outputs. + // TODO Caffe2 does not provide a way to detect model inputs and outputs. For now assume that: + // - there is exactly one output; + // - the output is from the last layer. + const auto &output_name = _predict_net->op().rbegin()->output(0); + auto output = getOutputForTensor(output_name); + _graph->create(output); +} + +const std::map Caffe2Importer::_operatorTypes = { + {"Add", SupportedCaffe2OpType::add}, + {"AveragePool", SupportedCaffe2OpType::averagePool}, + {"Conv", SupportedCaffe2OpType::conv}, + {"Concat", SupportedCaffe2OpType::concat}, + {"ConstantFill", SupportedCaffe2OpType::constantFill}, + {"Dropout", SupportedCaffe2OpType::dropout}, + {"FC", SupportedCaffe2OpType::FC}, + {"GivenTensorFill", SupportedCaffe2OpType::givenTensorFill}, + {"MaxPool", SupportedCaffe2OpType::maxPool}, + {"Mul", SupportedCaffe2OpType::mul}, + {"Relu", SupportedCaffe2OpType::relu}, + {"ResizeNearest", SupportedCaffe2OpType::resizeNearest}, + {"Sigmoid", SupportedCaffe2OpType::sigmoid}, + {"Softmax", SupportedCaffe2OpType::softmax}, + {"SpatialBN", SupportedCaffe2OpType::spatialBN}, + {"Sum", SupportedCaffe2OpType::sum}, + {"Clip", SupportedCaffe2OpType::clip}, + {"Reshape", SupportedCaffe2OpType::reshape}, + {"GivenTensorInt64Fill", SupportedCaffe2OpType::givenTensorInt64Fill}, +}; +} + +namespace mir_caffe2 +{ + +std::unique_ptr loadModel(std::string predict_net, std::string init_net, + const std::vector> &input_shapes) +{ + Caffe2Importer importer(std::move(predict_net), std::move(init_net), input_shapes); + return importer.importModel(); +} + +} // namespace mir_caffe2 diff --git a/compiler/mir-caffe2-importer/caffe2_importer.h b/compiler/mir-caffe2-importer/caffe2_importer.h new file mode 100644 index 00000000000..213fbe98d0d --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_importer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE2_IMPORTER_H +#define MIR_CAFFE2_IMPORTER_H + +#include +#include +#include + +#include "mir/Graph.h" + +namespace mir_caffe2 +{ + +std::unique_ptr loadModel(std::string predict_net, std::string init_net, + const std::vector> &input_shapes); + +} // namespace mir_caffe2 + +#endif // MIR_CAFFE2_IMPORTER_H diff --git a/compiler/mir-caffe2-importer/caffe2_op_creator.cpp b/compiler/mir-caffe2-importer/caffe2_op_creator.cpp new file mode 100644 index 00000000000..d279fb1ed19 --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_op_creator.cpp @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "caffe2_op_creator.h" +#include "caffe2_proto_helper.h" + +#include "mir/ops/AddOp.h" +#include "mir/ops/AvgPool2DOp.h" +#include "mir/ops/CappedReluOp.h" +#include "mir/ops/ConcatOp.h" +#include "mir/ops/ConstantOp.h" +#include "mir/ops/Conv2DOp.h" +#include "mir/ops/FullyConnectedOp.h" +#include "mir/ops/MaxPool2DOp.h" +#include "mir/ops/MulOp.h" +#include "mir/ops/ReluOp.h" +#include "mir/ops/ReshapeOp.h" +#include "mir/ops/ResizeOp.h" +#include "mir/ops/SigmoidOp.h" +#include "mir/ops/SoftmaxOp.h" +#include "mir/ops/TransposeOp.h" + +#include "mir/Index.h" +#include "mir/Shape.h" +#include "mir/ShapeRange.h" +#include "mir/Tensor.h" +#include "mir/TensorUtil.h" + +#include +#include + +namespace mir_caffe2 +{ + +using namespace ::caffe2; +using namespace mir; + +// +// Helper functions +// + +static std::pair, std::vector> +getPadding(const ::caffe2::OperatorDef &op) +{ + + if (hasArgument(op.arg(), "pads")) + { + // pads order: t l b r + auto pads_arg = findArgumentByName(op.arg(), "pads"); + + std::vector paddings; + for (const auto &pad : pads_arg.ints()) + paddings.push_back(static_cast(pad)); + + assert(paddings.size() == 4); + + int32_t pad_t = paddings[0]; + int32_t pad_l = paddings[1]; + int32_t pad_b = paddings[2]; + int32_t pad_r = paddings[3]; + + std::vector padding_before{pad_t, pad_l}; + std::vector padding_after{pad_b, pad_r}; + return {padding_before, padding_after}; + } + + bool has_custom_pad = hasArgument(op.arg(), "pad_l") || hasArgument(op.arg(), "pad_r") || + hasArgument(op.arg(), "pad_t") || hasArgument(op.arg(), "pad_b"); + + if (has_custom_pad) + { + int32_t pad_l = getSingleArgument(op, "pad_l", 0); + int32_t pad_t = getSingleArgument(op, "pad_t", 0); + int32_t pad_r = getSingleArgument(op, "pad_r", 0); + int32_t pad_b = getSingleArgument(op, "pad_b", 0); + + std::vector padding_before{pad_t, pad_l}; + std::vector padding_after{pad_b, pad_r}; + return {padding_before, padding_after}; + } + + int32_t pad = getSingleArgument(op, "pad", 0); + return {{pad, pad}, {pad, pad}}; +} + +static std::vector getStrides(const ::caffe2::OperatorDef &op) +{ + std::vector strides; + + if (hasArgument(op.arg(), "stride")) + { + std::int32_t stride = getSingleArgument(op, "stride", 1); + strides = {stride, stride}; + } + + if (hasArgument(op.arg(), "strides")) + { + // strides order: h w + auto strides_arg = findArgumentByName(op.arg(), "strides"); + for (const auto &s : strides_arg.ints()) + strides.push_back(s); + } + + assert(!strides.empty() && "Strides not found"); + + return strides; +} + +static std::vector getWindowSize(const ::caffe2::OperatorDef &op, + const std::vector &inputs) +{ + int is_global_pooling = getSingleArgument(op, "global_pooling", 0); + bool has_custom_kernel_size = + hasArgument(op.arg(), "kernel_h") || hasArgument(op.arg(), "kernel_w"); + bool has_custom_kernels_size = hasArgument(op.arg(), "kernels"); + + int kernel_h(0), kernel_w(0); + if (is_global_pooling) + { + const auto &input_shape = inputs[0]->getShape(); + assert(input_shape.rank() == 4 && "getWindowSize() inputs must be of rank 4"); + kernel_h = input_shape.dim(2); + kernel_w = input_shape.dim(3); + } + else + { + if (has_custom_kernel_size) + { + kernel_h = getSingleArgument(op, "kernel_h", 0); + kernel_w = getSingleArgument(op, "kernel_w", 0); + } + else + { + if (has_custom_kernels_size) + { + // kernels order: h w + std::vector kernels; + auto kernels_arg = findArgumentByName(op.arg(), "kernels"); + for (const auto &ker : kernels_arg.ints()) + kernels.push_back(static_cast(ker)); + assert(kernels.size() == 2); + kernel_h = kernels[0]; + kernel_w = kernels[1]; + } + else + { + kernel_h = kernel_w = getSingleArgument(op, "kernel", 0); + } + } + } + return {kernel_h, kernel_w}; +} + +// +// Check functions +// + +static void checkLayout(const OperatorDef &op) +{ + if (getSingleArgument(op, "order", "NCHW") != "NCHW") + throw std::runtime_error(op.type() + ": only 'NCHW' axis order is supported"); +} + +static void checkConvLikeOp(const ::caffe2::OperatorDef &op) +{ + checkLayout(op); + + // Padding + bool has_custom_pad = hasArgument(op.arg(), "pad_l") || hasArgument(op.arg(), "pad_r") || + hasArgument(op.arg(), "pad_t") || hasArgument(op.arg(), "pad_b"); + + if (has_custom_pad && hasArgument(op.arg(), "pad")) + throw std::runtime_error("Custom pad can't be combined with overall pad"); + + if (has_custom_pad && + !(hasArgument(op.arg(), "pad_l") && hasArgument(op.arg(), "pad_r") && + hasArgument(op.arg(), "pad_t") && hasArgument(op.arg(), "pad_b"))) + throw std::runtime_error("If one custom pad specified - all custom pads must be specified"); + + // Kernel size + bool has_custom_kernel_size = + hasArgument(op.arg(), "kernel_h") || hasArgument(op.arg(), "kernel_w"); + + if (has_custom_kernel_size && hasArgument(op.arg(), "kernel")) + throw std::runtime_error("Custom kernel size can't be combined with overall kernel size"); + + if (has_custom_kernel_size && + !(hasArgument(op.arg(), "kernel_h") && hasArgument(op.arg(), "kernel_w"))) + throw std::runtime_error( + "If one custom kernel size specified - all custom kernel sizes must be specified"); +} + +static mir::TensorVariant createTensor(const OperatorDef &op) +{ + assert(hasArgument(op.arg(), "shape") && hasArgument(op.arg(), "values")); + + const auto &shape = findArgumentByName(op.arg(), "shape"); + const auto &values = findArgumentByName(op.arg(), "values"); + + mir::DataType element_type; + const void *src_data; + // if values on floats + if (!values.floats().empty()) + { + element_type = mir::DataType::FLOAT32; + src_data = values.floats().data(); + } + else + { + assert(!values.ints().empty()); + if (op.type() == "GivenTensorInt64Fill") + { + element_type = mir::DataType::INT64; + } + else + { + element_type = mir::DataType::INT32; + } + src_data = values.ints().data(); + } + + mir::Shape tensor_shape(shape.ints_size()); + + for (int i = 0; i < shape.ints_size(); ++i) + { + tensor_shape.dim(i) = shape.ints(i); + } + + return mir::TensorVariant({element_type, tensor_shape}, src_data); +} + +// +// Convert functions +// + +std::vector +Caffe2OpCreator::convertConstant(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + // Constant may not contain any data if it is a fake input. + if (!hasArgument(op.arg(), "values")) + return {}; + + return {createOp(createTensor(op))->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertAdd(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + assert(inputs.size() == 2); + auto lhs = inputs[0]; + auto rhs = inputs[1]; + + if (getSingleArgument(op, "broadcast", 0) != 0) + { + // FIXME This only works when 'axis' == 1 and the second input is 1-D. + rhs = createOp(rhs, Shape{1, rhs->getShape().dim(0), 1, 1})->getOutput(0); + auto result = createOp(lhs, rhs)->getOutput(0); + return {result}; + } + + auto result = createOp(lhs, rhs)->getOutput(0); + return {result}; +} + +std::vector +Caffe2OpCreator::convertAveragePool(const std::vector &inputs, + const OperatorDef &op) +{ + checkConvLikeOp(op); + + assert(inputs.size() == 1); + auto input = inputs[0]; + + AvgPool2DOpAttributes attributes; + std::tie(attributes.padding_before, attributes.padding_after) = getPadding(op); + attributes.window = getWindowSize(op, inputs); + attributes.strides = getStrides(op); + attributes.include_pad = false; + attributes.data_format = DataFormat::NCHW; + auto result = createOp(input, attributes)->getOutput(0); + return {result}; +} + +std::vector +Caffe2OpCreator::convertConv(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + // dilation order: h w (not used) + mir::Conv2DOpAttributes attributes; + attributes.strides = getStrides(op); + std::tie(attributes.padding_before, attributes.padding_after) = getPadding(op); + attributes.num_groups = getSingleArgument(op, "group", 1); + attributes.data_format = DataFormat::NCHW; + + std::vector perm{0, 2, 3, 1}; // OIHW -> OHWI + auto kernel = createOp(inputs[1], perm)->getOutput(0); + auto result = createOp(inputs[0], kernel, attributes)->getOutput(0); + + if (op.input_size() > 2) + { + auto bias = inputs[2]; + bias = createOp(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, bias)->getOutput(0); + } + + return {result}; +} + +std::vector +Caffe2OpCreator::convertConcat(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + checkLayout(op); + + // `1` corresponds to the default (channels) axis. + int axis = getSingleArgument(op, "axis", 1); + auto result = createOp(inputs, axis); + return {result->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertDropout(const std::vector &inputs, + const ::caffe2::OperatorDef &) +{ + // This is a no-op in inference mode. + return {inputs[0]}; +} + +std::vector +Caffe2OpCreator::convertFC(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + for (auto &s : {"axis", "axis_w", "float16_compute"}) + if (hasArgument(op.arg(), s)) + throw std::runtime_error(std::string("FC: only default '") + s + "' value is supported"); + + const auto &input_shape = inputs[0]->getShape(); + // Transform input into 2-D tensor by flattening axes + Shape shape{input_shape.dim(0), input_shape.numElements() / input_shape.dim(0)}; + + auto reshape = createOp(inputs[0], shape)->getOutput(0); + auto weights = + createOp(inputs[1], std::vector{1, 0})->getOutput(0); + auto result = createOp(reshape, weights)->getOutput(0); + result = createOp(result, inputs[2])->getOutput(0); + + return {result}; +} + +std::vector +Caffe2OpCreator::convertMaxPool(const std::vector &inputs, + const OperatorDef &op) +{ + checkConvLikeOp(op); + + assert(inputs.size() == 1); + auto input = inputs[0]; + + MaxPool2DOpAttributes attributes; + std::tie(attributes.padding_before, attributes.padding_after) = getPadding(op); + attributes.window = getWindowSize(op, inputs); + attributes.strides = getStrides(op); + attributes.data_format = DataFormat::NCHW; + auto result = createOp(input, attributes)->getOutput(0); + return {result}; +} + +std::vector +Caffe2OpCreator::convertMul(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + assert(inputs.size() == 2); + auto lhs = inputs[0]; + auto rhs = inputs[1]; + + if (getSingleArgument(op, "broadcast", 0) != 0) + { + // FIXME This only works when `axis` == 1 and the second input is 1-D. + rhs = createOp(rhs, Shape{1, rhs->getShape().dim(0), 1, 1})->getOutput(0); + auto result = createOp(lhs, rhs)->getOutput(0); + return {result}; + } + + auto result = createOp(lhs, rhs)->getOutput(0); + return {result}; +} + +std::vector +Caffe2OpCreator::convertRelu(const std::vector &inputs) +{ + auto relu = createOp(inputs[0]); + return {relu->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertResizeNearest(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + std::vector scales(4); + assert(inputs[0]->getShape().rank() == 4 && "only 4d tensors is supported"); + // Assuming NCHW format. + scales[0] = 1.0f; + scales[1] = 1.0f; + scales[2] = getSingleArgument(op, "height_scale", 1.0f); + scales[3] = getSingleArgument(op, "width_scale", 1.0f); + auto result = + createOp(inputs[0], ops::ResizeOp::ResizeMethod::nearestNeighbor, scales) + ->getOutput(0); + return {result}; +} + +std::vector +Caffe2OpCreator::convertSigmoid(const std::vector &inputs) +{ + auto result = createOp(inputs[0]); + return {result->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertSoftmax(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + int axis = getSingleArgument(op, "axis", 1); + auto softmax = createOp(inputs[0], axis); + return {softmax->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertSpatialBN(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + checkLayout(op); + + // Sanity checks + if (op.input_size() != 5) + throw std::runtime_error( + "SpatialBN must have exactly 5 inputs ('sums' and 'sumsq' are not supported yet)"); + if (getSingleArgument(op, "is_test", 1) != 1) + throw std::runtime_error("SpatialBN: only test mode supported"); + + // overall_res = (X - mean) / sqrt(var + epsilon) * scale + bias + + auto scale_op = dynamic_cast(inputs[1]->getNode()); + auto bias_op = dynamic_cast(inputs[2]->getNode()); + auto mean_op = dynamic_cast(inputs[3]->getNode()); + auto var_op = dynamic_cast(inputs[4]->getNode()); + if (scale_op == nullptr || bias_op == nullptr || mean_op == nullptr || var_op == nullptr) + throw std::runtime_error( + "SpatialBN: non-constant 'scale', 'bias', 'mean' and 'var' inputs are not supported yet."); + + const auto &scale_tensor = scale_op->getValue(); + const auto &bias_tensor = bias_op->getValue(); + const auto &mean_tensor = mean_op->getValue(); + const auto &var_tensor = var_op->getValue(); + float eps = getSingleArgument(op, "epsilon", 1e-5f); + + // res1 = X - mean + Tensor bias_data(mean_tensor); + for (auto &idx : ShapeRange(bias_data.getShape())) + bias_data.at(idx) *= -1; + + auto mean = createOp(mean_tensor)->getOutput(0); + mean = createOp(mean, Shape{1, mean->getShape().dim(0), 1, 1})->getOutput(0); + auto result = createOp(inputs[0], mean)->getOutput(0); + + // res2 = res1 * scale / (var + epsilon) + Tensor multiplier(scale_tensor); + for (auto &idx : ShapeRange(scale_tensor.getShape())) + multiplier.at(idx) /= std::sqrt(*reinterpret_cast(var_tensor.at(idx)) + eps); + auto scale = createOp(scale_tensor)->getOutput(0); + scale = createOp(scale, Shape{1, scale->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, scale)->getOutput(0); + + // overall_res = res2 + bias + auto bias = createOp(bias_tensor)->getOutput(0); + bias = createOp(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0); + result = createOp(result, bias)->getOutput(0); + + return {result}; +} + +std::vector +Caffe2OpCreator::convertSum(const std::vector &inputs) +{ + auto result = createOp(inputs[0], inputs[1])->getOutput(0); + for (int i = 2; i < static_cast(inputs.size()); ++i) + { + result = createOp(result, inputs[i])->getOutput(0); + } + return {result}; +} + +std::vector +Caffe2OpCreator::convertClip(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + + float max = getSingleArgument(op, "max", float(0)); + float min = getSingleArgument(op, "min", float(0)); + + assert(max > 0.0 && min == 0.0 && "Support only if clip is CappedRelu"); + auto cap_relu = createOp(inputs[0], max); + + return {cap_relu->getOutput(0)}; +} + +std::vector +Caffe2OpCreator::convertReshape(const std::vector &inputs, + const ::caffe2::OperatorDef &op) +{ + auto shape_op = dynamic_cast(inputs[1]->getNode()); + if (shape_op == nullptr) + throw std::runtime_error("Reshape: non-constant shape is not supported yet."); + + const auto &shape_tensor = shape_op->getValue(); + + Tensor out_shape_tensor(shape_tensor); + + ShapeRange range(out_shape_tensor.getShape()); + std::vector shape_vec; + for (const auto &index : range) + { + shape_vec.push_back(static_cast(out_shape_tensor.at(index))); + } + Shape out_shape(shape_vec); + + auto reshape = createOp(inputs[0], out_shape); + + return {reshape->getOutput(0)}; +} + +} // namespace mir_caffe2 diff --git a/compiler/mir-caffe2-importer/caffe2_op_creator.h b/compiler/mir-caffe2-importer/caffe2_op_creator.h new file mode 100644 index 00000000000..2b29378e9ea --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_op_creator.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE2_OP_CREATOR_H +#define MIR_CAFFE2_OP_CREATOR_H + +#include +#include +#include +#include + +#include "mir/Graph.h" +#include "mir/Operation.h" +#include "mir/TensorVariant.h" +#include "mir/Shape.h" + +#include "caffe2/proto/caffe2.pb.h" + +namespace mir_caffe2 +{ + +using mir::Operation; +using mir::Shape; + +class Caffe2OpCreator +{ +public: + explicit Caffe2OpCreator(mir::Graph *g) : _graph(g) {} + + std::vector + convertConstant(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertAdd(const std::vector &inputs, const ::caffe2::OperatorDef &op); + + std::vector + convertAveragePool(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertConv(const std::vector &inputs, const ::caffe2::OperatorDef &op); + + std::vector + convertConcat(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertDropout(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertFC(const std::vector &inputs, const ::caffe2::OperatorDef &op); + + std::vector + convertMaxPool(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertMul(const std::vector &inputs, const ::caffe2::OperatorDef &op); + + std::vector + convertRelu(const std::vector &inputs); + + std::vector + convertResizeNearest(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertSigmoid(const std::vector &inputs); + + std::vector + convertSoftmax(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertSpatialBN(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + + std::vector + convertSum(const std::vector &inputs); + + std::vector + convertClip(const std::vector &inputs, const ::caffe2::OperatorDef &op); + + std::vector + convertReshape(const std::vector &inputs, + const ::caffe2::OperatorDef &op); + +private: + mir::Graph *_graph = nullptr; + + template mir::Operation *createOp(Types &&... args); +}; + +template +mir::Operation *Caffe2OpCreator::createOp(Types &&... args) +{ + return _graph->create(std::forward(args)...); +} + +} // namespace mir_caffe2 + +#endif // MIR_CAFFE2_OP_CREATOR_H diff --git a/compiler/mir-caffe2-importer/caffe2_op_types.h b/compiler/mir-caffe2-importer/caffe2_op_types.h new file mode 100644 index 00000000000..b5e7e7631af --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_op_types.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE2_OP_TYPES_H +#define MIR_CAFFE2_OP_TYPES_H + +namespace mir_caffe2 +{ + +enum class SupportedCaffe2OpType +{ + add, + averagePool, + clip, + concat, + conv, + constantFill, + dropout, + FC, + givenTensorFill, + givenTensorInt64Fill, + maxPool, + mul, + relu, + reshape, + resizeNearest, + sigmoid, + softmax, + spatialBN, + sum, +}; + +} // namespace mir_caffe2 + +#endif // MIR_CAFFE2_OP_TYPES_H diff --git a/compiler/mir-caffe2-importer/caffe2_proto_helper.cpp b/compiler/mir-caffe2-importer/caffe2_proto_helper.cpp new file mode 100644 index 00000000000..a7cde64cf31 --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_proto_helper.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "caffe2_proto_helper.h" + +namespace mir_caffe2 +{ + +const ::caffe2::Argument &findArgumentByName(RepArgument args, const std::string &name) +{ + for (auto &arg : args) + if (arg.name() == name) + return arg; + throw std::runtime_error("Can't find argument with name: " + name); +} + +const bool hasArgument(RepArgument args, const std::string &name) +{ + for (auto &arg : args) + if (arg.name() == name) + return true; + return false; +} + +int getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + const int default_value) +{ + if (hasArgument(op.arg(), argument_name)) + return static_cast(findArgumentByName(op.arg(), argument_name).i()); + return default_value; +} + +float getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + const float default_value) +{ + if (hasArgument(op.arg(), argument_name)) + return findArgumentByName(op.arg(), argument_name).f(); + return default_value; +} + +std::string getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + const std::string &default_value) +{ + if (hasArgument(op.arg(), argument_name)) + return findArgumentByName(op.arg(), argument_name).s(); + return default_value; +} + +} // namespace mir_caffe2 diff --git a/compiler/mir-caffe2-importer/caffe2_proto_helper.h b/compiler/mir-caffe2-importer/caffe2_proto_helper.h new file mode 100644 index 00000000000..4c47edec85a --- /dev/null +++ b/compiler/mir-caffe2-importer/caffe2_proto_helper.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef MIR_CAFFE2_PROTO_HELPER_H +#define MIR_CAFFE2_PROTO_HELPER_H + +#include "caffe2/proto/caffe2.pb.h" + +namespace mir_caffe2 +{ + +using RepArgument = const ::google::protobuf::RepeatedPtrField<::caffe2::Argument> &; + +const ::caffe2::Argument &findArgumentByName(RepArgument args, const std::string &name); + +const bool hasArgument(RepArgument args, const std::string &name); + +int getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + int default_value); +float getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + float default_value); +std::string getSingleArgument(const ::caffe2::OperatorDef &op, const std::string &argument_name, + const std::string &default_value); + +} // namespace mir_caffe2 + +#endif // MIR_CAFFE2_PROTO_HELPER_H diff --git a/compiler/mir-caffe2-importer/requires.cmake b/compiler/mir-caffe2-importer/requires.cmake new file mode 100644 index 00000000000..1059c50d360 --- /dev/null +++ b/compiler/mir-caffe2-importer/requires.cmake @@ -0,0 +1 @@ +require("mir") diff --git a/compiler/mir-interpreter/CMakeLists.txt b/compiler/mir-interpreter/CMakeLists.txt new file mode 100644 index 00000000000..814612ae910 --- /dev/null +++ b/compiler/mir-interpreter/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB_RECURSE interp_src ./*.cpp ./*.h) +add_library(mir_interpreter SHARED ${interp_src}) +target_link_libraries(mir_interpreter PUBLIC mir) +target_include_directories(mir_interpreter PUBLIC include) diff --git a/compiler/mir-interpreter/include/MirInterpreter.h b/compiler/mir-interpreter/include/MirInterpreter.h new file mode 100644 index 00000000000..2f4f52dcbb2 --- /dev/null +++ b/compiler/mir-interpreter/include/MirInterpreter.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _MIR_INTERPRETER_ +#define _MIR_INTERPRETER_ + +#include "mir/Visitor.h" +#include "mir/Operation.h" +#include "mir/TensorVariant.h" +#include +#include + +namespace mir_interpreter +{ + +class MIRInterpreter : public mir::Visitor +{ +public: + explicit MIRInterpreter() = default; + + ~MIRInterpreter() override = default; + + /// @deprecated Use `setTensor` instead. + void setOutputTensors(const mir::Operation &op, std::vector &&outputs); + + /// @deprecated Use `getTensor` instead. + mir::TensorVariant getResult(const mir::Operation::Output *tensor); + + /// @brief Set tensor to the interpreter environment. + void setTensor(const mir::Operation::Output *output, mir::TensorVariant tensor); + + /// @brief Get tensor from the interpreter environment. + const mir::TensorVariant &getTensor(const mir::Operation::Output *) const; + + void visit(mir::ops::AddOp &op) override; + void visit(mir::ops::AbsOp &op) override; + void visit(mir::ops::AvgPool2DOp &op) override; + void visit(mir::ops::CappedReluOp &op) override; + void visit(mir::ops::ConcatOp &op) override; + void visit(mir::ops::ConstantOp &op) override; + void visit(mir::ops::Conv2DOp &op) override; + void visit(mir::ops::DeConv2DOp &op) override; + void visit(mir::ops::DepthwiseConv2DOp &op) override; + void visit(mir::ops::DequantizeOp &op) override; + void visit(mir::ops::DivOp &op) override; + void visit(mir::ops::EluOp &op) override; + void visit(mir::ops::EqualOp &op) override; + void visit(mir::ops::FullyConnectedOp &op) override; + void visit(mir::ops::GatherOp &op) override; + void visit(mir::ops::GreaterOp &op) override; + void visit(mir::ops::HardSwishOp &op) override; + void visit(mir::ops::InputOp &op) override; + void visit(mir::ops::LeakyReluOp &op) override; + void visit(mir::ops::LessOp &op) override; + void visit(mir::ops::MaxOp &op) override; + void visit(mir::ops::MaxPool2DOp &op) override; + void visit(mir::ops::MulOp &op) override; + void visit(mir::ops::OutputOp &op) override; + void visit(mir::ops::PadOp &op) override; + void visit(mir::ops::QuantizeOp &op) override; + void visit(mir::ops::ReduceMeanOp &op) override; + void visit(mir::ops::ReluOp &op) override; + void visit(mir::ops::ReshapeOp &op) override; + void visit(mir::ops::ResizeOp &op) override; + void visit(mir::ops::SigmoidOp &op) override; + void visit(mir::ops::SliceOp &op) override; + void visit(mir::ops::SoftmaxOp &op) override; + void visit(mir::ops::SqrtOp &op) override; + void visit(mir::ops::SqueezeOp &op) override; + void visit(mir::ops::SubOp &op) override; + void visit(mir::ops::TanhOp &op) override; + void visit(mir::ops::TransposeOp &op) override; + void visit(mir::ops::BroadcastOp &op) override; + +protected: + void visit_fallback(mir::Operation &op) override; + +private: + /// @brief Gets the computed inputs for the operation. + std::vector> + getInputTensors(const mir::Operation &op); + + std::vector> + getOutputTensors(const mir::Operation &op); + + /// @brief Mapping of operation outputs to corresponding tensors. + std::unordered_map _tensors; +}; + +} // namespace mir_interpreter + +#endif // _MIR_INTERPRETER_ diff --git a/compiler/mir-interpreter/requires.cmake b/compiler/mir-interpreter/requires.cmake new file mode 100644 index 00000000000..1059c50d360 --- /dev/null +++ b/compiler/mir-interpreter/requires.cmake @@ -0,0 +1 @@ +require("mir") diff --git a/compiler/mir-interpreter/src/MirInterpreter.cpp b/compiler/mir-interpreter/src/MirInterpreter.cpp new file mode 100644 index 00000000000..474a451c90b --- /dev/null +++ b/compiler/mir-interpreter/src/MirInterpreter.cpp @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "MirInterpreter.h" + +#include "ops/Add.h" +#include "ops/Abs.h" +#include "ops/AvgPool2D.h" +#include "ops/CappedReLU.h" +#include "ops/Concat.h" +#include "ops/Conv2D.h" +#include "ops/DeConv2D.h" +#include "ops/DepthwiseConv2D.h" +#include "ops/Div.h" +#include "ops/ELU.h" +#include "ops/Equal.h" +#include "ops/Fill.h" +#include "ops/FullyConnected.h" +#include "ops/Gather.h" +#include "ops/Greater.h" +#include "ops/HardSwish.h" +#include "ops/LeakyReLU.h" +#include "ops/Less.h" +#include "ops/Max.h" +#include "ops/MaxPool2D.h" +#include "ops/Mul.h" +#include "ops/Pad.h" +#include "ops/Quantization.h" +#include "ops/ReduceMean.h" +#include "ops/ReLU.h" +#include "ops/Reshape.h" +#include "ops/Sigmoid.h" +#include "ops/Slice.h" +#include "ops/Softmax.h" +#include "ops/Sqrt.h" +#include "ops/Sub.h" +#include "ops/Tanh.h" +#include "ops/Transpose.h" + +#include "ops/Common.h" + +#include "mir/OpDefs.h" + +#include +#include +#include +#include + +namespace mir_interpreter +{ + +using namespace mir; + +void MIRInterpreter::setTensor(const Operation::Output *output, TensorVariant tensor) +{ + const auto result = _tensors.emplace(output, std::move(tensor)); + if (!result.second) + { + const std::string &name = output->getName(); + throw std::runtime_error("Attempt to overwrite data for tensor \"" + name + "\"."); + } +} + +const TensorVariant &MIRInterpreter::getTensor(const Operation::Output *output) const +{ + const auto it = _tensors.find(output); + if (it == _tensors.end()) + { + const std::string &name = output->getName(); + throw std::runtime_error("Can't find data for tensor \"" + name + "\"."); + } + return it->second; +} + +std::vector> +MIRInterpreter::getInputTensors(const Operation &op) +{ + std::vector> tensors; + for (const Operation::Output *input : op.getInputs()) + { + tensors.emplace_back(getTensor(input)); + } + return tensors; +} + +std::vector> +MIRInterpreter::getOutputTensors(const Operation &op) +{ + // Create and register output tensors. + for (const Operation::Output &res : op.getOutputs()) + { + assert(res.getElementType() != mir::DataType::UNKNOWN); + _tensors.emplace(&res, res.getType()); + } + + // Gather references to output tensors. + std::vector> tensors; + for (const Operation::Output &output : op.getOutputs()) + { + tensors.emplace_back(_tensors.at(&output)); + } + return tensors; +} + +// Deprecated, will be removed. +void MIRInterpreter::setOutputTensors(const Operation &op, std::vector &&outputs) +{ + assert(outputs.size() == op.getNumOutputs()); + for (std::size_t i = 0; i < op.getNumOutputs(); ++i) + { + setTensor(op.getOutput(i), std::move(outputs[i])); + } +} + +// Deprecated, will be removed. +TensorVariant MIRInterpreter::getResult(const Operation::Output *tensor) +{ + return getTensor(tensor); +} + +void MIRInterpreter::visit(ops::InputOp &op) +{ + assert(_tensors.find(op.getOutput(0)) != _tensors.end()); +} + +void MIRInterpreter::visit(ops::AvgPool2DOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + AvgPool2D(op, inputs[0], outputs[0]); +} + +void MIRInterpreter::visit(ops::ConstantOp &op) { setOutputTensors(op, {op.getValue()}); } + +void MIRInterpreter::visit(ops::ConcatOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Concat(inputs, op.getAxis(), outputs[0]); +} + +void MIRInterpreter::visit(ops::Conv2DOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + const mir::TensorVariant *bias = nullptr; + if (inputs.size() > 2) + { + bias = &(inputs[2].get()); + } + Conv2D(inputs[0], inputs[1], op.getAttributes(), outputs[0], bias); +} + +void MIRInterpreter::visit(ops::MaxPool2DOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + MaxPool2D(inputs[0], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::ReshapeOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = Reshape(inputs[0], op.getOutputShape(0)); + setOutputTensors(op, std::move(outputs)); +} + +void MIRInterpreter::visit(ops::ReluOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + ReLU(args[0], results[0]); +} + +void MIRInterpreter::visit(ops::SigmoidOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + Sigmoid(args[0], results[0]); +} + +void MIRInterpreter::visit(ops::SoftmaxOp &op) +{ + auto inputs = getInputTensors(op); + assert(inputs.size() == 1); + auto outputs = getOutputTensors(op); + Softmax(inputs[0], op.getAxis(), outputs[0]); +} + +void MIRInterpreter::visit(ops::FullyConnectedOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + const mir::TensorVariant *bias = nullptr; + if (inputs.size() > 2) + { + bias = &(inputs[3].get()); + } + FullyConnected(inputs[0], inputs[1], op, outputs[0], bias); +} + +void MIRInterpreter::visit(ops::CappedReluOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + CappedReLU(args[0], op.getCap(), results[0]); +} + +void MIRInterpreter::visit(ops::DepthwiseConv2DOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + const mir::TensorVariant *bias = nullptr; + if (inputs.size() > 2) + { + bias = &inputs[3].get(); + } + DepthwiseConv2D(op, inputs[0], inputs[1], outputs[0], bias); +} + +void MIRInterpreter::visit(ops::SliceOp &op) +{ + auto inputs = getInputTensors(op); + auto input = inputs[0]; + auto outputs = getOutputTensors(op); + Slice(input, op.getStarts(), outputs[0]); +} + +void MIRInterpreter::visit(ops::TanhOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + Tanh(args[0], results[0]); +} + +void MIRInterpreter::visit(ops::DeConv2DOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + DeConv2D(inputs[0], inputs[1], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::EluOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + ELU(args[0], op.getAlpha(), results[0]); +} + +void MIRInterpreter::visit(ops::SqueezeOp &op) +{ + auto inputs = getInputTensors(op); + // Squeeze is just a special case of reshape. + auto outputs = Reshape(inputs[0], op.getOutputShape(0)); + setOutputTensors(op, std::move(outputs)); +} + +void MIRInterpreter::visit(ops::PadOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Pad(inputs[0], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::SqrtOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + Sqrt(args[0], results[0]); +} + +void MIRInterpreter::visit(ops::ResizeOp &op) +{ + // TODO support types other than float32 + auto inputs = getInputTensors(op); + assert(inputs[0].get().getElementType() == mir::DataType::FLOAT32); + auto outputs = getOutputTensors(op); + + Tensor input(inputs[0]); + assert(op.getMode() == ops::ResizeOp::ResizeMethod::nearestNeighbor); + + auto scales = op.getScales(); + Fill(outputs[0], [&scales, &input](const Index &id) { + Index in_idx; + in_idx.resize(4); + for (int i = 0; i < input.getShape().rank(); i++) + { + in_idx.at(i) = static_cast(floorf(id.at(i) / scales[i])); + } + return input.at(in_idx); + }); +} + +void MIRInterpreter::visit(ops::ReduceMeanOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + ReduceMean(inputs[0], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::TransposeOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Transpose(inputs[0], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::GatherOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Gather(inputs[0], inputs[1], op, outputs[0]); +} + +void MIRInterpreter::visit(ops::LeakyReluOp &op) +{ + auto args = getInputTensors(op); + auto results = getOutputTensors(op); + LeakyReLU(args[0], op.getAlpha(), results[0]); +} + +void MIRInterpreter::visit(ops::OutputOp &op) +{ + assert(_tensors.find(op.getInput(0)) != _tensors.end()); +} + +void MIRInterpreter::visit(ops::AddOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Add(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::DivOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Div(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::MaxOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Max(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::MulOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Mul(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::SubOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Sub(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::DequantizeOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Dequantize(inputs[0], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::QuantizeOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Quantize(inputs[0], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::HardSwishOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + HardSwish(inputs[0], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::GreaterOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Greater(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::LessOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Less(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::EqualOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Equal(inputs[0], inputs[1], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::AbsOp &op) +{ + auto inputs = getInputTensors(op); + auto outputs = getOutputTensors(op); + Abs(inputs[0], outputs[0]); +} + +void MIRInterpreter::visit(mir::ops::BroadcastOp &op) +{ + auto inputs = getInputTensors(op); + TensorVariant tv{inputs[0], op.getOutputShape(0)}; + setOutputTensors(op, {tv}); +} + +void MIRInterpreter::visit_fallback(mir::Operation &) { throw std::runtime_error("NYI operation"); } + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/Abs.cpp b/compiler/mir-interpreter/src/ops/Abs.cpp new file mode 100644 index 00000000000..547009ffdfc --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Abs.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "Abs.h" +#include "Common.h" + +#include +#include + +#include + +namespace mir_interpreter +{ + +template struct AbsImpl +{ + static void run(const mir::TensorVariant &arg, mir::TensorVariant &result) + { + mir::Tensor arg_accessor(arg); + mir::Tensor res_accessor(result); + + for (const auto &index : mir::ShapeRange(result.getShape())) + { + res_accessor.at(index) = std::abs(arg_accessor.at(index)); + } + } +}; + +template <> struct AbsImpl +{ + static void run(const mir::TensorVariant &arg, mir::TensorVariant &result) + { + throw std::runtime_error{"NYI"}; + } +}; + +void Abs(const mir::TensorVariant &arg, mir::TensorVariant &result) +{ + dispatch(arg.getElementType(), arg, result); +}; + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/Abs.h b/compiler/mir-interpreter/src/ops/Abs.h new file mode 100644 index 00000000000..1ba59e647ef --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Abs.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _NNC_CORE_BACKEND_INTERPRETER_ABS_ +#define _NNC_CORE_BACKEND_INTERPRETER_ABS_ + +#include + +namespace mir_interpreter +{ + +void Abs(const mir::TensorVariant &arg, mir::TensorVariant &result); + +} // namespace mir_interpreter + +#endif //_NNC_CORE_BACKEND_INTERPRETER_ABS_ diff --git a/compiler/mir-interpreter/src/ops/Add.cpp b/compiler/mir-interpreter/src/ops/Add.cpp new file mode 100644 index 00000000000..631b854b71d --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Add.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2019 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "Add.h" +#include "Common.h" + +#include "QuantizationHelpers.h" +#include "mir/Tensor.h" +#include "mir/ShapeRange.h" + +#include + +namespace mir_interpreter +{ + +using namespace mir; + +template struct AddImpl +{ + static void run(const TensorVariant &lhs, const TensorVariant &rhs, TensorVariant &res); +}; + +template +void AddImpl::run(const TensorVariant &lhs, const TensorVariant &rhs, TensorVariant &res) +{ + TensorVariant broadcasted_lhs(lhs, res.getShape()); + TensorVariant broadcasted_rhs(rhs, res.getShape()); + Tensor lhs_accessor(broadcasted_lhs); + Tensor rhs_accessor(broadcasted_rhs); + Tensor res_accessor(res); + + for (const auto &index : ShapeRange(res.getShape())) + { + res_accessor.at(index) = lhs_accessor.at(index) + rhs_accessor.at(index); + } +} + +template <> struct AddImpl +{ + static void run(const TensorVariant &lhs, const TensorVariant &rhs, TensorVariant &res); +}; + +void AddImpl::run(const TensorVariant &lhs, const TensorVariant &rhs, TensorVariant &res) +{ + const auto &lhs_type = lhs.getType(); + const auto &rhs_type = rhs.getType(); + const auto &res_type = res.getType(); + + assert(lhs_type.isQuantized()); + assert(rhs_type.isQuantized()); + assert(res_type.isQuantized()); + + int32_t lhs_offset = -lhs_type.getQuantization().getZeroPoint(); + int32_t rhs_offset = -rhs_type.getQuantization().getZeroPoint(); + int32_t output_offset = res_type.getQuantization().getZeroPoint(); + + double lhs_scale = lhs_type.getQuantization().getScale(); + double rhs_scale = rhs_type.getQuantization().getScale(); + double output_scale = res_type.getQuantization().getScale(); + + int left_shift = 20; + const double twice_max_input_scale = 2 * std::max(lhs_scale, rhs_scale); + const double real_lhs_multiplier = lhs_scale / twice_max_input_scale; + const double real_rhs_multiplier = rhs_scale / twice_max_input_scale; + const double real_output_multiplier = twice_max_input_scale / ((1 << left_shift) * output_scale); + + int32_t lhs_multiplier = 0; + int32_t rhs_multiplier = 0; + int32_t output_multiplier = 0; + int lhs_shift = 0; + int rhs_shift = 0; + int output_shift = 0; + + QuantizeMultiplierSmallerThanOneExp(real_lhs_multiplier, &lhs_multiplier, &lhs_shift); + QuantizeMultiplierSmallerThanOneExp(real_rhs_multiplier, &rhs_multiplier, &rhs_shift); + QuantizeMultiplierSmallerThanOneExp(real_output_multiplier, &output_multiplier, &output_shift); + + TensorVariant broadcasted_lhs(lhs, res_type.getShape()); + TensorVariant broadcasted_rhs(rhs, res_type.getShape()); + + Tensor lhs_accessor(broadcasted_lhs); + Tensor rhs_accessor(broadcasted_rhs); + Tensor res_accessor(res); + + int32_t output_min = std::numeric_limits::min(); + int32_t output_max = std::numeric_limits::max(); + + for (const auto &index : ShapeRange(res_type.getShape())) + { + const int32_t lhs_val = lhs_accessor.at(index) + lhs_offset; + const int32_t rhs_val = rhs_accessor.at(index) + rhs_offset; + const int32_t shifted_lhs_val = lhs_val * (1 << left_shift); + const int32_t shifted_rhs_val = rhs_val * (1 << left_shift); + const int32_t scaled_lhs_val = + MultiplyByQuantizedMultiplierSmallerThanOneExp(shifted_lhs_val, lhs_multiplier, lhs_shift); + const int32_t scaled_rhs_val = + MultiplyByQuantizedMultiplierSmallerThanOneExp(shifted_rhs_val, rhs_multiplier, rhs_shift); + const int32_t raw_sum = scaled_lhs_val + scaled_rhs_val; + const int32_t raw_output = + MultiplyByQuantizedMultiplierSmallerThanOneExp(raw_sum, output_multiplier, output_shift) + + output_offset; + const int32_t clamped_output = std::min(output_max, std::max(output_min, raw_output)); + res_accessor.at(index) = static_cast(clamped_output); + } +} + +void Add(const TensorVariant &lhs, const TensorVariant &rhs, TensorVariant &res) +{ + if (lhs.getElementType() != rhs.getElementType()) + { + throw std::runtime_error{"Add with different input types is unsupported"}; + } + dispatch(res.getElementType(), lhs, rhs, res); +} + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/Add.h b/compiler/mir-interpreter/src/ops/Add.h new file mode 100644 index 00000000000..48508226f5b --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Add.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _NNC_CORE_BACKEND_INTERPRETER_ADD_ +#define _NNC_CORE_BACKEND_INTERPRETER_ADD_ + +#include "mir/TensorVariant.h" + +namespace mir_interpreter +{ + +void Add(const mir::TensorVariant &lhs, const mir::TensorVariant &rhs, mir::TensorVariant &res); + +} // namespace mir_interpreter + +#endif //_NNC_CORE_BACKEND_INTERPRETER_ADD_ diff --git a/compiler/mir-interpreter/src/ops/AvgPool2D.cpp b/compiler/mir-interpreter/src/ops/AvgPool2D.cpp new file mode 100644 index 00000000000..3f1d65100d4 --- /dev/null +++ b/compiler/mir-interpreter/src/ops/AvgPool2D.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * Copyright 2019 The TensorFlow Authors. All Rights Reserved. + * + * 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. + */ + +#include "AvgPool2D.h" +#include "Common.h" + +#include "mir/ShapeRange.h" +#include "mir/Tensor.h" + +namespace mir_interpreter +{ + +using namespace mir; + +template class AvgPool2DImpl +{ +public: + static void run(const mir::ops::AvgPool2DOp &op, const mir::TensorVariant &input_var, + mir::TensorVariant &output); +}; + +template +void AvgPool2DImpl::run(const ops::AvgPool2DOp &op, const TensorVariant &input_var, + TensorVariant &output) +{ + const auto &input_shape = op.getInputShape(0); + const auto &output_shape = op.getOutputShape(0); + const auto &window_size = op.getWindowSize(); + const auto &strides = op.getStrides(); + const auto &padding_before = op.getPaddingBefore(); + const auto &padding_after = op.getPaddingAfter(); + (void)padding_after; + + constexpr int num_spatial_dims = 2; + assert(input_var.getShape().rank() == 4); + assert(window_size.size() == num_spatial_dims); + assert(strides.size() == num_spatial_dims); + assert(padding_before.size() == num_spatial_dims); + assert(padding_after.size() == num_spatial_dims); + + Tensor res_accessor(output); + Tensor input(input_var); + + ShapeRange in_range(input_shape); + Index in_index(input_shape.rank()); + + for (const auto &out_index : ShapeRange(output_shape)) + { + T result = 0; + size_t num_elements = 0; + + // Assuming NHWC format. + in_index.at(0) = out_index.at(0); + in_index.at(3) = out_index.at(3); + + for (const auto &window_index : ShapeRange(Shape(window_size))) + { + // Assuming NHWC format. + for (int i = 0; i < num_spatial_dims; ++i) + in_index.at(1 + i) = + out_index.at(1 + i) * strides[i] + window_index.at(i) - padding_before[i]; + + if (in_range.contains(in_index)) + { + num_elements++; + result += input.at(in_index); + } + else if (op.getIncludePad()) + { + num_elements++; + } + } + + result /= num_elements; + res_accessor.at(out_index) = result; + } +} + +template <> struct AvgPool2DImpl +{ + static void run(const mir::ops::AvgPool2DOp &op, const mir::TensorVariant &input, + mir::TensorVariant &output); +}; + +void AvgPool2DImpl::run(const ops::AvgPool2DOp &op, const TensorVariant &input, + TensorVariant &output) +{ + const auto &input_type = input.getType(); + const auto &output_type = op.getOutput(0)->getType(); + (void)input_type; + + assert(input_type.isQuantized()); + assert(output_type.isQuantized()); + assert(input_type.getElementType() == DataType::UINT8); + + const auto &input_shape = op.getInputShape(0); + const auto &output_shape = op.getOutputShape(0); + const auto &window_size = op.getWindowSize(); + const auto &strides = op.getStrides(); + const auto &padding_before = op.getPaddingBefore(); + const auto &padding_after = op.getPaddingAfter(); + (void)padding_after; + + constexpr int num_spatial_dims = 2; + assert(input.getShape().rank() == 4); + assert(window_size.size() == num_spatial_dims); + assert(strides.size() == num_spatial_dims); + assert(padding_before.size() == num_spatial_dims); + assert(padding_after.size() == num_spatial_dims); + + Tensor input_accessor(input); + Tensor res_accessor(output); + + ShapeRange in_range(input_shape); + Index in_index(input_shape.rank()); + + int32_t output_min = std::numeric_limits::min(); + int32_t output_max = std::numeric_limits::max(); + + for (const auto &out_index : ShapeRange(output_shape)) + { + int32_t result = 0; + size_t num_elements = 0; + + // Assuming NHWC format. + in_index.at(0) = out_index.at(0); + in_index.at(3) = out_index.at(3); + + for (const auto &window_index : ShapeRange(Shape(window_size))) + { + // Assuming NHWC format. + for (int i = 0; i < num_spatial_dims; ++i) + in_index.at(1 + i) = + out_index.at(1 + i) * strides[i] + window_index.at(i) - padding_before[i]; + + if (in_range.contains(in_index)) + { + num_elements++; + result += input_accessor.at(in_index); + } + else if (op.getIncludePad()) + { + num_elements++; + } + } + result = (result + num_elements / 2) / num_elements; + result = std::max(result, output_min); + result = std::min(result, output_max); + res_accessor.at(out_index) = static_cast(result); + } +} + +void AvgPool2D(const mir::ops::AvgPool2DOp &op, const mir::TensorVariant &input, + mir::TensorVariant &output) +{ + dispatch(output.getElementType(), op, input, output); +} + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/AvgPool2D.h b/compiler/mir-interpreter/src/ops/AvgPool2D.h new file mode 100644 index 00000000000..b30574cee54 --- /dev/null +++ b/compiler/mir-interpreter/src/ops/AvgPool2D.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _NNC_CORE_BACKEND_INTERPRETER_AVG_POOL_2D_ +#define _NNC_CORE_BACKEND_INTERPRETER_AVG_POOL_2D_ + +#include "mir/ops/AvgPool2DOp.h" +#include "mir/TensorVariant.h" + +namespace mir_interpreter +{ + +void AvgPool2D(const mir::ops::AvgPool2DOp &op, const mir::TensorVariant &input, + mir::TensorVariant &output); + +} // namespace mir_interpreter + +#endif //_NNC_CORE_BACKEND_INTERPRETER_AVG_POOL_2D_ diff --git a/compiler/mir-interpreter/src/ops/CappedReLU.cpp b/compiler/mir-interpreter/src/ops/CappedReLU.cpp new file mode 100644 index 00000000000..1ac95ac16ea --- /dev/null +++ b/compiler/mir-interpreter/src/ops/CappedReLU.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include "CappedReLU.h" + +#include "mir/ShapeRange.h" +#include "mir/Tensor.h" + +#include "Common.h" + +#include +#include + +namespace mir_interpreter +{ + +template struct CappedReLUImpl +{ + static void run(const mir::TensorVariant &arg, float cap, mir::TensorVariant &result); +}; + +template +void CappedReLUImpl::run(const mir::TensorVariant &arg, float cap, mir::TensorVariant &result) +{ + mir::Tensor arg_accessor(arg); + mir::Tensor res_accessor(result); + + for (const auto &index : mir::ShapeRange(result.getShape())) + { + res_accessor.at(index) = std::min(std::max(arg_accessor.at(index), T(0)), static_cast(cap)); + } +} + +static float dequantize(uint8_t x, const mir::AffineQuantization &q) +{ + return (static_cast(x) - q.getZeroPoint()) * q.getScale(); +} + +static uint8_t quantize(float x, const mir::AffineQuantization &q) +{ + return (static_cast(x) / q.getScale() + q.getZeroPoint()); +} + +template <> struct CappedReLUImpl +{ + static void run(const mir::TensorVariant &arg, float cap, mir::TensorVariant &result) + { + mir::Tensor arg_accessor(arg); + mir::Tensor res_accessor(result); + + auto quant_info = arg.getType().getQuantization(); + assert(!quant_info.empty()); + + for (const auto &index : mir::ShapeRange(result.getShape())) + { + auto value = dequantize(arg_accessor.at(index), quant_info); + auto out_value = + quantize(std::min(std::max(value, 0.0f), cap), result.getType().getQuantization()); + res_accessor.at(index) = out_value; + } + } +}; + +void CappedReLU(const mir::TensorVariant &arg, float cap, mir::TensorVariant &result) +{ + dispatch(arg.getElementType(), arg, cap, result); +} + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/CappedReLU.h b/compiler/mir-interpreter/src/ops/CappedReLU.h new file mode 100644 index 00000000000..ffb756d2acc --- /dev/null +++ b/compiler/mir-interpreter/src/ops/CappedReLU.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _NNC_CORE_BACKEND_INTERPRETER_CAPPEDRELU_ +#define _NNC_CORE_BACKEND_INTERPRETER_CAPPEDRELU_ + +#include + +namespace mir_interpreter +{ + +void CappedReLU(const mir::TensorVariant &arg, float cap, mir::TensorVariant &result); + +} // namespace mir_interpreter + +#endif //_NNC_CORE_BACKEND_INTERPRETER_CAPPEDRELU_ diff --git a/compiler/mir-interpreter/src/ops/Common.cpp b/compiler/mir-interpreter/src/ops/Common.cpp new file mode 100644 index 00000000000..dae207f2ec4 --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Common.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#include + +#include "Common.h" + +namespace mir_interpreter +{ + +using namespace mir; + +Index shift(const Index &in_index, const Shape &shift_from) +{ + Index index = in_index; + assert(index.rank() == shift_from.rank()); + for (int32_t d = 0; d < in_index.rank(); ++d) + { + index.at(d) = index.at(d) + shift_from.dim(d); + } + return index; +} + +} // namespace mir_interpreter diff --git a/compiler/mir-interpreter/src/ops/Common.h b/compiler/mir-interpreter/src/ops/Common.h new file mode 100644 index 00000000000..43336216e0d --- /dev/null +++ b/compiler/mir-interpreter/src/ops/Common.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved + * + * 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. + */ + +#ifndef _NNC_CORE_BACKEND_INTERPRETER_COMMON_ +#define _NNC_CORE_BACKEND_INTERPRETER_COMMON_ + +#include "mir/Tensor.h" +#include "mir/TensorVariant.h" +#include "mir/DataType.h" +#include "mir/Shape.h" +#include "mir/Index.h" + +namespace mir_interpreter +{ + +template