diff --git a/.circleci/config.yml b/.circleci/config.yml index dcb2f03f..286fcda4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: - run: pip3 install . - run: name: Test - command: pytest -vv tests + command: LC_ALL=C.UTF-8 LANG=C.UTF-8 pytest -vv tests -m 'not slow_test and not deprecated' #- run: genhtml main_coverage.info --output-directory /tmp/coverage #- store_test_results: # path: /tmp/test_results diff --git a/.gitignore b/.gitignore index f65ce068..1817ee1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +#system files +__pycache__ +.DS_Store # virtual env .venv venv @@ -20,4 +23,4 @@ tests/deep_mlp/data .vscode # docs doc/build -doc/source/_build +doc/source/_build \ No newline at end of file diff --git a/Pipfile b/Pipfile index 62bfd748..13c552e8 100644 --- a/Pipfile +++ b/Pipfile @@ -1,12 +1,13 @@ [[source]] url = "https://pypi.python.org/simple" -verify_ssl = true +verify_ssl = false name = "pypi" [packages] e1839a8 = {path = ".",editable = true} [dev-packages] +matplotlib = "*" pylint = "*" "flake8" = "*" pytest = "*" @@ -16,3 +17,6 @@ scipy = "*" graphviz = "*" sphinx = "*" sphinx-autoapi = "*" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index 403da44e..1f0fe3a4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ff0b58731e6f51ecb106fdbf32c5a60479ca4aea1b27a1670e6cf6d61e3ac739" + "sha256": "2d2fa0bb78c2073c03bb527a14d8cc8dee5629f499f975395f44c66a86c87e3a" }, "pipfile-spec": 6, "requires": {}, @@ -9,7 +9,7 @@ { "name": "pypi", "url": "https://pypi.python.org/simple", - "verify_ssl": true + "verify_ssl": false } ] }, @@ -34,77 +34,128 @@ ], "version": "==19.3.0" }, + "cachetools": { + "hashes": [ + "sha256:1d057645db16ca7fe1f3bd953558897603d6f0b9c51ed9d11eb4d071ec4e2aab", + "sha256:de5d88f87781602201cde465d3afe837546663b168e8b39df67411b0bf10cefc" + ], + "version": "==4.1.0" + }, + "certifi": { + "hashes": [ + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + ], + "version": "==2020.4.5.1" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], - "version": "==7.0" + "version": "==7.1.2" + }, + "cycler": { + "hashes": [ + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" + ], + "version": "==0.10.0" }, "e1839a8": { "editable": true, "path": "." }, + "flatbuffers": { + "hashes": [ + "sha256:63bb9a722d5e373701913e226135b28a6f6ac200d5cc7b4d919fa38d73b44610", + "sha256:9e9ef47fa92625c4721036e7c4124182668dc6021d9e7c73704edd395648deb9" + ], + "version": "==1.12" + }, + "future": { + "hashes": [ + "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" + ], + "version": "==0.18.2" + }, "gast": { "hashes": [ - "sha256:5c7617f1f6c8b8b426819642b16b9016727ddaecd16af9a07753e537eba8a3a5" + "sha256:fe939df4583692f0512161ec1c880e0a10e71e6a232da045ab8edd3756fbadf0" ], - "version": "==0.3.2" + "version": "==0.2.2" + }, + "google-auth": { + "hashes": [ + "sha256:0c41a453b9a8e77975bfa436b8daedac00aed1c545d84410daff8272fff40fbb", + "sha256:e63b2210e03c4ed829063b72c4af0c4b867c2788efb3210b6b9439b488bd3afd" + ], + "version": "==1.14.1" + }, + "google-auth-oauthlib": { + "hashes": [ + "sha256:88d2cd115e3391eb85e1243ac6902e76e77c5fe438b7276b297fbe68015458dd", + "sha256:a92a0f6f41a0fb6138454fbc02674e64f89d82a244ea32f98471733c8ef0e0e1" + ], + "version": "==0.4.1" + }, + "google-pasta": { + "hashes": [ + "sha256:4612951da876b1a10fe3960d7226f0c7682cf901e16ac06e473b267a5afa8954", + "sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed", + "sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e" + ], + "version": "==0.2.0" }, "graphviz": { "hashes": [ - "sha256:241fb099e32b8e8c2acca747211c8237e40c0b89f24b1622860075d59f4c4b25", - "sha256:60acbeee346e8c14555821eab57dbf68a169e6c10bce40e83c1bf44f63a62a01" + "sha256:cb0e878f90378489f17aab140b68e64e44b79e4cb59a530c8863d84bf2e2e5f5", + "sha256:e104ba036c8aef84320ec80560e544cd3cad68c9f90394b4e2b87bc44ab09791" ], - "version": "==0.13.2" + "version": "==0.14" }, "grpcio": { "hashes": [ - "sha256:066630f6b62bffa291dacbee56994279a6a3682b8a11967e9ccaf3cc770fc11e", - "sha256:07e95762ca6b18afbeb3aa2793e827c841152d5e507089b1db0b18304edda105", - "sha256:0a0fb2f8e3a13537106bc77e4c63005bc60124a6203034304d9101921afa4e90", - "sha256:0c61b74dcfb302613926e785cb3542a0905b9a3a86e9410d8cf5d25e25e10104", - "sha256:13383bd70618da03684a8aafbdd9e3d9a6720bf8c07b85d0bc697afed599d8f0", - "sha256:1c6e0f6b9d091e3717e9a58d631c8bb4898be3b261c2a01fe46371fdc271052f", - "sha256:1cf710c04689daa5cc1e598efba00b028215700dcc1bf66fcb7b4f64f2ea5d5f", - "sha256:2da5cee9faf17bb8daf500cd0d28a17ae881ab5500f070a6aace457f4c08cac4", - "sha256:2f78ebf340eaf28fa09aba0f836a8b869af1716078dfe8f3b3f6ff785d8f2b0f", - "sha256:33a07a1a8e817d733588dbd18e567caad1a6fe0d440c165619866cd490c7911a", - "sha256:3d090c66af9c065b7228b07c3416f93173e9839b1d40bb0ce3dd2aa783645026", - "sha256:42b903a3596a10e2a3727bae2a76f8aefd324d498424b843cfa9606847faea7b", - "sha256:4fffbb58134c4f23e5a8312ac3412db6f5e39e961dc0eb5e3115ce5aa16bf927", - "sha256:57be5a6c509a406fe0ffa6f8b86904314c77b5e2791be8123368ad2ebccec874", - "sha256:5b0fa09efb33e2af4e8822b4eb8b2cbc201d562e3e185c439be7eaeee2e8b8aa", - "sha256:5ef42dfc18f9a63a06aca938770b69470bb322e4c137cf08cf21703d1ef4ae5c", - "sha256:6a43d2f2ff8250f200fdf7aa31fa191a997922aa9ea1182453acd705ad83ab72", - "sha256:6d8ab28559be98b02f8b3a154b53239df1aa5b0d28ff865ae5be4f30e7ed4d3f", - "sha256:6e47866b7dc14ca3a12d40c1d6082e7bea964670f1c5315ea0fb8b0550244d64", - "sha256:6edda1b96541187f73aab11800d25f18ee87e53d5f96bb74473873072bf28a0e", - "sha256:7109c8738a8a3c98cfb5dda1c45642a8d6d35dc00d257ab7a175099b2b4daecd", - "sha256:8d866aafb08657c456a18c4a31c8526ea62de42427c242b58210b9eae6c64559", - "sha256:9939727d9ae01690b24a2b159ac9dbca7b7e8e6edd5af6a6eb709243cae7b52b", - "sha256:99fd873699df17cb11c542553270ae2b32c169986e475df0d68a8629b8ef4df7", - "sha256:b6fda5674f990e15e1bcaacf026428cf50bce36e708ddcbd1de9673b14aab760", - "sha256:bdb2f3dcb664f0c39ef1312cd6acf6bc6375252e4420cf8f36fff4cb4fa55c71", - "sha256:bfd7d3130683a1a0a50c456273c21ec8a604f2d043b241a55235a78a0090ee06", - "sha256:c6c2db348ac73d73afe14e0833b18abbbe920969bf2c5c03c0922719f8020d06", - "sha256:cb7a4b41b5e2611f85c3402ac364f1d689f5d7ecbc24a55ef010eedcd6cf460f", - "sha256:cd3d3e328f20f7c807a862620c6ee748e8d57ba2a8fc960d48337ed71c6d9d32", - "sha256:d1a481777952e4f99b8a6956581f3ee866d7614100d70ae6d7e07327570b85ce", - "sha256:d1d49720ed636920bb3d74cedf549382caa9ad55aea89d1de99d817068d896b2", - "sha256:d42433f0086cccd192114343473d7dbd4aae9141794f939e2b7b83efc57543db", - "sha256:d44c34463a7c481e076f691d8fa25d080c3486978c2c41dca09a8dd75296c2d7", - "sha256:d7e5b7af1350e9c8c17a7baf99d575fbd2de69f7f0b0e6ebd47b57506de6493a", - "sha256:d9542366a0917b9b48bab1fee481ac01f56bdffc52437b598c09e7840148a6a9", - "sha256:df7cdfb40179acc9790a462c049e0b8e109481164dd7ad1a388dd67ff1528759", - "sha256:e1a9d9d2e7224d981aea8da79260c7f6932bf31ce1f99b7ccfa5eceeb30dc5d0", - "sha256:ed10e5fad105ecb0b12822f924e62d0deb07f46683a0b64416b17fd143daba1d", - "sha256:f0ec5371ce2363b03531ed522bfbe691ec940f51f0e111f0500fc0f44518c69d", - "sha256:f6580a8a4f5e701289b45fd62a8f6cb5ec41e4d77082424f8b676806dcd22564", - "sha256:f7b83e4b2842d44fce3cdc0d54db7a7e0d169a598751bf393601efaa401c83e0", - "sha256:ffec45b0db18a555fdfe0c6fa2d0a3fceb751b22b31e8fcd14ceed7bde05481e" - ], - "version": "==1.26.0" + "sha256:085bbf7fd0070b8d65e84aa32979f17cfe624d27b5ce23955ef770c19d2d9623", + "sha256:0ae207a47ec0ad66eb1f53a27d566674d13a236c62ced409891335318ea9b8c5", + "sha256:0c130204ff5de0b9f041bf3126db0d29369d69883592e4b0d3c19868ba0ced7e", + "sha256:0ef6b380a588c2c6b29c6cfa0ba7f5d367beb33d5504bcc68658fa241ad498d2", + "sha256:16e1edb367763ea08d0994d4635ec05f4f8db9db59c39304b061097e3b93df43", + "sha256:16f5523dacae5aaeda4cf900da7e980747f663298c38c18eb4e5317704aa007a", + "sha256:181b5078cf568f37915b8a118afcef5fc9f3128c59c38998ed93e7dd793e3928", + "sha256:245564713cb4ac7bccb0f11be63781beb62299a44d8ab69031c859dbd9461728", + "sha256:271abbe28eb99fa5c70b3f272c0c66b67dab7bb11e1d29d8e616b4e0e099d29a", + "sha256:2e1b01cba26988c811c7fb91a0bca19c9afb776cc3d228993f08d324bdd0510a", + "sha256:3366bd6412c1e73acb1ee27d7f0c7d7dbee118ad8d98c957c8173691b2effeec", + "sha256:3893b39a0a17d857dc3a42fdb02a26aa53a59bfce49987187bcc0261647f1f55", + "sha256:3c7864d5ae63b787001b01b376f6315aef1a015aa9c809535235ed0ead907919", + "sha256:42c6716adf3ec1f608b2b56e885f26dd86e80d2fc1617f51fc92d1b0b649e28e", + "sha256:4bef0756b9e0df78e8d67a5b1e0e89b7daf41525d575f74e1f14a993c55b680d", + "sha256:4fe081862e58b8fbef0e479aefc9a64f8f17f53074df1085d8c1fe825a6e5df4", + "sha256:505a8d1b4ac571a51f10c4c995d5d4714f03c886604dc3c097ef5fd57bcfcf0b", + "sha256:5c2e81b6ab9768c43f2ca1c9a4c925823aad79ae95efb351007df4b92ebce592", + "sha256:70ff2df0c1795c5cf585a72d95bb458838b40bad5653c314b9067ba819e918f9", + "sha256:97b5612fc5d4bbf0490a2d80bed5eab5b59112ef1640440c1a9ac824bafa6968", + "sha256:a35f8f4a0334ed8b05db90383aecef8e49923ab430689a4360a74052f3a89cf4", + "sha256:aafe85a8210dfa1da3c46831b7f00c3735240b7b028eeba339eaea6ffdb593fb", + "sha256:c2e53eb253840f05278a8410628419ba7060815f86d48c9d83b6047de21c9956", + "sha256:c3645887db3309fc87c3db740b977d403fb265ebab292f1f6a926c4661231fd5", + "sha256:c6565cc92853af13237b2233f331efdad07339d27fe1f5f74256bfde7dc2f587", + "sha256:cbc322c5d5615e67c2a15be631f64e6c2bab8c12505bc7c150948abdaa0bdbac", + "sha256:df749ee982ec35ab76d37a1e637b10a92b4573e2b4e1f86a5fa8a1273c40a850", + "sha256:e9439d7b801c86df13c6cbb4c5a7e181c058f3c119d5e119a94a5f3090a8f060", + "sha256:f493ac4754717f25ace3614a51dd408a32b8bff3c9c0c85e9356e7e0a120a8c8", + "sha256:f80d10bdf1a306f7063046321fd4efc7732a606acdd4e6259b8a37349079b704", + "sha256:f83b0c91796eb42865451a20e82246011078ba067ea0744f7301e12a94ae2e1b" + ], + "version": "==1.28.1" }, "h5py": { "hashes": [ @@ -140,6 +191,13 @@ ], "version": "==2.10.0" }, + "idna": { + "hashes": [ + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + ], + "version": "==2.9" + }, "idx2numpy": { "hashes": [ "sha256:c609d5b260e6e7b538d458ba266c55b5b478e43e581d24d7017351813e67fcae" @@ -148,10 +206,10 @@ }, "jinja2": { "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.10.3" + "version": "==3.0.0a1" }, "keras-applications": { "hashes": [ @@ -167,78 +225,112 @@ ], "version": "==1.1.0" }, + "kiwisolver": { + "hashes": [ + "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c", + "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd", + "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f", + "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74", + "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1", + "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c", + "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec", + "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b", + "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7", + "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113", + "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec", + "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf", + "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e", + "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657", + "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8", + "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2" + ], + "version": "==1.2.0" + }, "markdown": { "hashes": [ - "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", - "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c" + "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902", + "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d" ], - "version": "==3.1.1" + "version": "==3.2.1" }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" - }, - "mock": { - "hashes": [ - "sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3", - "sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8" - ], - "version": "==3.0.5" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" + }, + "matplotlib": { + "hashes": [ + "sha256:2466d4dddeb0f5666fd1e6736cc5287a4f9f7ae6c1a9e0779deff798b28e1d35", + "sha256:282b3fc8023c4365bad924d1bb442ddc565c2d1635f210b700722776da466ca3", + "sha256:4bb50ee4755271a2017b070984bcb788d483a8ce3132fab68393d1555b62d4ba", + "sha256:56d3147714da5c7ac4bc452d041e70e0e0b07c763f604110bd4e2527f320b86d", + "sha256:7a9baefad265907c6f0b037c8c35a10cf437f7708c27415a5513cf09ac6d6ddd", + "sha256:aae7d107dc37b4bb72dcc45f70394e6df2e5e92ac4079761aacd0e2ad1d3b1f7", + "sha256:af14e77829c5b5d5be11858d042d6f2459878f8e296228c7ea13ec1fd308eb68", + "sha256:c1cf735970b7cd424502719b44288b21089863aaaab099f55e0283a721aaf781", + "sha256:ce378047902b7a05546b6485b14df77b2ff207a0054e60c10b5680132090c8ee", + "sha256:d35891a86a4388b6965c2d527b9a9f9e657d9e110b0575ca8a24ba0d4e34b8fc", + "sha256:e06304686209331f99640642dee08781a9d55c6e32abb45ed54f021f46ccae47", + "sha256:e20ba7fb37d4647ac38f3c6d8672dd8b62451ee16173a0711b37ba0ce42bf37d", + "sha256:f4412241e32d0f8d3713b68d3ca6430190a5e8a7c070f1c07d7833d8c5264398", + "sha256:ffe2f9cdcea1086fc414e82f42271ecf1976700b8edd16ca9d376189c6d93aee" + ], + "version": "==3.2.1" }, "numpy": { "hashes": [ - "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", - "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", - "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", - "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", - "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", - "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", - "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", - "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", - "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", - "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", - "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", - "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", - "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", - "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", - "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", - "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", - "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", - "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", - "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", - "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", - "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" - ], - "version": "==1.17.4" + "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d", + "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897", + "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88", + "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6", + "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7", + "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26", + "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a", + "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d", + "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961", + "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5", + "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2", + "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032", + "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba", + "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085", + "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509", + "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170", + "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae", + "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d", + "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c", + "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720", + "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec" + ], + "version": "==1.18.4" + }, + "oauthlib": { + "hashes": [ + "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", + "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" + ], + "version": "==3.1.0" }, "onnx": { "hashes": [ @@ -272,119 +364,204 @@ ], "version": "==1.2.1" }, + "opt-einsum": { + "hashes": [ + "sha256:83b76a98d18ae6a5cc7a0d88955a7f74881f0e567a0f4c949d24c942753eb998", + "sha256:96f819d46da2f937eaf326336a114aaeccbcbdb9de460d42e8b5f480a69adca7" + ], + "version": "==3.2.1" + }, + "ortools": { + "hashes": [ + "sha256:05273768c38b603e4605ef5ee2beb80f877f0b7a2cc59639a2d4c5d877c659db", + "sha256:200eea97fd5ca524cbcb2a54cacde3da0df47061a0e048ab4f34ccdb9633aef5", + "sha256:271ab0cad19c598f83877ea8b37484a3f87aacc741ac0cf56ad9900965c9f041", + "sha256:2a1f0095989b8e4dfe40e0c806b4a7f51e5a42806fdac1b2750aecb2d424626e", + "sha256:2dc97b706f618c019997c4441ad47a3fca99f8cb76af25e169bfdda5dd767695", + "sha256:4ad95879c30863f9a753c8bf6388a316d92e0ba4b6c73dbad1d81f37c011b2ac", + "sha256:8984b89437effb070ac030335f3ffa300da5a403c92c09454759739a0252aa86", + "sha256:be2969390a8d13a13fc3e6aedfc90e19df344f916e4db050200df3c489fdb05b", + "sha256:c6c99da57d1921eb248bc4f188be724e6783ee05a2cc37b04d4658d5643f7ae6", + "sha256:d26d0bb5ab1427b7b95278a82ca951798bcb12abd44128e2f9de3640b5e26c0f" + ], + "version": "==7.6.7691" + }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" - ], - "version": "==6.2.1" + "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107", + "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3", + "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9", + "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523", + "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3", + "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079", + "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd", + "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705", + "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd", + "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3", + "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891", + "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f", + "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088", + "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac", + "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7", + "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d", + "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa", + "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344", + "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2", + "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457", + "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276", + "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d" + ], + "version": "==7.1.2" }, "protobuf": { "hashes": [ - "sha256:0329e86a397db2a83f9dcbe21d9be55a47f963cdabc893c3a24f4d3a8f117c37", - "sha256:0a7219254afec0d488211f3d482d8ed57e80ae735394e584a98d8f30a8c88a36", - "sha256:14d6ac53df9cb5bb87c4f91b677c1bc5cec9c0fd44327f367a3c9562de2877c4", - "sha256:180fc364b42907a1d2afa183ccbeffafe659378c236b1ec3daca524950bb918d", - "sha256:3d7a7d8d20b4e7a8f63f62de2d192cfd8b7a53c56caba7ece95367ca2b80c574", - "sha256:3f509f7e50d806a434fe4a5fbf602516002a0f092889209fff7db82060efffc0", - "sha256:4571da974019849201fc1ec6626b9cea54bd11b6bed140f8f737c0a33ea37de5", - "sha256:56bd1d84fbf4505c7b73f04de987eef5682e5752c811141b0186a3809bfb396f", - "sha256:680c668d00b5eff08b86aef9e5ba9a705e621ea05d39071cfea8e28cb2400946", - "sha256:6b5b947dc8b3f2aec0eaad65b0b5113fcd642c358c31357c647da6281ee31104", - "sha256:6e96dffaf4d0a9a329e528b353ba62fd9ef13599688723d96bc9c165d0b6871e", - "sha256:919f0d6f6addc836d08658eba3b52be2e92fd3e76da3ce00c325d8e9826d17c7", - "sha256:9c7b19c30cf0644afd0e4218b13f637ce54382fdcb1c8f75bf3e84e49a5f6d0a", - "sha256:a2e6f57114933882ec701807f217df2fb4588d47f71f227c0a163446b930d507", - "sha256:a6b970a2eccfcbabe1acf230fbf112face1c4700036c95e195f3554d7bcb04c1", - "sha256:bc45641cbcdea068b67438244c926f9fd3e5cbdd824448a4a64370610df7c593", - "sha256:d61b14a9090da77fe87e38ba4c6c43d3533dcbeb5d84f5474e7ac63c532dcc9c", - "sha256:d6faf5dbefb593e127463f58076b62fcfe0784187be8fe1aa9167388f24a22a1" - ], - "version": "==3.11.2" + "sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab", + "sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f", + "sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a", + "sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0", + "sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4", + "sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2", + "sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee", + "sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07", + "sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151", + "sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a", + "sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f", + "sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7", + "sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956", + "sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306", + "sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961", + "sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481", + "sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a", + "sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80" + ], + "version": "==3.11.3" + }, + "pyasn1": { + "hashes": [ + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba" + ], + "version": "==0.4.8" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", + "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74" + ], + "version": "==0.2.8" + }, + "pyparsing": { + "hashes": [ + "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", + "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" + ], + "version": "==3.0.0a1" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" }, "pyyaml": { "hashes": [ - "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", - "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", - "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", - "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", - "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", - "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", - "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", - "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", - "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", - "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", - "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" + ], + "version": "==5.3.1" + }, + "requests": { + "hashes": [ + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==5.2" + "version": "==2.23.0" + }, + "requests-oauthlib": { + "hashes": [ + "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", + "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a" + ], + "version": "==1.3.0" + }, + "rsa": { + "hashes": [ + "sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66", + "sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487" + ], + "version": "==4.0" + }, + "scipy": { + "hashes": [ + "sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4", + "sha256:0902a620a381f101e184a958459b36d3ee50f5effd186db76e131cbefcbb96f7", + "sha256:1e3190466d669d658233e8a583b854f6386dd62d655539b77b3fa25bfb2abb70", + "sha256:2cce3f9847a1a51019e8c5b47620da93950e58ebc611f13e0d11f4980ca5fecb", + "sha256:3092857f36b690a321a662fe5496cb816a7f4eecd875e1d36793d92d3f884073", + "sha256:386086e2972ed2db17cebf88610aab7d7f6e2c0ca30042dc9a89cf18dcc363fa", + "sha256:71eb180f22c49066f25d6df16f8709f215723317cc951d99e54dc88020ea57be", + "sha256:770254a280d741dd3436919d47e35712fb081a6ff8bafc0f319382b954b77802", + "sha256:787cc50cab3020a865640aba3485e9fbd161d4d3b0d03a967df1a2881320512d", + "sha256:8a07760d5c7f3a92e440ad3aedcc98891e915ce857664282ae3c0220f3301eb6", + "sha256:8d3bc3993b8e4be7eade6dcc6fd59a412d96d3a33fa42b0fa45dc9e24495ede9", + "sha256:9508a7c628a165c2c835f2497837bf6ac80eb25291055f56c129df3c943cbaf8", + "sha256:a144811318853a23d32a07bc7fd5561ff0cac5da643d96ed94a4ffe967d89672", + "sha256:a1aae70d52d0b074d8121333bc807a485f9f1e6a69742010b33780df2e60cfe0", + "sha256:a2d6df9eb074af7f08866598e4ef068a2b310d98f87dc23bd1b90ec7bdcec802", + "sha256:bb517872058a1f087c4528e7429b4a44533a902644987e7b2fe35ecc223bc408", + "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d", + "sha256:cc971a82ea1170e677443108703a2ec9ff0f70752258d0e9f5433d00dda01f59", + "sha256:dba8306f6da99e37ea08c08fef6e274b5bf8567bb094d1dbe86a20e532aca088", + "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", + "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" + ], + "markers": "python_version >= '3'", + "version": "==1.4.1" }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "tensorboard": { "hashes": [ - "sha256:53d8f40589c903dae65f39a799c2bc49defae3703754984d90613d26ebd714a4", - "sha256:b664fe7772be5670d8b04200342e681af7795a12cd752709aed565c06c0cc196" + "sha256:3a36da59d4e13fb140d04636aaf544f1a38364a7e3609a15cdaf01a5d3073b37" ], - "version": "==1.13.1" + "version": "==2.1.1" }, "tensorflow": { "hashes": [ - "sha256:0de5887495c20e1130ae4d9bcfaf80cec87f579a9c27a84141a588a46e5aa853", - "sha256:0f305f3c461ed2ce5e0b65fccc7b7452f483c7935dd8a52a466d622e642fdea8", - "sha256:4325f20b5a703b80a5f7a8807f07ad8735025bd2a947093ffff1c26fbdc7980b", - "sha256:4c86be0e476b64cedf4ffa059d71b764e75b895effb697345687e3057929a7b5", - "sha256:6b0a0a413390302ce7c22c98695983d6fb8406861cfb418b25536f57a96c0b89", - "sha256:77eec2351d0a9b5312ea01ee4c78c13996f249cf1bead2e68256a65e533f45ef", - "sha256:87bf719a564f11d63e4f614e933e5a612dd4e67c88266b774236e0982f5fcf69", - "sha256:ba29e66331cd2a8f824e0fa937ce44bd624bc37739f2f083694e473051d89ace", - "sha256:bc374f5a662b6e164cd1c4da61ccc752ec208a44893d2f9dcf47d2a0a2cef311", - "sha256:bcf86966b7554e407bb7d73341f2e108df62a910d40b4cd2a914867f2a5de51c", - "sha256:c3abffd51c168cfd62a557243c47a29ab48deb52a64465e6818060f20755ddb4", - "sha256:c41862c65628261229db22e33f9e570d845eeb5cea66dcbaebe404405edaa69b", - "sha256:d7341617aedd73c2c847755e87697e9c19eb625c73da26d6cd669220c5565119", - "sha256:de0425b58cb34006e4500565239b4c3a3055b95bff132f097fa46c87d8e463c9", - "sha256:f21fb65c8e874f40c654bc9b3ff3db3ec26f98f03fe64a541bc768f6f5c52ac2" - ], - "version": "==1.13.1" + "sha256:1cf129ccda0aea616b122f34b0c4bc39da959d34c4a4d8c23ed944555c5e47ab", + "sha256:2e8fc9764b7ea87687a4c80c2fbde69aeeb459a536eb5a591938d7931ab004c2", + "sha256:33e4b16e8f8905ee088bf8f413dcce2820b777fdf7f799009b3a47f354ebb23f", + "sha256:513d48dd751e0076d1b1e5e498e3522891305bedd2840f3cb4b1c57ffcb7d97d", + "sha256:5cfa729fc71f6f2dca0ea77ebe768ea293e723e22ecb086a0b3ab26cc1776e37", + "sha256:7bad8ea686a1f33d9dac13eb578c4597346789d4f826980c8bbcfbd08e7dc921", + "sha256:8c0fae0f9f772ed7e3370f1b286f88c27debbcf09468e5036670ea2c67e239ec", + "sha256:92c4f1c939de438fbe484d011e5eebe059fc8e5244cfe32a81c6891b3357d109", + "sha256:c420e70d4127c2ac00054aece54cf04a1a43d5d4f25de90267f247873f1bd5a8", + "sha256:e631f55cf30054fee3230c89a7f998fd08748aa3045651a5a760cec2c5b9f9d6", + "sha256:e877fbf373d5be42fb118269df1670b8d3c0df9be223904a2584a8f8ed23b082" + ], + "version": "==2.1.0" }, "tensorflow-estimator": { "hashes": [ - "sha256:7cfdaa3e83e3532f31713713feb98be7ea9f3065722be4267e49b6c301271419" + "sha256:e5c5f648a636f18d1be4cf7ed46132b108a2f0f3fd9f1c850eba924263dc6972" ], - "version": "==1.13.0" + "version": "==2.1.0" }, "termcolor": { "hashes": [ @@ -401,54 +578,66 @@ }, "torch": { "hashes": [ - "sha256:0cec2e13a2e95c24c34f17d437f354ee2a40902e8d515a524556b350e12555dd", - "sha256:134e8291a97151b1ffeea09cb9ddde5238beb4e6d9dfb66657143d6990bfb865", - "sha256:31062923ac2e60eac676f6a0ae14702b051c158bbcf7f440eaba266b0defa197", - "sha256:3b05233481b51bb636cee63dc761bb7f602e198178782ff4159d385d1759608b", - "sha256:458f1d87e5b7064b2c39e36675d84e163be3143dd2fc806057b7878880c461bc", - "sha256:72a1c85bffd2154f085bc0a1d378d8a54e55a57d49664b874fe7c949022bf071", - "sha256:77fd8866c0bf529861ffd850a5dada2190a8d9c5167719fb0cfa89163e23b143", - "sha256:b6f01d851d1c5989d4a99b50ae0187762b15b7718dcd1a33704b665daa2402f9", - "sha256:d8e1d904a6193ed14a4fed220b00503b2baa576e71471286d1ebba899c851fae" + "sha256:3cc72d36eaeda96488e3a29373f739b887338952417b3e1620871063bf5d14d2", + "sha256:402951484443bb49b5bc2129414ac6c644c07b8378e79922cf3645fd08cbfdc9", + "sha256:6fcfe5deaf0788bbe8639869d3c752ff5fe1bdedce11c7ed2d44379b1fbe6d6c", + "sha256:7f3d6af2d7e2576b9640aa684f0c18a773efffe8b37f9056272287345c1dcba5", + "sha256:865d4bec21542647e0822e8b753e05d67eee874974a3937273f710edd99a7516", + "sha256:931b79aed9aba50bf314214be6efaaf7972ea9539a3d63f82622bc5860a1fd81", + "sha256:cb4412c6b00117ab5e014d07dac45b87f1e918e31fbb849e7e39f1f9140fff59", + "sha256:dfaac4c5d27ac80705956743c34fb1ab5fb37e1646a6c8e45f05f7e739f6ea7c", + "sha256:ecdc2ea4011e3ec04937b6b9e803ab671c3ac04e81b1df20354e01453e508b2f" ], - "version": "==1.3.1" + "version": "==1.5.0" }, "torchvision": { "hashes": [ - "sha256:0f8245d6378acc86917f58492675f93df5279abae8bc5f832e3510722191f6c9", - "sha256:1ad7593d94f6612ccb84a59467f0d10cdc213fb3e2bb91f1e773eb844787fa4c", - "sha256:2553405b9afe3cedb410873b9877eb18b1526f8b01cb7c2747e51b69a936e0b5", - "sha256:276a385f2f5fe484bf08467b5d081d9144b97eb458ba5b4a11e4640389e53149", - "sha256:66deba9c577e36f4f071decdd894bf7ba794ac133dae64b3fd02fc3f0c6b989d", - "sha256:7a458330e4efcd66f9f70127ab21fcf8cfea84acda8e707322fd2843aa6dd396", - "sha256:8ff715c2323d9eca89126824ebfa74b282a95d6f64a4743fbe9b738d2de21c77", - "sha256:dca4aadc12a123730957b501f9c5c2870d2f6727a2c28552cb7907b68b0ea10c", - "sha256:dda25ce304978bba19e6543f7dcfee4f37d2f128ec83d4ab0c7e8f991d64865f" + "sha256:0ea04a7e0f64599c158d36da01afd0cb3bc49033d2a145be4eb80c17c4c0482b", + "sha256:0fa9e4a8381e5e04d0da0acd93f1429347053497ec343fe6d625b1b7fb2ce36e", + "sha256:691d68f3726b7392fe37db7184aef8a6b6f7cf6ff38fae769b287b3d6e1eb69a", + "sha256:6eb4e0d7dc61030447b98d412162f222a95d848b3b0e484a81282c057af6dd25", + "sha256:8992f10a7860e0991766a788b546d5f11e3e7465e87a72eb9c78675dd2616400", + "sha256:a9b08435fdadd89520a78f5a54d196c05878d1a15e37f760d43f72f10bae308f", + "sha256:ea39bed9e9497a67c5f66e37d3d5a663a0284868ae8616de81f65c66d9ad802b", + "sha256:f43dae3b348afa5778439913ba1f3f176362ffc9e684ef01dc54dae7cf1b82e4" ], - "version": "==0.4.2" + "version": "==0.6.0" }, "typing-extensions": { "hashes": [ - "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", - "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", - "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" + "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5", + "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae", + "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392" ], - "version": "==3.7.4.1" + "version": "==3.7.4.2" + }, + "urllib3": { + "hashes": [ + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + ], + "version": "==1.25.9" }, "werkzeug": { "hashes": [ - "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", - "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4" + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" ], - "version": "==0.16.0" + "version": "==1.0.1" }, "wheel": { "hashes": [ - "sha256:10c9da68765315ed98850f8e048347c3eb06dd81822dc2ab1d4fde9dc9702646", - "sha256:f4da1763d3becf2e2cd92a14a7c920f0f00eca30fdde9ea992c836685b9faf28" + "sha256:8788e9155fe14f54164c1b9eb0a319d98ef02c160725587ad60f14ddc57b6f96", + "sha256:df277cb51e61359aba502208d680f90c0493adec6f0e848af94948778aed386e" ], "markers": "python_version >= '3'", - "version": "==0.33.6" + "version": "==0.34.2" + }, + "wrapt": { + "hashes": [ + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" + ], + "version": "==1.12.1" } }, "develop": { @@ -461,11 +650,11 @@ }, "astroid": { "hashes": [ - "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", - "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" + "sha256:29fa5d46a2404d01c834fcb802a3943685f1fc538eb2a02a161349f5505ac196", + "sha256:2fecea42b20abb1922ed65c7b5be27edfba97211b04b2b6abc6a43549a024ea6" ], "markers": "python_version >= '3'", - "version": "==2.3.3" + "version": "==2.4.0" }, "attrs": { "hashes": [ @@ -476,17 +665,17 @@ }, "babel": { "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" + "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", + "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4" ], - "version": "==2.7.0" + "version": "==2.8.0" }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -495,57 +684,56 @@ ], "version": "==3.0.4" }, - "docutils": { + "cycler": { "hashes": [ - "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0", - "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827", - "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99" + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" ], - "version": "==0.15.2" + "version": "==0.10.0" }, - "entrypoints": { + "docutils": { "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" ], - "version": "==0.3" + "version": "==0.16" }, "flake8": { "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + "sha256:c09e7e4ea0d91fa36f7b8439ca158e592be56524f0b67c39ab0ea2b85ed8f9a4", + "sha256:f33c5320eaa459cdee6367016a4bf4ba2a9b81499ce56e6a32abbf0b8d3a2eb4" ], "index": "pypi", - "version": "==3.7.9" + "version": "==3.8.0a2" }, "graphviz": { "hashes": [ - "sha256:241fb099e32b8e8c2acca747211c8237e40c0b89f24b1622860075d59f4c4b25", - "sha256:60acbeee346e8c14555821eab57dbf68a169e6c10bce40e83c1bf44f63a62a01" + "sha256:cb0e878f90378489f17aab140b68e64e44b79e4cb59a530c8863d84bf2e2e5f5", + "sha256:e104ba036c8aef84320ec80560e544cd3cad68c9f90394b4e2b87bc44ab09791" ], - "version": "==0.13.2" + "version": "==0.14" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "imagesize": { "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" + "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", + "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1" ], - "version": "==1.1.0" + "version": "==1.2.0" }, "importlib-metadata": { "hashes": [ - "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", - "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], "markers": "python_version < '3.8'", - "version": "==1.3.0" + "version": "==1.6.0" }, "isort": { "hashes": [ @@ -556,10 +744,31 @@ }, "jinja2": { "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.10.3" + "version": "==3.0.0a1" + }, + "kiwisolver": { + "hashes": [ + "sha256:03662cbd3e6729f341a97dd2690b271e51a67a68322affab12a5b011344b973c", + "sha256:18d749f3e56c0480dccd1714230da0f328e6e4accf188dd4e6884bdd06bf02dd", + "sha256:247800260cd38160c362d211dcaf4ed0f7816afb5efe56544748b21d6ad6d17f", + "sha256:443c2320520eda0a5b930b2725b26f6175ca4453c61f739fef7a5847bd262f74", + "sha256:4eadb361baf3069f278b055e3bb53fa189cea2fd02cb2c353b7a99ebb4477ef1", + "sha256:556da0a5f60f6486ec4969abbc1dd83cf9b5c2deadc8288508e55c0f5f87d29c", + "sha256:603162139684ee56bcd57acc74035fceed7dd8d732f38c0959c8bd157f913fec", + "sha256:60a78858580761fe611d22127868f3dc9f98871e6fdf0a15cc4203ed9ba6179b", + "sha256:7cc095a4661bdd8a5742aaf7c10ea9fac142d76ff1770a0f84394038126d8fc7", + "sha256:c31bc3c8e903d60a1ea31a754c72559398d91b5929fcb329b1c3a3d3f6e72113", + "sha256:c955791d80e464da3b471ab41eb65cf5a40c15ce9b001fdc5bbc241170de58ec", + "sha256:d069ef4b20b1e6b19f790d00097a5d5d2c50871b66d10075dab78938dc2ee2cf", + "sha256:d52b989dc23cdaa92582ceb4af8d5bcc94d74b2c3e64cd6785558ec6a879793e", + "sha256:e586b28354d7b6584d8973656a7954b1c69c93f708c0c07b77884f91640b7657", + "sha256:efcf3397ae1e3c3a4a0a0636542bcad5adad3b1dd3e8e629d0b6e201347176c8", + "sha256:fccefc0d36a38c57b7bd233a9b485e2f1eb71903ca7ad7adacad6c28a56d62d2" + ], + "version": "==1.2.0" }, "lazy-object-proxy": { "hashes": [ @@ -589,36 +798,49 @@ }, "markupsafe": { "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" + "sha256:06358015a4dee8ee23ae426bf885616ab3963622defd829eb45b44e3dee3515f", + "sha256:0b0c4fc852c5f02c6277ef3b33d23fcbe89b1b227460423e3335374da046b6db", + "sha256:267677fc42afed5094fc5ea1c4236bbe4b6a00fe4b08e93451e65ae9048139c7", + "sha256:303cb70893e2c345588fb5d5b86e0ca369f9bb56942f03064c5e3e75fa7a238a", + "sha256:3c9b624a0d9ed5a5093ac4edc4e823e6b125441e60ef35d36e6f4a6fdacd5054", + "sha256:42033e14cae1f6c86fc0c3e90d04d08ce73ac8e46ba420a0d22d545c2abd4977", + "sha256:4e4a99b6af7bdc0856b50020c095848ec050356a001e1f751510aef6ab14d0e0", + "sha256:4eb07faad54bb07427d848f31030a65a49ebb0cec0b30674f91cf1ddd456bfe4", + "sha256:63a7161cd8c2bc563feeda45df62f42c860dd0675e2b8da2667f25bb3c95eaba", + "sha256:68e0fd039b68d2945b4beb947d4023ca7f8e95b708031c345762efba214ea761", + "sha256:8092a63397025c2f655acd42784b2a1528339b90b987beb9253f22e8cdbb36c3", + "sha256:841218860683c0f2223e24756843d84cc49cccdae6765e04962607754a52d3e0", + "sha256:94076b2314bd2f6cfae508ad65b4d493e3a58a50112b7a2cbb6287bdbc404ae8", + "sha256:9d22aff1c5322e402adfb3ce40839a5056c353e711c033798cf4f02eb9f5124d", + "sha256:b0e4584f62b3e5f5c1a7bcefd2b52f236505e6ef032cc508caa4f4c8dc8d3af1", + "sha256:b1163ffc1384d242964426a8164da12dbcdbc0de18ea36e2c34b898ed38c3b45", + "sha256:beac28ed60c8e838301226a7a85841d0af2068eba2dcb1a58c2d32d6c05e440e", + "sha256:c29f096ce79c03054a1101d6e5fe6bf04b0bb489165d5e0e9653fb4fe8048ee1", + "sha256:c58779966d53e5f14ba393d64e2402a7926601d1ac8adeb4e83893def79d0428", + "sha256:cfe14b37908eaf7d5506302987228bff69e1b8e7071ccd4e70fd0283b1b47f0b", + "sha256:e834249c45aa9837d0753351cdca61a4b8b383cc9ad0ff2325c97ff7b69e72a6", + "sha256:eed1b234c4499811ee85bcefa22ef5e466e75d132502226ed29740d593316c1f" + ], + "version": "==2.0.0a1" + }, + "matplotlib": { + "hashes": [ + "sha256:2466d4dddeb0f5666fd1e6736cc5287a4f9f7ae6c1a9e0779deff798b28e1d35", + "sha256:282b3fc8023c4365bad924d1bb442ddc565c2d1635f210b700722776da466ca3", + "sha256:4bb50ee4755271a2017b070984bcb788d483a8ce3132fab68393d1555b62d4ba", + "sha256:56d3147714da5c7ac4bc452d041e70e0e0b07c763f604110bd4e2527f320b86d", + "sha256:7a9baefad265907c6f0b037c8c35a10cf437f7708c27415a5513cf09ac6d6ddd", + "sha256:aae7d107dc37b4bb72dcc45f70394e6df2e5e92ac4079761aacd0e2ad1d3b1f7", + "sha256:af14e77829c5b5d5be11858d042d6f2459878f8e296228c7ea13ec1fd308eb68", + "sha256:c1cf735970b7cd424502719b44288b21089863aaaab099f55e0283a721aaf781", + "sha256:ce378047902b7a05546b6485b14df77b2ff207a0054e60c10b5680132090c8ee", + "sha256:d35891a86a4388b6965c2d527b9a9f9e657d9e110b0575ca8a24ba0d4e34b8fc", + "sha256:e06304686209331f99640642dee08781a9d55c6e32abb45ed54f021f46ccae47", + "sha256:e20ba7fb37d4647ac38f3c6d8672dd8b62451ee16173a0711b37ba0ce42bf37d", + "sha256:f4412241e32d0f8d3713b68d3ca6430190a5e8a7c070f1c07d7833d8c5264398", + "sha256:ffe2f9cdcea1086fc414e82f42271ecf1976700b8edd16ca9d376189c6d93aee" + ], + "version": "==3.2.1" }, "mccabe": { "hashes": [ @@ -629,78 +851,70 @@ }, "more-itertools": { "hashes": [ - "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", - "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" + "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", + "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" ], - "version": "==8.0.2" + "version": "==8.2.0" }, "numpy": { "hashes": [ - "sha256:0a7a1dd123aecc9f0076934288ceed7fd9a81ba3919f11a855a7887cbe82a02f", - "sha256:0c0763787133dfeec19904c22c7e358b231c87ba3206b211652f8cbe1241deb6", - "sha256:3d52298d0be333583739f1aec9026f3b09fdfe3ddf7c7028cb16d9d2af1cca7e", - "sha256:43bb4b70585f1c2d153e45323a886839f98af8bfa810f7014b20be714c37c447", - "sha256:475963c5b9e116c38ad7347e154e5651d05a2286d86455671f5b1eebba5feb76", - "sha256:64874913367f18eb3013b16123c9fed113962e75d809fca5b78ebfbb73ed93ba", - "sha256:683828e50c339fc9e68720396f2de14253992c495fdddef77a1e17de55f1decc", - "sha256:6ca4000c4a6f95a78c33c7dadbb9495c10880be9c89316aa536eac359ab820ae", - "sha256:75fd817b7061f6378e4659dd792c84c0b60533e867f83e0d1e52d5d8e53df88c", - "sha256:7d81d784bdbed30137aca242ab307f3e65c8d93f4c7b7d8f322110b2e90177f9", - "sha256:8d0af8d3664f142414fd5b15cabfd3b6cc3ef242a3c7a7493257025be5a6955f", - "sha256:9679831005fb16c6df3dd35d17aa31dc0d4d7573d84f0b44cc481490a65c7725", - "sha256:a8f67ebfae9f575d85fa859b54d3bdecaeece74e3274b0b5c5f804d7ca789fe1", - "sha256:acbf5c52db4adb366c064d0b7c7899e3e778d89db585feadd23b06b587d64761", - "sha256:ada4805ed51f5bcaa3a06d3dd94939351869c095e30a2b54264f5a5004b52170", - "sha256:c7354e8f0eca5c110b7e978034cd86ed98a7a5ffcf69ca97535445a595e07b8e", - "sha256:e2e9d8c87120ba2c591f60e32736b82b67f72c37ba88a4c23c81b5b8fa49c018", - "sha256:e467c57121fe1b78a8f68dd9255fbb3bb3f4f7547c6b9e109f31d14569f490c3", - "sha256:ede47b98de79565fcd7f2decb475e2dcc85ee4097743e551fe26cfc7eb3ff143", - "sha256:f58913e9227400f1395c7b800503ebfdb0772f1c33ff8cb4d6451c06cabdf316", - "sha256:fe39f5fd4103ec4ca3cb8600b19216cd1ff316b4990f4c0b6057ad982c0a34d5" - ], - "version": "==1.17.4" + "sha256:00d7b54c025601e28f468953d065b9b121ddca7fff30bed7be082d3656dd798d", + "sha256:02ec9582808c4e48be4e93cd629c855e644882faf704bc2bd6bbf58c08a2a897", + "sha256:0e6f72f7bb08f2f350ed4408bb7acdc0daba637e73bce9f5ea2b207039f3af88", + "sha256:1be2e96314a66f5f1ce7764274327fd4fb9da58584eaff00b5a5221edefee7d6", + "sha256:2466fbcf23711ebc5daa61d28ced319a6159b260a18839993d871096d66b93f7", + "sha256:2b573fcf6f9863ce746e4ad00ac18a948978bb3781cffa4305134d31801f3e26", + "sha256:3f0dae97e1126f529ebb66f3c63514a0f72a177b90d56e4bce8a0b5def34627a", + "sha256:50fb72bcbc2cf11e066579cb53c4ca8ac0227abb512b6cbc1faa02d1595a2a5d", + "sha256:57aea170fb23b1fd54fa537359d90d383d9bf5937ee54ae8045a723caa5e0961", + "sha256:709c2999b6bd36cdaf85cf888d8512da7433529f14a3689d6e37ab5242e7add5", + "sha256:7d59f21e43bbfd9a10953a7e26b35b6849d888fc5a331fa84a2d9c37bd9fe2a2", + "sha256:904b513ab8fbcbdb062bed1ce2f794ab20208a1b01ce9bd90776c6c7e7257032", + "sha256:96dd36f5cdde152fd6977d1bbc0f0561bccffecfde63cd397c8e6033eb66baba", + "sha256:9933b81fecbe935e6a7dc89cbd2b99fea1bf362f2790daf9422a7bb1dc3c3085", + "sha256:bbcc85aaf4cd84ba057decaead058f43191cc0e30d6bc5d44fe336dc3d3f4509", + "sha256:dccd380d8e025c867ddcb2f84b439722cf1f23f3a319381eac45fd077dee7170", + "sha256:e22cd0f72fc931d6abc69dc7764484ee20c6a60b0d0fee9ce0426029b1c1bdae", + "sha256:ed722aefb0ebffd10b32e67f48e8ac4c5c4cf5d3a785024fdf0e9eb17529cd9d", + "sha256:efb7ac5572c9a57159cf92c508aad9f856f1cb8e8302d7fdb99061dbe52d712c", + "sha256:efdba339fffb0e80fcc19524e4fdbda2e2b5772ea46720c44eaac28096d60720", + "sha256:f22273dd6a403ed870207b853a856ff6327d5cbce7a835dfa0645b3fc00273ec" + ], + "version": "==1.18.4" }, "packaging": { "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" ], - "version": "==19.2" + "version": "==20.3" }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" - ], - "version": "==6.2.1" + "sha256:04766c4930c174b46fd72d450674612ab44cca977ebbcc2dde722c6933290107", + "sha256:0e2a3bceb0fd4e0cb17192ae506d5f082b309ffe5fc370a5667959c9b2f85fa3", + "sha256:0f01e63c34f0e1e2580cc0b24e86a5ccbbfa8830909a52ee17624c4193224cd9", + "sha256:12e4bad6bddd8546a2f9771485c7e3d2b546b458ae8ff79621214119ac244523", + "sha256:1f694e28c169655c50bb89a3fa07f3b854d71eb47f50783621de813979ba87f3", + "sha256:3d25dd8d688f7318dca6d8cd4f962a360ee40346c15893ae3b95c061cdbc4079", + "sha256:4b02b9c27fad2054932e89f39703646d0c543f21d3cc5b8e05434215121c28cd", + "sha256:9744350687459234867cbebfe9df8f35ef9e1538f3e729adbd8fde0761adb705", + "sha256:a0b49960110bc6ff5fead46013bcb8825d101026d466f3a4de3476defe0fb0dd", + "sha256:ae2b270f9a0b8822b98655cb3a59cdb1bd54a34807c6c56b76dd2e786c3b7db3", + "sha256:b37bb3bd35edf53125b0ff257822afa6962649995cbdfde2791ddb62b239f891", + "sha256:b532bcc2f008e96fd9241177ec580829dee817b090532f43e54074ecffdcd97f", + "sha256:b67a6c47ed963c709ed24566daa3f95a18f07d3831334da570c71da53d97d088", + "sha256:b943e71c2065ade6fef223358e56c167fc6ce31c50bc7a02dd5c17ee4338e8ac", + "sha256:ccc9ad2460eb5bee5642eaf75a0438d7f8887d484490d5117b98edd7f33118b7", + "sha256:d23e2aa9b969cf9c26edfb4b56307792b8b374202810bd949effd1c6e11ebd6d", + "sha256:eaa83729eab9c60884f362ada982d3a06beaa6cc8b084cf9f76cae7739481dfa", + "sha256:ee94fce8d003ac9fd206496f2707efe9eadcb278d94c271f129ab36aa7181344", + "sha256:f455efb7a98557412dc6f8e463c1faf1f1911ec2432059fa3e582b6000fc90e2", + "sha256:f46e0e024346e1474083c729d50de909974237c72daca05393ee32389dabe457", + "sha256:f54be399340aa602066adb63a86a6a5d4f395adfdd9da2b9a0162ea808c7b276", + "sha256:f784aad988f12c80aacfa5b381ec21fd3f38f851720f652b9f33facc5101cf4d" + ], + "version": "==7.1.2" }, "pluggy": { "hashes": [ @@ -711,93 +925,100 @@ }, "py": { "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" ], - "version": "==1.8.0" + "version": "==1.8.1" }, "pycodestyle": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:933bfe8d45355fbb35f9017d81fc51df8cb7ce58b82aca2568b870bf7bea1611", + "sha256:c1362bf675a7c0171fa5f795917c570c2e405a97e5dc473b51f3656075d73acc" ], - "version": "==2.5.0" + "version": "==2.6.0a1" }, "pyflakes": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" ], - "version": "==2.1.1" + "version": "==2.2.0" }, "pygments": { "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "version": "==2.5.2" + "version": "==2.6.1" }, "pylint": { "hashes": [ - "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", - "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" + "sha256:588e114e3f9a1630428c35b7dd1c82c1c93e1b0e78ee312ae4724c5e1a1e0245", + "sha256:bd556ba95a4cf55a1fc0004c00cf4560b1e70598a54a74c6904d933c8f3bd5a8" ], "index": "pypi", - "version": "==2.4.4" + "version": "==2.5.0" }, "pyparsing": { "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" + "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", + "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" ], - "version": "==2.4.5" + "version": "==3.0.0a1" }, "pytest": { "hashes": [ - "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", - "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" + "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", + "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" ], "index": "pypi", - "version": "==5.3.2" + "version": "==5.4.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" }, "pytz": { "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" ], - "version": "==2019.3" + "version": "==2020.1" }, "pyyaml": { "hashes": [ - "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", - "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", - "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", - "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", - "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", - "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", - "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", - "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", - "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", - "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", - "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" ], - "version": "==5.2" + "version": "==5.3.1" }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "rope": { "hashes": [ - "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", - "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", - "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf" + "sha256:52423a7eebb5306a6d63bdc91a7c657db51ac9babfb8341c9a1440831ecf3203", + "sha256:ae1fa2fd56f64f4cc9be46493ce54bed0dd12dee03980c61a4393d89d84029ad", + "sha256:d2830142c2e046f5fc26a022fe680675b6f48f81c7fc1f03a950706e746e9dfe" ], "index": "pypi", - "version": "==0.14.0" + "version": "==0.16.0" }, "scipy": { "hashes": [ @@ -823,15 +1044,15 @@ "sha256:dc60bb302f48acf6da8ca4444cfa17d52c63c5415302a9ee77b3b21618090521", "sha256:dee1bbf3a6c8f73b6b218cb28eed8dd13347ea2f87d572ce19b289d6fd3fbc59" ], - "index": "pypi", + "markers": "python_version >= '3'", "version": "==1.4.1" }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "snowballstemmer": { "hashes": [ @@ -842,40 +1063,40 @@ }, "sphinx": { "hashes": [ - "sha256:0a11e2fd31fe5c7e64b4fc53c2c022946512f021d603eb41ac6ae51d5fcbb574", - "sha256:138e39aa10f28d52aa5759fc6d1cba2be6a4b750010974047fa7d0e31addcf63" + "sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572", + "sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83" ], "index": "pypi", - "version": "==2.3.0" + "version": "==3.0.3" }, "sphinx-autoapi": { "hashes": [ - "sha256:623646ed1be7127486cab5f4e3ffc17a9327c2d2f3fce18effc39d73975a2973", - "sha256:b6b5f008edc174768a4d1fa87bc8ba1287744c9a9ff82e49064783da7a2ad03e" + "sha256:90ee5e312edaa204d7c0ce5312a8e56898d34620616662e1aec26e33ea84f758", + "sha256:abba752d7de5fea52ebc79198e4a3798ce76b42cfd89e2b0a41b6068885704a6" ], "index": "pypi", - "version": "==1.2.1" + "version": "==1.3.0" }, "sphinxcontrib-applehelp": { "hashes": [ - "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", - "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" + "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", + "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-devhelp": { "hashes": [ - "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", - "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", + "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" ], - "version": "==1.0.1" + "version": "==1.0.2" }, "sphinxcontrib-htmlhelp": { "hashes": [ - "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", - "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" + "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f", + "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "sphinxcontrib-jsmath": { "hashes": [ @@ -886,43 +1107,51 @@ }, "sphinxcontrib-qthelp": { "hashes": [ - "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", - "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", + "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" ], - "version": "==1.0.2" + "version": "==1.0.3" }, "sphinxcontrib-serializinghtml": { "hashes": [ - "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", - "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc", + "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a" ], - "version": "==1.1.3" + "version": "==1.1.4" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" }, "typed-ast": { "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" ], "markers": "implementation_name == 'cpython' and python_version < '3.8'", - "version": "==1.4.0" + "version": "==1.4.1" }, "unidecode": { "hashes": [ @@ -933,30 +1162,30 @@ }, "urllib3": { "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==1.25.7" + "version": "==1.25.9" }, "wcwidth": { "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" ], - "version": "==0.1.7" + "version": "==0.1.9" }, "wrapt": { "hashes": [ - "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" + "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7" ], - "version": "==1.11.2" + "version": "==1.12.1" }, "zipp": { "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==0.6.0" + "version": "==3.1.0" } } } diff --git a/README.rst b/README.rst index 68ab57d3..eff75d7b 100644 --- a/README.rst +++ b/README.rst @@ -226,12 +226,32 @@ Use Case: Dropout Layer Removal \ |cnn-dropout| -.. subgraph-match-end - We use mainly `Tensorflow`_ for declaring the pattern graph for matcher now. High-level graph builder is on its way, see `Future Works <#future-works>`_ for detail. +.. subgraph-match-end + +.. offline-tensor-alloc-start + +Offline Tensor Memory Allocation +-------------------------------- + +Considering following simple multi layers perceptron (`simple_mnist.pb`_): + +\ |mlp-alloc-graph| + +Once enabled the optimization transformer, ``tensor_alloc``, an offline tensor memory allocation planner, +``utensor-cli`` will generate ``uTensor`` runtime codes that use following optimized allocation plan: + +\ |mlp-alloc| + +- y-axis: tensor names ordered by topological sorting +- x-axis: these are the memory span occupied by each tensor, that is, the memory address offset and +the size of the tensor + +.. offline-tensor-alloc-end + Tutorials ========= @@ -313,6 +333,7 @@ Future Works .. _Tensorflow: https://www.tensorflow.org .. _PyTorch: https://pytorch.org/ .. _uTensor: https://github.com/uTensor/uTensor +.. _simple_mnist.pb: https://github.com/uTensor/utensor_cgen/blob/develop/tests/deep_mlp/simple_mnist.pb .. readme_end @@ -322,7 +343,10 @@ Future Works :alt: conv-pool-fuse .. |convert-example| image:: doc/source/_images/convert_example.png :alt: convert-example - +.. |mlp-alloc| image:: doc/source/_images/mlp_alloc.png + :alt: mlp-alloc +.. |mlp-alloc-graph| image:: doc/source/_images/mlp_alloc_graph.png + :alt: mlp-alloc-graph .. TODOs .. ===== diff --git a/doc/source/_images/mlp_alloc.png b/doc/source/_images/mlp_alloc.png new file mode 100644 index 00000000..d14aad86 Binary files /dev/null and b/doc/source/_images/mlp_alloc.png differ diff --git a/doc/source/_images/mlp_alloc_graph.png b/doc/source/_images/mlp_alloc_graph.png new file mode 100644 index 00000000..f6b0df8c Binary files /dev/null and b/doc/source/_images/mlp_alloc_graph.png differ diff --git a/doc/source/images.rst b/doc/source/images.rst index 68fb8d90..4c9edcdf 100644 --- a/doc/source/images.rst +++ b/doc/source/images.rst @@ -8,3 +8,9 @@ :height: 300 .. |convert-example| image:: /_images/convert_example.png :alt: convert-example +.. |mlp-alloc| image:: /_images/mlp_alloc.png + :alt: mlp-alloc +.. |mlp-alloc-graph| image:: /_images/mlp_alloc_graph.png + :alt: mlp-alloc-graph + :width: 300 + :height: 450 diff --git a/example_config.toml b/example_config.toml index 6cc4500a..e3262077 100644 --- a/example_config.toml +++ b/example_config.toml @@ -1,15 +1,50 @@ # https://github.com/toml-lang/toml # .. +# we use string 'None' to represent python None value +# you should convert the string to None if you try to write extension for utensor_cgen [utensor.backend] legacy-api = true +[utensor.backend.tensor_alloc_planner] +max_pool_size = 1048576 +include_inputs = false +out_fname = "None" +enabled = true + [utensor.backend.legacy_code_generator] src_fname = "None" params_dir = "data" embed_params_dir = "/fs/data" model_dir = "models" -transform_methods = [ "dropout(name_pattern=r\"(dropout[_\\w\\d]*)/.*\")", "linear_reorder", "quantize", "conv_pool", "inline", "biasAdd", "remove_id_op", "fake_gather_v2", "refcnt",] -save_graph = false debug_cmt = false -[utensor.backend.graph_lower] +[utensor.backend.legacy_graph_lower] + +[utensor.backend.rearch_code_generator] +src_fname = "None" +header_fname = "None" +params_dir = "data" +model_dir = "models" +meta_data_pool_size = "auto" +ram_data_pool_size = "auto" + +[utensor.backend.rearch_graph_lower] + +[utensor.backend.pipeline_transformer] +save_graph = false +transform_methods = [ "dropout(name_pattern=r'(dropout[_\\w\\d]*)/.*')", "linear_reorder", "quantize", "conv_pool", "inline", "biasAdd", "remove_id_op", "fake_gather_v2", "refcnt",] + +[utensor.backend.tensor_alloc_planner.aesthetic_kwargs] +split_on_large_graph = true +num_tensors_per_split = 20 +figsize = "None" +fontsize = 12 +lw = 12 +rand_seed = 1111 + +[utensor.backend.tensor_alloc_planner.dtype_size_map] +float = 4 +double = 8 +uint8 = 1 +int = 4 +long = 8 diff --git a/plugins/dummy_backend/__init__.py b/plugins/dummy_backend/__init__.py index 2e422f1e..e03300ae 100644 --- a/plugins/dummy_backend/__init__.py +++ b/plugins/dummy_backend/__init__.py @@ -1,34 +1,33 @@ from textwrap import wrap -from utensor_cgen.backend.base import Backend from utensor_cgen.backend import BackendManager +from utensor_cgen.backend.base import Backend from utensor_cgen.utils import class_property + @BackendManager.register class DummyBackend(Backend): - TARGET = 'dummy-backend' + TARGET = 'dummy-backend' - def __init__(self, config): - if not config: - config = self.default_config - self.output_file = config[self.TARGET][self.COMPONENT]['output-file'] + def __init__(self, config): + self.output_file = self.config[self.TARGET][self.COMPONENT]['output-file'] - def apply(self, ugraph): - with open(self.output_file, 'w') as fid: - fid.write('#include \n\n') - fid.write('int main(int argc, char* argv[]) {\n') - fid.write(' printf("graph name: {}\\n");\n'.format(ugraph.name)) - fid.write(' printf("ops in topological sorted order:\\n");\n') - for op_name in ugraph.topo_order: - fid.write(' printf(" {}\\n");\n'.format(op_name)) - fid.write(' return 0;\n}') + def apply(self, ugraph): + with open(self.output_file, 'w') as fid: + fid.write('#include \n\n') + fid.write('int main(int argc, char* argv[]) {\n') + fid.write(' printf("graph name: {}\\n");\n'.format(ugraph.name)) + fid.write(' printf("ops in topological sorted order:\\n");\n') + for op_name in ugraph.topo_order: + fid.write(' printf(" {}\\n");\n'.format(op_name)) + fid.write(' return 0;\n}') - @class_property - def default_config(cls): - return { - cls.TARGET: { - cls.COMPONENT: { - 'output-file': 'list_op.c' - } - } + @class_property + def default_config(cls): + return { + cls.TARGET: { + cls.COMPONENT: { + 'output-file': 'list_op.c' } + } + } diff --git a/requirements.txt b/requirements.txt index 77ff5802..d695ee6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,102 +1,135 @@ -absl-py==0.7.1 +absl-py==0.9.0 alabaster==0.7.12 +appdirs==1.4.3 appnope==0.1.0 -astor==0.7.1 -astroid==2.2.5 -atomicwrites==1.3.0 -attrs==19.1.0 -Babel==2.7.0 +astor==0.8.1 +astroid==2.3.3 +attrs==19.3.0 +Babel==2.8.0 backcall==0.1.0 -certifi==2019.3.9 +better-setuptools-git-version==1.0.5 +black==19.10b0 +bleach==3.1.1 +cachetools==4.1.0 +certifi==2019.11.28 chardet==3.0.4 -Click==7.0 -decorator==4.4.0 -doc8==0.8.0 -docutils==0.14 +click==7.1.1 +cycler==0.10.0 +decorator==4.4.2 +defusedxml==0.6.0 +docutils==0.16 entrypoints==0.3 -flake8==3.7.7 +flake8==3.7.9 +flatbuffers==1.12 gast==0.2.2 -graphviz==0.11 -grpcio==1.19.0 -h5py==2.9.0 -idna==2.8 +google-auth==1.14.1 +google-auth-oauthlib==0.4.1 +google-pasta==0.2.0 +graphviz==0.13.2 +grpcio==1.28.1 +h5py==2.10.0 +idna==2.9 idx2numpy==1.2.2 -imagesize==1.1.0 -importlib-metadata==0.17 -ipdb==0.12 -ipykernel==5.1.0 -ipython==7.4.0 +imagesize==1.2.0 +importlib-metadata==1.5.0 +ipykernel==5.1.4 +ipython==7.13.0 ipython-genutils==0.2.0 -isort==4.3.15 -jedi==0.13.3 -Jinja2==2.10 -jupyter-client==5.2.4 -jupyter-core==4.4.0 -Keras-Applications==1.0.7 -Keras-Preprocessing==1.0.9 -lazy-object-proxy==1.3.1 -Markdown==3.0.1 -MarkupSafe==1.1.1 +ipywidgets==7.5.1 +isort==4.3.21 +jedi==0.16.0 +Jinja2==3.0.0a1 +jsonschema==3.2.0 +jupyter==1.0.0 +jupyter-client==6.0.0 +jupyter-console==6.1.0 +jupyter-contrib-core==0.3.3 +jupyter-core==4.6.3 +jupyter-nbextensions-configurator==0.4.1 +Keras-Applications==1.0.8 +Keras-Preprocessing==1.1.0 +kiwisolver==1.2.0 +lazy-object-proxy==1.4.3 +Markdown==3.2.1 +MarkupSafe==2.0.0a1 +matplotlib==3.2.1 mccabe==0.6.1 -mock==2.0.0 -more-itertools==6.0.0 -numpy==1.16.2 -onnx==1.4.1 +mistune==0.8.4 +mock==4.0.2 +more-itertools==8.2.0 +nbconvert==5.6.1 +nbformat==5.0.4 +notebook==6.0.3 +numpy==1.18.3 +oauthlib==3.1.0 +onnx==1.6.0 onnx-tf==1.2.1 -packaging==19.0 -parso==0.3.4 -pbr==5.1.3 -pexpect==4.6.0 +opt-einsum==3.2.1 +ortools==7.5.7466 +packaging==20.1 +pandocfilters==1.4.2 +parso==0.6.2 +pathspec==0.7.0 +pexpect==4.8.0 pickleshare==0.7.5 -Pillow==6.0.0 -pluggy==0.12.0 -prompt-toolkit==2.0.9 -protobuf==3.7.0 +Pillow==7.1.1 +pluggy==0.13.1 +prometheus-client==0.7.1 +prompt-toolkit==3.0.3 +protobuf==3.11.3 ptyprocess==0.6.0 -py==1.8.0 +py==1.8.1 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 pycodestyle==2.5.0 pyflakes==2.1.1 -Pygments==2.3.1 -pylint==2.3.1 -pyparsing==2.4.0 -pytest==5.0.1 -python-dateutil==2.8.0 -pytz==2019.1 -PyYAML==5.1 -pyzmq==18.0.1 -requests==2.22.0 -restructuredtext-lint==1.3.0 -rope==0.14.0 -scipy==1.3.0 -six==1.12.0 -snowballstemmer==1.2.1 -Sphinx==2.1.2 -sphinx-autoapi==1.1.0 +Pygments==2.5.2 +pylint==2.4.4 +pyparsing==3.0.0a1 +pyrsistent==0.15.7 +pytest==5.4.1 +python-dateutil==2.8.1 +pytz==2019.3 +PyYAML==5.3.1 +pyzmq==19.0.0 +qtconsole==4.6.0 +regex==2020.2.20 +requests==2.23.0 +requests-oauthlib==1.3.0 +rope==0.16.0 +rsa==4.0 +rstcheck==3.3.1 +scipy==1.4.1 +Send2Trash==1.5.0 +six==1.14.0 +snowballstemmer==2.0.0 +Sphinx==2.4.3 +sphinx-autoapi==1.2.1 sphinxcontrib-applehelp==1.0.1 sphinxcontrib-devhelp==1.0.1 -sphinxcontrib-dotnetdomain==0.4 -sphinxcontrib-golangdomain==0.2.0.dev0 -sphinxcontrib-htmlhelp==1.0.2 +sphinxcontrib-htmlhelp==1.0.3 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.2 sphinxcontrib-serializinghtml==1.1.3 -stevedore==1.30.1 -tensorboard==1.13.1 -tensorflow==1.13.1 -tensorflow-estimator==1.13.0 +tensorboard==2.1.1 +tensorflow==2.1.0 +tensorflow-estimator==2.1.0 termcolor==1.1.0 +terminado==0.8.3 +testpath==0.4.4 toml==0.10.0 -torch==1.0.1.post2 -torchvision==0.2.1 -tornado==6.0.2 -traitlets==4.3.2 -typed-ast==1.3.1 -typing==3.6.6 -typing-extensions==3.7.2 +torch==1.4.0 +torchvision==0.5.0 +tornado==6.0.3 +traitlets==4.3.3 +typed-ast==1.4.1 +typing-extensions==3.7.4.2 Unidecode==1.1.1 -urllib3==1.25.3 --e git+https://github.com/uTensor/utensor_cgen.git@4cc432ad08900e453bbc3c0512e45950874dc02d#egg=utensor_cgen -wcwidth==0.1.7 -Werkzeug==0.14.1 -wrapt==1.11.1 -zipp==0.5.1 +urllib3==1.25.8 +-e git+https://dboyliao@github.com/uTensor/utensor_cgen.git@d6352a329291221efafa9c838cbc7209b9b1c412#egg=utensor_cgen +wcwidth==0.1.8 +webencodings==0.5.1 +Werkzeug==1.0.1 +widgetsnbextension==3.5.1 +wrapt==1.11.2 +zipp==3.0.0 diff --git a/setup.py b/setup.py index 23812db2..a11f4f21 100644 --- a/setup.py +++ b/setup.py @@ -51,15 +51,18 @@ class _Develop(_CompileFlatbuffMixin, _develop): pass ]}, install_requires=[ 'Jinja2', - 'tensorflow==1.13.1', + 'tensorflow==2.1.0', + 'onnx', 'idx2numpy', 'attrs', 'click', 'torch', 'torchvision', - 'onnx-tf==1.2.1', 'graphviz', + 'matplotlib', 'toml', + 'flatbuffers', + 'ortools', ], extras_require={ 'dev': ['pytest'] diff --git a/tests/deep_cnn/.gitignore b/tests/deep_cnn/.gitignore index 1269488f..9b64c748 100644 --- a/tests/deep_cnn/.gitignore +++ b/tests/deep_cnn/.gitignore @@ -1 +1,2 @@ data +*.toml \ No newline at end of file diff --git a/tests/deep_cnn/buggy.pb b/tests/deep_cnn/buggy.pb new file mode 100644 index 00000000..9d723332 Binary files /dev/null and b/tests/deep_cnn/buggy.pb differ diff --git a/tests/deep_cnn/cifar10_cnn.pb b/tests/deep_cnn/cifar10_cnn.pb index 464077e8..9f8cad47 100644 Binary files a/tests/deep_cnn/cifar10_cnn.pb and b/tests/deep_cnn/cifar10_cnn.pb differ diff --git a/tests/deep_mlp/.gitignore b/tests/deep_mlp/.gitignore new file mode 100644 index 00000000..405ec2c3 --- /dev/null +++ b/tests/deep_mlp/.gitignore @@ -0,0 +1 @@ +*.toml diff --git a/tests/deep_mlp/end_to_end.ipynb b/tests/deep_mlp/end_to_end.ipynb index cd69e021..d61a2519 100644 --- a/tests/deep_mlp/end_to_end.ipynb +++ b/tests/deep_mlp/end_to_end.ipynb @@ -10,23 +10,8 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:36:04.135548Z", - "start_time": "2018-05-20T08:35:58.619420Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/base.py:198: retry (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Use the retry module or similar alternatives.\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "import tensorflow as tf\n", "from tensorflow.python.framework import graph_util\n", @@ -36,19 +21,12 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:36:04.143346Z", - "start_time": "2018-05-20T08:36:04.138305Z" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "1.7.0\n" - ] + "text": "1.13.1\n" } ], "source": [ @@ -58,44 +36,16 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:36:10.734171Z", - "start_time": "2018-05-20T08:36:10.056448Z" - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "WARNING:tensorflow:From :1: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n", - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please write your own downloading logic.\n", - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please use tf.data to implement this functionality.\n", - "Extracting data/train-images-idx3-ubyte.gz\n", - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please use tf.data to implement this functionality.\n", - "Extracting data/train-labels-idx1-ubyte.gz\n", - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:110: dense_to_one_hot (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please use tf.one_hot on tensors.\n", - "Extracting data/t10k-images-idx3-ubyte.gz\n", - "Extracting data/t10k-labels-idx1-ubyte.gz\n", - "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n", - "Instructions for updating:\n", - "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n" - ] + "text": "WARNING:tensorflow:From :1: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease use alternatives such as official/mnist/dataset.py from tensorflow/models.\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/contrib/learn/python/learn/datasets/mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease write your own downloading logic.\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/contrib/learn/python/learn/datasets/mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease use tf.data to implement this functionality.\nExtracting mnist_data/train-images-idx3-ubyte.gz\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/contrib/learn/python/learn/datasets/mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease use tf.data to implement this functionality.\nExtracting mnist_data/train-labels-idx1-ubyte.gz\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/contrib/learn/python/learn/datasets/mnist.py:110: dense_to_one_hot (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease use tf.one_hot on tensors.\nExtracting mnist_data/t10k-images-idx3-ubyte.gz\nExtracting mnist_data/t10k-labels-idx1-ubyte.gz\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\nInstructions for updating:\nPlease use alternatives such as official/mnist/dataset.py from tensorflow/models.\n" } ], "source": [ - "mnist = read_data_sets('data', one_hot=True)" + "mnist = read_data_sets('mnist_data', one_hot=True)" ] }, { @@ -108,42 +58,47 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:36:15.435585Z", - "start_time": "2018-05-20T08:36:15.431209Z" - } - }, + "metadata": {}, "outputs": [], "source": [ "def weight_variable(shape, name):\n", " \"\"\"weight_variable generates a weight variable of a given shape.\"\"\"\n", " initial = tf.truncated_normal(shape, stddev=0.1)\n", - " return tf.Variable(initial, name)\n", + " return tf.Variable(initial, name=name)\n", "\n", "\n", "def bias_variable(shape, name):\n", " \"\"\"bias_variable generates a bias variable of a given shape.\"\"\"\n", " initial = tf.constant(0.1, shape=shape)\n", - " return tf.Variable(initial, name)" + " return tf.Variable(initial, name=name)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "batch_size = 10" ] }, { "cell_type": "code", "execution_count": 6, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:38:12.753876Z", - "start_time": "2018-05-20T08:38:12.568009Z" + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.\nInstructions for updating:\nColocations handled automatically by placer.\n" } - }, - "outputs": [], + ], "source": [ "graph = tf.Graph()\n", "\n", "with graph.as_default():\n", - " x = tf.placeholder(tf.float32, [None, 784], name=\"x\")\n", - " y = tf.placeholder(tf.float32, [None, 10], name=\"y\")\n", + " x = tf.placeholder(tf.float32, [batch_size, 784], name=\"x\")\n", + " y = tf.placeholder(tf.float32, [batch_size, 10], name=\"y\")\n", "\n", " with tf.name_scope(\"Layer1\"):\n", " W_fc1 = weight_variable([784, 128], name='W_fc1')\n", @@ -176,21 +131,14 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:44:06.198243Z", - "start_time": "2018-05-20T08:44:06.193588Z" - } - }, + "execution_count": 7, + "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'y_pred'" - ] + "text/plain": "'y_pred'" }, - "execution_count": 12, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -201,40 +149,13 @@ }, { "cell_type": "code", - "execution_count": 7, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:38:43.202667Z", - "start_time": "2018-05-20T08:38:14.106117Z" - } - }, + "execution_count": 8, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "step 1000, training accuracy 0.88\n", - "step 2000, training accuracy 0.98\n", - "step 3000, training accuracy 0.92\n", - "step 4000, training accuracy 0.96\n", - "step 5000, training accuracy 0.96\n", - "step 6000, training accuracy 0.96\n", - "step 7000, training accuracy 0.96\n", - "step 8000, training accuracy 0.94\n", - "step 9000, training accuracy 0.94\n", - "step 10000, training accuracy 0.94\n", - "step 11000, training accuracy 1\n", - "step 12000, training accuracy 1\n", - "step 13000, training accuracy 0.96\n", - "step 14000, training accuracy 0.98\n", - "step 15000, training accuracy 0.96\n", - "step 16000, training accuracy 1\n", - "step 17000, training accuracy 0.96\n", - "step 18000, training accuracy 0.96\n", - "step 19000, training accuracy 1\n", - "step 20000, training accuracy 1\n", - "test accuracy 0.9704\n" - ] + "text": "step 1000, training accuracy 0.7\nstep 2000, training accuracy 0.9\nstep 3000, training accuracy 0.8\nstep 4000, training accuracy 1\nstep 5000, training accuracy 0.9\nstep 6000, training accuracy 1\nstep 7000, training accuracy 1\nstep 8000, training accuracy 0.9\nstep 9000, training accuracy 0.8\nstep 10000, training accuracy 1\nstep 11000, training accuracy 0.8\nstep 12000, training accuracy 1\nstep 13000, training accuracy 0.9\nstep 14000, training accuracy 1\nstep 15000, training accuracy 1\nstep 16000, training accuracy 1\nstep 17000, training accuracy 0.8\nstep 18000, training accuracy 0.9\nstep 19000, training accuracy 1\nstep 20000, training accuracy 0.9\n" } ], "source": [ @@ -243,32 +164,24 @@ " saver = tf.train.Saver()\n", "\n", " for i in range(1, 20001):\n", - " images, labels = mnist.train.next_batch(50)\n", + " images, labels = mnist.train.next_batch(batch_size)\n", " train_step.run(feed_dict={x: images, y: labels})\n", " if i % 1000 == 0:\n", " train_accuracy = accuracy.eval(feed_dict={x: images, y: labels})\n", " print('step %d, training accuracy %g' % (i, train_accuracy))\n", - " print('test accuracy %g' % accuracy.eval(feed_dict={x: mnist.test.images, y: mnist.test.labels}))\n", " chkp_path = saver.save(sess, \"./my-model/model.ckpt\")" ] }, { "cell_type": "code", - "execution_count": 8, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:38:43.221800Z", - "start_time": "2018-05-20T08:38:43.205483Z" - } - }, + "execution_count": 9, + "metadata": {}, "outputs": [ { "data": { - "text/plain": [ - "'./my-model/model.ckpt'" - ] + "text/plain": "'./my-model/model.ckpt'" }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -286,22 +199,13 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:39:09.259145Z", - "start_time": "2018-05-20T08:39:09.191322Z" - } - }, + "execution_count": 10, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "INFO:tensorflow:Restoring parameters from ./my-model/model.ckpt\n", - "INFO:tensorflow:Froze 6 variables.\n", - "Converted 6 variables to const ops.\n" - ] + "text": "WARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/python/training/saver.py:1266: checkpoint_exists (from tensorflow.python.training.checkpoint_management) is deprecated and will be removed in a future version.\nInstructions for updating:\nUse standard file APIs to check for files with this prefix.\nINFO:tensorflow:Restoring parameters from ./my-model/model.ckpt\nWARNING:tensorflow:From :4: remove_training_nodes (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\nInstructions for updating:\nUse tf.compat.v1.graph_util.remove_training_nodes\nWARNING:tensorflow:From :5: convert_variables_to_constants (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\nInstructions for updating:\nUse tf.compat.v1.graph_util.convert_variables_to_constants\nWARNING:tensorflow:From /Users/dboyliao/Work/open_source/uTensor/utensor_cgen/.venv/lib/python3.6/site-packages/tensorflow-1.13.1-py3.6-macosx-10.15-x86_64.egg/tensorflow/python/framework/graph_util_impl.py:245: extract_sub_graph (from tensorflow.python.framework.graph_util_impl) is deprecated and will be removed in a future version.\nInstructions for updating:\nUse tf.compat.v1.graph_util.extract_sub_graph\nINFO:tensorflow:Froze 6 variables.\nINFO:tensorflow:Converted 6 variables to const ops.\n" } ], "source": [ @@ -314,20 +218,13 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:39:11.127661Z", - "start_time": "2018-05-20T08:39:11.120094Z" - } - }, + "execution_count": 11, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [ - "written graph to: ./my-model/simple_mnist.pb\n" - ] + "text": "written graph to: ./my-model/simple_mnist.pb\n" } ], "source": [ @@ -340,13 +237,8 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": { - "ExecuteTime": { - "end_time": "2018-05-20T08:40:11.092109Z", - "start_time": "2018-05-20T08:40:11.087863Z" - } - }, + "execution_count": 12, + "metadata": {}, "outputs": [], "source": [ "# you are ready to use simple_mnist.pb to generate uTensor implementation" @@ -376,7 +268,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4" + "version": "3.6.8-final" }, "toc": { "nav_menu": {}, @@ -391,4 +283,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/tests/deep_mlp/simple_mnist.pb b/tests/deep_mlp/simple_mnist.pb index 4609616c..3c0ca455 100644 Binary files a/tests/deep_mlp/simple_mnist.pb and b/tests/deep_mlp/simple_mnist.pb differ diff --git a/tests/simple_graph.pb b/tests/simple_graph.pb new file mode 100644 index 00000000..895b9d7f Binary files /dev/null and b/tests/simple_graph.pb differ diff --git a/tests/test_backend/conftest.py b/tests/test_backend/conftest.py index 51cfa229..f33815f2 100644 --- a/tests/test_backend/conftest.py +++ b/tests/test_backend/conftest.py @@ -1,14 +1,28 @@ import os + from pytest import fixture +test_dir = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + '..' + ) +) + @fixture(scope='session', name='mlp_ugraph') def mlp_ugraph(): from utensor_cgen.frontend import FrontendSelector model_file = os.path.join( - os.path.abspath( - os.path.join( - os.path.dirname(__file__), '..') - ), - 'deep_mlp/simple_mnist.pb' + test_dir, + 'deep_mlp/simple_mnist.pb' ) return FrontendSelector.parse(model_file, output_nodes=['y_pred']) + +@fixture(scope='session', name='simple_ugraph') +def simple_ugraph(): + from utensor_cgen.frontend import FrontendSelector + model_file = os.path.join( + test_dir, + 'simple_graph.pb' + ) + return FrontendSelector.parse(model_file, output_nodes=['u']) diff --git a/tests/test_backend/test_utensor.py b/tests/test_backend/test_utensor.py index aa41b5d8..20597a10 100644 --- a/tests/test_backend/test_utensor.py +++ b/tests/test_backend/test_utensor.py @@ -1,19 +1,55 @@ import os +import pytest + + def test_legacy_utensor(mlp_ugraph): from utensor_cgen.backend.utensor import uTensorBackend this_dir = os.path.dirname(__file__) - uTensorBackend(config={ + backend = uTensorBackend(config={ 'utensor': { 'backend': { 'legacy-api': True, - 'code_generator': { + 'legacy_code_generator': { + 'model_dir': os.path.join(this_dir, 'models'), + 'params_dir': os.path.join(this_dir, 'data'), + }, + }, + } + }) + backend.apply(mlp_ugraph) + +def test_rearch_utensor(simple_ugraph): + from utensor_cgen.backend.utensor import uTensorBackend + + this_dir = os.path.dirname(__file__) + + backend = uTensorBackend(config={ + 'utensor': { + 'backend': { + 'legacy-api': False, + 'rearch_code_generator': { 'model_dir': os.path.join(this_dir, 'models'), 'params_dir': os.path.join(this_dir, 'data'), }, - 'graph_lower': {} + 'pipeline_transformer': { + 'transform_methods': ['inline'] + } }, } - }).apply(mlp_ugraph) + }) + backend.apply(simple_ugraph) + + +@pytest.mark.slow_test +def test_offlinememory(mlp_ugraph): + from utensor_cgen.backend.graph_lower.generic_graph_lower import BrutalForceMemoryPlanner + + BrutalForceMemoryPlanner(config={ + 'size_float': 4, + 'size_int': 4, + 'size_uint8_t': 1 + } + ).apply(mlp_ugraph) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..95f3007d --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,14 @@ +import time + + +def test_cli_load_speed(): + from utensor_cgen.cli import cli + + durations = [] + for _ in range(1000): + start_time = time.time() + cli.main(args=['-h'], standalone_mode=False, ) + end_time = time.time() + durations.append(end_time - start_time) + mean_duration = sum(durations) / len(durations) + assert mean_duration <= 0.005, 'cli is too slow: {:0.5f}'.format(mean_duration) diff --git a/tests/test_frontend/conftest.py b/tests/test_frontend/conftest.py new file mode 100644 index 00000000..840f530c --- /dev/null +++ b/tests/test_frontend/conftest.py @@ -0,0 +1,22 @@ +import os + +import pytest + + +@pytest.fixture(name='tflm_mnist_path', scope='session') +def tflm_mnist_path(): + model_path = os.path.join( + os.path.dirname(__file__), + 'model_files', + 'quant_mnist_cnn.tflite' + ) + return model_path + +@pytest.fixture(name='onnx_model_path', scope='session') +def onnx_model_path(): + model_path = os.path.join( + os.path.dirname(__file__), + 'model_files', + 'model.onnx' + ) + return model_path diff --git a/tests/test_frontend/model_files/model.onnx b/tests/test_frontend/model_files/model.onnx new file mode 100644 index 00000000..c449cf1d Binary files /dev/null and b/tests/test_frontend/model_files/model.onnx differ diff --git a/tests/test_frontend/model_files/quant_mnist_cnn.tflite b/tests/test_frontend/model_files/quant_mnist_cnn.tflite new file mode 100644 index 00000000..4c50ff0d Binary files /dev/null and b/tests/test_frontend/model_files/quant_mnist_cnn.tflite differ diff --git a/tests/test_frontend/test_onnx.py b/tests/test_frontend/test_onnx.py new file mode 100644 index 00000000..2efa454d --- /dev/null +++ b/tests/test_frontend/test_onnx.py @@ -0,0 +1,11 @@ +from utensor_cgen.frontend.onnx import OnnxParser + + +def test_onnx_parser(onnx_model_path): + parser = OnnxParser({}) + ugraph = parser.parse(onnx_model_path) + + assert ugraph.lib_name == 'onnx' + assert ugraph.output_nodes + assert ugraph.topo_order + assert ugraph.ops_info diff --git a/tests/test_frontend/test_selector.py b/tests/test_frontend/test_selector.py index 5fa0f418..e070b365 100644 --- a/tests/test_frontend/test_selector.py +++ b/tests/test_frontend/test_selector.py @@ -1,8 +1,13 @@ -from utensor_cgen.frontend import FrontendSelector, Parser as _Parser +import pytest +from utensor_cgen.frontend import FrontendSelector +from utensor_cgen.frontend import Parser as _Parser -def test_select_parser(): + +def test_select_pb_parser(): FrontendSelector.select_parser('.pb') + +def test_select_onnx_parser(): FrontendSelector.select_parser('.onnx') def test_register(): diff --git a/tests/test_frontend/test_tensorflow.py b/tests/test_frontend/test_tensorflow.py new file mode 100644 index 00000000..4e61541c --- /dev/null +++ b/tests/test_frontend/test_tensorflow.py @@ -0,0 +1,53 @@ +import numpy as np +import tensorflow.compat.v1 as tf + + +def test_scalar_shape(): + from utensor_cgen.frontend.tensorflow import GraphDefParser + + graph = tf.Graph() + with graph.as_default(): + tf.constant(1, dtype=tf.float32, name='x') + parser = GraphDefParser({}) + ugraph = parser.parse(graph.as_graph_def(), output_nodes=['x']) + # shape of scalar tensor should be empty list + out_tensor = ugraph.ops_info['x'].output_tensors[0] + assert out_tensor.shape == [] + assert out_tensor.dtype is np.dtype('float32') + +def test_placeholder_shape(): + from utensor_cgen.frontend.tensorflow import GraphDefParser + + graph = tf.Graph() + with graph.as_default(): + tf.placeholder(dtype=tf.float32, name='x') + parser = GraphDefParser({}) + ugraph = parser.parse(graph.as_graph_def(), output_nodes=['x']) + # nondeterministic shape, can be any shape + out_tensor = ugraph.ops_info['x'].output_tensors[0] + assert out_tensor.shape is None + assert out_tensor.dtype is np.dtype('float32') + + graph = tf.Graph() + with graph.as_default(): + tf.placeholder(dtype=tf.float32, name='x', shape=[None, 5]) + parser = GraphDefParser({}) + ugraph = parser.parse(graph.as_graph_def(), output_nodes=['x']) + # nondeterministic dimension + out_tensor = ugraph.ops_info['x'].output_tensors[0] + assert out_tensor.shape == [None, 5] + assert out_tensor.dtype is np.dtype('float32') + +def test_normal_tensor_shape(): + from utensor_cgen.frontend.tensorflow import GraphDefParser + shape = np.random.randint(1, 10, size=(10,)).tolist() + + graph = tf.Graph() + with graph.as_default(): + tf.constant(np.random.rand(*shape), dtype=tf.float32, name='x') + parser = GraphDefParser({}) + ugraph = parser.parse(graph.as_graph_def(), output_nodes=['x']) + # deterministic shape + out_tensor = ugraph.ops_info['x'].output_tensors[0] + assert out_tensor.shape == shape, 'expecting {}, get {}'.format(shape, out_tensor.shape) + assert out_tensor.dtype is np.dtype('float32') diff --git a/tests/test_frontend/test_tflite.py b/tests/test_frontend/test_tflite.py new file mode 100644 index 00000000..c2a69bab --- /dev/null +++ b/tests/test_frontend/test_tflite.py @@ -0,0 +1,11 @@ +def test_parser(tflm_mnist_path): + from utensor_cgen.frontend.tflite import TFLiteParser + + parser = TFLiteParser({}) + ugraph = parser.parse(tflm_mnist_path) + + assert ugraph.output_nodes, \ + 'output_nodes is empty: {}'.format(ugraph.output_nodes) + assert ugraph.topo_order, \ + 'topo_order is empty: {}'.format(ugraph.topo_order) + assert ugraph.lib_name == 'tflite' diff --git a/tests/test_graph_constructor/test_ops/conftest.py b/tests/test_graph_constructor/test_ops/conftest.py index 10ca60c4..0d5eead0 100644 --- a/tests/test_graph_constructor/test_ops/conftest.py +++ b/tests/test_graph_constructor/test_ops/conftest.py @@ -5,8 +5,3 @@ def _ugraph(): from utensor_cgen.ir import uTensorGraph return uTensorGraph(output_nodes=[]) - -@fixture(name='quant_trans') -def _quant_trans(): - from utensor_cgen.transformer.quantize import QuantizeTransformer - return QuantizeTransformer() diff --git a/tests/test_graph_constructor/test_ops/test_op_add.py b/tests/test_graph_constructor/test_ops/test_op_add.py index 970d0fe1..f69a29df 100644 --- a/tests/test_graph_constructor/test_ops/test_op_add.py +++ b/tests/test_graph_constructor/test_ops/test_op_add.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_add(ugraph, quant_trans): +def test_op_add(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( op_type='Const', @@ -22,4 +22,3 @@ def test_op_add(ugraph, quant_trans): ) assert tensor_z.shape == [1, 3, 5] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_argmax.py b/tests/test_graph_constructor/test_ops/test_op_argmax.py index d31f4ad1..375b1895 100644 --- a/tests/test_graph_constructor/test_ops/test_op_argmax.py +++ b/tests/test_graph_constructor/test_ops/test_op_argmax.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_argmax(ugraph, quant_trans): +def test_op_argmax(ugraph): with ugraph.begin_construction(): tensor_logits, = ugraph.add_op( np.random.rand(3, 5, 7).astype('float32'), @@ -28,4 +28,3 @@ def test_op_argmax(ugraph, quant_trans): assert tensor_out1.dtype == np.dtype('int32') assert tensor_out2.shape == [3, 5] assert tensor_out2.dtype == np.dtype('int64') - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_const.py b/tests/test_graph_constructor/test_ops/test_op_const.py index 33499c93..4b1cab18 100644 --- a/tests/test_graph_constructor/test_ops/test_op_const.py +++ b/tests/test_graph_constructor/test_ops/test_op_const.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_const(ugraph, quant_trans): +def test_op_const(ugraph): with ugraph.begin_construction(): out_tensor, = ugraph.add_op( op_type='Const', @@ -11,4 +11,3 @@ def test_op_const(ugraph, quant_trans): ) assert out_tensor.shape == [3, 3] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_conv2d.py b/tests/test_graph_constructor/test_ops/test_op_conv2d.py index 7af35bfd..b2ddd99b 100644 --- a/tests/test_graph_constructor/test_ops/test_op_conv2d.py +++ b/tests/test_graph_constructor/test_ops/test_op_conv2d.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_conv2d(ugraph, quant_trans): +def test_op_conv2d(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( np.random.rand(10, 512, 512, 5), @@ -24,4 +24,3 @@ def test_op_conv2d(ugraph, quant_trans): is_output=True ) assert out.shape == [10, 256, 256, 10] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_matmul.py b/tests/test_graph_constructor/test_ops/test_op_matmul.py index bc309828..bfd5d442 100644 --- a/tests/test_graph_constructor/test_ops/test_op_matmul.py +++ b/tests/test_graph_constructor/test_ops/test_op_matmul.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_matmul(ugraph, quant_trans): +def test_op_matmul(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( op_type='Const', @@ -21,4 +21,3 @@ def test_op_matmul(ugraph, quant_trans): ) assert tensor_z.shape == [3, 4] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_max.py b/tests/test_graph_constructor/test_ops/test_op_max.py index 4f6d562b..2311ae1c 100644 --- a/tests/test_graph_constructor/test_ops/test_op_max.py +++ b/tests/test_graph_constructor/test_ops/test_op_max.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_max(ugraph, quant_trans): +def test_op_max(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( np.random.rand(3, 5, 9, 2).astype('float32'), @@ -35,4 +35,3 @@ def test_op_max(ugraph, quant_trans): assert tensor_out1.shape == [1, 5, 9, 2] assert tensor_out2.shape == [3, 1, 9, 2] assert tensor_out3.shape == [3, 5, 9] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_maxpool.py b/tests/test_graph_constructor/test_ops/test_op_maxpool.py index 10593ceb..0bf3964e 100644 --- a/tests/test_graph_constructor/test_ops/test_op_maxpool.py +++ b/tests/test_graph_constructor/test_ops/test_op_maxpool.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_maxpool(ugraph, quant_trans): +def test_op_maxpool(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( np.random.rand(10, 256, 256, 5), @@ -20,4 +20,3 @@ def test_op_maxpool(ugraph, quant_trans): is_output=True ) assert tensor_out.shape == [10, 128, 128, 5] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_min.py b/tests/test_graph_constructor/test_ops/test_op_min.py index 82c47038..2b148d23 100644 --- a/tests/test_graph_constructor/test_ops/test_op_min.py +++ b/tests/test_graph_constructor/test_ops/test_op_min.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_min(ugraph, quant_trans): +def test_op_min(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( np.random.rand(3, 5, 9, 2).astype('float32'), @@ -35,4 +35,3 @@ def test_op_min(ugraph, quant_trans): assert tensor_out1.shape == [1, 5, 9, 2] assert tensor_out2.shape == [3, 1, 9, 2] assert tensor_out3.shape == [3, 5, 9] - quant_trans.transform(ugraph) diff --git a/tests/test_graph_constructor/test_ops/test_op_relu.py b/tests/test_graph_constructor/test_ops/test_op_relu.py index 878cf758..8b838d5a 100644 --- a/tests/test_graph_constructor/test_ops/test_op_relu.py +++ b/tests/test_graph_constructor/test_ops/test_op_relu.py @@ -1,7 +1,7 @@ import numpy as np -def test_op_relu(ugraph, quant_trans): +def test_op_relu(ugraph): with ugraph.begin_construction(): tensor_x, = ugraph.add_op( op_type='Const', @@ -15,4 +15,3 @@ def test_op_relu(ugraph, quant_trans): is_output=True ) assert out.shape == [3, 5] - quant_trans.transform(ugraph) diff --git a/tests/test_ir/__init__.py b/tests/test_ir/__init__.py index e69de29b..b05419d3 100644 --- a/tests/test_ir/__init__.py +++ b/tests/test_ir/__init__.py @@ -0,0 +1,4 @@ +import ortools.sat.python.cp_model as _ +import tensorflow.compat.v1 as _tf + +_tf.disable_v2_behavior() diff --git a/tests/test_ir/test_AttrValueConverter/conftest.py b/tests/test_ir/test_AttrValueConverter/conftest.py index ba22cf7c..c6cd7171 100644 --- a/tests/test_ir/test_AttrValueConverter/conftest.py +++ b/tests/test_ir/test_AttrValueConverter/conftest.py @@ -1,5 +1,5 @@ import pytest -from tensorflow import AttrValue, NameAttrList +from tensorflow.compat.v1 import AttrValue, NameAttrList @pytest.fixture(scope='session') diff --git a/tests/test_ir/test_TensorProtoConverter/test_converter.py b/tests/test_ir/test_TensorProtoConverter/test_converter.py index 867f61b6..49966646 100644 --- a/tests/test_ir/test_TensorProtoConverter/test_converter.py +++ b/tests/test_ir/test_TensorProtoConverter/test_converter.py @@ -5,7 +5,7 @@ def test_generic_array(generic_array): tf_value = TensorProtoConverter.get_tf_value(generic_array) - assert isinstance(tf_value, TensorProtoConverter.__tfproto_type__) + assert type(tf_value).__name__ == TensorProtoConverter.__tfproto_type__.__name__ generic = TensorProtoConverter.get_generic_value(tf_value) assert isinstance(generic, type(generic_array)) assert (generic.np_array == generic_array.np_array).all() diff --git a/tests/test_ir/test_uTensorGraph/test_graph.py b/tests/test_ir/test_uTensorGraph/test_graph.py index 6a60e595..af8e441d 100644 --- a/tests/test_ir/test_uTensorGraph/test_graph.py +++ b/tests/test_ir/test_uTensorGraph/test_graph.py @@ -1,8 +1,8 @@ from copy import deepcopy import numpy as np - import tensorflow as tf + from utensor_cgen.frontend.tensorflow import GraphDefParser from utensor_cgen.ir import OperationInfo, uTensorGraph from utensor_cgen.ir.converter import TensorProtoConverter @@ -10,7 +10,7 @@ def test_ugraph_topo_order(graph_tuple): graph_def, output_nodes = graph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes) first_out, second_out = output_nodes meet_first = False for node_name in ugraph.topo_order: @@ -21,7 +21,7 @@ def test_ugraph_topo_order(graph_tuple): def test_ugraph_copy(graph_tuple): graph_def, output_nodes = graph_tuple - ugraph_1 = GraphDefParser.parse(graph_def, output_nodes) + ugraph_1 = GraphDefParser(config={}).parse(graph_def, output_nodes) ugraph_2 = deepcopy(ugraph_1) assert ugraph_1 is not ugraph_2 assert ugraph_1.graph_def == ugraph_2.graph_def @@ -53,7 +53,7 @@ def test_op_info(): def test_in_out_nodes(graph_tuple): graph_def, output_nodes = graph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes) x3 = ugraph.ops_info['x3'] assert x3.ugraph is ugraph assert len(x3.input_nodes) == len(set([op.name for op in x3.input_nodes])) @@ -65,7 +65,7 @@ def test_in_out_nodes(graph_tuple): def test_tensor_ops(graph_tuple): graph_def, output_nodes = graph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes) for op in ugraph.ops_info.values(): for tensor in op.output_tensors: assert tensor.op is op diff --git a/tests/test_matcher/__init__.py b/tests/test_matcher/__init__.py new file mode 100644 index 00000000..5b059508 --- /dev/null +++ b/tests/test_matcher/__init__.py @@ -0,0 +1 @@ +import ortools.sat.python.cp_model as _ diff --git a/tests/test_matcher/test_permutation/conftest.py b/tests/test_matcher/test_permutation/conftest.py index ddc6a422..51a89666 100644 --- a/tests/test_matcher/test_permutation/conftest.py +++ b/tests/test_matcher/test_permutation/conftest.py @@ -1,6 +1,6 @@ import pytest +import tensorflow.compat.v1 as tf -import tensorflow as tf from utensor_cgen.frontend.tensorflow import GraphDefParser @@ -12,7 +12,7 @@ def patrn_ugraph(): ptrn_input1 = tf.placeholder(dtype=tf.float32, name='input1') ptrn_add0 = tf.add(ptrn_input0, ptrn_input1, name='add0') ptrn_out = tf.add(ptrn_add0, ptrn_input1, name='output') - ugraph = GraphDefParser.parse(graph.as_graph_def(), [ptrn_out.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), [ptrn_out.op.name]) # ugraph.ops_info[ptrn_input0.op.name].add_null_input_tensor() return ugraph @@ -26,7 +26,7 @@ def subject_ugraph1(): sub_add0 = tf.add(sub_input0, sub_input1, name='sub_add0') sub_add1 = tf.add(sub_add0, sub_input1, name='sub_add1') sub_output = tf.add(sub_add1, sub_input2, name='sub_output') - ugraph = GraphDefParser.parse(graph.as_graph_def(), [sub_output.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), [sub_output.op.name]) return ugraph @pytest.fixture(scope='function', name='subject_ugraph1_1') @@ -40,7 +40,7 @@ def subject_ugraph1_1(): sub_add0 = tf.add(sub_input1, sub_input0, name='sub_add0') sub_add1 = tf.add(sub_add0, sub_input1, name='sub_add1') sub_output = tf.multiply(sub_add1, sub_input2, name='sub_output') - ugraph = GraphDefParser.parse(graph.as_graph_def(), [sub_output.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), [sub_output.op.name]) return ugraph @pytest.fixture(scope='function', name='subject_ugraph1_2') @@ -53,6 +53,6 @@ def subject_ugraph1_2(): sub_add0 = tf.add(sub_input0, sub_input1, name='sub_add0') sub_add1 = tf.add(sub_input1, sub_add0, name='sub_add1') sub_output = tf.multiply(sub_add1, sub_input2, name='sub_output') - ugraph = GraphDefParser.parse(graph.as_graph_def(), [sub_output.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), [sub_output.op.name]) # ugraph.ops_info[sub_input1.op.name].add_null_input_tensor() return ugraph diff --git a/tests/test_matcher/test_permutation/test_match_patrn1.py b/tests/test_matcher/test_permutation/test_match_patrn1.py index 9c2664b4..d4c65b07 100644 --- a/tests/test_matcher/test_permutation/test_match_patrn1.py +++ b/tests/test_matcher/test_permutation/test_match_patrn1.py @@ -1,8 +1,11 @@ +# FIXME: remove uTensorOpEqualityDelegate import after we have generic op_eq_deleate +from utensor_cgen.backend.utensor.code_generator.legacy._operators import \ + uTensorOpEqualityDelegate from utensor_cgen.matcher import uTensorGraphMatcher def test_id_match(patrn_ugraph): - matcher = uTensorGraphMatcher(patrn_ugraph) + matcher = uTensorGraphMatcher(patrn_ugraph, op_equality_delegate=uTensorOpEqualityDelegate) matches = matcher.match(patrn_ugraph) assert matches, 'expecting matches, get {} matches'.format(len(matches)) match = matches[0] @@ -27,7 +30,7 @@ def test_id_match(patrn_ugraph): '{} is missing'.format(tensor.name) def test_match_sub1(patrn_ugraph, subject_ugraph1): - matcher = uTensorGraphMatcher(patrn_ugraph) + matcher = uTensorGraphMatcher(patrn_ugraph, op_equality_delegate=uTensorOpEqualityDelegate) matches = matcher.match_all(subject_ugraph1) assert matches, 'expecting matches, get {} matches'.format(len(matches)) match = matches[0] @@ -39,7 +42,7 @@ def test_match_sub1(patrn_ugraph, subject_ugraph1): assert match.patrn2subj_op_map['output'].name == 'sub_add1', match def test_match_sub1_1(patrn_ugraph, subject_ugraph1_1): - matcher = uTensorGraphMatcher(patrn_ugraph) + matcher = uTensorGraphMatcher(patrn_ugraph, op_equality_delegate=uTensorOpEqualityDelegate) matches = matcher.match(subject_ugraph1_1) assert matches, 'expecting matches, get {} matches'.format(len(matches)) match = matches[0] @@ -49,7 +52,7 @@ def test_match_sub1_1(patrn_ugraph, subject_ugraph1_1): assert match.patrn2subj_op_map['output'].name == 'sub_add1' def test_match_sub1_2(patrn_ugraph, subject_ugraph1_2): - matcher = uTensorGraphMatcher(patrn_ugraph) + matcher = uTensorGraphMatcher(patrn_ugraph, op_equality_delegate=uTensorOpEqualityDelegate) matches = matcher.match(subject_ugraph1_2) assert matches, 'expecting matches, get {} matches'.format(len(matches)) match = matches[0] diff --git a/tests/test_matcher/test_replace/conftest.py b/tests/test_matcher/test_replace/conftest.py index 64d8a6e3..4a510978 100644 --- a/tests/test_matcher/test_replace/conftest.py +++ b/tests/test_matcher/test_replace/conftest.py @@ -1,7 +1,7 @@ import numpy as np +import tensorflow.compat.v1 as tf from pytest import fixture -import tensorflow as tf from utensor_cgen.frontend.tensorflow import GraphDefParser from utensor_cgen.utils import prune_graph, topologic_order_graph @@ -14,7 +14,7 @@ def fully_connect_pattern1(): w_prime = tf.constant(np.random.rand(3, 3), name='w_prime', dtype=tf.float32) a_prime = tf.matmul(z_prime, w_prime, name='a_prime') r_prime = tf.nn.relu(a_prime, name='r_prime') - patrn_ugraph = GraphDefParser.parse(patrn_graph.as_graph_def(), output_nodes=[r_prime.op.name]) + patrn_ugraph = GraphDefParser(config={}).parse(patrn_graph.as_graph_def(), output_nodes=[r_prime.op.name]) for i in range(2): patrn_ugraph.ops_info['a_prime'].replace_with_null_input_tensor(i) patrn_ugraph = prune_graph(patrn_ugraph) @@ -33,5 +33,5 @@ def subject_ugraph_1(): a = tf.matmul(z, w, name='a') r = tf.nn.relu(a, name='r') out = tf.add(x, r, name='out') - subj_ugraph = GraphDefParser.parse(subj_graph.as_graph_def(), output_nodes=[out.op.name]) + subj_ugraph = GraphDefParser(config={}).parse(subj_graph.as_graph_def(), output_nodes=[out.op.name]) return subj_ugraph diff --git a/tests/test_matcher/test_replace/test_replace.py b/tests/test_matcher/test_replace/test_replace.py index 9615b0f2..59410708 100644 --- a/tests/test_matcher/test_replace/test_replace.py +++ b/tests/test_matcher/test_replace/test_replace.py @@ -1,6 +1,8 @@ -import numpy as np +import tensorflow.compat.v1 as tf -import tensorflow as tf +# FIXME: remove uTensorOpEqualityDelegate import after we have generic op_eq_deleate +from utensor_cgen.backend.utensor.code_generator.legacy._operators import \ + uTensorOpEqualityDelegate from utensor_cgen.frontend.tensorflow import GraphDefParser from utensor_cgen.matcher import uTensorGraphMatcher from utensor_cgen.utils import prune_graph, topologic_order_graph @@ -13,7 +15,7 @@ def callback(match): a = tf.placeholder(dtype=tf.float32, name='a') b = tf.placeholder(dtype=tf.float32, name='b') out = tf.add(a, b, name='fused_node') - ugraph = GraphDefParser.parse(graph.as_graph_def(), output_nodes=[out.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), output_nodes=[out.op.name]) ugraph.ops_info['fused_node'].replace_with_null_input_tensor(0) ugraph.ops_info['fused_node'].replace_with_null_input_tensor(1) topologic_order_graph(ugraph) @@ -28,7 +30,7 @@ def callback(match): patrn_ugraph.ops_info['r_prime'].output_tensors[0]: ugraph.ops_info['fused_node'].output_tensors[0] } return ugraph, input_map, output_map - matcher = uTensorGraphMatcher(patrn_fc_1) + matcher = uTensorGraphMatcher(patrn_fc_1, op_equality_delegate=uTensorOpEqualityDelegate) matches = matcher.match(subj_graph_1) assert matches, 'no match found' match = matches[0] diff --git a/tests/test_transformer/__init__.py b/tests/test_transformer/__init__.py index e69de29b..b05419d3 100644 --- a/tests/test_transformer/__init__.py +++ b/tests/test_transformer/__init__.py @@ -0,0 +1,4 @@ +import ortools.sat.python.cp_model as _ +import tensorflow.compat.v1 as _tf + +_tf.disable_v2_behavior() diff --git a/tests/test_transformer/test_convpool/conftest.py b/tests/test_transformer/test_convpool/conftest.py index e9125cbb..0d18a7d3 100644 --- a/tests/test_transformer/test_convpool/conftest.py +++ b/tests/test_transformer/test_convpool/conftest.py @@ -1,9 +1,9 @@ from random import sample import numpy as np +import tensorflow.compat.v1 as tf from pytest import fixture -import tensorflow as tf from utensor_cgen.frontend.tensorflow import GraphDefParser @@ -39,5 +39,5 @@ def gen_vgg_graph(): name='pool_{}'.format(i), padding='SAME', ) - ugraph = GraphDefParser.parse(graph.as_graph_def(), output_nodes=[in_feat.op.name]) + ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), output_nodes=[in_feat.op.name]) return ugraph diff --git a/tests/test_transformer/test_convpool/test_vgg.py b/tests/test_transformer/test_convpool/test_vgg.py index f9af19c1..9efecd63 100644 --- a/tests/test_transformer/test_convpool/test_vgg.py +++ b/tests/test_transformer/test_convpool/test_vgg.py @@ -1,7 +1,10 @@ +import pytest + from utensor_cgen.transformer.pipeline import TransformerPipeline def factory(): + @pytest.mark.deprecated def test(vgg_ugraph): trans = TransformerPipeline([ 'linear_reorder', diff --git a/tests/test_transformer/test_dropout/conftest.py b/tests/test_transformer/test_dropout/conftest.py index d4937bff..dfe7a9bf 100644 --- a/tests/test_transformer/test_dropout/conftest.py +++ b/tests/test_transformer/test_dropout/conftest.py @@ -3,9 +3,11 @@ import numpy as np import pytest -import tensorflow as tf -from utensor_cgen.frontend.tensorflow import GraphDefParser -from utensor_cgen.utils import random_str +import tensorflow.compat.v1 as tf # isort:skip +tf.disable_v2_behavior() # isort:skip + +from utensor_cgen.frontend.tensorflow import GraphDefParser # isort:skip +from utensor_cgen.utils import random_str # isort:skip @pytest.fixture(scope='session', name='droput_graph_tuple') @@ -14,13 +16,12 @@ def dropout_graph_tuple(): with graph.as_default(): x = tf.constant(np.ones((5, 5)), name='x', dtype=tf.float32) - keep_prob = tf.placeholder(dtype=tf.float32, - name='keep_prob') - dropout_x = tf.nn.dropout(x, rate=1-keep_prob, name='dropout_x') + rate = tf.placeholder(dtype=tf.float32, name='rate') + dropout_x = tf.nn.dropout(x, rate=rate, name='dropout_x') bias = tf.constant(0.5, name='bias', dtype=tf.float32) y = tf.add(dropout_x, bias, name='y') return (graph.as_graph_def(), - [keep_prob.name, dropout_x.name], + [rate.name, dropout_x.name], [y.op.name]) @pytest.fixture(name='dropout_graph_tuple2') @@ -68,4 +69,4 @@ def gen_vgg_graph(): ) if i != num_layers: in_feat = tf.nn.dropout(in_feat, rate=rate, name='dropout_{}'.format(i)) - return GraphDefParser.parse(graph.as_graph_def(), output_nodes=[in_feat.op.name]) + return GraphDefParser(config={}).parse(graph.as_graph_def(), output_nodes=[in_feat.op.name]) diff --git a/tests/test_transformer/test_dropout/test_dropout_transormer.py b/tests/test_transformer/test_dropout/test_dropout_transormer.py index 98593527..400e1b25 100644 --- a/tests/test_transformer/test_dropout/test_dropout_transormer.py +++ b/tests/test_transformer/test_dropout/test_dropout_transormer.py @@ -1,4 +1,6 @@ -import tensorflow as tf +import pytest +import tensorflow.compat.v1 as tf + from utensor_cgen.frontend.tensorflow import GraphDefParser from utensor_cgen.transformer.ns_transformer import (DropoutTransformer, DropoutTransformerV2) @@ -6,10 +8,11 @@ def test_dropout_trans_1_1(droput_graph_tuple): (graph_def, - (keep_prob_name, dropout_output_name), + (rate_name, dropout_output_name), output_nodes) = droput_graph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes=output_nodes) transformer = DropoutTransformer() + assert transformer.prune_graph new_ugraph = transformer.transform(ugraph) for op in new_ugraph.ops_info.values(): assert op.ugraph @@ -23,25 +26,27 @@ def test_dropout_trans_1_1(droput_graph_tuple): with graph_2.as_default(): tf.import_graph_def(new_ugraph.graph_def, name='') with tf.Session(graph=graph_1): - keep_prob = graph_1.get_tensor_by_name(keep_prob_name) + rate = graph_1.get_tensor_by_name(rate_name) dropout_output = graph_1.get_tensor_by_name(dropout_output_name) output = graph_1.get_tensor_by_name(output_nodes[0]+":0") # test the dropout ops are gone - assert keep_prob.op.name not in new_ugraph.ops_info + assert rate.op.name not in new_ugraph.ops_info assert dropout_output.op.name not in new_ugraph.ops_info - output_1 = output.eval({keep_prob:1.0}) + output_1 = output.eval({rate: 0.0}) with tf.Session(graph=graph_2): output = graph_2.get_tensor_by_name(output_nodes[0]+":0") output_2 = output.eval() # expecting the same outputs with keep_prob == 1.0 assert (output_1 == output_2).all() +@pytest.mark.deprecated def test_dropout_trans_1_2(droput_graph_tuple): (graph_def, (keep_prob_name, dropout_output_name), output_nodes) = droput_graph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes=output_nodes) transformer = DropoutTransformerV2() + assert transformer.prune_graph new_ugraph = transformer.transform(ugraph) for op in new_ugraph.ops_info.values(): assert op.ugraph @@ -68,14 +73,16 @@ def test_dropout_trans_1_2(droput_graph_tuple): # expecting the same outputs with keep_prob == 1.0 assert (output_1 == output_2).all() +@pytest.mark.deprecated def test_dropout_trans_2(dropout_graph_tuple2): graph_def, output_nodes = dropout_graph_tuple2 - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes=output_nodes) trans = DropoutTransformerV2() new_ugraph = trans.transform(ugraph) assert len(new_ugraph.ops_info) == 1 assert 'x' in new_ugraph.ops_info +@pytest.mark.deprecated def test_dropout_vgg(vgg_ugraph): trans = DropoutTransformerV2() new_ugraph = trans.transform(vgg_ugraph) diff --git a/tests/test_transformer/test_id_remove/test_transformer.py b/tests/test_transformer/test_id_remove/test_transformer.py index d09654eb..2726a65a 100644 --- a/tests/test_transformer/test_id_remove/test_transformer.py +++ b/tests/test_transformer/test_id_remove/test_transformer.py @@ -1,8 +1,13 @@ from utensor_cgen.frontend.tensorflow import GraphDefParser from utensor_cgen.transformer.optimizer import IdOpRemoveOptimizer + +def test_id_rm_prune_graph(): + optimizer = IdOpRemoveOptimizer() + assert optimizer.prune_graph + def test_id_rm_transform_1(id_graph_def_1): - ugraph = GraphDefParser.parse(id_graph_def_1, output_nodes=['z']) + ugraph = GraphDefParser(config={}).parse(id_graph_def_1, output_nodes=['z']) optimizer = IdOpRemoveOptimizer() new_ugraph = optimizer.transform(ugraph) for op in new_ugraph.ops_info.values(): @@ -12,11 +17,11 @@ def test_id_rm_transform_1(id_graph_def_1): assert set(['x', 'y']) == in_op_names def test_id_rm_transform_2(id_graph_def_2): - ugraph = GraphDefParser.parse(id_graph_def_2, output_nodes=['z']) + ugraph = GraphDefParser(config={}).parse(id_graph_def_2, output_nodes=['z']) optimizer = IdOpRemoveOptimizer() new_ugraph = optimizer.transform(ugraph) for op in new_ugraph.ops_info.values(): assert op.op_type != 'Identity' op_z = new_ugraph.ops_info['z'] in_op_names = set([op.name for op in op_z.input_nodes]) - assert set(['w', 'y']) == in_op_names \ No newline at end of file + assert set(['w', 'y']) == in_op_names diff --git a/tests/test_transformer/test_inline/test_inline_optimizer.py b/tests/test_transformer/test_inline/test_inline_optimizer.py index 48b82dfd..79a449d0 100644 --- a/tests/test_transformer/test_inline/test_inline_optimizer.py +++ b/tests/test_transformer/test_inline/test_inline_optimizer.py @@ -4,8 +4,9 @@ def test_inline_optimizer(inlinegraph_tuple): (graph_def, inline_ans, output_nodes)= inlinegraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes) + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes) transformer = InlineTransformer() + assert not transformer.prune_graph ugraph = transformer.transform(ugraph) for node_name in ugraph.topo_order: if node_name in inline_ans: diff --git a/tests/test_transformer/test_linear_reorder/conftest.py b/tests/test_transformer/test_linear_reorder/conftest.py index e69dd354..bcdaae6f 100644 --- a/tests/test_transformer/test_linear_reorder/conftest.py +++ b/tests/test_transformer/test_linear_reorder/conftest.py @@ -1,6 +1,6 @@ +import tensorflow.compat.v1 as tf from pytest import fixture -import tensorflow as tf from utensor_cgen.frontend.tensorflow import GraphDefParser @@ -15,5 +15,5 @@ def subject_ugraph_1(): relu_2 = tf.nn.relu(input_2, name='relu_2') max_pool_2 = tf.nn.max_pool(relu_2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], padding='VALID', name='pool_2') output = tf.add(max_pool_1, max_pool_2, name='output') - subj_ugraph = GraphDefParser.parse(graph.as_graph_def(), output_nodes=[output.op.name]) + subj_ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), output_nodes=[output.op.name]) return subj_ugraph diff --git a/tests/test_transformer/test_linear_reorder/test_transformer.py b/tests/test_transformer/test_linear_reorder/test_transformer.py index 405b3c94..4ec42347 100644 --- a/tests/test_transformer/test_linear_reorder/test_transformer.py +++ b/tests/test_transformer/test_linear_reorder/test_transformer.py @@ -2,6 +2,7 @@ def test_linear_reorder_1(subj_ugraph_1): from utensor_cgen.transformer.linear_reoder import LinearReorderTransformerV2 transformer = LinearReorderTransformerV2() + assert not transformer.prune_graph new_ugraph = transformer.transform(subj_ugraph_1) for op in new_ugraph['output'].input_nodes: assert op.op_type == 'Relu', 'expecting Relu, get {}'.format(op.op_type) diff --git a/tests/test_transformer/test_pipeline/conftest.py b/tests/test_transformer/test_pipeline/conftest.py index a71cf887..1b3795e0 100644 --- a/tests/test_transformer/test_pipeline/conftest.py +++ b/tests/test_transformer/test_pipeline/conftest.py @@ -3,7 +3,7 @@ import pytest from utensor_cgen.transformer import (BatchNormTransformer, DropoutTransformer, - QuantizeTransformer, RefCntOptimizer) + RefCntOptimizer) @pytest.fixture(scope='function', name='methods') @@ -11,7 +11,6 @@ def pipeline_methods(): all_methods = [ BatchNormTransformer.METHOD_NAME, DropoutTransformer.METHOD_NAME, - QuantizeTransformer.METHOD_NAME, RefCntOptimizer.METHOD_NAME, ] shuffle(all_methods) diff --git a/tests/test_transformer/test_refcnt/test_refcnt_optimizer.py b/tests/test_transformer/test_refcnt/test_refcnt_optimizer.py index e032e725..47244a80 100644 --- a/tests/test_transformer/test_refcnt/test_refcnt_optimizer.py +++ b/tests/test_transformer/test_refcnt/test_refcnt_optimizer.py @@ -3,9 +3,10 @@ def test_refcnt_optimizer(refgraph_tuple): - (graph_def, refcnt_ans, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) + (graph_def, refcnt_ans, output_nodes) = refgraph_tuple + ugraph = GraphDefParser(config={}).parse(graph_def, output_nodes=output_nodes) transformer = RefCntOptimizer() + assert not transformer.prune_graph ugraph = transformer.transform(ugraph) for node_name in ugraph.topo_order: if node_name in refcnt_ans: diff --git a/tests/test_transformer/test_tensorallocator/conftest.py b/tests/test_transformer/test_tensorallocator/conftest.py deleted file mode 100644 index e3a6f948..00000000 --- a/tests/test_transformer/test_tensorallocator/conftest.py +++ /dev/null @@ -1,16 +0,0 @@ -import pytest -import tensorflow as tf - - -@pytest.fixture(scope='session', name='refgraph_tuple') -def refgraph(): - graph = tf.Graph() - with graph.as_default(): - x = tf.constant(1, name='x', dtype=tf.float32) - y = tf.constant(1, name='y', dtype=tf.float32) - z = tf.add(x, y, name='z') - w = tf.add(x, 2.0, name='w') - k = tf.add(z, w, name='k') - - - return graph.as_graph_def(), [k.op.name] \ No newline at end of file diff --git a/tests/test_transformer/test_tensorallocator/test_allocator_optimizer.py b/tests/test_transformer/test_tensorallocator/test_allocator_optimizer.py deleted file mode 100644 index 491de548..00000000 --- a/tests/test_transformer/test_tensorallocator/test_allocator_optimizer.py +++ /dev/null @@ -1,141 +0,0 @@ -from utensor_cgen.frontend.tensorflow import GraphDefParser -from utensor_cgen.transformer import TensorLifeProbe - - -def test_create_resource_table(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - resource_ans = { - 'x:0' : [0, 4], - 'y:0' : [1, 2], - 'z:0' : [2, 5], - 'w/y:0': [3, 4], - 'w:0' : [4, 5], - 'k:0' : [5, 5] - } - for t in table: - assert table[t]['start'] == resource_ans[t][0] - assert table[t]['end'] == resource_ans[t][1] - -def test_create_allocate_table(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[3] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - result = transformer._update_allocation_table(allocate_table, table, x, 0, 5) - assert result['w/y:0']['offsetstart'] == 0 - assert result['w/y:0']['offsetend'] == 5 - - - -def test_query_offset_address(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[3] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - result = transformer._update_allocation_table(allocate_table, table, x, 0, 5) - start, end = transformer._query_offset_fromallocate_table(allocate_table, 1, 5) - assert start == 0 - assert end == 5 - -def test_query_timeline(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[3] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - result = transformer._update_allocation_table(allocate_table, table, x, 0, 5) - start, end = transformer._query_time_fromallocate_table(allocate_table, 1, 5) - assert start == 1 - assert end == 5 - -def test_query_result(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[0] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - result = transformer._update_allocation_table(allocate_table, table, x, 5, 10) - l = ugraph.topo_order[1] - g = ugraph.ops_info[l] - y = g.output_tensors[0] - #address and time overlap - s = transformer._query_result(allocate_table, 3, 6, 1, 2) - assert s - s = transformer._query_result(allocate_table, 6, 8, 1, 2) - assert s - s = transformer._query_result(allocate_table, 9, 11, 1, 2) - assert s - #address overlap, but time doesn't - s = transformer._query_result(allocate_table, 3, 6, 5, 6) - assert not s - #time overlap, but address doesn't - s = transformer._query_result(allocate_table, 3, 4, 1, 2) - assert s - -def test_allocate_tensor(refgraph_tuple): - (graph_def, output_nodes) = refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - tensors = [] - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[3] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - tensors.append(x) - unit_size = 4 - buffer_size = 30000 #1k bytes - result = transformer.allocate_tensor(tensors, 0, allocate_table, table, buffer_size, unit_size) - - assert result == True - -def test_allocate_graph(refgraph_tuple): - (graph_def, output_nodes) = refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - use_def_table = transformer._create_resource_table(ugraph) - unit_size = 4 - buffer_size = 3000 #1k bytes - allocate_table = dict() - result = transformer.allocate_graph(ugraph, allocate_table, use_def_table, buffer_size, unit_size) - assert result == True - - -def test_query_check(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - table = transformer._create_resource_table(ugraph) - allocate_table = dict() - l = ugraph.topo_order[3] - g = ugraph.ops_info[l] - x = g.output_tensors[0] - result = transformer._update_allocation_table(allocate_table, table, x, 0, 5) - l = ugraph.topo_order[1] - g = ugraph.ops_info[l] - y = g.output_tensors[0] - valid = transformer._check(result, table, y, 4, 10) - assert valid == False - -def test_memory_allocation(refgraph_tuple): - (graph_def, output_nodes)= refgraph_tuple - ugraph = GraphDefParser.parse(graph_def, output_nodes=output_nodes) - transformer = TensorLifeProbe() - ugraph = transformer.transform(ugraph) diff --git a/tests/test_transformer/test_tensorallocator/__init__.py b/tests/test_transformer/test_tflm/test_export/__init__.py similarity index 100% rename from tests/test_transformer/test_tensorallocator/__init__.py rename to tests/test_transformer/test_tflm/test_export/__init__.py diff --git a/tests/test_transformer/test_tflm/test_export/conftest.py b/tests/test_transformer/test_tflm/test_export/conftest.py new file mode 100644 index 00000000..54b11e71 --- /dev/null +++ b/tests/test_transformer/test_tflm/test_export/conftest.py @@ -0,0 +1,136 @@ +import numpy as np +from pytest import fixture + +from utensor_cgen.backend.utensor.code_generator.legacy._operators import ( + OperatorFactory, _Operator, uTensorOpEqualityDelegate) +from utensor_cgen.ir import OperationInfo, TensorInfo, uTensorGraph +from utensor_cgen.ir.converter import AttrValueConverter, DataTypeConverter + + +@OperatorFactory.register +@uTensorOpEqualityDelegate.is_associative( + permutations=((0, 1), (1, 0)) +) +class _TFLM_AddOperator(_Operator): + + op_type = "TFLM_ADD" # tf op type + + def __init__(self, op_info, **kwargs): + _Operator.__init__(self) + inputs = [tensor_info.name for tensor_info in op_info.input_tensors] + output = op_info.output_tensors[0].name + tf_dtype = op_info.input_tensors[0].dtype + + @classmethod + def build_op_info(cls, ugraph, name, tensor_x, tensor_y, **kwargs): + # broadcast the shape and promote types + dummy_x = np.empty(tensor_x.shape) + dummy_y = np.empty(tensor_y.shape) + output_shape = np.broadcast(dummy_x, dummy_y).shape + output_dtype = np.promote_types(tensor_x.dtype, tensor_y.dtype) + return OperationInfo( + name=name, + input_tensors=[tensor_x, tensor_y], + output_tensors=[ + TensorInfo( + name='{}:0'.format(name), + op_name=name, + dtype=output_dtype, + shape=list(output_shape), + ugraph=ugraph + ) + ], + op_type=cls.op_type, + op_attr={ + 'T': AttrValueConverter.__utensor_generic_type__( + value_name='type', + value=DataTypeConverter.get_tf_value(output_dtype) + ) + }, + ugraph=ugraph, + lib_name=kwargs.get('lib_name', 'tflite') + ) + + +@OperatorFactory.register +class _TFLM_FULLY_CONNECTED_Operator(_Operator): + + op_type="TFLM_FULLY_CONNECTED" + + def __init__(self, op_info, **kwargs): + _Operator.__init__(self) + inputs = [tensor_info.name for tensor_info in op_info.input_tensors] + output = op_info.output_tensors[0].name + out_dtype = op_info.output_tensors[0].dtype + in_dtypes = [tensor_info.dtype for tensor_info in op_info.input_tensors] + #assert (op_info.input_tensors[0].shape[1] == None or op_info.input_tensors[0].shape[1] == 1) + + @classmethod + def build_op_info(cls, ugraph, name, tensor_x, tensor_w, tensor_b, **kwargs): + output_shape = [tensor_w.shape[0], tensor_x.shape[1]] + #output_dtype = np.promote_types(tensor_x.dtype, tensor_y.dtype) + output_dtype = tensor_x.dtype + return OperationInfo( + name=name, + input_tensors=[tensor_x, tensor_w, tensor_b], + output_tensors=[ + TensorInfo( + name='{}:0'.format(name), + op_name=name, + dtype=output_dtype, + shape=list(output_shape), + ugraph=ugraph + ) + ], + op_type=cls.op_type, + op_attr={ + 'T': AttrValueConverter.__utensor_generic_type__( + value_name='type', + value=DataTypeConverter.get_tf_value(output_dtype) + ) + }, + ugraph=ugraph, + lib_name=kwargs.get('lib_name', 'tflite') + ) + +@fixture(name='hybrid_quant_output') +def simple_tflm_graph(): + ugraph = uTensorGraph() + + with ugraph.begin_construction(): + tensor_x0, = ugraph.add_op( + op_type='Const', + name='x0', + value=np.array([1, 1, 1, 1], dtype=np.float32)[:, np.newaxis] + ) + tensor_x1, = ugraph.add_op( + op_type='Const', + name='x1', + value=np.array([2, 4, 6, 8], dtype=np.float32)[:, np.newaxis] + ) + tensor_w, = ugraph.add_op( + op_type='Const', + name='w', + value=np.array([10, 20, 30, 40], dtype=np.float32)[np.newaxis, :] + ) + tensor_b, = ugraph.add_op( + op_type='Const', + name='b', + value=np.array([7], dtype=np.float32) + ) + + + tensor_addout, = ugraph.add_op( + tensor_x0, tensor_x1, + op_type='TFLM_ADD', + name='TFLM_ADD0' + ) + + tensor_out, = ugraph.add_op( + tensor_addout, tensor_w, tensor_b, + op_type='TFLM_FULLY_CONNECTED', + name='TFLM_FULLY_CONNECTED00', + is_output=True + ) + + return [ugraph, ["x0:0", "x1:0"], ["w:0", "b:0", tensor_out.name]] diff --git a/tests/test_transformer/test_tflm/test_export/test_write.py b/tests/test_transformer/test_tflm/test_export/test_write.py new file mode 100644 index 00000000..7fdaf3e2 --- /dev/null +++ b/tests/test_transformer/test_tflm/test_export/test_write.py @@ -0,0 +1,114 @@ +import numpy as np + +import tensorflow as tf +from utensor_cgen.frontend.tensorflow import GraphDefParser +from utensor_cgen.matcher import uTensorGraphMatcher +from utensor_cgen.utils import prune_graph, topologic_order_graph +from utensor_cgen.transformer import TFLiteExporter +import utensor_cgen.third_party.flatbuffers as flatbuffers +import utensor_cgen.third_party.tflite as tflite +from utensor_cgen.third_party.tflite.BuiltinOperator import BuiltinOperator +from utensor_cgen.third_party.tflite.Model import Model +from utensor_cgen.third_party.tflite.BuiltinOptions import BuiltinOptions +from utensor_cgen.third_party.tflite.TensorType import TensorType + +builtin_ops = {v: k for k, v in BuiltinOperator.__dict__.items()} +op_options = {v: k for k, v in BuiltinOptions.__dict__.items()} + +tensor_np_type = dict() +tensor_np_type[0] = np.float32 +tensor_np_type[1] = np.float16 +tensor_np_type[2] = np.int32 +tensor_np_type[3] = np.uint8 +tensor_np_type[4] = np.uint64 +tensor_np_type[5] = np.ubyte #FIXME: supposed to be string +tensor_np_type[6] = np.bool +tensor_np_type[7] = np.int16 +tensor_np_type[8] = np.cdouble +tensor_np_type[9] = np.int8 + +def print_tflite_graph(byte_buff): + + model = Model.GetRootAsModel(byte_buff, 0) + subgraphs_len = model.SubgraphsLength() + subgraph = model.Subgraphs(0) + n_ops = subgraph.OperatorsLength() + print("version: ", model.Version()) + print("subgraph len: ", subgraphs_len) + print("number of operators: ", n_ops) + print("number of t buff: ", model.BuffersLength()) + print("flat buffer length: ", len(byte_buff), " bytes") + op_codes = [] + for i in range(0, model.OperatorCodesLength()): + op_code = model.OperatorCodes(i) + op_codes.append(op_code) + print("op code length: ", len(op_codes)) + + for i in range(0, subgraph.OperatorsLength()): + op = subgraph.Operators(i) + print("op code index: ", op.OpcodeIndex()) + opIndex = op.OpcodeIndex() + op_code = op_codes[opIndex] + builtin_code = op_code.BuiltinCode() + op_type = builtin_ops[builtin_code] + print(op_type) + + input_tensors = [subgraph.Tensors(input_idx) for input_idx in op.InputsAsNumpy()] + for tensor in input_tensors: + print() + print(tensor.Name(), ", ", tensor.ShapeAsNumpy()) + print("variable: ", tensor.IsVariable()) + if tensor.Type() == np.uint8 or tensor.Type() == np.int8: + q = tensor.Quantization() + assert q != None + print("quantization info: ") + print(" Detail Type: ", q.DetailsType()) + print(" Scales: ", q.ScaleAsNumpy()) + print(" Zeros: ", q.ZeroPointAsNumpy()) + print(" Scale: ", q.ScaleAsNumpy()) + print(" Zero Point: ", q.ZeroPointAsNumpy()) + print(" Dimension: ", q.QuantizedDimension()) + + print(tensor.IsVariable()) + if not tensor.IsVariable(): + buffer_index = tensor.Buffer() + assert buffer_index >= 0 + assert model.Buffers(buffer_index).DataLength() > 0 + buffer_content = model.Buffers(buffer_index).DataAsNumpy() + print("Tensor values: ", buffer_content.astype(tensor_np_type[tensor.Type()])) + else: + print("None") + +def test_tflite_fb_write(hybrid_quant_output): + [sample_ugraph, input_tensors, output_tensors] = hybrid_quant_output + exporter = TFLiteExporter(input_tensors=input_tensors, output_tensors=output_tensors) + ugraph = exporter.transform(sample_ugraph) + model_content = exporter.output() + + print_tflite_graph(model_content) + + # referece_model_content = open('/Users/neitan01/Documents/tflm/sinExample/sine_model.tflite', "rb").read() + # print_tflite_graph(referece_model_content) + + open("tflm_test_model.tflite", "wb").write(model_content) + test_model = tf.lite.Interpreter('tflm_test_model.tflite') + test_model.allocate_tensors() + input_data = np.array(np.ones([4,1]), dtype=np.float32) + test_model.set_tensor(test_model.get_input_details()[0]['index'], input_data) + test_model.invoke() + + print("0 :", test_model.get_tensor(0)) + print("1 :", test_model.get_tensor(1)) + print("2 :", test_model.get_tensor(2)) + print("3 :", test_model.get_tensor(3)) + + + print(test_model.get_tensor_details()) + print("out0 :", test_model.get_tensor(test_model.get_output_details()[0]["index"])) + print("out1 :", test_model.get_tensor(test_model.get_output_details()[1]["index"])) + print("out2 :", test_model.get_tensor(test_model.get_output_details()[2]["index"])) + + + output = test_model.get_tensor(test_model.get_output_details()[2]["index"]) + + assert np.abs(output - 707) <= 0.0001, 'error is greater than 0.0001' diff --git a/tests/test_utils/conftest.py b/tests/test_utils/conftest.py index e69de29b..a310b8f1 100644 --- a/tests/test_utils/conftest.py +++ b/tests/test_utils/conftest.py @@ -0,0 +1,38 @@ +import pytest + + +@pytest.fixture(name='config_user_values') +def config_user_values(): + from utensor_cgen.utils import Configuration + + return Configuration( + defaults={ + 'x': 1, + 'y': 2 + }, + user_config={ + 'x': 2 + } + ) + +@pytest.fixture(name='config_nested') +def config_nested(): + from utensor_cgen.utils import Configuration + + return Configuration( + defaults={ + 'dict1': { + 'inner': { + 'x': 3, + 'y': 4 + } + } + }, + user_config={ + 'dict1': { + 'inner': { + 'x': 2, + } + } + } + ) diff --git a/tests/test_utils/test_config.py b/tests/test_utils/test_config.py new file mode 100644 index 00000000..1a054fa7 --- /dev/null +++ b/tests/test_utils/test_config.py @@ -0,0 +1,9 @@ +def test_user_values(config_user_values): + assert config_user_values['x'] == 2 + assert config_user_values['y'] == 2 + +def test_config_nested(config_nested): + assert isinstance(config_nested['dict1'], type(config_nested)) + assert isinstance(config_nested['dict1']['inner'], type(config_nested)) + assert config_nested['dict1']['inner']['x'] == 2 + assert config_nested['dict1']['inner']['y'] == 4 diff --git a/utensor_cgen/__init__.py b/utensor_cgen/__init__.py index c8e03414..bb0f8db3 100644 --- a/utensor_cgen/__init__.py +++ b/utensor_cgen/__init__.py @@ -1,7 +1,14 @@ +""" + isort:skip_file +""" import sys import pkg_resources +# https://github.com/google/or-tools/issues/1830 +# we need to import ortools before tensorflow +from ortools.sat.python import cp_model as _ + from utensor_cgen._extensions import _ExtensionsLoader __version__ = ( diff --git a/utensor_cgen/_extensions.py b/utensor_cgen/_extensions.py index 5a748c3b..c2db87bc 100644 --- a/utensor_cgen/_extensions.py +++ b/utensor_cgen/_extensions.py @@ -1,8 +1,11 @@ -import sys -import re +"""Experimental, DON'T USE +""" import importlib +import re +import sys from types import ModuleType + class _ExtensionsLoader(ModuleType): _ext_cache = {} _dunder_pattern = re.compile(r'__[A-Za-z0-9_]+__') diff --git a/utensor_cgen/api/__init__.py b/utensor_cgen/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utensor_cgen/api/backend.py b/utensor_cgen/api/backend.py new file mode 100644 index 00000000..72b79949 --- /dev/null +++ b/utensor_cgen/api/backend.py @@ -0,0 +1,29 @@ +from toml import dumps + +from utensor_cgen import __version__ +from utensor_cgen.backend.api import BackendManager + + +def get_backends(): + return BackendManager.backends + +def get_trans_methods(): + from utensor_cgen.transformer import TransformerPipeline + + return TransformerPipeline.TRANSFORMER_MAP + +def generate_config(target, output='utensor_cli.toml'): + backend_cls = BackendManager.get_backend(target) + config = backend_cls.default_config + + with open(output, 'w') as fid: + fid.write( + '# utensor-cli version {}\n'.format(__version__) + \ + '# https://github.com/toml-lang/toml\n' + \ + '# ..\n' + ) + fid.write( + '# we use string \'None\' to represent python None value\n' + '# you should convert the string to None if you try to write extension for utensor_cgen\n' + ) + fid.write(dumps(config)) diff --git a/utensor_cgen/api/convert.py b/utensor_cgen/api/convert.py new file mode 100644 index 00000000..38742c26 --- /dev/null +++ b/utensor_cgen/api/convert.py @@ -0,0 +1,23 @@ +import os + +from toml import loads + +from utensor_cgen.backend.api import BackendManager + + +def convert_graph(model_file, output_nodes=None, config='utensor_cli.toml', target='utensor', model_name=None): + from utensor_cgen.frontend import FrontendSelector + + if os.path.exists(config): + with open(config) as fid: + config = loads(fid.read()) + else: + config = {} + ugraph = FrontendSelector.parse( + model_file, output_nodes, + config=config, + model_name=model_name + ) + backend = BackendManager.get_backend(target)(config) + backend.apply(ugraph) + return ugraph diff --git a/utensor_cgen/api/export.py b/utensor_cgen/api/export.py new file mode 100644 index 00000000..7d29f995 --- /dev/null +++ b/utensor_cgen/api/export.py @@ -0,0 +1,40 @@ +import tempfile +from pathlib import Path + +import tensorflow as tf + +from .convert import convert_graph + + +def tflm_keras_export( + model_or_path, + representive_dataset, + model_name=None, + optimizations=None, + config_file='utensor_cli.toml', + target='utensor', +): + with tempfile.TemporaryDirectory(prefix='utensor_') as tmp_dir: + dir_path = Path(tmp_dir) + if isinstance(model_or_path, str): + converter = tf.lite.TFLiteConverter.from_saved_model(model_or_path) + elif isinstance(model_or_path, tf.keras.Model): + model_path = str(dir_path / 'saved_model') + model_or_path.save(model_path) + converter = tf.lite.TFLiteConverter.from_saved_model(model_path) + else: + raise RuntimeError( + "expecting a keras model or a path to saved model, get {}".format( + model_or_path + ) + ) + if optimizations is None: + optimizations = [tf.lite.Optimize.DEFAULT] + converter.representative_dataset = representive_dataset + converter.optimizations = optimizations + tflm_model_content = converter.convert() + + with (dir_path / 'tflm_model.tflite').open('wb') as fid: + fid.write(tflm_model_content) + fid.flush() + convert_graph(fid.name, config=config_file, model_name=model_name, target=target) diff --git a/utensor_cgen/api/utils.py b/utensor_cgen/api/utils.py new file mode 100644 index 00000000..d90e7322 --- /dev/null +++ b/utensor_cgen/api/utils.py @@ -0,0 +1,59 @@ +import textwrap + +import click + + +def show_ugraph(ugraph, oneline=False, ignore_unknown_op=False): + from utensor_cgen.backend.utensor.code_generator.legacy._operators import OperatorFactory + + unknown_ops = set([]) + if oneline: + tmpl = click.style("{op_name} ", fg='yellow', bold=True) + \ + "op_type: {op_type}, inputs: {inputs}, outputs: {outputs}" + for op_name in ugraph.topo_order: + op_info = ugraph.ops_info[op_name] + msg = tmpl.format(op_name=op_name, op_type=op_info.op_type, + inputs=[tensor.name for tensor in op_info.input_tensors], + outputs=[tensor.name for tensor in op_info.output_tensors]) + click.echo(msg) + if not OperatorFactory.is_supported(op_info.op_type): + unknown_ops.add(op_info) + else: + tmpl = click.style('op_name: {op_name}\n', fg='yellow', bold=True) + \ + '''\ + op_type: {op_type} + input(s): + {inputs} + {input_shapes} + ouptut(s): + {outputs} + {output_shapes} + ''' + tmpl = textwrap.dedent(tmpl) + paragraphs = [] + for op_name in ugraph.topo_order: + op_info = ugraph.ops_info[op_name] + op_str = tmpl.format( + op_name=op_name, + op_type=op_info.op_type, + inputs=op_info.input_tensors, + outputs=op_info.output_tensors, + input_shapes=[tensor.shape for tensor in op_info.input_tensors], + output_shapes=[tensor.shape for tensor in op_info.output_tensors]) + paragraphs.append(op_str) + if not OperatorFactory.is_supported(op_info.op_type): + unknown_ops.add(op_info) + click.echo('\n'.join(paragraphs)) + click.secho( + 'topological ordered ops: {}'.format(ugraph.topo_order), + fg='white', bold=True, + ) + if unknown_ops and not ignore_unknown_op: + click.echo( + click.style('Unknown Ops Detected', fg='red', bold=True) + ) + for op_info in unknown_ops: + click.echo( + click.style(' {}: {}'.format(op_info.name, op_info.op_type), fg='red') + ) + return 0 diff --git a/utensor_cgen/backend/base.py b/utensor_cgen/backend/base.py index ed74618a..7b586937 100644 --- a/utensor_cgen/backend/base.py +++ b/utensor_cgen/backend/base.py @@ -1,7 +1,7 @@ -from utensor_cgen.utils import ( - MUST_OVERWRITEN, class_property, - parse_toml, Configuration, -) +from abc import abstractmethod + +from utensor_cgen.utils import (MUST_OVERWRITE, Configuration, class_property, + is_abstract, parse_toml) class _BackendBase(object): @@ -16,15 +16,33 @@ def __new__(cls, config, *args, **kwargs): validator(config, *args, **kwargs) if isinstance(config, dict): config = Configuration(cls.default_config, config) + elif config is None: + config = Configuration(cls.default_config, {}) self._config = config return self + @abstractmethod def apply(self, ugraph): - raise NotImplementedError('all backend object must overwrite apply method') + """Applying side-effect to ugraph + + Any backend part that implement apply method can create side-effect on given graph, + such as adding attribute or creating files. + """ + raise NotImplementedError('base apply method invoked: %s' % self) + + @abstractmethod + def transform(self, ugraph): + """Transform Graph + + transform should not create side-effect on the given graph and should + return a new ugraph that is the result of transformation applied to the + given ugraph. + """ + raise NotImplementedError('base transform method invoked: %s' % self) @class_property def default_config(cls): - return NotImplementedError('All backends should overwrite default config') + raise NotImplementedError('All backends should overwrite default config') def __call__(self, *args, **kwargs): return self.apply(*args, **kwargs) @@ -33,18 +51,32 @@ def __call__(self, *args, **kwargs): def config(self): return self._config - def _validate_config(self, config): - assert isinstance(config, (dict, Configuration)), \ + def _validate_config(self, config, *args, **kwargs): + assert isinstance(config, (dict, Configuration, type(None))), \ 'expecting {}, get {}'.format(dict, type(config)) + def _validate_abstracts(self, config, *args, **kwargs): + if is_abstract(self.apply) and is_abstract(self.transform): + raise ValueError('must overwrite at least one of apply or transorm: %s' % self) -class Backend(_BackendBase): - TARGET = MUST_OVERWRITEN +class Backend(_BackendBase): + """ + - Constrcutor signature must be ``__init__(self, config, *args, **kwargs)`` + - ``config`` should be a dictionay + - It will run through various check in ``__new__``, so it's better to access the value + of config via ``self.config``, which is an instance of ``Configuration`` + - It will make sure if users do not provide the value required, default one will be used + - You must at least implement one of ``apply`` or ``transform`` method + - ``apply`` will introduce side-effect on given ugraph **in place** and return nothing + - ``transform`` will create a new ugraph, applying side-effect on new ugraph and return it + """ + + TARGET = MUST_OVERWRITE @classmethod def _validate_target(cls, config, *args, **kwargs): - if cls.TARGET is MUST_OVERWRITEN: + if cls.TARGET is MUST_OVERWRITE: raise ValueError( 'Every Backend must overwrite TARGET attribute: {}'.format(cls) ) @@ -58,13 +90,14 @@ def from_file(cls, file_or_path, *args, **kwargs): def from_config(cls, config, *args, **kwargs): return cls(config, *args, **kwargs) + class BackendPart(Backend): - PART = MUST_OVERWRITEN + PART = MUST_OVERWRITE @classmethod def _validate_part(cls, config, *args, **kwargs): - if cls.PART is MUST_OVERWRITEN: + if cls.PART is MUST_OVERWRITE: raise ValueError( 'Every BackendPart must overwrite PART attribute: {}'.format(cls) ) diff --git a/utensor_cgen/backend/graph_lower/__init__.py b/utensor_cgen/backend/graph_lower/__init__.py new file mode 100644 index 00000000..7e9dcad6 --- /dev/null +++ b/utensor_cgen/backend/graph_lower/__init__.py @@ -0,0 +1,7 @@ +r"""Generic Graph Lowering + +Any graph lower exported within this module should be generic. +That is, it should work with any uTensorGraph for any backend. +""" +from .generic_graph_lower import TensorAllocationPlanner +from .generic_graph_lower import BrutalForceMemoryPlanner diff --git a/utensor_cgen/backend/graph_lower/alloc_plan.py b/utensor_cgen/backend/graph_lower/alloc_plan.py new file mode 100644 index 00000000..6e76c792 --- /dev/null +++ b/utensor_cgen/backend/graph_lower/alloc_plan.py @@ -0,0 +1,123 @@ +import attr +import six +from attr.validators import instance_of + +from utensor_cgen.logger import logger + +__all__ = ['TimeslotAllocation', 'SpaceAllocation', 'TimeSpaceAllocation', 'AllocationPlan'] + +@attr.s +class TimeslotAllocation(object): + time_slot_start = attr.ib(validator=instance_of(int)) + # if time_slot_end is None, it's a time sapn with no end + time_slot_end = attr.ib(validator=instance_of((int, type(None)))) + + def __attrs_post_init__(self): + assert self.time_slot_start >= 0, \ + 'invalid time_slot_start: %s' % self.time_slot_start + if self.time_slot_end is not None: + assert self.time_slot_end >= self.time_slot_start, \ + 'invalid time_slot_end: %s ~ %s' % (self.time_slot_start, self.time_slot_end) + + def __contains__(self, slot): + assert isinstance(slot, int), 'incorrect slot type: %s' % type(slot) + is_in = self.time_slot_start <= slot + if self.time_slot_end is not None: + is_in = is_in and slot <= self.time_slot_end + return is_in + + +@attr.s +class SpaceAllocation(object): + offset_start = attr.ib(validator=instance_of(int)) + size = attr.ib(validator=instance_of(int)) + data_alignment = attr.ib(validator=instance_of(int)) + offset_end = attr.ib(init=False) + + def __attrs_post_init__(self): + assert self.offset_start >= 0, \ + 'invalid offset_start: %s' % self.offset_start + assert self.size > 0, \ + 'invalid size: %s' % self.size + errmsg = ( self.data_alignment > 1 and + 'the memory offset is not aligned: %s (not %ss aligned)' or + 'the memory offset is not aligned: %s (not %s aligned)' + ) + assert self.size % self.data_alignment == 0, \ + errmsg % (self.size, self.data_alignment) + self.offset_end = self.offset_start + self.size - 1 + + def __contains__(self, offset): + return self.offset_start <= offset <= self.offset_end + + +@attr.s +class TimeSpaceAllocation(object): + entity_name = attr.ib(validator=instance_of(six.string_types)) + _time_alloc = attr.ib(validator=instance_of(TimeslotAllocation), repr=False) + _space_alloc = attr.ib(validator=instance_of(SpaceAllocation), repr=False) + time_slot_start = attr.ib(init=False) + time_slot_end = attr.ib(init=False) + offset_start = attr.ib(init=False) + offset_end = attr.ib(init=False) + size = attr.ib(init=False) + + def __attrs_post_init__(self): + self.time_slot_start = self._time_alloc.time_slot_start + self.time_slot_end = self._time_alloc.time_slot_end + self.offset_start = self._space_alloc.offset_start + self.offset_end = self._space_alloc.offset_end + self.size = self._space_alloc.size + + @classmethod + def init(cls, entity_name, time_slot_start, time_slot_end, offset_start, size): + time_alloc = TimeslotAllocation(time_slot_start, time_slot_end) + space_alloc = SpaceAllocation(offset_start, size) + return cls( + entity_name=entity_name, + time_alloc=time_alloc, + space_alloc=space_alloc + ) + + def is_alive_in_timeslot(self, time_slot): + return time_slot in self._time_alloc + + def is_occupied(self, offset): + return offset in self._space_alloc + + +class AllocationPlan(object): + + def __init__(self, allocs, total_size): + for alloc in allocs: + if not isinstance(alloc, TimeSpaceAllocation): + raise ValueError( + 'expecting value of {} of type {}, get {}'.format(k, TimeSpaceAllocation, type(v)) + ) + self.plan = {alloc.entity_name: alloc for alloc in allocs} + self.total_size = total_size + + def __setitem__(self, entity_name, alloc): + if not isinstance(alloc, TimeSpaceAllocation): + raise ValueError( + 'the value should be of type {}, get {}'.format(TimeSpaceAllocation, type(alloc)) + ) + if entity_name in self._plan: + logger.warning( + 'duplicate entity_name detected: {}'.format(entity_name) + ) + self._plan[entity_name] = alloc + + def __getitem__(self, entity_name): + if entity_name not in self.plan: + raise KeyError('%s not found' % entity_name) + return self.plan[entity_name] + + def __contains__(self, entity_name): + return entity_name in self.plan + + def __delitem__(self, entity_name): + del self.plan[entity_name] + + def __getattr__(self, attr_name): + return getattr(self.plan, attr_name) diff --git a/utensor_cgen/backend/graph_lower/generic_graph_lower.py b/utensor_cgen/backend/graph_lower/generic_graph_lower.py new file mode 100644 index 00000000..01f94dc3 --- /dev/null +++ b/utensor_cgen/backend/graph_lower/generic_graph_lower.py @@ -0,0 +1,505 @@ +r"""Generic Graph Lowering + +All graph lower in this module should be generic. +That is, it should be able to apply to any graph (i.e target independent) +""" +import os +import pickle +from collections import Counter, defaultdict, namedtuple +from copy import deepcopy +from itertools import chain, combinations, product +from math import ceil, log10 + +import numpy as np + +import tensorflow as tf +from ortools.sat.python import cp_model +from utensor_cgen.backend.base import BackendPart +from utensor_cgen.backend.utensor.snippets._types import NP_TYPES_MAP +from utensor_cgen.logger import logger +from utensor_cgen.utils import Configuration, class_property, timed + +from .alloc_plan import (AllocationPlan, SpaceAllocation, TimeslotAllocation, + TimeSpaceAllocation) + +__all__ = ['TensorAllocationPlanner', 'BrutalForceMemoryPlanner'] + +_VarMemorySpan = namedtuple('_VarMemorySpan', ['start', 'end', 'size']) + +class TopoOrderTensorTimeslotPlanner(BackendPart): + TARGET = 'generic' + PART = 'tensor_timeslot_planner' + KWARGS_NAMESCOPE = '_tensor_timeslot' + + @timed + def apply(self, ugraph): + ref_cnts = Counter() + life_span = defaultdict(lambda: [None, None]) + for op_info in ugraph.ops_info.values(): + for tensor in op_info.input_tensors: + ref_cnts[tensor.name] += 1 + for time_slot, op_name in enumerate(ugraph.topo_order): + op_info = ugraph.ops_info[op_name] + for tensor in op_info.output_tensors: + life_span[tensor.name][0] = time_slot + for tensor in op_info.input_tensors: + ref_cnts[tensor.name] -= 1 + if ref_cnts[tensor.name] == 0: + life_span[tensor.name][1] = time_slot + time_alloc_plan = {} + for tensor_name, (start, end) in life_span.items(): + time_alloc = TimeslotAllocation( + time_slot_start=start, + time_slot_end=end + ) + time_alloc_plan[tensor_name] = time_alloc + logger.info('topo ordered tensor life span analysis done') + ugraph.attributes[self.KWARGS_NAMESCOPE] = time_alloc_plan + + @class_property + def default_config(cls): + return {} + + +class TensorAllocationPlanner(BackendPart): + """ + Offline Tensor Allocation Optimizer + + analyse tensor lifetime and find the optimal allocation offset in the managed memory pool + + :param max_pool_size: the size of the memory pool (default: 1 KB) + :type max_pool_size: int + + :param include_inputs: include the input tensors (Placeholder) in the allocation plan + :type include_inputs: bool + + :param out_fname: the file name of memory allocation visualization (will NOT generate if not given) + :type out_fname: str + + :param aesthetic_kwargs: the keyword arguments controlling the aesthetic of the visualization of allocation plan + :type aesthetic_kwargs: dict + """ + TARGET = 'generic' + PART = 'tensor_alloc_planner' + KWARGS_NAMESCOPE = "_tensor_alloc" + + def __init__(self, config): + self.max_pool_size = self.config['max_pool_size'] + self.include_inputs = self.config['include_inputs'] + self.include_outputs = self.config['include_outputs'] + self.out_fname = self.config['out_fname'] + if self.out_fname == 'None': + self.out_fname = None + self.aesthetic_kwargs = self.config['aesthetic_kwargs'].to_dict() + if self.aesthetic_kwargs['figsize'] == 'None': + self.aesthetic_kwargs['figsize'] = None + self.enabled = self.config['enabled'] + self.data_alignment = self.config['data_alignment'] + self.dtype_size_map = self._parse_dtype_size_map(self.config) + + def apply(self, ugraph): + if not self.enabled: + # not enabled, do nothing + return + time_alloc_plan = ugraph.attributes.get( + TopoOrderTensorTimeslotPlanner.KWARGS_NAMESCOPE + ) + if time_alloc_plan is None: + TopoOrderTensorTimeslotPlanner(config={}).apply(ugraph) + time_alloc_plan = ugraph.attributes[TopoOrderTensorTimeslotPlanner.KWARGS_NAMESCOPE] + ops_to_ignore = set( + ugraph.get_ops_by_type('Inline') + ) + if not self.include_inputs: + ops_to_ignore.update( + ugraph.get_ops_by_type('Placeholder') + ) + if not self.include_outputs: + ops_to_ignore.update( + ugraph.output_ops + ) + tensors_to_schedule = set() + nonoverlap_map = defaultdict(set) + for time_slot, op_name in enumerate(ugraph.topo_order): + op_info = ugraph.ops_info[op_name] + if op_info in ops_to_ignore: + continue + # all output tensor should not overlap with tensors that's still alive + for out_tensor, known_tensor in product(op_info.output_tensors, tensors_to_schedule): + time_alloc = time_alloc_plan[known_tensor.name] + if time_slot in time_alloc: + nonoverlap_map[out_tensor].add(known_tensor) + # all output tensors should not overlap with each other + for out_tensor1, out_tensor2 in combinations(op_info.output_tensors, 2): + nonoverlap_map[out_tensor1].add(out_tensor2) + # update tensors to be scheduled + tensors_to_schedule.update(op_info.output_tensors) + space_alloc_plan, opt_mempool_size = self._solve_space_alloc(tensors_to_schedule, nonoverlap_map) + time_space_allocs = [] + if space_alloc_plan: + for tensor_name in space_alloc_plan: + space_alloc = space_alloc_plan[tensor_name] + time_alloc = time_alloc_plan[tensor_name] + time_space_allocs.append( + TimeSpaceAllocation( + tensor_name, + time_alloc=time_alloc, + space_alloc=space_alloc + ) + ) + ugraph.attributes[self.KWARGS_NAMESCOPE] = AllocationPlan( + allocs=time_space_allocs, + total_size=opt_mempool_size + ) + if self.out_fname: + figs = viz_memalloc(ugraph=ugraph, **self.aesthetic_kwargs) + if len(figs) == 1: + logger.info('saving tensor mem allocation to %s', self.out_fname) + figs[0].savefig(self.out_fname) + else: + num_digits = ceil(log10(len(figs))) + file_format = '{{}}_{{:0{}d}}{{}}'.format(num_digits) + for i, fig in enumerate(figs, 1): + fname, ext = os.path.splitext(self.out_fname) + fname = file_format.format(fname, i, ext) + logger.info('saving tensor mem allocation to %s', fname) + fig.savefig(fname) + with open('{}.pkl'.format(self.out_fname), 'wb') as fid: + pickle.dump(figs, fid) + logger.info('matplotlib figure object dumped (pickle): %s', fid.name) + + @timed + def _solve_space_alloc(self, tensors_to_schedule, nonoverlap_map): + model = cp_model.CpModel() + inter_vars = {} + tensor_allocs = {} + for tensor in tensors_to_schedule: + var_start = model.NewIntVar(0, self.max_pool_size, '{}_start'.format(tensor.name)) + var_end = model.NewIntVar(0, self.max_pool_size, '{}_end'.format(tensor.name)) + size = self._compute_tensor_bytes_size(tensor) + intv_var = model.NewIntervalVar(var_start, size, var_end, '{}_alloc'.format(tensor.name)) + inter_vars[tensor.name] = intv_var + tensor_allocs[tensor.name] = _VarMemorySpan(var_start, var_end, size) + for tensor in tensors_to_schedule: + inter_var = inter_vars[tensor.name] + nonoverlap_vars = [inter_vars[t.name] for t in nonoverlap_map[tensor]] + for other in nonoverlap_vars: + model.AddNoOverlap([inter_var, other]) + var_mempool_size = model.NewIntVar(0, self.max_pool_size, 'mempool_size') + model.AddMaxEquality(var_mempool_size, [alloc.end for alloc in tensor_allocs.values()]) + model.Minimize(var_mempool_size) + + solver = cp_model.CpSolver() + status = solver.Solve(model) + alloc_plan = {} + opt_mempool_size = None + if status == cp_model.OPTIMAL: + opt_mempool_size = solver.Value(var_mempool_size) + for name, alloc in tensor_allocs.items(): + alloc_plan[name] = SpaceAllocation( + offset_start=solver.Value(alloc.start), + size=alloc.size, + data_alignment=self.data_alignment, + ) + logger.info('optimal tensor allocation plan solved, total memory required: %i bytes', opt_mempool_size) + logger.info('number of tensors allocated: %i' % len(alloc_plan)) + else: + logger.info('tensor allocation plan not found, status: %s', solver.StatusName(status)) + if status == cp_model.INFEASIBLE: + logger.info( + 'the optimal plan is infeasible, please set `max_pool_size` a larger value: %s' % self.max_pool_size + ) + return alloc_plan, opt_mempool_size + + def _compute_tensor_bytes_size(self, tensor_info): + """ + compute aligned memory size (in bytes) + """ + size = tensor_info.size + # use user-defined element size or fall back to numpy size + elem_size = self.dtype_size_map.get(tensor_info.dtype, tensor_info.dtype.itemsize) + total_size = elem_size * size + if total_size % self.data_alignment != 0: + # if not aligned, compute the smallest aligned size + # TODO: do we need to take care how data is zero-padded? + total_size = ceil(total_size / self.data_alignment) * self.data_alignment + return total_size + + @classmethod + def _parse_dtype_size_map(cls, config): + dtype_size_map = {} + for dtype_str, size in config['dtype_size_map'].items(): + if dtype_str == 'float': + dtype_size_map[np.dtype('float32')] = size + elif dtype_str == 'uint8_t': + dtype_size_map[np.dtype('uint8')] = size + else: + # TODO: user-defined data types + # https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html + dtype_size_map[np.dtype(dtype_str)] = size + return dtype_size_map + + @class_property + def default_config(cls): + return { + 'max_pool_size': 1024*1024, # 1G bytes + 'include_inputs': False, + 'include_outputs': True, # for RNN, this may be False + 'out_fname': 'None', + 'aesthetic_kwargs': { + 'split_on_large_graph': True, + 'num_tensors_per_split': 20, + 'figsize': 'None', + 'fontsize': 12, + 'lw': 12, + 'rand_seed': 1111 + }, + 'enabled': True, + 'data_alignment': 2, # 2-bytes alignment + 'dtype_size_map': { + 'float': 4, + 'double': 8, + 'uint8': 1, + 'int': 4, + 'long': 8, + }, + } + + +class BrutalForceMemoryPlanner(BackendPart): + TARGET = 'utensor' + PART = 'brutal_force_mem_alloc' + KWARGS_NAMESCOPE = '_brutal_force_mem_alloc' + + def __init__(self, config): + self.buff_size = self.config['buff_size'] + self.unit_size = self.config['unit_size'] + self._type = {} + self._type[np.dtype(tf.float32.as_numpy_dtype)] = self.config['size_float'] + self._type[np.dtype(tf.int32.as_numpy_dtype)] = self.config['size_int'] + self._type[np.dtype(tf.quint8.as_numpy_dtype)] = self.config['size_uint8_t'] + self._type[np.dtype(tf.qint8.as_numpy_dtype)] = self.config['size_uint8_t'] + self._type[np.dtype(tf.qint32.as_numpy_dtype)] = self.config['size_int'] + self._type[np.dtype(tf.int64.as_numpy_dtype)] = self.config['size_int'] + + def apply(self, ugraph): + # ugraph.setup_data_manager(self.KWARGS_NAMESCOPE, {}) + # use_def_table: dict, tensor_name -> {'start': op_idx, 'end': op_idx} + use_def_table = self._create_resource_table(ugraph) + allocate_table = dict() + allocate_success = self.allocate_graph(ugraph, allocate_table, use_def_table, self.buff_size, self.unit_size) + if allocate_success: + allocs = [] + max_offset_end = 0 + for node_name in ugraph.topo_order: + in_t_infos = ugraph.ops_info[node_name].input_tensors + for in_o in in_t_infos: + if in_o.name in allocate_table: + time_alloc = TimeslotAllocation( + time_slot_start=use_def_table[in_o.name]['start'], + time_slot_end=use_def_table[in_o.name]['end'] + ) + space_alloc = SpaceAllocation( + offset_start=allocate_table[in_o.name]['offsetstart'], + size=allocate_table[in_o.name]['offsetend'] - allocate_table[in_o.name]['offsetstart'], + ) + if allocate_table[in_o.name]['offsetend'] >= max_offset_end: + max_offset_end = allocate_table[in_o.name]['offsetend'] + allocs.append( + TimeSpaceAllocation( + entity_name=in_o.name, + time_alloc=time_alloc, + space_alloc=space_alloc, + ) + ) + # new_ugraph.data_manager.address = (in_o.name, allocate_table[in_o.name]['offsetstart']) + out_t_infos = ugraph.ops_info[node_name].output_tensors + for out_o in out_t_infos: + if out_o.name in allocate_table: + time_alloc = TimeslotAllocation( + time_slot_start=use_def_table[out_o.name]['start'], + time_slot_end=use_def_table[out_o.name]['end'] + ) + space_alloc = SpaceAllocation( + offset_start=allocate_table[out_o.name]['offsetstart'], + size=allocate_table[out_o.name]['offsetend'] - allocate_table[out_o.name]['offsetstart'], + ) + if allocate_table[out_o.name]['offsetend'] >= max_offset_end: + max_offset_end = allocate_table[out_o.name]['offsetend'] + allocs.append( + TimeSpaceAllocation( + entity_name=out_o.name, + time_alloc=time_alloc, + space_alloc=space_alloc, + ) + ) + # new_ugraph.data_manager.address = (out_o.name, allocate_table[out_o.name]['offsetstart']) + ugraph.attributes[self.KWARGS_NAMESCOPE] = AllocationPlan(allocs=allocs, total_size=max_offset_end) + + def _query_offset_fromallocate_table(self, allocate_table, start, end): + new_start = start + new_end = end + for key in allocate_table: + if allocate_table[key]['offsetstart'] >= start and allocate_table[key]['offsetend'] <= end: + continue + elif allocate_table[key]['offsetstart'] <= start and allocate_table[key]['offsetend'] >= start: + new_start = allocate_table[key]['offsetstart'] + if allocate_table[key]['offsetend'] >= end: + new_end = max(new_end, allocate_table[key]['offsetend']) + else: + new_end = max(end, new_end) + elif allocate_table[key]['offsetstart'] >= start and allocate_table[key]['offsetend'] >= start: + if allocate_table[key]['offsetend'] >= end: + new_end = max(new_end, allocate_table[key]['offsetend']) + else: + new_end = max(end, new_end) + return new_start, new_end + + def _query_time_fromallocate_table(self, allocate_table, start, end): + time_start = start + time_end = end + for key in allocate_table: + if allocate_table[key]['start'] >= start and allocate_table[key]['end'] <= end: + continue + elif allocate_table[key]['start'] <= start and allocate_table[key]['end'] >= start: + if allocate_table[key]['end'] >= end: + time_end = max(time_end, allocate_table[key]['end']) + else: + time_end = max(end, time_end) + elif allocate_table[key]['start'] >= start and allocate_table[key]['end'] >= start: + if allocate_table[key]['end'] >= end: + time_end = max(time_end, allocate_table[key]['end']) + else: + time_end = max(end, time_end) + return time_start, time_end + + def _query_result(self, allocate_table, offset, length, timestart, timeend): + for key in allocate_table: + mem_occupied = ( + (allocate_table[key]['offsetstart'] >= offset and allocate_table[key]['offsetstart'] <= offset + length) or + (allocate_table[key]['offsetstart'] <= offset and allocate_table[key]['offsetend'] >= offset) + ) + life_span_occupied = ( + (allocate_table[key]['start'] >= timestart and allocate_table[key]['start'] <= timeend) or + (allocate_table[key]['start'] <= timestart and allocate_table[key]['end'] >= timestart) + ) + if mem_occupied and life_span_occupied: + return True + return False + + def allocate_tensor(self, tensors, tensor_index, allocate_table, use_def_table, buffer_size, unit_size): + if tensor_index == len(tensors): + return True + if tensors[tensor_index].name in allocate_table: + return self.allocate_tensor(tensors, tensor_index + 1, allocate_table, use_def_table, buffer_size, unit_size) + + tensor = tensors[tensor_index] + candidates = self._get_candidates(allocate_table, use_def_table, buffer_size, unit_size, tensor) + if not candidates: + return False + success = False + for candidate in candidates: + self._update_allocation_table(allocate_table, use_def_table, tensor, candidate, candidate + tensor.size) + success = self.allocate_tensor(tensors, tensor_index + 1, allocate_table, use_def_table, buffer_size, unit_size) + if success: + break + else: + self._remove_allocate_table(allocate_table, tensor) + return success + + def allocate_graph(self, ugraph, allocate_table, use_def_table, buffer_size, unit_size): + tensors = [] + + for node_name in ugraph.topo_order: + in_t_infos = [ + tensor + for tensor in ugraph.ops_info[node_name].input_tensors + if tensor.op.op_type != 'Inline' + ] + out_t_infos = [ + tensor + for tensor in ugraph.ops_info[node_name].output_tensors + if tensor.op.op_type != 'Inline' + ] + tensors.extend(in_t_infos) + tensors.extend(out_t_infos) + + succ = self.allocate_tensor(tensors, 0, allocate_table, use_def_table, buffer_size, unit_size) + return succ + + def _check(self, allocate_table, use_def_table, tensor, tensor_offset_start, tensor_offset_end): + valid = False + timestart = use_def_table[tensor.name]['start'] + timeend = use_def_table[tensor.name]['end'] + offset, length = self._query_offset_fromallocate_table(allocate_table, tensor_offset_start, tensor_offset_end) + timestart, timeend = self._query_time_fromallocate_table(allocate_table, timestart, timeend) + occupied = self._query_result(allocate_table, offset, length, timestart, timeend) + if not occupied: + valid = True + return valid + + def _get_candidates(self, allocate_table, use_def_table, buffer_size, unit_size, in_o): + ret = [] + for i in range(0, buffer_size, unit_size): + if self._check(allocate_table, use_def_table, in_o, i, i + in_o.size * self._type[in_o.dtype]): + ret.append(i) + return ret + + def _update_allocation_table( + self, + allocate_table, + use_def_table, + tensor, + offset_start, + offset_end + ): + time_start = use_def_table[tensor.name]['start'] + time_end = use_def_table[tensor.name]['end'] + attribute = dict() + attribute['start'] = time_start + attribute['end'] = time_end + attribute['offsetstart'] = offset_start + attribute['offsetend'] = offset_end + allocate_table[tensor.name] = attribute + return allocate_table + + def _remove_allocate_table(self, allocate_table, tensor): + del allocate_table[tensor.name] + + def _create_resource_table(self, ugraph): + resource_table = dict() + len_map = { + op_name: idx + for idx, op_name in enumerate(ugraph.topo_order) + } + for node_name in ugraph.topo_order: + for tensor_info in ugraph.ops_info[node_name].input_tensors: + if tensor_info.name not in resource_table: + lifetime = dict() + lifetime['start'] = len_map[node_name] + lifetime['end'] = len_map[node_name] + resource_table[tensor_info.name] = lifetime + resource_table[tensor_info.name]['end']= len_map[node_name] + + for outtensor in ugraph.ops_info[node_name].output_tensors: + if outtensor.name not in resource_table: + lifetime = dict() + lifetime['start'] = len_map[node_name] + lifetime['end'] = len_map[node_name] + resource_table[outtensor.name] = lifetime + + return resource_table + + @class_property + def default_config(cls): + config = {} + config['size_float'] = 4 + config['size_int'] = 4 + config['size_uint8_t'] = 1 + config['buff_size'] = 100000 #1k bytes + config['unit_size'] = 4 + + return config + +# FIXME: cyclic import +from utensor_cgen.ir.misc.graph_viz import viz_memalloc # isort:skip diff --git a/utensor_cgen/backend/transformer.py b/utensor_cgen/backend/transformer.py new file mode 100644 index 00000000..92ae47f1 --- /dev/null +++ b/utensor_cgen/backend/transformer.py @@ -0,0 +1,75 @@ +import pickle + +from utensor_cgen.logger import logger +from utensor_cgen.transformer.pipeline import TransformerPipeline +from utensor_cgen.utils import class_property + +from .base import BackendPart + +__all__ = ['PipelineTransformer'] + +class PipelineTransformer(BackendPart): + TARGET = 'generic' + PART = 'pipeline_transformer' + + def __init__(self, config): + self.transformer = TransformerPipeline( + methods=self.config['transform_methods'] + ) + self.trans_methods = self.config['transform_methods'] + self.save_graph = self.config['save_graph'] + + def transform(self, ugraph): + logger.info("Transforming graph: %s", ugraph.name) + logger.info("Transform pipeline: %s", ' -> '.join(self.trans_methods)) + self._check_non_quantized(ugraph) + new_ugraph = self.transformer.transform(ugraph) + new_ugraph.name = ugraph.name + logger.info('Graph transormation done') + if self.save_graph: + logger.info('Saving transformed graph') + pkl_fname = "{}_transformed.pkl".format(ugraph.name) + with open(pkl_fname, 'wb') as fid: + pickle.dump(new_ugraph, fid) + logger.info('{} saved'.format(pkl_fname)) + return new_ugraph + + @classmethod + def _check_non_quantized(cls, ugraph): + is_quantized = False + quant_ops = set([ + "Dequantize", "QuantizedMaxPool", + "QuantizeV2", "QuantizedMatMul", + "QuantizedRelu", "QuantizedAdd", + "RequantizationRange", + "Requantize", + "QuantizedReshape", + "QuantizedConv2D" + ]) + for op_info in ugraph.ops_info.values(): + if op_info.op_type in quant_ops: + is_quantized = True + break + if is_quantized: + logger.warning(( + "Expecting non-quantized graph, " + "graph transformation/optimization might not work properly" + )) + + @class_property + def default_config(cls): + return { + 'save_graph': False, + 'transform_methods': [ + "dropout(name_pattern=r'(dropout[_\w\d]*)/.*')", + "linear_reorder", + # these methods are deprecated + # "quantize", + # "conv_pool", + "inline", + "biasAdd", + "remove_id_op", + "fake_gather_v2", + "refcnt", + ] + } diff --git a/utensor_cgen/backend/utensor/_backend_impl.py b/utensor_cgen/backend/utensor/_backend_impl.py index cc5db5b7..c37784e7 100644 --- a/utensor_cgen/backend/utensor/_backend_impl.py +++ b/utensor_cgen/backend/utensor/_backend_impl.py @@ -1,39 +1,45 @@ from utensor_cgen.backend.base import Backend -from utensor_cgen.utils import ( - class_property, parse_toml, - LazyLoader, LazyAttrib, - Configuration, -) +from utensor_cgen.utils import (LazyAttrib, LazyLoader, class_property, + parse_toml) code_generator = LazyLoader(submod_name='backend.utensor.code_generator') -_graph_lower = LazyLoader(submod_name='backend.utensor._graph_lower') +transformer = LazyLoader(submod_name='backend.transformer') +_op_lower = LazyLoader(submod_name='backend.utensor._graph_lower._op_lower') +generic_graph_lower = LazyLoader(submod_name='backend.graph_lower') uTensorLegacyCodeGenerator = LazyAttrib(code_generator, 'uTensorLegacyCodeGenerator') uTensorRearchCodeGenerator = LazyAttrib(code_generator, 'uTensorRearchCodeGenerator') -uTensorLegacyGraphLower = LazyAttrib(_graph_lower, 'uTensorLegacyGraphLower') -uTensorRearchGraphLower = LazyAttrib(_graph_lower, 'uTensorRearchGraphLower') +uTensorLegacyGraphLower = LazyAttrib(_op_lower, 'uTensorLegacyGraphLower') +uTensorRearchGraphLower = LazyAttrib(_op_lower, 'uTensorRearchGraphLower') +TensorAllocationPlanner = LazyAttrib(generic_graph_lower, 'TensorAllocationPlanner') +PipelineTransformer = LazyAttrib(transformer, 'PipelineTransformer') -del code_generator, _graph_lower +del code_generator, _op_lower, generic_graph_lower class uTensorBackend(Backend): TARGET = 'utensor' - def __init__(self, config, code_generator=None, graph_lower=None): - default_config = self.default_config[self.TARGET][self.COMPONENT] - config = Configuration(default_config, config.get( - self.TARGET, - {self.COMPONENT: {}} - ).get( - self.COMPONENT, {} - ) - ) + def __init__( + self, + config, + code_generator=None, + graph_transformer=None, + graph_op_lower=None, + graph_alloc_lower=None, + ): + config = self.config[self.TARGET][self.COMPONENT] if config['legacy-api']: - code_generator = code_generator or uTensorLegacyCodeGenerator(config=config[uTensorLegacyCodeGenerator.PART]) - graph_lower = graph_lower or uTensorLegacyGraphLower(config=config[uTensorLegacyGraphLower.PART]) + code_generator = code_generator or uTensorLegacyCodeGenerator(config=config[uTensorLegacyCodeGenerator.PART].to_dict()) + graph_op_lower = graph_op_lower or uTensorLegacyGraphLower(config=config[uTensorLegacyGraphLower.PART].to_dict()) + graph_alloc_lower = graph_alloc_lower or TensorAllocationPlanner(config=config[TensorAllocationPlanner.PART].to_dict()) else: - code_generator = code_generator or uTensorRearchCodeGenerator(config=config[uTensorRearchCodeGenerator.PART]) - graph_lower = graph_lower or uTensorRearchGraphLower(config=config[uTensorRearchGraphLower.PART]) - self._graph_lower = graph_lower + code_generator = code_generator or uTensorRearchCodeGenerator(config=config[uTensorRearchCodeGenerator.PART].to_dict()) + graph_op_lower = graph_op_lower or uTensorRearchGraphLower(config=config[uTensorRearchGraphLower.PART].to_dict()) + graph_alloc_lower = TensorAllocationPlanner(config=config[TensorAllocationPlanner.PART].to_dict()) + graph_transformer = graph_transformer or PipelineTransformer(config=config[PipelineTransformer.PART].to_dict()) + self._graph_op_lower = graph_op_lower + self._graph_transformer = graph_transformer + self._graph_alloc_lower = graph_alloc_lower self._code_generator = code_generator @class_property @@ -41,17 +47,24 @@ def default_config(cls): config = {} config[cls.TARGET] = {} config[cls.TARGET][cls.COMPONENT] = {} - config[cls.TARGET][cls.COMPONENT]['legacy-api'] = True + config[cls.TARGET][cls.COMPONENT]['legacy-api'] = False + config[cls.TARGET][cls.COMPONENT][TensorAllocationPlanner.PART] = TensorAllocationPlanner.default_config config[cls.TARGET][cls.COMPONENT][uTensorLegacyCodeGenerator.PART] = uTensorLegacyCodeGenerator.default_config - config[cls.TARGET][cls.COMPONENT][uTensorRearchCodeGenerator.PART] = uTensorRearchCodeGenerator.default_config config[cls.TARGET][cls.COMPONENT][uTensorLegacyGraphLower.PART] = uTensorLegacyGraphLower.default_config + config[cls.TARGET][cls.COMPONENT][uTensorRearchCodeGenerator.PART] = uTensorRearchCodeGenerator.default_config config[cls.TARGET][cls.COMPONENT][uTensorRearchGraphLower.PART] = uTensorRearchGraphLower.default_config + config[cls.TARGET][cls.COMPONENT][PipelineTransformer.PART] = PipelineTransformer.default_config return config def apply(self, ugraph): - lower_ugraph = self._graph_lower.apply(ugraph) - self._code_generator.apply(lower_ugraph) - return lower_ugraph + # 1. graph optimization + opt_ugraph = self._graph_transformer.transform(ugraph) + # 2. lowering to target specific graph + self._graph_op_lower.apply(opt_ugraph) + # 3. apply memory allocation planner + self._graph_alloc_lower.apply(opt_ugraph) + # 4. generate target files + self._code_generator.apply(opt_ugraph) def __call__(self, ugraph): return self.apply(ugraph) diff --git a/utensor_cgen/backend/utensor/_graph_lower.py b/utensor_cgen/backend/utensor/_graph_lower.py deleted file mode 100644 index 8ba62a17..00000000 --- a/utensor_cgen/backend/utensor/_graph_lower.py +++ /dev/null @@ -1,61 +0,0 @@ -from copy import deepcopy - -from utensor_cgen.backend.base import BackendPart -from utensor_cgen.utils import class_property - - -class uTensorLegacyGraphLower(BackendPart): - - TARGET = 'utensor' - PART = 'legacy_graph_lower' - - def apply(self, ugraph): - handler = getattr(self, 'handle_{}'.format(ugraph.lib_name)) - if handler is None: - raise RuntimeError( - 'can not lower ugraph from {} to utensor'.format(ugraph.lib_name) - ) - return handler(ugraph) - - def handle_tensorflow(self, ugraph): - return ugraph - - @class_property - def default_config(cls): - return {} - - -class uTensorRearchGraphLower(BackendPart): - TARGET = 'utensor' - PART = 'rearch_graph_lower' - - class OptypRenameManager(object): - NAME_MAP = { - 'Add': 'AddOperator', - 'Conv2D': 'ConvOperator', - 'MatMul': 'MatrixMultOperator' - } - - @classmethod - def get_new_optype(cls, op_type): - return cls.NAME_MAP.get(op_type, op_type) - - def apply(self, ugraph): - handler = getattr(self, 'handle_{}'.format(ugraph.lib_name)) - if handler is None: - raise RuntimeError( - 'can not lower ugraph from {} to utensor'.format(ugraph.lib_name) - ) - return handler(ugraph) - - def handle_tensorflow(self, ugraph): - new_ugraph = deepcopy(ugraph) - for op_info in new_ugraph.ops_info.values(): - op_info.op_type = self.OptypRenameManager.get_new_optype(op_info.op_type) - return new_ugraph - - @class_property - def default_config(cls): - return {} - - diff --git a/utensor_cgen/backend/utensor/_graph_lower/__init__.py b/utensor_cgen/backend/utensor/_graph_lower/__init__.py new file mode 100644 index 00000000..84bb477d --- /dev/null +++ b/utensor_cgen/backend/utensor/_graph_lower/__init__.py @@ -0,0 +1 @@ +from ._op_lower import uTensorLegacyGraphLower, uTensorRearchGraphLower diff --git a/utensor_cgen/backend/utensor/_graph_lower/_op_lower.py b/utensor_cgen/backend/utensor/_graph_lower/_op_lower.py new file mode 100644 index 00000000..7d0ed1e2 --- /dev/null +++ b/utensor_cgen/backend/utensor/_graph_lower/_op_lower.py @@ -0,0 +1,75 @@ +from copy import deepcopy + +from utensor_cgen.backend.base import BackendPart +from utensor_cgen.logger import logger +from utensor_cgen.utils import Configuration, class_property + + +class uTensorGraphLowerBase(BackendPart): + TARGET = 'utensor' + + def handle_default(self, ugraph): + logger.warning('fall back to default graph lowering (do nothing)') + return ugraph + + def get_handler(self, ugraph): + handler = getattr(self, 'handle_{}'.format(ugraph.lib_name), self.handle_default) + return handler + + def apply(self, ugraph): + handler = self.get_handler(ugraph) + return handler(ugraph) + +class uTensorLegacyGraphLower(uTensorGraphLowerBase): + PART = 'legacy_graph_lower' + + def handle_tensorflow(self, ugraph): + return ugraph + + @class_property + def default_config(cls): + return {} + + +class uTensorRearchGraphLower(uTensorGraphLowerBase): + PART = 'rearch_graph_lower' + + def __init__(self, config): + final_config = Configuration(self.default_config, config) + self.tflite_use_quant_dws_conv = final_config['tflite_use_quant_dws_conv'] + + class OptypeRenameManager(object): + NAME_MAP = { + 'Add': 'AddOperator', + 'Conv2D': 'ConvOperator', + 'MatMul': 'MatrixMultOperator' + } + + @classmethod + def get_new_optype(cls, op_type): + return cls.NAME_MAP.get(op_type, op_type) + + class AddCodegenAttributes(object): + + @classmethod + def add_attributes(cls, ugraph): + for op_info in ugraph.get_ops_by_type('DepthwiseSeparableConvOperator'): + op_info.code_gen_attributes['namespaces'] = ('TFLM',) + + @classmethod + def add_name_map(cls, generic_name, target_specific_name): + cls.OptypeRenameManager.NAME_MAP[generic_name] = target_specific_name + + def handle_tensorflow(self, ugraph): + for op_info in ugraph.ops_info.values(): + op_info.op_type = self.OptypeRenameManager.get_new_optype(op_info.op_type) + + def handle_tflite(self, ugraph): + if self.tflite_use_quant_dws_conv: + self.AddCodegenAttributes.add_attributes(ugraph) + + @class_property + def default_config(cls): + return { + 'tflite_use_quant_dws_conv': True, + } diff --git a/utensor_cgen/backend/utensor/code_generator/legacy/_code_generator.py b/utensor_cgen/backend/utensor/code_generator/legacy/_code_generator.py index da269a0f..80650c36 100644 --- a/utensor_cgen/backend/utensor/code_generator/legacy/_code_generator.py +++ b/utensor_cgen/backend/utensor/code_generator/legacy/_code_generator.py @@ -1,24 +1,15 @@ # -*- coding:utf8 -*- import logging import os -import pickle -from tempfile import NamedTemporaryFile - -import numpy as np from utensor_cgen.backend.base import BackendPart -from utensor_cgen.frontend import FrontendSelector -from utensor_cgen.ir import uTensorGraph -from utensor_cgen.transformer.optimizer import RefCntOptimizer -from utensor_cgen.transformer.pipeline import TransformerPipeline -from utensor_cgen.utils import (NamescopedKWArgsParser, class_property, - parse_toml, LazyLoader, Configuration) -from utensor_cgen.backend.utensor.snippets.legacy import ( - CommentSnippet, ContextGlobalArrayContainer, - ContextHeaderSnippet, ContextSnippetsContainer, - CreateTensorBinarySnippet, CreateTensorIdxSnippet -) from utensor_cgen.backend.utensor.snippets.composer import Composer +from utensor_cgen.backend.utensor.snippets.legacy import ( + CommentSnippet, ContextGlobalArrayContainer, ContextHeaderSnippet, + ContextSnippetsContainer) +from utensor_cgen.transformer.optimizer import RefCntOptimizer +from utensor_cgen.utils import (Configuration, LazyLoader, + NamescopedKWArgsParser, class_property) from ._operators import OperatorFactory @@ -26,7 +17,7 @@ _logger = logging.getLogger('utensor-cli') tf = LazyLoader('tensorflow') -class uTensorLegacyCodeGenerator(BackendPart, object): +class uTensorLegacyCodeGenerator(BackendPart): TARGET = 'utensor' PART = 'legacy_code_generator' @@ -39,8 +30,6 @@ def __init__(self, config): os.makedirs(self.params_dir) self.embed_data_dir = final_config['embed_params_dir'].rstrip('/') self.model_dir = final_config['model_dir'].rstrip('/') - self.trans_methods = final_config['transform_methods'] - self.save_graph = final_config['save_graph'] self.debug_cmt = final_config['debug_cmt'] @classmethod @@ -65,24 +54,10 @@ def apply(self, ugraph): opFactory = OperatorFactory() - self._check_non_quantized(ugraph) - _logger.info("Transforming graph: %s", ugraph.name) - _logger.info("Transform pipeline: %s", ' -> '.join(self.trans_methods)) - quant_ugraph = self._transform_graph(ugraph, - self.trans_methods) - _logger.info('Graph transormation done') - - if self.save_graph: - _logger.info('Saving transformed graph') - pkl_fname = "quant_{}.pkl".format(ugraph.name) - with open(pkl_fname, 'wb') as fid: - pickle.dump(quant_ugraph, fid) - _logger.info('{} saved'.format(pkl_fname)) - if not os.path.exists(os.path.join(self.params_dir, ugraph.name)): os.makedirs(os.path.join(self.params_dir, ugraph.name)) - for op_id, op_name in enumerate(quant_ugraph.topo_order): - op_info = quant_ugraph.ops_info[op_name] + for op_id, op_name in enumerate(ugraph.topo_order): + op_info = ugraph.ops_info[op_name] op_type = op_info.op_type # TODO: better abstraction for snippet if op_type == "Placeholder": @@ -96,11 +71,12 @@ def apply(self, ugraph): else: # TODO: the operator may correspond to multiple snippets (such as InlinTensor) # weight_container is passed to function for workaround - snippet = opFactory.createOperatorSnippet(op_info, - idx_dir=os.path.join(self.params_dir, ugraph.name), - embed_data_dir=self.embed_data_dir, - weight_container=weight_container, - data_manager=quant_ugraph.data_manager) + snippet = opFactory.createOperatorSnippet( + op_info, + idx_dir=os.path.join(self.params_dir, ugraph.name), + embed_data_dir=self.embed_data_dir, + weight_container=weight_container, + ) container.add_snippet(snippet) if self.debug_cmt: @@ -113,7 +89,7 @@ def apply(self, ugraph): # generate cpp/hpp files if not os.path.exists(self.model_dir): os.makedirs(self.model_dir) - if any([method == 'inline' for method in self.trans_methods]): + if weight_container.snippets: _logger.info("Generate weight file: %s", weight_header_fname) with open(os.path.join(self.model_dir, weight_header_fname), "w") as wf: wf.write('// Auto generated by utensor-cli\n\n') @@ -137,46 +113,5 @@ def default_config(cls): config['params_dir'] = 'data' config['embed_params_dir'] = '/fs/data' config['model_dir'] = 'models' - config['transform_methods'] = [ - 'dropout(name_pattern=r"(dropout[_\w\d]*)/.*")', - 'linear_reorder', - 'quantize', - 'conv_pool', - 'inline', - 'biasAdd', - 'remove_id_op', - 'fake_gather_v2', - 'refcnt' - ] - config['save_graph'] = False config['debug_cmt'] = False return config - - @classmethod - def _check_non_quantized(cls, ugraph): - is_quantized = False - for op_info in ugraph.ops_info.values(): - if op_info.op_type in [ - "Dequantize", "QuantizedMaxPool", - "QuantizeV2", "QuantizedMatMul", - "QuantizedRelu", "QuantizedAdd", - "RequantizationRange", - "Requantize", - "QuantizedReshape", - "QuantizedConv2D" - ]: - is_quantized = True - break - if is_quantized: - _logger.warning(("Expecting non-quantized graph, " - "graph transformation/optimization might not work properly")) - - def _transform_graph(self, ugraph, methods): - pipeline = TransformerPipeline(methods) - return pipeline.transform(ugraph) - - def _tf_load_graph_def(self, pb_fname): - with tf.gfile.FastGFile(pb_fname, 'rb') as fid: - graph_def = tf.GraphDef() - graph_def.ParseFromString(fid.read()) - return graph_def diff --git a/utensor_cgen/backend/utensor/code_generator/legacy/_operators.py b/utensor_cgen/backend/utensor/code_generator/legacy/_operators.py index bcc87450..2ec7a58d 100644 --- a/utensor_cgen/backend/utensor/code_generator/legacy/_operators.py +++ b/utensor_cgen/backend/utensor/code_generator/legacy/_operators.py @@ -7,19 +7,21 @@ import numpy as np import idx2numpy as idx2np +from utensor_cgen.backend.graph_lower import TensorAllocationPlanner +from utensor_cgen.backend.utensor.snippets.legacy import * # pylint: disable=W0401,W0614 from utensor_cgen.ir import OperationInfo, TensorInfo from utensor_cgen.ir.converter import (AttrValueConverter, DataTypeConverter, GenericTensorConverterMixin) from utensor_cgen.logger import logger -from utensor_cgen.matcher import OpEqualityDelegate, _morphism +from utensor_cgen.matcher import OpEqualityDelegateBase, _morphism from utensor_cgen.transformer.optimizer import RefCntOptimizer -from utensor_cgen.utils import NamescopedKWArgsParser, LazyLoader - -from utensor_cgen.backend.utensor.snippets.legacy import * # pylint: disable=W0401,W0614 +from utensor_cgen.utils import LazyLoader, NamescopedKWArgsParser __all__ = ['OperatorFactory', 'OpNotSupportedError'] tf = LazyLoader('tensorflow') +class uTensorOpEqualityDelegate(OpEqualityDelegateBase): pass + class OpNotSupportedError(Exception): pass @@ -85,9 +87,17 @@ def snippet(self): def build_op_info(cls, ugraph, name, *args, **kwargs): raise NotImplementedError('%s does not have build_op_info method' % cls) + def get_mem_address(self, op_info): + alloc_plan = op_info.ugraph.attributes.get(TensorAllocationPlanner.KWARGS_NAMESCOPE) + address = [] + if alloc_plan: + for tensor in op_info.output_tensors: + if tensor.name in alloc_plan: + address.append(alloc_plan[tensor.name].offset_start) + return address @OperatorFactory.register -@OpEqualityDelegate.is_compatible_with("Inline", _morphism.Const2InlineMorphism) +@uTensorOpEqualityDelegate.is_compatible_with("Inline", _morphism.Const2InlineMorphism) class _ConstOperator(_Operator): op_type = "Const" @@ -158,7 +168,7 @@ def _tf_save_data(self, path, value): @OperatorFactory.register -@OpEqualityDelegate.is_associative( +@uTensorOpEqualityDelegate.is_associative( permutations=((0, 1), (1, 0)) ) class _AddOperator(_Operator): @@ -218,14 +228,13 @@ def __init__(self, op_info, **kwargs): out_tensor_info = op_info.output_tensors[0] output, out_dtype = out_tensor_info.name, out_tensor_info.dtype in_dtype = op_info.input_tensors[0].dtype - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_count = parser.get('ref_counts', [0])[0] to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = ArgMaxOpSnippet(inputs, output, in_dtype, out_dtype, ref_count, to_eval, address) @classmethod @@ -279,15 +288,14 @@ def __init__(self, op_info, **kwargs): _Operator.__init__(self) inputs = [tensor_info.name for tensor_info in op_info.input_tensors] out_tensor_info = op_info.output_tensors[0] - data_manager = kwargs['data_manager'] output, out_dtype = out_tensor_info.name, out_tensor_info.dtype - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_count = parser.get('ref_counts', [0])[0] to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = DequantizeOpSnippet(inputs, output, out_dtype, ref_count, to_eval, address) @@ -300,20 +308,19 @@ def __init__(self, op_info, **kwargs): _Operator.__init__(self) inputs = [tensor_info.name for tensor_info in op_info.input_tensors] out_tensor_info = op_info.output_tensors[0] - data_manager = kwargs['data_manager'] output, out_dtype, out_shape = (out_tensor_info.name, out_tensor_info.dtype, out_tensor_info.shape) # FIXME: automatic alloc for uTensor fail if not out_shape: out_shape = [1] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_count = parser.get('ref_counts', [0])[0] to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = MaxOpSnippet(inputs, output, out_dtype, out_shape, ref_count, to_eval, address) @classmethod @@ -517,20 +524,19 @@ def __init__(self, op_info, **kwargs): _Operator.__init__(self) inputs = [tensor_info.name for tensor_info in op_info.input_tensors] out_info = op_info.output_tensors[0] - data_manager = kwargs['data_manager'] output, out_dtype, out_shape = (out_info.name, out_info.dtype, out_info.shape) # FIXME: automatic alloc for uTensor fail if not out_shape: out_shape = [1] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_count = parser.get('ref_counts', [0])[0] to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = MinOpSnippet(inputs, output, out_dtype, out_shape, ref_count, to_eval, address) @classmethod @@ -584,14 +590,13 @@ def __init__(self, op_info, **kwargs): inputs = [tensor_info.name for tensor_info in op_info.input_tensors] outputs = [tensor_info.name for tensor_info in op_info.output_tensors] out_dtype = op_info.output_tensors[0].dtype - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = QuantizeV2OpSnippet(inputs, outputs, out_dtype, ref_counts, to_eval, address) @@ -672,14 +677,13 @@ def __init__(self, op_info, **kwargs): x_dtype, w_dtype, out_dtype = (op_info.input_tensors[0].dtype, op_info.input_tensors[1].dtype, op_info.output_tensors[0].dtype) - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = QuantizedMatMulOpSnippet(inputs, outputs, x_dtype, w_dtype, out_dtype, ref_counts, to_eval, address) @@ -743,15 +747,14 @@ def __init__(self, op_info, **kwargs): in_dtype, qout_dtype = (op_info.input_tensors[0].dtype, op_info.output_tensors[0].dtype) #NT: why separate this out? #DB: I don't know, it's in the uTensor C code - data_manager = kwargs['data_manager'] out_dtypes = [tensor_info.dtype for tensor_info in op_info.output_tensors[1:]] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = QuantizedReluOpSnippet(inputs, outputs, in_dtype, out_dtypes, qout_dtype, ref_counts, to_eval, address) @@ -769,14 +772,13 @@ def __init__(self, op_info, **kwargs): x_dtype, w_dtype, out_dtype = (op_info.input_tensors[0].dtype, op_info.input_tensors[1].dtype, op_info.output_tensors[0].dtype) - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr, + ) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = QuantizedAddOpSnippet(inputs, outputs, x_dtype, w_dtype, out_dtype, ref_counts, to_eval, address) @@ -813,14 +815,11 @@ def __init__(self, op_info, **kwargs): inputs = [tensor_info.name for tensor_info in op_info.input_tensors] outputs = [tensor_info.name for tensor_info in op_info.output_tensors] out_dtype = op_info.output_tensors[0].dtype - data_manager = kwargs['data_manager'] parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + op_info.op_attr) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = RequantizationRangeOpSnippet(inputs, outputs, out_dtype, ref_counts, to_eval, address) @@ -835,14 +834,13 @@ def __init__(self, op_info, **kwargs): outputs = [tensor_info.name for tensor_info in op_info.output_tensors] qout_dtype = op_info.output_tensors[0].dtype range_dtype = op_info.output_tensors[1].dtype - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr, + ) ref_counts = parser.get('ref_counts', []) to_eval = parser.get('to_eval', False) - address = parser.get('address', []) + address = self.get_mem_address(op_info) self._snippet = RequantizeOpSnippet(inputs, outputs, qout_dtype, range_dtype, ref_counts, to_eval, address) @@ -857,15 +855,14 @@ def __init__(self, op_info, **kwargs): _Operator.__init__(self) inputs = [tensor_info.name for tensor_info in op_info.input_tensors] output = op_info.output_tensors[0].name - data_manager = kwargs['data_manager'] - parser = NamescopedKWArgsParser(RefCntOptimizer.KWARGS_NAMESCOPE, - op_info.op_attr, - data_manager, - op_info) + parser = NamescopedKWArgsParser( + RefCntOptimizer.KWARGS_NAMESCOPE, + op_info.op_attr + ) ref_count = parser.get('ref_counts', [0])[0] to_eval = parser.get('to_eval', False) - address = parser.get('address', []) dtype = op_info.input_tensors[0].dtype + address = self.get_mem_address(op_info) self._snippet = ReshapeOpSnippet(inputs, output, dtype, ref_count, to_eval, address) @@ -1087,7 +1084,7 @@ def __init__(self, op_info, **kwargs): @OperatorFactory.register -@OpEqualityDelegate.is_compatible_with("Const", _morphism.Inline2ConstMorphism) +@uTensorOpEqualityDelegate.is_compatible_with("Const", _morphism.Inline2ConstMorphism) class _InlineOperator(_Operator): op_type = "Inline" diff --git a/utensor_cgen/backend/utensor/code_generator/rearch/_code_generator.py b/utensor_cgen/backend/utensor/code_generator/rearch/_code_generator.py index 65c53e1d..3a122d7d 100644 --- a/utensor_cgen/backend/utensor/code_generator/rearch/_code_generator.py +++ b/utensor_cgen/backend/utensor/code_generator/rearch/_code_generator.py @@ -1,5 +1,3 @@ -import os -import pickle import re from itertools import chain from pathlib import Path @@ -8,9 +6,9 @@ from utensor_cgen.backend.utensor.snippets.composer import Composer from utensor_cgen.backend.utensor.snippets.legacy import ( ContextGlobalArrayContainer, WeightSnippet) -from utensor_cgen.backend.utensor.snippets.rearch import SimpleContainer +from utensor_cgen.backend.utensor.snippets.rearch import ( + DeclareRamTensorSnippet, DeclareRomTensorSnippet, SimpleContainer) from utensor_cgen.backend.utensor.snippets.template_env import env -from utensor_cgen.transformer.pipeline import TransformerPipeline from utensor_cgen.utils import Configuration, class_property from ._operators import OperatorFactory @@ -26,26 +24,49 @@ def __init__(self, config): self.src_fname = final_config['src_fname'] self.header_fname = final_config['header_fname'] self.params_dir = final_config['params_dir'].rstrip('/') - self.trans_methods = final_config['transform_methods'] self.meta_data_pool_size = final_config['meta_data_pool_size'] self.ram_data_pool_size = final_config['ram_data_pool_size'] self.model_dir = final_config['model_dir'].rstrip('/') - self.save_graph = final_config['save_graph'] def apply(self, ugraph): + # take a look of template file "simple.cpp", which is under templates/container/rearch/ directory + # in submodule utensor_cgen.backend.utensor.snippets + # you will see how declare_snippets and eval_snippets works and have better understanding of + # the rearch code generator src_fname = self.src_fname if src_fname == 'None': src_fname = '{}.cpp'.format(ugraph.name) - pipeline = TransformerPipeline(self.trans_methods) - new_ugraph = pipeline.transform(ugraph) - if self.save_graph: - with open('transformed_{}.pkl'.format(ugraph.name), 'wb') as fid: - pickle.dump(new_ugraph, fid) - # 1. find all ops required + # find all required ops and the variable names for the tensors in the generate files + ( + ops, # Set[Operator], no Placeholder or Inline ops + placeholders, # Set[String], variable names of planceholder tensors + tensor_var_map, # dict, tensor name -> var name + ) = self._find_required_ops(ugraph) + ( + ops_map, # dict, op_info -> variable name of op in the output files + declare_global_snippets, # list of Snippet objects, which will rendered in global scop + declare_local_snippets, # list of Snippet objects, which will rendered in local function scope + weight_snippets, # snippets for generating weights header file + ) = self._get_declare_snippets(ugraph, ops, tensor_var_map) + # eval_snippets: List of snippet objects, which will render code snippets for tensor evaluation + eval_snippets = self._get_evaluation_snippets(ugraph, ops_map, tensor_var_map) + # generate files + self._generate_files( + ugraph, + placeholders=placeholders, + tensor_var_map=tensor_var_map, + weight_snippets=weight_snippets, + declare_global_snippets=declare_global_snippets, + declare_local_snippets=declare_local_snippets, + eval_snippets=eval_snippets + ) + + def _find_required_ops(self, ugraph): + # find all ops required ops = set() placeholders = set() tensor_var_map = {} # tensor name -> var name - for op_info in new_ugraph.ops_info.values(): + for op_info in ugraph.ops_info.values(): for tensor in op_info.output_tensors: tensor_var_name = re.sub(r'[:/]', '', tensor.name) tensor_var_map[tensor.name] = tensor_var_name @@ -55,71 +76,97 @@ def apply(self, ugraph): ops.add( OperatorFactory.get_opertor(op_info) ) - # 2. ops/tensors declaration - declare_snippets = [] + return ops, placeholders, tensor_var_map + + def _get_declare_snippets(self, ugraph, ops, tensor_var_map): + # get ops/tensors declaration snippets + declare_global_snippets = [] + declare_local_snippets = [] ops_map = {} # op -> op variable name + weight_snippets = [] for i, op in enumerate(ops): op_var_name = 'op_{:03d}'.format(i) ops_map[op] = op_var_name - declare_snippets.append(op.get_declare_snippet(op_var_name)) - weight_snippets = [] - for op_info in filter(lambda op_info: op_info.op_type == 'Inline', new_ugraph.ops_info.values()): - tensor = op_info.output_tensors[0] - buffer_name = 'data_{}'.format(tensor.name.replace(':', '_').replace('/', '_')) + declare_local_snippets.append(op.get_declare_snippet(op_var_name, tensor_var_map)) + for op_info in filter(lambda op_info: op_info.op_type not in ["Inline", "Placeholder"], ugraph.ops_info.values()): + for out_tensor in op_info.output_tensors: + declare_local_snippets.append( + DeclareRamTensorSnippet(out_tensor, tensor_var_map[out_tensor.name]) + ) + for op_info in filter(lambda op_info: op_info.op_type == 'Inline', ugraph.ops_info.values()): + tensor_info = op_info.output_tensors[0] + tensor_var = tensor_var_map[tensor_info.name] + buffer_name = 'data_{}'.format(tensor_info.name.replace(':', '_').replace('/', '_')) weight_snippets.append( WeightSnippet( buffer_name, - tensor.dtype, - tensor.shape, + tensor_info.dtype, + tensor_info.shape, op_info.op_attr['value'].value.np_array.ravel() ) ) - declare_snippets.append( - OperatorFactory.get_opertor(op_info).get_declare_snippet( - tensor_var_name=tensor_var_map[tensor.name], - buffer_var_name=buffer_name, - tensor=tensor + declare_local_snippets.append( + DeclareRomTensorSnippet( + tensor_info=tensor_info, + tensor_var=tensor_var, + buffer_var=buffer_name, + # static=True, ) ) - # 3. evaluation snippets + return ops_map, declare_global_snippets, declare_local_snippets, weight_snippets + + def _get_evaluation_snippets(self, ugraph, ops_map, tensor_var_map): eval_snippets = [] - for op_name in new_ugraph.topo_order: - op_info = new_ugraph.ops_info[op_name] + for op_name in ugraph.topo_order: + op_info = ugraph.ops_info[op_name] if op_info.op_type in ['Placeholder', 'Inline']: continue op = OperatorFactory.get_opertor(op_info) op_name = ops_map[op] eval_snippets.append( - op.get_eval_snippet(op_info, op_name, tensor_var_map) + op.get_eval_snippet(op_name, op_info, tensor_var_map) ) + return eval_snippets + + def _generate_files( + self, ugraph, placeholders, tensor_var_map, + weight_snippets, declare_global_snippets, declare_local_snippets, + eval_snippets + ): template_vars = {} template_vars['model_name'] = ugraph.name - template_vars['meta_data_pool_size'] = self._compute_meta_data_size(new_ugraph) - template_vars['ram_data_pool_size'] = self._compute_ram_data_size(new_ugraph) + template_vars['meta_data_pool_size'] = self._compute_meta_data_size(ugraph) + template_vars['ram_data_pool_size'] = self._compute_ram_data_size(ugraph) template_vars['placeholders'] = placeholders template_vars['out_tensor_var_names'] = [ tensor_var_map[tensor.name] for tensor in chain(*[ - new_ugraph.ops_info[op_name].output_tensors - for op_name in new_ugraph.output_nodes + ugraph.ops_info[op_name].output_tensors + for op_name in ugraph.output_nodes ]) ] - # 4. write files params_dir = Path(self.params_dir) / ugraph.name params_dir.mkdir(parents=True, exist_ok=True) weight_header_fname = None if weight_snippets: with (params_dir / 'params_{}.hpp'.format(ugraph.name)).open('w') as fid: - weight_container = ContextGlobalArrayContainer(snippets=weight_snippets) + fid.write("/* Auto-generated by utensor cli */\n") + weight_container = ContextGlobalArrayContainer( + snippets=weight_snippets + ) fid.write(weight_container.render()) weight_header_fname = fid.name - # # generate the computation function + # generate the compute function model_file_dir = Path(self.model_dir) header_fname = self.header_fname == 'None' and '{}.hpp'.format(ugraph.name) or self.header_fname - container_snippet = SimpleContainer(declare_snippets=declare_snippets, eval_snippests=eval_snippets) + container_snippet = SimpleContainer() + container_snippet.add_declare_global_snippets(*declare_global_snippets) + container_snippet.add_declare_local_snippets(*declare_local_snippets) + container_snippet.add_eval_snippets(*eval_snippets) container_snippet.template_vars.update(template_vars) (model_file_dir / ugraph.name).mkdir(parents=True, exist_ok=True) with (model_file_dir / ugraph.name / header_fname).open('w') as fid: + fid.write("/* Auto-generated by utensor cli */\n") template = env.get_template('snippets/rearch/simple.hpp') fid.write(template.render(**template_vars)) container_snippet.add_header(fid.name) @@ -128,6 +175,7 @@ def apply(self, ugraph): composer = Composer(snippets=[container_snippet]) src_fname = self.src_fname == 'None' and '{}.cpp'.format(ugraph.name) or self.src_fname with (model_file_dir / ugraph.name / src_fname ).open('w') as fid: + fid.write("/* Auto-generated by utensor cli */\n") fid.write(composer.compose()) @class_property @@ -137,23 +185,12 @@ def default_config(cls): config['header_fname'] = 'None' config['params_dir'] = 'data' config['model_dir'] = 'models' - config['transform_methods'] = [ - 'dropout(name_pattern=r"(dropout[_\w\d]*)/.*")', - # 'linear_reorder', - # 'quantize', - # 'conv_pool', - 'inline', - 'biasAdd', - 'remove_id_op', - 'fake_gather_v2', - # 'refcnt' - ] config['meta_data_pool_size'] = 'auto' config['ram_data_pool_size'] = 'auto' - config['save_graph'] = False return config - def _compute_meta_data_size(self, ugraph): + def _compute_meta_data_size(self, ugraph, mem_optimizer=None): + # TODO: if mem_optimizer is None, use a default mem optimizer if self.meta_data_pool_size == 'auto': # TODO: compute actual meta data size with ugraph size = 256 @@ -161,7 +198,8 @@ def _compute_meta_data_size(self, ugraph): size = self.meta_data_pool_size return size - def _compute_ram_data_size(self, ugraph): + def _compute_ram_data_size(self, ugraph, mem_optimizer=None): + # TODO: if mem_optimizer is None, use a default mem optimizer if self.ram_data_pool_size == 'auto': # TODO: compute actual ram data size with ugraph size = 256 diff --git a/utensor_cgen/backend/utensor/code_generator/rearch/_operators.py b/utensor_cgen/backend/utensor/code_generator/rearch/_operators.py deleted file mode 100644 index 9f34857a..00000000 --- a/utensor_cgen/backend/utensor/code_generator/rearch/_operators.py +++ /dev/null @@ -1,127 +0,0 @@ -from six import with_metaclass - -from utensor_cgen.backend.utensor.snippets._types import NP_TYPES_MAP -from utensor_cgen.backend.utensor.snippets.rearch import (AddOpEvalSnippet, - DeclareOpSnippet, - RomTensorSnippet) - -__all__ = ['OperatorFactory', 'OpNotSupportedError'] - - -class OpNotSupportedError(Exception): pass - - -class OperatorFactory(object): - - _operators = {} - - @classmethod - def get_opertor(cls, op_info): - op_type = op_info.op_type - op_cls = cls._operators.get(op_type) - if op_cls is None: - raise OpNotSupportedError( - '{} not supported in utensor_cgen'.format(op_type) - ) - return op_cls(op_info) - - @classmethod - def register(cls, op_cls): - cls._operators[op_cls.op_type] = op_cls - return op_cls - - @classmethod - def support_op_types(cls): - """Return the set of all supported ops - """ - return set(cls._operators.keys()) - - @classmethod - def is_supported(cls, op_type): - if op_type != 'Placeholder' and op_type not in cls._operators: - return False - return True - - -class _OperatorMeta(type): - - def __new__(mcls, name, bases, attrib): - attrib['_cache'] = {} - cls = type.__new__(mcls, name, bases, attrib) - return cls - - -class _Operator(with_metaclass(_OperatorMeta), object): - - def __new__(cls, op_info): - in_dtypes = tuple(t.dtype for t in op_info.input_tensors) - out_dtypes = tuple(t.dtype for t in op_info.output_tensors) - type_signature = (in_dtypes, out_dtypes) - if type_signature not in cls._cache: - self = object.__new__(cls) - self.in_dtypes = in_dtypes - self.out_dtypes = out_dtypes - cls._cache[type_signature] = self - return cls._cache[type_signature] - - def get_declare_snippet(self, op_var_name, **kwargs): - raise NotImplementedError( - 'base get_declare_snippet invoked: {}'.format(type(self)) - ) - - def get_eval_snippet(self, op_info, op_name, tensor_var_map, **kwargs): - raise NotImplementedError( - 'base get_eval_snippet invoked: {}'.format(type(self)) - ) - - -@OperatorFactory.register -class _AddOperator(_Operator): - - op_type = 'AddOperator' - - def get_declare_snippet(self, op_var_name): - snippet = DeclareOpSnippet( - op_type=self.op_type, - dtypes=[NP_TYPES_MAP[self.in_dtypes[0]].tensor_type_str], - op_var_name=op_var_name, - ) - return snippet - - def get_eval_snippet(self, op_info, op_name, tensor_var_map): - snippet = AddOpEvalSnippet( - op_info=op_info, - op_name=op_name, - tensor_var_map=tensor_var_map, - dtypes=[op_info.input_tensors[0].dtype] - ) - return snippet - - -@OperatorFactory.register -class _MatmulOperator(_Operator): - - op_type = 'MatrixMultOperator' - - -@OperatorFactory.register -class _ConvOperator(_Operator): - - op_type = 'ConvOperator' - - -@OperatorFactory.register -class _InlineOperator(_Operator): - - op_type = 'Inline' - - def __init__(self, op_info): - self._tensor = op_info.output_tensors[0] - - def get_declare_snippet(self, tensor_var_name, buffer_var_name, tensor): - snippet = RomTensorSnippet( - tensor_var_name=tensor_var_name, - buffer_var_name=buffer_var_name, - tensor=tensor - ) - return snippet diff --git a/utensor_cgen/backend/utensor/code_generator/rearch/_operators/__init__.py b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/__init__.py new file mode 100644 index 00000000..d5f883eb --- /dev/null +++ b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/__init__.py @@ -0,0 +1,2 @@ +from . import _impls as _ +from ._base import OperatorFactory, OpNotSupportedError diff --git a/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_base.py b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_base.py new file mode 100644 index 00000000..daf4c694 --- /dev/null +++ b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_base.py @@ -0,0 +1,107 @@ +from copy import deepcopy +from typing import Hashable + +from six import with_metaclass + +from utensor_cgen.utils import MUST_OVERWRITE, must_return_type + +__all__ = ["OperatorFactory", "OpNotSupportedError"] + + +class OpNotSupportedError(Exception): + pass + + +class OperatorFactory(object): + + _operators = {} + + @classmethod + def get_opertor(cls, op_info): + op_type = op_info.op_type + namespaces = op_info.code_gen_attributes.get('namespaces', tuple()) + op_cls = cls._operators.get((namespaces, op_type)) + if op_cls is None: + raise OpNotSupportedError( + "{}::{} not supported in utensor_cgen".format("::".join(namespaces), op_type) + ) + return op_cls(op_info) + + @classmethod + def register(cls, op_cls): + cls._operators[ + (op_cls.namespaces, op_cls.op_type) + ] = op_cls + return op_cls + + @classmethod + def support_op_types(cls): + """Return the set of all supported ops + """ + return set(cls._operators.keys()) + + @classmethod + def is_supported(cls, op_type): + if op_type != "Placeholder" and op_type not in cls._operators: + return False + return True + + +class _OperatorMeta(type): + def __new__(mcls, name, bases, attrib): + attrib["_cache"] = {} + for key in ["get_type_signature", "get_constructor_parameters"]: + func = attrib.get(key) + if func is None: + continue + if not must_return_type.return_type_is_ensured(func): + attrib[key] = must_return_type(Hashable)(func) + elif not issubclass(must_return_type.get_expect_type(func), Hashable): + raise RuntimeError( + "{}.{} must be ensured to return {}".format(name, key, Hashable) + ) + cls = type.__new__(mcls, name, bases, attrib) + return cls + + +class _Operator(with_metaclass(_OperatorMeta), object): + namespaces = tuple() + op_type = MUST_OVERWRITE + + def __new__(cls, op_info): + if cls.op_type is MUST_OVERWRITE: + raise ValueError('op_type must be overwritten: {}'.format(cls)) + + type_signature = cls.get_type_signature(op_info) + construct_params = cls.get_constructor_parameters(op_info) + full_signature = (cls.namespaces, type_signature, construct_params) + in_dtypes, out_dtypes = type_signature + if full_signature not in cls._cache: + self = object.__new__(cls) + self.in_dtypes = in_dtypes + self.out_dtypes = out_dtypes + self.construct_params = construct_params + self.op_type = op_info.op_type + cls._cache[full_signature] = self + return cls._cache[full_signature] + + @classmethod + @must_return_type(Hashable) + def get_type_signature(cls, op_info): + in_dtypes = tuple(t.dtype for t in op_info.input_tensors) + out_dtypes = tuple(t.dtype for t in op_info.output_tensors) + return (in_dtypes, out_dtypes) + + @classmethod + @must_return_type(Hashable) + def get_constructor_parameters(cls, op_info): + return tuple() + + def get_declare_snippet(self, op_var_name, tensor_var_map, **kwargs): + raise NotImplementedError( + "base get_declare_snippet invoked: {}".format(type(self)) + ) + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map, **kwargs): + raise NotImplementedError( + "base get_eval_snippet invoked: {}".format(type(self)) + ) diff --git a/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_impls.py b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_impls.py new file mode 100644 index 00000000..60105ea8 --- /dev/null +++ b/utensor_cgen/backend/utensor/code_generator/rearch/_operators/_impls.py @@ -0,0 +1,361 @@ +from typing import Hashable + +from utensor_cgen.backend.utensor.snippets._types import NP_TYPES_MAP +from utensor_cgen.backend.utensor.snippets.rearch import * +from utensor_cgen.utils import must_return_type + +from ._base import OperatorFactory, _Operator + + +@OperatorFactory.register +class _AddOperator(_Operator): + op_type = 'AddOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return AddOpEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _ReshapeOperator(_Operator): + op_type = "ReshapeOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return ReshahpeEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _MatmulOperator(_Operator): + op_type = 'MatrixMultOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return MatrixMultEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _ArgMinOperator(_Operator): + op_type = "ArgMinOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return ArgMinEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _ArgMaxOperator(_Operator): + op_type = "ArgMaxOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return ArgMaxEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _QuantizeOperator(_Operator): + op_type = "QuantizeOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.out_dtypes[0], self.in_dtypes[0]], + op_var_name=op_var_name, + nested_namespaces=['TFLM'], + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return QuantizeEvalSnippet( + op_info=op_info, + templ_dtypes=[self.out_dtypes[0], self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + nested_namespaces=['TFLM'], + ) + + +@OperatorFactory.register +class _DequantizeOperator(_Operator): + op_type = "DequantizeOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.out_dtypes[0], self.in_dtypes[0]], + op_var_name=op_var_name, + nested_namespaces=['TFLM'], + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return DequantizeEvalSnippet( + op_info=op_info, + templ_dtypes=[self.out_dtypes[0], self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + nested_namespaces=['TFLM'], + ) + + +@OperatorFactory.register +class _ReLUOperator(_Operator): + op_type = "ReLUOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return ReLUEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _ReLU6Operator(_Operator): + op_type = "ReLU6Operator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return ReLU6EvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _MinOperator(_Operator): + op_type = 'MinOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return MinEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _MaxOperator(_Operator): + op_type = 'MaxOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return MaxEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +class _PoolingOperatorMixin(object): + + @classmethod + @must_return_type(Hashable) + def get_constructor_parameters(cls, op_info): + if op_info.ugraph.lib_name == "tensorflow": + strides = op_info.op_attr['strides'].value.ints_value + ksize = op_info.op_attr['ksize'].value.ints_value[1:3] + padding = op_info.op_attr['padding'].value.decode('utf8') + elif op_info.ugraph.lib_name == 'tflite': + strides = [ + 1, + op_info.op_attr['StrideW'], + op_info.op_attr['StrideH'], + 1, + ] + ksize = [ + op_info.op_attr['FilterWidth'], + op_info.op_attr['FilterHeight'], + ] + padding = op_info.op_attr['Padding'] == 1 and "VALID" or "SAME" + else: + raise RuntimeError("dont know to to get constructor signature") + stride_str = "{{ {} }}".format(", ".join(map(str, strides))) + ksize_str = "{{ {} }}".format(", ".join(map(str, ksize))) + return (stride_str, ksize_str, padding) + + +@OperatorFactory.register +class _MaxPoolOperator(_PoolingOperatorMixin, _Operator): + op_type = 'MaxPoolOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return MaxPoolEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _MinPoolOperator(_PoolingOperatorMixin, _Operator): + op_type = 'MinPoolOperator' + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.in_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return MinPoolEvalSnippet( + op_info=op_info, + templ_dtypes=[self.in_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _QuantDWSConvOperator(_Operator): + namespaces = ('TFLM',) + op_type = "DepthwiseSeparableConvOperator" + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.out_dtypes[0]], + op_var_name=op_var_name, + nested_namespaces=self.namespaces, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return QuantDepthwiseSeperateConvOpEvalSnippet( + op_info=op_info, + templ_dtypes=[self.out_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + nested_namespaces=self.namespaces, + ) + + +@OperatorFactory.register +class _DWSConvOperator(_Operator): + op_type = "DepthwiseSeparableConvOperator" + + @classmethod + @must_return_type(Hashable) + def get_constructor_parameters(cls, op_info): + strides = [ + 1, + op_info.op_attr['StrideW'], + op_info.op_attr['StrideH'], + 1, + ] + padding = op_info.op_attr['Padding'] == 1 and "VALID" or "SAME" + strides_str = ','.join(map(str, strides)) + return ("{{ {} }}".format(strides_str), padding) + + def get_declare_snippet(self, op_var_name, tensor_var_map): + return DeclareOpSnippet( + op=self, + templ_dtypes=[self.out_dtypes[0]], + op_var_name=op_var_name, + ) + + def get_eval_snippet(self, op_var_name, op_info, tensor_var_map): + return DepthwiseSeperateConvOpEvalSnippet( + op_info=op_info, + templ_dtypes=[self.out_dtypes[0]], + op_name=op_var_name, + tensor_var_map=tensor_var_map, + ) + + +@OperatorFactory.register +class _ConvOperator(_Operator): + + op_type = 'ConvOperator' diff --git a/utensor_cgen/backend/utensor/snippets/_base.py b/utensor_cgen/backend/utensor/snippets/_base.py index 0d03add4..49daf799 100644 --- a/utensor_cgen/backend/utensor/snippets/_base.py +++ b/utensor_cgen/backend/utensor/snippets/_base.py @@ -2,7 +2,7 @@ from abc import ABCMeta from copy import deepcopy -from utensor_cgen.utils import MUST_OVERWRITEN +from utensor_cgen.utils import MUST_OVERWRITE from .template_env import env as _env @@ -11,13 +11,13 @@ class SnippetBase(object): __metaclass__ = ABCMeta - __template_name__ = MUST_OVERWRITEN - __headers__ = MUST_OVERWRITEN + __template_name__ = MUST_OVERWRITE + __headers__ = MUST_OVERWRITE def __init__(self): - if self.__template_name__ is MUST_OVERWRITEN: + if self.__template_name__ is MUST_OVERWRITE: raise ValueError('must overwrite {}.__template_name__'.format(type(self))) - if self.__headers__ is MUST_OVERWRITEN: + if self.__headers__ is MUST_OVERWRITE: raise ValueError('must overwrite {}.__headers__'.format(type(self))) if not isinstance(self.__headers__, set): raise ValueError('__headers__ should be of type set, get {}'.format(type(self.__headers__))) @@ -62,6 +62,10 @@ def __init__(self, snippets=None): self._snippets = snippets for snp in self._snippets: self.__headers__.update(snp.headers) + + @property + def snippets(self): + return self._snippets def add_snippet(self, snippet): """Add snippet into containers diff --git a/utensor_cgen/backend/utensor/snippets/_types.py b/utensor_cgen/backend/utensor/snippets/_types.py index 24f875ef..d65f720e 100644 --- a/utensor_cgen/backend/utensor/snippets/_types.py +++ b/utensor_cgen/backend/utensor/snippets/_types.py @@ -39,22 +39,42 @@ def _init(cls): if not cls._inited: _TYPE_MAP_VALUE = namedtuple("_TYPE_MAP_VALUE", ["importer_type_str", "tensor_type_str"]) cls._NP_TYPES_MAP = { - np.dtype(tf.float32.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="float", - tensor_type_str="float"), - np.dtype(tf.qint8.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="byte", - tensor_type_str="uint8_t"), - np.dtype(tf.int32.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="int", - tensor_type_str="int"), - np.dtype(tf.int64.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="int", - tensor_type_str="int"), - np.dtype(tf.quint8.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="ubyte", - tensor_type_str="uint8_t"), - np.dtype(tf.qint32.as_numpy_dtype): _TYPE_MAP_VALUE(importer_type_str="int", - tensor_type_str="int"), - np.dtype('uint16'): _TYPE_MAP_VALUE(importer_type_str="ushort", - tensor_type_str="uint16_t"), - np.dtype('int8'): _TYPE_MAP_VALUE(importer_type_str="int8", - tensor_type_str="q7_t"), + np.dtype(tf.float32.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="float", + tensor_type_str="float" + ), + np.dtype(tf.qint8.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="byte", + tensor_type_str="uint8_t" + ), + np.dtype(tf.int32.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="int", + tensor_type_str="int" + ), + np.dtype(tf.int64.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="int", + tensor_type_str="int" + ), + np.dtype(tf.quint8.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="ubyte", + tensor_type_str="uint8_t" + ), + np.dtype(tf.qint32.as_numpy_dtype): _TYPE_MAP_VALUE( + importer_type_str="int", + tensor_type_str="int" + ), + np.dtype('uint16'): _TYPE_MAP_VALUE( + importer_type_str="ushort", + tensor_type_str="uint16_t" + ), + np.dtype('int8'): _TYPE_MAP_VALUE( + importer_type_str="int8", + tensor_type_str="int8_t" #"q7_t" + ), + np.dtype('uint8'): _TYPE_MAP_VALUE( + importer_type_str="uint8", + tensor_type_str="uint8_t" + ), } cls._inited = True @@ -94,4 +114,4 @@ def _init(cls): } cls._inited = True -UTENSOR_TYPES_MAP = Numpy2uTensorTypesMap() \ No newline at end of file +UTENSOR_TYPES_MAP = Numpy2uTensorTypesMap() diff --git a/utensor_cgen/backend/utensor/snippets/legacy/_snippets.py b/utensor_cgen/backend/utensor/snippets/legacy/_snippets.py index f798012a..3387224f 100644 --- a/utensor_cgen/backend/utensor/snippets/legacy/_snippets.py +++ b/utensor_cgen/backend/utensor/snippets/legacy/_snippets.py @@ -451,6 +451,7 @@ def __init__(self, inputs, output, in_dtype, out_dtype, self.template_vars["out_dtype"] = NP_TYPES_MAP[out_dtype].tensor_type_str self.template_vars["to_eval"] = to_eval + class QuantizedReluOpSnippet(Snippet): __template_name__ = "snippets/legacy/qrelu_op.cpp" __headers__ = set(['"uTensor/ops/NnOps.hpp"']) @@ -475,6 +476,7 @@ def __init__(self, inputs, outputs, in_dtype, out_dtypes, qout_dtype, self.template_vars["to_eval"] = to_eval self.template_vars["address"] = address + class RequantizationRangeOpSnippet(Snippet): __template_name__ = "snippets/legacy/requant_range_op.cpp" __headers__ = set(['"uTensor/ops/MathOps.hpp"']) @@ -490,6 +492,8 @@ def __init__(self, inputs, outputs, out_dtype, err_msg = ("incorrect number of ref_counts and outputs: {}, {}" .format(ref_counts, outputs)) assert len(ref_counts) == len(outputs), err_msg + if address is None: + address = [] self.template_vars["inputs"] = inputs self.template_vars["outputs"] = outputs self.template_vars["out_dtype"] = NP_TYPES_MAP[out_dtype].tensor_type_str diff --git a/utensor_cgen/backend/utensor/snippets/rearch/_snippets.py b/utensor_cgen/backend/utensor/snippets/rearch/_snippets.py index 552469a6..4fd295ab 100644 --- a/utensor_cgen/backend/utensor/snippets/rearch/_snippets.py +++ b/utensor_cgen/backend/utensor/snippets/rearch/_snippets.py @@ -1,33 +1,94 @@ -from utensor_cgen.backend.utensor.snippets._types import UTENSOR_TYPES_MAP, NP_TYPES_MAP +import re + +import numpy as np + from utensor_cgen.backend.utensor.snippets._base import Snippet, SnippetBase +from utensor_cgen.backend.utensor.snippets._types import (NP_TYPES_MAP, + UTENSOR_TYPES_MAP) -__all__ = ['RomTensorSnippet', 'DeclareOpSnippet', 'AddOpEvalSnippet', 'SimpleContainer'] +__all__ = [ + "DeclareRomTensorSnippet", + "DeclareRamTensorSnippet", + "DeclareOpSnippet", + "DepthwiseSeperateConvOpEvalSnippet", + "QuantDepthwiseSeperateConvOpEvalSnippet", + "AddOpEvalSnippet", + "ReshahpeEvalSnippet", + "QuantizeEvalSnippet", + "MatrixMultEvalSnippet", + "ArgMaxEvalSnippet", + "ArgMinEvalSnippet", + "DequantizeEvalSnippet", + "ReLUEvalSnippet", + "ReLU6EvalSnippet", + "MinEvalSnippet", + "MaxEvalSnippet", + "MinPoolEvalSnippet", + "MaxPoolEvalSnippet", + "SimpleContainer", +] class _SnippetBase(Snippet): - __headers__ = set(['"uTensor/uTensor.hpp"']) + __headers__ = set(['"uTensor/uTensor.h"']) + + @staticmethod + def get_quant_param(tensor_info): + quant_params = {} + if 'quantization_zeros' in tensor_info.attributes: + zeros = tensor_info.attributes['quantization_zeros'] + scales = tensor_info.attributes["quantization_scales"] + quant_params['zero_point'] = { + 'value': zeros[0], + 'type_str': ['uint8_t', 'int8_t'][zeros.dtype == np.dtype('int8')] + } + quant_params['scale'] = { + 'value': scales[0], + 'type_str': 'float' + } + return quant_params -class RomTensorSnippet(_SnippetBase): +class DeclareRomTensorSnippet(_SnippetBase): __template_name__ = 'snippets/rearch/declare_rom_tensor.cpp' - def __init__(self, tensor_var_name, buffer_var_name, tensor): - Snippet.__init__(self) - shape = tensor.shape or [1] - self.template_vars = { - 'tensor_var_name': tensor_var_name, - 'shape': shape, - 'buffer_var_name': buffer_var_name, - 'utensor_dtype': UTENSOR_TYPES_MAP[tensor.dtype] - } + def __init__(self, tensor_info, tensor_var, buffer_var, static=False): + _SnippetBase.__init__(self) + self.template_vars['tensor_var'] = tensor_var + self.template_vars['shape'] = tensor_info.shape or [1] + self.template_vars['buffer_var'] = buffer_var + self.template_vars['static'] = static + self.template_vars['utensor_dtype'] = UTENSOR_TYPES_MAP[tensor_info.dtype] + self.template_vars['quantize_params'] = self.get_quant_param(tensor_info) + + +class DeclareRamTensorSnippet(_SnippetBase): + __template_name__ = 'snippets/rearch/declare_ram_tensor.cpp' + + def __init__(self, tensor_info, tensor_var): + _SnippetBase.__init__(self) + self.template_vars['tensor_var'] = tensor_var + self.template_vars['shape'] = tensor_info.shape or [1] + self.template_vars['utensor_dtype'] = UTENSOR_TYPES_MAP[tensor_info.dtype] + self.template_vars['quantize_params'] = self.get_quant_param(tensor_info) class DeclareOpSnippet(_SnippetBase): __template_name__ = 'snippets/rearch/declare_op.cpp' - def __init__(self, op_type, dtypes, op_var_name): - Snippet.__init__(self) + def __init__(self, op, templ_dtypes, op_var_name, nested_namespaces=None): + _SnippetBase.__init__(self) + if nested_namespaces is None: + nested_namespaces = [] + else: + nested_namespaces = list(nested_namespaces) + op_type = op.op_type + if templ_dtypes: + templ_params = ', '.join([NP_TYPES_MAP[dtype].tensor_type_str for dtype in templ_dtypes]) + op_type = '{}<{}>'.format(op_type, templ_params) + if nested_namespaces: + op_type = "::".join(nested_namespaces + [op_type]) self.template_vars['op_type'] = op_type - self.template_vars['dtypes'] = dtypes + self.template_vars['construct_params'] = op.construct_params self.template_vars['op_var_name'] = op_var_name @@ -35,10 +96,13 @@ class OpEvalSnippet(_SnippetBase): __template_name__ = 'snippets/rearch/eval_op.cpp' __inputs__ = [] __outputs__ = [] - - def __init__(self, op_info, op_name, tensor_var_map, dtypes): + def __init__(self, op_info, templ_dtypes, op_name, tensor_var_map, nested_namespaces=None): Snippet.__init__(self) + if nested_namespaces is None: + nested_namespaces = [] + else: + nested_namespaces = list(nested_namespaces) input_map = { name: tensor_var_map[tensor.name] for name, tensor in zip(self.__inputs__, op_info.input_tensors) @@ -47,25 +111,65 @@ def __init__(self, op_info, op_name, tensor_var_map, dtypes): name: tensor_var_map[tensor.name] for name, tensor in zip(self.__outputs__, op_info.output_tensors) } - out_shapes_map = { - tensor_var_map[tensor.name]: tensor.shape or [1] - for tensor in op_info.output_tensors - } - out_dtypes_map = { - tensor_var_map[tensor.name]: UTENSOR_TYPES_MAP[tensor.dtype] - for tensor in op_info.output_tensors - } - utensor_dtypes = [NP_TYPES_MAP[dtype].tensor_type_str for dtype in dtypes] - if utensor_dtypes: - op_type = '{}<{}>'.format(op_info.op_type, ', '.join(utensor_dtypes)) + quantize_params_map = {} + for tensor_info in op_info.output_tensors: + quant_param = self.get_quant_param(tensor_info) + if quant_param: + tensor_var = tensor_var_map[tensor_info.name] + quantize_params_map[tensor_var] = quant_param + if templ_dtypes: + templ_params = ', '.join([NP_TYPES_MAP[dtype].tensor_type_str for dtype in templ_dtypes]) + op_type = '{}<{}>'.format(op_info.op_type, templ_params) else: op_type = op_info.op_type + if nested_namespaces: + op_type = "::".join(nested_namespaces + [op_type]) self.template_vars['op_type'] = op_type - self.template_vars['op_name'] = op_name + self.template_vars['op_var_name'] = op_name self.template_vars['input_map'] = input_map self.template_vars['output_map'] = output_map - self.template_vars['out_shapes_map'] = out_shapes_map - self.template_vars['out_dtypes_map'] = out_dtypes_map + self.template_vars['quantize_params_map'] = quantize_params_map + + +class DepthwiseSeperateConvOpEvalSnippet(OpEvalSnippet): + __inputs__ = ["in", "depthwise_filter", "pointwise_filter"] + __outputs__ = ["out"] + + +class QuantDepthwiseSeperateConvOpEvalSnippet(OpEvalSnippet): + __template_name__ = 'snippets/rearch/eval_quant_dws_conv_op.cpp' + __inputs__ = ["in", "filter", "bias"] + __outputs__ = ["out"] + + _PADDING_MAP = { + 0: "TFLM::TfLitePadding::kTfLitePaddingUnknown", + 1: "TFLM::TfLitePadding::kTfLitePaddingSame", + 2: "TFLM::TfLitePadding::kTfLitePaddingValid" + } + _ACTIVATION_MAP = { + '0': 'kTfLiteActNone', + '1': 'kTfLiteActRelu', + '2': 'kTfLiteActRelu1', + '3': 'kTfLiteActRelu6', + '4': 'kTfLiteActTanh', + '5': 'kTfLiteActSignBit', + '6': 'kTfLiteActSigmoid', + } + _ACTIVATION_STR_PATTERN = re.compile(r'^(\d+) \(\w+\)$') + + def __init__(self, op_info, templ_dtypes, op_name, tensor_var_map, nested_namespaces=None): + OpEvalSnippet.__init__(self, op_info, templ_dtypes, op_name, tensor_var_map, nested_namespaces) + cls = type(self) + self.template_vars['padding'] = cls._PADDING_MAP[op_info.op_attr['Padding']] + self.template_vars['stride_width'] = op_info.op_attr['StrideW'] + self.template_vars['stride_height'] = op_info.op_attr['StrideH'] + self.template_vars['depth_multiplier'] = op_info.op_attr['DepthMultiplier'] + activation_idx = cls._ACTIVATION_STR_PATTERN.match( + op_info.op_attr['FusedActivationFunction'] + ).group(1) + self.template_vars['activation'] = cls._ACTIVATION_MAP[activation_idx] + self.template_vars['dilation_width_factor'] = op_info.op_attr['DilationWFactor'] + self.template_vars['dilation_height_factor'] = op_info.op_attr['DilationHFactor'] class AddOpEvalSnippet(OpEvalSnippet): @@ -73,30 +177,90 @@ class AddOpEvalSnippet(OpEvalSnippet): __outputs__ = ['c'] +class ReshahpeEvalSnippet(OpEvalSnippet): + __inputs__ = ["input"] + __outputs__ = ["output"] + + +class QuantizeEvalSnippet(OpEvalSnippet): + __inputs__ = ["input"] + __outputs__ = ["output"] + + +class MatrixMultEvalSnippet(OpEvalSnippet): + __inputs__ = ["a", "b"] + __outputs__ = ["c"] + + +class ArgMaxEvalSnippet(OpEvalSnippet): + __inputs__ = ["input"] + __outputs__ = ["output"] + + +class ArgMinEvalSnippet(OpEvalSnippet): + __inputs__ = ["input"] + __outputs__ = ["output"] + + +class DequantizeEvalSnippet(OpEvalSnippet): + __inputs__ = ["a"] + __outputs__ = ["b"] + + +class ReLUEvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + +class ReLU6EvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + +class MinEvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + +class MaxEvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + +class MinPoolEvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + +class MaxPoolEvalSnippet(OpEvalSnippet): + __inputs__ = ["in"] + __outputs__ = ["out"] + + class SimpleContainer(SnippetBase): __headers__ = set(['"uTensor/uTensor.hpp"', ""]) __template_name__ = 'containers/rearch/simple.cpp' - def __init__(self, declare_snippets=None, eval_snippests=None): - if declare_snippets is None: - declare_snippets = [] - if eval_snippests is None: - eval_snippests = [] + def __init__(self): SnippetBase.__init__(self) - self._declare_snippets = [] + self._declare_local_snippets = [] + self._declare_global_snippets = [] self._eval_snippests = [] - for snp in declare_snippets: - self.add_declare_snippet(snp) - for snp in eval_snippests: - self.add_eval_snippet(snp) - - def add_declare_snippet(self, snippet): - self.__headers__.update(snippet.headers) - self._declare_snippets.append(snippet) + + def add_declare_global_snippets(self, *snippets): + for snippet in snippets: + self.__headers__.update(snippet.headers) + self._declare_global_snippets.append(snippet) - def add_eval_snippet(self, snippet): - self.__headers__.update(snippet.headers) - self._eval_snippests.append(snippet) + def add_declare_local_snippets(self, *snippets): + for snippet in snippets: + self.__headers__.update(snippet.headers) + self._declare_local_snippets.append(snippet) + + def add_eval_snippets(self, *snippets): + for snippet in snippets: + self.__headers__.update(snippet.headers) + self._eval_snippests.append(snippet) def add_header(self, header, *headers): self._add_header(header) @@ -111,7 +275,8 @@ def _add_header(self, header): def render(self): return self.template.render( - declare_snippets=self._declare_snippets, + declare_global_snippets=self._declare_global_snippets, + declare_local_snippets=self._declare_local_snippets, eval_snippets=self._eval_snippests, **self.template_vars ) diff --git a/utensor_cgen/backend/utensor/snippets/templates/containers/rearch/simple.cpp b/utensor_cgen/backend/utensor/snippets/templates/containers/rearch/simple.cpp index fb1c60c3..28e2f197 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/containers/rearch/simple.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/containers/rearch/simple.cpp @@ -1,19 +1,30 @@ using namespace uTensor; -{%for snippet in declare_snippets%} +static const localCircularArenaAllocator<{{meta_data_pool_size}}> meta_allocator; +static const localCircularArenaAllocator<{{ram_data_pool_size}}> ram_allocator; + +{#ex: rom tensors, ops..etc#} +// start rendering global declare snippets +{%for snippet in declare_global_snippets%} {{snippet.render()}} {%endfor%} -localCircularArenaAllocator<{{meta_data_pool_size}}> meta_allocator; -localCircularArenaAllocator<{{ram_data_pool_size}}> ram_allocator; +// end of rendering global declare snippets void compute_{{model_name}}({%for pl in placeholders%}Tensor& {{pl}}, {%endfor%}std::vector& outputs){ Context::get_default_context()->set_metadata_allocator(&meta_allocator); Context::get_default_context()->set_ram_data_allocator(&ram_allocator); - + {#ex: ram tensors#} + // start rendering local declare snippets + {%for snippet in declare_local_snippets%} + {{snippet.render()}} + {%endfor%} + // end of rendering local declare snippets + // start rendering eval snippets {%for snippet in eval_snippets%} {{snippet.render()}} {%endfor%} + // end of rendering eval snippets {%for out_var in out_tensor_var_names%} outputs.push_back({{out_var}}); {%endfor%} diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/argmax_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/argmax_op.cpp index 07073749..0e4ebe0f 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/argmax_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/argmax_op.cpp @@ -3,9 +3,9 @@ S_TENSOR {{sptr_name}}; {% endif %} { {% if ref_count %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}", {{ref_count}}); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{output}}", {{ref_count}}); {% else %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}"); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{output}}"); {% endif %} ctx.push(new ArgMaxOp<{{in_dtype}}, {{out_dtype}}>(), { {% for tname in inputs[:-1]%}"{{tname}}", {%endfor%}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/dequantize_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/dequantize_op.cpp index 9ee9f8e4..58882518 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/dequantize_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/dequantize_op.cpp @@ -3,9 +3,9 @@ S_TENSOR {{sptr_name}}; {% endif %} { {% if ref_count %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}", {{ref_count}}); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{output}}", {{ref_count}}); {% else %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}"); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{output}}"); {% endif %} ctx.push(new DequantizeOp(), { {% for tname in inputs[:-1]%}"{{tname}}", {%endfor%}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/max_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/max_op.cpp index 5ebb3fd0..356a25cd 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/max_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/max_op.cpp @@ -4,9 +4,9 @@ S_TENSOR {{sptr_name}}; { RamTensor<{{out_dtype}}>* out_tensor; {%if out_shape %} - out_tensor = new RamTensor<{{out_dtype}}>({ {%for dim in out_shape[:-1]%}{{dim}}, {%endfor%}{{out_shape[-1]}} }{%if address %}, {{address[0]}}{%endif%}); + out_tensor = new RamTensor<{{out_dtype}}>({ {%for dim in out_shape[:-1]%}{{dim}}, {%endfor%}{{out_shape[-1]}} }); {%else%} - out_tensor = new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}); + out_tensor = new RamTensor<{{out_dtype}}>(); {%endif%} {%if ref_count %} ctx.add(out_tensor, "{{output}}", {{ref_count}}); diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/min_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/min_op.cpp index c6272255..4bc2aab4 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/min_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/min_op.cpp @@ -4,9 +4,9 @@ S_TENSOR {{sptr_name}}; { RamTensor<{{out_dtype}}>* out_tensor; {% if out_shape %} - out_tensor = new RamTensor<{{out_dtype}}>({ {%for shape in out_shape[:-1]%}{{shape}}, {%endfor%}{{out_shape[-1]}} }{%if address %}, {{address[0]}}{%endif%}); + out_tensor = new RamTensor<{{out_dtype}}>({ {%for shape in out_shape[:-1]%}{{shape}}, {%endfor%}{{out_shape[-1]}} }); {% else %} - out_tensor = new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}); + out_tensor = new RamTensor<{{out_dtype}}>(); {% endif %} {% if ref_count%} ctx.add(out_tensor, "{{output}}", {{ref_count}}); diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qadd_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qadd_op.cpp index 47a3aadc..9a33c897 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qadd_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qadd_op.cpp @@ -3,13 +3,13 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {% if ref_counts %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}", {{ref_counts[2]}}); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor({1}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor({1}), "{{outputs[2]}}", {{ref_counts[2]}}); {% else %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}"); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}"); + ctx.add(new RamTensor({1}), "{{outputs[1]}}"); + ctx.add(new RamTensor({1}), "{{outputs[2]}}"); {% endif %} ctx.push(new QuantizedAddOp<{{x_dtype}}, {{w_dtype}}, {{out_dtype}}>(), { {%for tname in inputs[:-1] %}"{{tname}}", {% endfor %} "{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qmatmul_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qmatmul_op.cpp index b09788cb..bf8320d1 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qmatmul_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qmatmul_op.cpp @@ -3,13 +3,13 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {% if ref_counts %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}", {{ref_counts[2]}}); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor({1}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor({1}), "{{outputs[2]}}", {{ref_counts[2]}}); {% else %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}"); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}"); + ctx.add(new RamTensor({1}), "{{outputs[1]}}"); + ctx.add(new RamTensor({1}), "{{outputs[2]}}"); {% endif %} ctx.push(new QntMatMulOp<{{x_dtype}}, {{w_dtype}}, {{out_dtype}}>(), { {%for tname in inputs[:-1] %}"{{tname}}", {% endfor %} "{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qrelu_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qrelu_op.cpp index b4516e43..cac38f62 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qrelu_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/qrelu_op.cpp @@ -3,13 +3,13 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {%if ref_counts%} - ctx.add(new RamTensor<{{qout_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor<{{out_dtypes[0]}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); - ctx.add(new RamTensor<{{out_dtypes[1]}}>({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}", {{ref_counts[2]}}); + ctx.add(new RamTensor<{{qout_dtype}}>(), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor<{{out_dtypes[0]}}>({1}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor<{{out_dtypes[1]}}>({1}), "{{outputs[2]}}", {{ref_counts[2]}}); {%else%} - ctx.add(new RamTensor<{{qout_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor<{{out_dtypes[0]}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); - ctx.add(new RamTensor<{{out_dtypes[1]}}>({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}"); + ctx.add(new RamTensor<{{qout_dtype}}>(), "{{outputs[0]}}"); + ctx.add(new RamTensor<{{out_dtypes[0]}}>({1}), "{{outputs[1]}}"); + ctx.add(new RamTensor<{{out_dtypes[1]}}>({1}), "{{outputs[2]}}"); {%endif%} ctx.push(new QuantizedReluOp<{{in_dtype}}, {{out_dtypes[0]}}, {{qout_dtype}}>(), { {% for tname in inputs[:-1]%}"{{tname}}", {% endfor %}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/quantV2_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/quantV2_op.cpp index e5ca1067..6f55c1a4 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/quantV2_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/quantV2_op.cpp @@ -3,13 +3,13 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {% if ref_counts%} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}", {{ref_counts[2]}}); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor({1}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor({1}), "{{outputs[2]}}", {{ref_counts[2]}}); {% else %} - ctx.add(new RamTensor<{{out_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); - ctx.add(new RamTensor({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}"); + ctx.add(new RamTensor<{{out_dtype}}>(), "{{outputs[0]}}"); + ctx.add(new RamTensor({1}), "{{outputs[1]}}"); + ctx.add(new RamTensor({1}), "{{outputs[2]}}"); {% endif %} ctx.push(new QuantizeV2Op(), { {% for tname in inputs[:-1]%} "{{tname}}", {% endfor %}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_op.cpp index e2072e93..4bac1905 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_op.cpp @@ -3,13 +3,13 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {%if ref_counts%} - ctx.add(new RamTensor<{{qout_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor<{{range_dtype}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); - ctx.add(new RamTensor<{{range_dtype}}>({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}", {{ref_counts[2]}}); + ctx.add(new RamTensor<{{qout_dtype}}>(), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor<{{range_dtype}}>({1}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor<{{range_dtype}}>({1}), "{{outputs[2]}}", {{ref_counts[2]}}); {%else%} - ctx.add(new RamTensor<{{qout_dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor<{{range_dtype}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); - ctx.add(new RamTensor<{{range_dtype}}>({1}{%if address %}, {{address[2]}}{%endif%}), "{{outputs[2]}}"); + ctx.add(new RamTensor<{{qout_dtype}}>(), "{{outputs[0]}}"); + ctx.add(new RamTensor<{{range_dtype}}>({1}), "{{outputs[1]}}"); + ctx.add(new RamTensor<{{range_dtype}}>({1}), "{{outputs[2]}}"); {%endif%} ctx.push(new RequantizeOp(), { {% for tname in inputs[:-1]%}"{{tname}}", {% endfor %}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_range_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_range_op.cpp index 422a59ad..97e126cf 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_range_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/requant_range_op.cpp @@ -3,11 +3,11 @@ S_TENSOR {%for sptr_name in sptr_names[:-1]%}{{sptr_name}}, {%endfor%} {{sptr_na {% endif %} { {%if ref_counts%} - ctx.add(new RamTensor<{{out_dtype}}>({1}{%if address %}, {{address[0]}}{%endif%}), "{{outputs[0]}}", {{ref_counts[0]}}); - ctx.add(new RamTensor<{{out_dtype}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}", {{ref_counts[1]}}); + ctx.add(new RamTensor<{{out_dtype}}>({1}), "{{outputs[0]}}", {{ref_counts[0]}}); + ctx.add(new RamTensor<{{out_dtype}}>({1}), "{{outputs[1]}}", {{ref_counts[1]}}); {%else%} - ctx.add(new RamTensor<{{out_dtype}}>({1}{%if address %}, {{address[0]}}{%endif%}), "{{outputs[0]}}"); - ctx.add(new RamTensor<{{out_dtype}}>({1}{%if address %}, {{address[1]}}{%endif%}), "{{outputs[1]}}"); + ctx.add(new RamTensor<{{out_dtype}}>({1}), "{{outputs[0]}}"); + ctx.add(new RamTensor<{{out_dtype}}>({1}), "{{outputs[1]}}"); {%endif%} ctx.push(new Requantization_RangeOp(), { {%for tname in inputs[:-1]%}"{{tname}}", {% endfor %}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/reshape_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/reshape_op.cpp index 3cf06fd9..092a5f3e 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/reshape_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/legacy/reshape_op.cpp @@ -3,9 +3,9 @@ S_TENSOR {{sptr_name}}; {% endif %} { {% if ref_count %} - ctx.add(new RamTensor<{{dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}", {{ref_count}}); + ctx.add(new RamTensor<{{dtype}}>(), "{{output}}", {{ref_count}}); {% else %} - ctx.add(new RamTensor<{{dtype}}>({%if address %}{{address[0]}}{%endif%}), "{{output}}"); + ctx.add(new RamTensor<{{dtype}}>(), "{{output}}"); {% endif %} ctx.push(new ReshapeOp(), { {% for tname in inputs[:-1]%}"{{tname}}", {%endfor%}"{{inputs[-1]}}" }, diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_op.cpp index 4fc8112d..0236957e 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_op.cpp @@ -1,5 +1,5 @@ -{%if dtypes %} -{{op_type}}<{%for t in dtypes[:-1]%}{{t}},{%endfor%}{{dtypes[-1]}}> {{op_var_name}}; +{%if construct_params%} +{{op_type}} {{op_var_name}}({%for param in construct_params%}{{param}}{%if not loop.last%}, {%endif%}{%endfor%}); {%else%} {{op_type}} {{op_var_name}}; {%endif%} \ No newline at end of file diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_ram_tensor.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_ram_tensor.cpp new file mode 100644 index 00000000..90570e56 --- /dev/null +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_ram_tensor.cpp @@ -0,0 +1,9 @@ +{%if shape%} +Tensor {{tensor_var}} = new RamTensor({ {%for s in shape%}{{s}}{%if not loop.last%}, {%endif%}{%endfor%} }, {{utensor_dtype}}); +{%else%} +Tensor {{tensor_var}} = new RamTensor({{utensor_dtype}}); +{%endif%} +{%if quantize_params%} +{{quantize_params["zero_point"]["type_str"]}} {{tensor_var_name}}_zp; +{{quantize_params["scale"]["type_str"]}} {{tensor_var_name}}_scale; +{%endif%} \ No newline at end of file diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_rom_tensor.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_rom_tensor.cpp index 7aea2018..6b8c06ad 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_rom_tensor.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/declare_rom_tensor.cpp @@ -1 +1,5 @@ -Tensor {{tensor_var_name}} = new RomTensor({ {%for s in shape[:-1]%}{{s}}, {%endfor%}{{shape[-1]}} }, {{utensor_dtype}}, {{buffer_var_name}}); \ No newline at end of file +{%if static %}static {%endif%}Tensor {{tensor_var}} = new RomTensor({ {%for s in shape%}{{s}}{%if not loop.last%}, {%endif%}{%endfor%} }, {{utensor_dtype}}, {{buffer_var}}); +{%if quantize_params%} +{{quantize_params["zero_point"]["type_str"]}} {{tensor_var_name}}_zp; +{{quantize_params["scale"]["type_str"]}} {{tensor_var_name}}_scale; +{%endif%} \ No newline at end of file diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_op.cpp index 6915a007..556b56e8 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_op.cpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_op.cpp @@ -1,15 +1,20 @@ -{%for tensor_var, shape in out_shapes_map.items()%} -Tensor {{tensor_var}} = new RamTensor({ {%for s in shape[:-1]%}{{s}}, {%endfor%}{{shape[-1]}} }, {{out_dtypes_map[tensor_var]}}); +{%if quantize_params_map%} +{%for tensor_var in output_map.values()%} +{%if tensor_var in quantize_params_map%} +{{tensor_var}}_zp = {{quantize_params_map[tensor_var]["zero_point"]["value"]}}; +{{tensor_var}}_scale = {{quantize_params_map[tensor_var]["scale"]["value"]}}; +{%endif%} {%endfor%} - {{op_name}} - .set_inputs({ -{%for name, tensor_var in input_map.items()%} - { {{op_type}}::{{name}}, {{tensor_var}} }, -{%endfor%} - }) - .set_outputs({ -{%for name, tensor_var in output_map.items()%} - { {{op_type}}::{{name}}, {{tensor_var}}} -{%endfor%} - }) - .eval(); +{%endif%} +{{op_var_name}} + .set_inputs({ + {%for name, tensor_var in input_map.items()%} + { {{op_type}}::{{name}}, {{tensor_var}} }, + {%endfor%} + }) + .set_outputs({ + {%for name, tensor_var in output_map.items()%} + { {{op_type}}::{{name}}, {{tensor_var}}} + {%endfor%} + }) + .eval(); diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_quant_dws_conv_op.cpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_quant_dws_conv_op.cpp new file mode 100644 index 00000000..3a7d3ea3 --- /dev/null +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/eval_quant_dws_conv_op.cpp @@ -0,0 +1,31 @@ +{%if quantize_params_map%} +{%for tensor_var in output_map.values()%} +{%if tensor_var in quantize_params_map%} +{{tensor_var}}_zp = {{quantize_params_map[tensor_var]["zero_point"]["value"]}}; +{{tensor_var}}_scale = {{quantize_params_map[tensor_var]["scale"]["value"]}}; +{%endif%} +{%endfor%} +{%endif%} +{ + TFLM::TfLiteDepthwiseConvParams dws_params; + dws_params.padding = {{padding}}; + dws_params.stride_width = {{stride_width}}; + dws_params.stride_height = {{stride_height}}; + dws_params.depth_multiplier = {{depth_multiplier}}; + dws_params.activation = TFLM::{{activation}}; + dws_params.dilation_width_factor = {{dilation_width_factor}}; + dws_params.dilation_height_factor = {{dilation_height_factor}}; + {{op_var_name}}.set_params(dws_params); + } + {{op_var_name}} + .set_inputs({ + {%for name, tensor_var in input_map.items()%} + { {{op_type}}::{{name}}, {{tensor_var}} }, + {%endfor%} + }) + .set_outputs({ + {%for name, tensor_var in output_map.items()%} + { {{op_type}}::{{name}}, {{tensor_var}}} + {%endfor%} + }) + .eval(); diff --git a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/simple.hpp b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/simple.hpp index 90e379a8..cc24e474 100644 --- a/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/simple.hpp +++ b/utensor_cgen/backend/utensor/snippets/templates/snippets/rearch/simple.hpp @@ -4,6 +4,6 @@ #include #include "uTensor/core/tensor.hpp" -void compute_{{model_name}}({%for pl in placeholders%}utensor::Tensor& {{pl}}, {%endfor%}std::vector& outputs); +void compute_{{model_name}}({%for pl in placeholders%}uTensor::Tensor& {{pl}}, {%endfor%}std::vector& outputs); #endif // __{{model_name.upper()}}_H \ No newline at end of file diff --git a/utensor_cgen/cli.py b/utensor_cgen/cli.py deleted file mode 100644 index 74cbeead..00000000 --- a/utensor_cgen/cli.py +++ /dev/null @@ -1,212 +0,0 @@ -#-*- coding:utf8 -*- -import importlib -import os -import re -import sys -from pathlib import Path - -import click -from toml import dumps, loads - -from utensor_cgen import __version__ -from utensor_cgen.backend.api import BackendManager -from utensor_cgen.utils import NArgsParam - - -def _get_pb_model_name(path): - return os.path.basename(os.path.splitext(path)[0]) - -def _load_plugin(path): - path = Path(path) - if not path.exists(): - raise RuntimeError('{} does not exists'.format(path)) - sys.path.insert(0, str(path.parent)) - mod_name = re.sub(r'.py$', '', path.name.split()[0]) - importlib.import_module(mod_name) - sys.path.pop(0) - -@click.group(name='utensor-cli') -@click.help_option('-h', '--help') -@click.version_option( - __version__, - '-V', '--version' -) -@click.option( - "-p", - "--plugin", - multiple=True, - help="path of the python module which will be loaded as plugin", - metavar="MODULE", -) -def cli(plugin): - for module in plugin: - _load_plugin(module) - -@cli.command(name='list-backends', help='list all available backends') -@click.help_option('-h', '--help') -def list_backends(): - click.secho('Available backends:', fg='green', bold=True) - for backend in BackendManager.backends: - click.secho( - ' - {}'.format(backend), fg='green' - ) - -@cli.command(name='generate-config', help='generate config toml file') -@click.help_option('-h', '--help') -@click.option('--target', required=True, help='target framework/platform') -@click.option('-o', '--output', default='utensor_cli.toml', metavar='CONFIG.toml', help='the output config file name') -def generate_config(target, output): - backend_cls = BackendManager.get_backend(target) - config = backend_cls.default_config - click.secho( - 'generating config file: {}'.format(output), - fg='white', - bold=True, - ) - with open(output, 'w') as fid: - fid.write( - '# https://github.com/toml-lang/toml\n' - '# ..\n' - ) - fid.write(dumps(config)) - -@cli.command(name='convert', help='convert graph to cpp/hpp files') -@click.help_option('-h', '--help') -@click.argument( - 'model_file', - required=True, - metavar='MODEL_FILE.{pb,onnx,pkl}', -) -@click.option( - '--output-nodes', - type=NArgsParam(), - metavar="NODE_NAME,NODE_NAME,...", - required=True, - help="list of output nodes" -) -@click.option('--config', default='utensor_cli.toml', show_default=True, metavar='CONFIG.toml') -@click.option('--target', - default='utensor', - show_default=True, - help='target framework/platform' -) -def convert_graph(model_file, output_nodes, config, target): - from utensor_cgen.frontend import FrontendSelector - - if os.path.exists(config): - with open(config) as fid: - config = loads(fid.read()) - else: - config = {} - ugraph = FrontendSelector.parse(model_file, output_nodes, config) - backend = BackendManager.get_backend(target)(config) - backend.apply(ugraph) - -@cli.command(name='list-trans-methods', help='list all available graph transformation') -@click.help_option('-h', '--help') -@click.option('--verbose', is_flag=True) -def list_trans_methods(verbose): - from utensor_cgen.transformer import TransformerPipeline - - if verbose: - for name, trans_cls in TransformerPipeline.TRANSFORMER_MAP.items(): - click.secho(name, fg='white', bold=True) - click.secho(trans_cls.__doc__, fg='yellow', bold=True) - else: - click.secho( - str(TransformerPipeline.all_transform_methods()), - fg='white', bold=True - ) - return 0 - -@cli.command(name='show', help='show node names in the pb file') -@click.help_option('-h', '--help') -@click.option('--oneline', is_flag=True, - help='show in oneline format (no detail information)') -@click.option('--ignore-unknown-op', is_flag=True, - help='ignore unknown/unsupported ops') -@click.option("--output-nodes", - type=NArgsParam(), - metavar="NODE_NAME,NODE_NAME,...", - help="list of output nodes") -@click.argument('model_file', required=True, metavar='MODEL.{pb,pkl}') -def show_graph(model_file, **kwargs): - import pickle - from utensor_cgen.frontend import FrontendSelector - _, ext = os.path.splitext(model_file) - output_nodes = kwargs.pop('output_nodes') - - if ext == '.pkl': - with open(model_file, 'rb') as fid: - ugraph = pickle.load(fid) - _show_ugraph(ugraph, **kwargs) - return 0 - - try: - parser = FrontendSelector.select_parser(ext) - ugraph = parser.parse(model_file, output_nodes) - _show_ugraph(ugraph, **kwargs) - return 0 - except RuntimeError as err: - msg = err.args[0] - click.secho(msg, fg='red', bold=True) - return 1 - -def _show_ugraph(ugraph, oneline=False, ignore_unknown_op=False): - import textwrap - from utensor_cgen.backend.utensor.code_generator.legacy._operators import OperatorFactory - - unknown_ops = set([]) - if oneline: - tmpl = click.style("{op_name} ", fg='yellow', bold=True) + \ - "op_type: {op_type}, inputs: {inputs}, outputs: {outputs}" - for op_name in ugraph.topo_order: - op_info = ugraph.ops_info[op_name] - msg = tmpl.format(op_name=op_name, op_type=op_info.op_type, - inputs=[tensor.name for tensor in op_info.input_tensors], - outputs=[tensor.name for tensor in op_info.output_tensors]) - click.echo(msg) - if not OperatorFactory.is_supported(op_info.op_type): - unknown_ops.add(op_info) - else: - tmpl = click.style('op_name: {op_name}\n', fg='yellow', bold=True) + \ - '''\ - op_type: {op_type} - input(s): - {inputs} - {input_shapes} - ouptut(s): - {outputs} - {output_shapes} - ''' - tmpl = textwrap.dedent(tmpl) - paragraphs = [] - for op_name in ugraph.topo_order: - op_info = ugraph.ops_info[op_name] - op_str = tmpl.format( - op_name=op_name, - op_type=op_info.op_type, - inputs=op_info.input_tensors, - outputs=op_info.output_tensors, - input_shapes=[tensor.shape for tensor in op_info.input_tensors], - output_shapes=[tensor.shape for tensor in op_info.output_tensors]) - paragraphs.append(op_str) - if not OperatorFactory.is_supported(op_info.op_type): - unknown_ops.add(op_info) - click.echo('\n'.join(paragraphs)) - click.secho( - 'topological ordered ops: {}'.format(ugraph.topo_order), - fg='white', bold=True, - ) - if unknown_ops and not ignore_unknown_op: - click.echo( - click.style('Unknown Ops Detected', fg='red', bold=True) - ) - for op_info in unknown_ops: - click.echo( - click.style(' {}: {}'.format(op_info.name, op_info.op_type), fg='red') - ) - return 0 - -if __name__ == '__main__': - cli() diff --git a/utensor_cgen/cli/__init__.py b/utensor_cgen/cli/__init__.py new file mode 100644 index 00000000..f91c785f --- /dev/null +++ b/utensor_cgen/cli/__init__.py @@ -0,0 +1,4 @@ +from . import backend as _ +from . import convert as _ +from . import utils as _ +from .main import cli diff --git a/utensor_cgen/cli/backend.py b/utensor_cgen/cli/backend.py new file mode 100644 index 00000000..9f965dd5 --- /dev/null +++ b/utensor_cgen/cli/backend.py @@ -0,0 +1,52 @@ +import click + +from utensor_cgen import __version__ +from utensor_cgen.api.backend import generate_config as _generate_config +from utensor_cgen.api.backend import get_backends, get_trans_methods +from utensor_cgen.backend.api import BackendManager + +from .main import cli + + +@cli.command(name='list-backends', help='list all available backends') +@click.help_option('-h', '--help') +def list_backends(): + backends = get_backends() + click.secho('Available backends:', fg='green', bold=True) + for backend in backends: + click.secho( + ' - {}'.format(backend), fg='green' + ) + return 0 + +@cli.command(name='list-trans-methods', help='list all available graph transformation') +@click.help_option('-h', '--help') +@click.option('--verbose', is_flag=True) +def list_trans_methods(verbose): + from pprint import pformat + + trans_methods = get_trans_methods() + + if verbose: + for name, trans_cls in trans_methods.items(): + click.secho(name, fg='white', bold=True) + click.secho(trans_cls.__doc__, fg='yellow', bold=True) + else: + click.secho( + pformat(list(trans_methods.keys())), + fg='white', bold=True + ) + return 0 + +@cli.command(name='generate-config', help='generate config toml file') +@click.help_option('-h', '--help') +@click.option('--target', required=True, help='target framework/platform') +@click.option('-o', '--output', default='utensor_cli.toml', metavar='CONFIG.toml', help='the output config file name') +def generate_config(target, output): + _generate_config(target, output) + click.secho( + 'config file generated: {}'.format(output), + fg='white', + bold=True, + ) + return 0 diff --git a/utensor_cgen/cli/convert.py b/utensor_cgen/cli/convert.py new file mode 100644 index 00000000..7a00ec9c --- /dev/null +++ b/utensor_cgen/cli/convert.py @@ -0,0 +1,37 @@ +import os + +import click + +from utensor_cgen.api.convert import convert_graph as _convert_graph +from utensor_cgen.utils import NArgsParam + +from .main import cli + + +@cli.command(name='convert', help='convert graph to cpp/hpp files') +@click.help_option('-h', '--help') +@click.argument( + 'model_file', + required=True, + metavar='MODEL_FILE.{pb,onnx,pkl}', +) +@click.option( + '--output-nodes', + type=NArgsParam(), + metavar="NODE_NAME,NODE_NAME,...", + help="list of output nodes" +) +@click.option('--config', default='utensor_cli.toml', show_default=True, metavar='CONFIG.toml') +@click.option('--target', + default='utensor', + show_default=True, + help='target framework/platform' +) +def convert_graph(model_file, output_nodes, config, target): + _convert_graph( + model_file=model_file, + output_nodes=output_nodes, + config=config, + target=target + ) + return 0 diff --git a/utensor_cgen/cli/main.py b/utensor_cgen/cli/main.py new file mode 100644 index 00000000..9d27f342 --- /dev/null +++ b/utensor_cgen/cli/main.py @@ -0,0 +1,36 @@ +#-*- coding:utf8 -*- +import importlib +import re +import sys +from pathlib import Path + +import click + +from utensor_cgen import __version__ + + +def _load_plugin(path): + path = Path(path) + if not path.exists(): + raise RuntimeError('{} does not exists'.format(path)) + sys.path.insert(0, str(path.parent)) + mod_name = re.sub(r'.py$', '', path.name.split()[0]) + importlib.import_module(mod_name) + sys.path.pop(0) + +@click.group(name='utensor-cli') +@click.help_option('-h', '--help') +@click.version_option( + __version__, + '-V', '--version' +) +@click.option( + "-p", + "--plugin", + multiple=True, + help="path of the python module which will be loaded as plugin (multiple)", + metavar="MODULE", +) +def cli(plugin): + for module_path in plugin: + _load_plugin(module_path) diff --git a/utensor_cgen/cli/utils.py b/utensor_cgen/cli/utils.py new file mode 100644 index 00000000..d5aa78db --- /dev/null +++ b/utensor_cgen/cli/utils.py @@ -0,0 +1,49 @@ +import os + +import click +from toml import loads + +from utensor_cgen.api.utils import show_ugraph as _show_ugraph +from utensor_cgen.utils import NArgsParam + +from .main import cli + + +@cli.command(name='show', help='show node names in the pb file') +@click.help_option('-h', '--help') +@click.option('--oneline', is_flag=True, + help='show in oneline format (no detail information)') +@click.option('--ignore-unknown-op', is_flag=True, + help='ignore unknown/unsupported ops') +@click.option("--output-nodes", + type=NArgsParam(), + metavar="NODE_NAME,NODE_NAME,...", + help="list of output nodes") +@click.option('--config', default='utensor_cli.toml', show_default=True, metavar='CONFIG.toml') +@click.argument('model_file', required=True, metavar='MODEL.{pb,pkl}') +def show_graph(model_file, config, **kwargs): + import pickle + from utensor_cgen.frontend import FrontendSelector + + _, ext = os.path.splitext(model_file) + output_nodes = kwargs.pop('output_nodes') + + if ext == '.pkl': + with open(model_file, 'rb') as fid: + ugraph = pickle.load(fid) + _show_ugraph(ugraph, **kwargs) + return 0 + + if os.path.exists(config): + with open(config) as fid: + config = loads(fid.read()) + else: + config = {} + try: + ugraph = FrontendSelector.parse(model_file, output_nodes, config) + _show_ugraph(ugraph, **kwargs) + return 0 + except RuntimeError as err: + msg = err.args[0] + click.secho(msg, fg='red', bold=True) + return 1 diff --git a/utensor_cgen/frontend/__init__.py b/utensor_cgen/frontend/__init__.py index f122ea18..e11076ce 100644 --- a/utensor_cgen/frontend/__init__.py +++ b/utensor_cgen/frontend/__init__.py @@ -22,12 +22,12 @@ def _register(parser_cls): return _register @classmethod - def parse(cls, model_file, output_nodes, config=None): + def parse(cls, model_file, output_nodes=None, config=None, model_name=None): if config is None: config = {} _, ext = os.path.splitext(model_file) parser = cls.select_parser(ext)(config) - return parser.parse(model_file, output_nodes) + return parser.parse(model_file, output_nodes, model_name=model_name) @classmethod def select_parser(cls, file_ext): diff --git a/utensor_cgen/frontend/base.py b/utensor_cgen/frontend/base.py index 86131f65..52eb9a8f 100644 --- a/utensor_cgen/frontend/base.py +++ b/utensor_cgen/frontend/base.py @@ -11,7 +11,11 @@ def __new__(cls, config): self._config = config return self + @property + def config(self): + return self._config + @classmethod @abstractmethod - def parse(cls, fname, outupt_nodes): + def parse(cls, fname, outupt_nodes, model_name=None): raise RuntimeError('abstract parse method involded') diff --git a/utensor_cgen/frontend/onnx.py b/utensor_cgen/frontend/onnx.py index fc681bae..b9e91da4 100644 --- a/utensor_cgen/frontend/onnx.py +++ b/utensor_cgen/frontend/onnx.py @@ -1,19 +1,289 @@ +import os +import re +from collections import Counter +from numbers import Number + +import numpy as np import onnx -from onnx_tf.backend import prepare +import tensorflow.compat.v1 as tf +from onnx import mapping, numpy_helper +from onnx.onnx_pb import TensorProto from utensor_cgen.frontend import FrontendSelector from utensor_cgen.frontend.base import Parser +from utensor_cgen.ir import OperationInfo, TensorInfo, uTensorGraph +from utensor_cgen.ir.converter import AttrValueConverter, TensorProtoConverter +from utensor_cgen.legalizer import Legalizer +from utensor_cgen.utils import topologic_order_graph from .tensorflow import GraphDefParser +def _convert_op_attribute(attrib_pb): + # TODO: integrate with ir.converter + if attrib_pb.HasField('f'): + return attrib_pb.f + elif attrib_pb.HasField('i'): + return attrib_pb.i + elif attrib_pb.HasField('s'): + return attrib_pb.s + else: + raise ValueError('Unknown attribute value: {}'.format(attrib_pb)) + +# I stole these code snippets from [onnx-tf](https://github.com/onnx/onnx-tensorflow) +# I have to do so since TF2.0 fuck up onnx-tf's backend +def _onnx_dtype(dtype): + if isinstance(dtype, Number): + onnx_dype = dtype + elif isinstance(dtype, str): + onnx_dype = TensorProto.DataType.Value(dtype) + else: + raise RuntimeError("dtype should be number or str.") + return onnx_dype + +def onnx2tf(dtype): + return tf.as_dtype(mapping.TENSOR_TYPE_TO_NP_TYPE[_onnx_dtype(dtype)]) + +def _onnx_initializer_to_input_dict_items(initializer): + """ Convert ONNX graph initializer to input dict items. + + :param initializer: ONNX graph initializer, list of TensorProto. + :return: List of input dict items. + """ + def tensor2list(onnx_tensor): + # Use the onnx.numpy_helper because the data may be raw + return numpy_helper.to_array(onnx_tensor).flatten().tolist() + + return [(init.name, + tf.constant( + tensor2list(init), + shape=init.dims, + dtype=onnx2tf(init.data_type))) + for init in initializer] + + @FrontendSelector.register(target_exts=['.onnx']) class OnnxParser(Parser): + # https://github.com/onnx/onnx/blob/master/onnx/onnx.proto + # https://pytorch.org/docs/stable/onnx.html - @classmethod - def parse(cls, onnx_file, output_nodes): - onnx_model = onnx.load(onnx_file) - tf_rep = prepare(onnx_model) - graph_def = tf_rep.graph.as_graph_def() - ugraph = GraphDefParser.parse(graph_def, output_nodes) + def parse(self, onnx_file, output_nodes=None, model_name=None): + if model_name: + graph_name = model_name + else: + graph_name, _ = os.path.splitext( + os.path.basename(onnx_file) + ) + tf.reset_default_graph() + model = onnx.load(onnx_file) + onnx_graph = model.graph + ugraph = uTensorGraph( + name=graph_name, + output_nodes=[], + lib_name='onnx', + ops_info={}, + ) + self._build_graph(onnx_graph, ugraph) + ugraph = Legalizer.legalize(ugraph) + tf.reset_default_graph() return ugraph + + def _build_graph(self, onnx_graph, ugraph): + op_types_cnt = Counter() # op_type (str) -> count (int) + tensor_names_map = {} # tensor_name (str) -> tensor_info (TensorInfo) + # these methods will update inputs **inplace** + self._build_param_ops( + onnx_graph, ugraph, op_types_cnt, tensor_names_map + ) + self._build_input_ops( + onnx_graph, ugraph, op_types_cnt, tensor_names_map + ) + self._build_intermediate_ops( + onnx_graph, ugraph, op_types_cnt, tensor_names_map, + ) + # find outupt nodes + distinct_out_ops = set() + graph_output = set([v.name for v in onnx_graph.output]) + for name, tensor_info in tensor_names_map.items(): + if name in graph_output: + distinct_out_ops.add(tensor_info.op_name) + ugraph.output_nodes = list(distinct_out_ops) + topologic_order_graph(ugraph) + _PostProcessing.post_process(ugraph) + + def _build_param_ops(self, onnx_graph, ugraph, op_types_cnt, tensor_names_map): + """Find all tensors in initialization list in onnx_graph, normally constants + + Note that this method will update op_types_cnt and tensor_names_map **inplace** + """ + # find Const ops + params_dict = {} + # FIXME: avoid using internal api of other library + dict_items = _onnx_initializer_to_input_dict_items(onnx_graph.initializer) + for name, tf_tensor in dict_items: + params_dict[name] = AttrValueConverter.GenericType( + value_name='value', + value=TensorProtoConverter.get_generic_value( + tf_tensor.op.get_attr('value') + ) + ) + # build Const ops + for tensor_name, tensor_value in params_dict.items(): + cnt = op_types_cnt['Const'] + node_name = self._format_node_name(tensor_name, 'Const', cnt) + op_types_cnt['Const'] += 1 + tensor_names_map[tensor_name] = TensorInfo( + name=self._format_tensor_name('', node_name, 0), + op_name=node_name, + dtype=tensor_value.value.dtype, + shape=list(tensor_value.value.np_array.shape), + ugraph=ugraph + ) + OperationInfo( + name=node_name, + input_tensors=[], + output_tensors=[tensor_names_map[tensor_name]], + op_type='Const', + lib_name='onnx', + ugraph=ugraph, + op_attr={ + 'value': tensor_value + } + ) + + def _build_input_ops(self, onnx_graph, ugraph, op_types_cnt, tensor_names_map): + """Find placeholders + That is, those ops in the input list but not in initialization list + + Note this method will update inputs **inplace** + """ + # placeholders + for value in onnx_graph.input: + # value: ValueInfoProto + tensor_name = value.name + if tensor_name in tensor_names_map: + # tensor in initializers MAY appears in input, ignore + continue + cnt = op_types_cnt['Placeholder'] + node_name = self._format_node_name(tensor_name, 'Placeholder', cnt) + op_types_cnt['Placeholder'] += 1 + assert value.type.HasField('tensor_type'), 'invalid graph input value' + tensor_type = value.type.tensor_type + dtype_str = TensorProto.DataType.Name(tensor_type.elem_type).lower() + if dtype_str == 'float': + dtype_str = 'float32' + dtype = np.dtype(dtype_str) + shape = [ + dim.dim_value for dim in tensor_type.shape.dim + ] + tensor_names_map[tensor_name] = TensorInfo( + name=self._format_tensor_name('', node_name, 0), + op_name=node_name, + dtype=dtype, + shape=shape, + ugraph=ugraph, + ) + OperationInfo( + name=node_name, + input_tensors=[], + output_tensors=[tensor_names_map[tensor_name]], + op_type='Placeholder', + ugraph=ugraph, + lib_name='onnx', + op_attr={} + ) + + def _build_intermediate_ops(self, onnx_graph, ugraph, op_types_cnt, tensor_names_map): + """Build all intermediate nodes, the nodes that is not in neither initialization list nor input list + """ + # create all outupt tensors + for node in onnx_graph.node: + cnt = op_types_cnt[node.op_type] + node_name = self._format_node_name(node.name, node.op_type, cnt) + op_types_cnt[node.op_type] += 1 + for i, name in enumerate(node.output): + tensor_names_map[name] = TensorInfo( + name=self._format_tensor_name(name, node_name, i), + op_name=node_name, + dtype=None, + shape=None, + ugraph=ugraph + ) + # create ops + for node in onnx_graph.node: + input_tensors = [ + tensor_names_map[name] for name in node.input + ] + output_tensors = [ + tensor_names_map[name] for name in node.output + ] + op_attr = { + attrib_pb.name: _convert_op_attribute(attrib_pb) + for attrib_pb in node.attribute + } + node_name = output_tensors[0].op_name + OperationInfo( + name=node_name, + input_tensors=input_tensors, + output_tensors=output_tensors, + op_type=node.op_type, + lib_name='onnx', + ugraph=ugraph, + op_attr=op_attr + ) + + def _format_node_name(self, node_name, op_type, op_cnt): + if node_name == '': + node_name = '{}_{}'.format(op_type, op_cnt) + return re.sub(r'[\.:/]', '_', node_name) + + def _format_tensor_name(self, name, node_name, offset): + if re.match(r'[a-zA-Z][a-zA-Z0-9]*:[0-9]+', name): + return name + return '{}:{}'.format(node_name, offset) + + +class _PostProcessing(object): + + @classmethod + def post_process(cls, ugraph): + for op_name in ugraph.topo_order: + op_info = ugraph.ops_info[op_name] + handler = getattr(cls, '_handle_{}'.format(op_info.op_type.lower()), lambda op_info: op_info) + handler(op_info) + + @staticmethod + def _handle_gemm(op_info): + ugraph = op_info.ugraph + output_tensor = op_info.output_tensors[0] + if output_tensor.dtype is not None and output_tensor.shape is not None: + return + input_a, input_w, input_bias = op_info.input_tensors + a = np.zeros(input_a.shape, dtype=input_a.dtype) + w = np.zeros(input_w.shape, dtype=input_w.dtype) + b = np.zeros(input_bias.shape, dtype=input_bias.dtype) + out = np.matmul(a, w.T) + b + output_tensor.dtype = out.dtype + output_tensor.shape = list(out.shape) + for op in output_tensor.op.output_nodes: + for i, in_tensor in enumerate(op.input_tensors): + if in_tensor.name == output_tensor.name: + op.input_tensors[i] = output_tensor + + @staticmethod + def _handle_relu(op_info): + input_tensor = op_info.input_tensors[0] + op_info.output_tensors[0].dtype = input_tensor.dtype + op_info.output_tensors[0].shape = input_tensor.shape[:] + + @staticmethod + def _handle_softmax(op_info): + input_tensor = op_info.input_tensors[0] + output_tensor = op_info.output_tensors[0] + if output_tensor.dtype is not None and output_tensor.shape is not None: + return + logistics = np.zeros(input_tensor.shape, dtype=input_tensor.dtype) + out = np.exp(-logistics) * 1e-6 + out /= out.sum(axis=1) + output_tensor.shape = list(out.shape) + output_tensor.dtype = out.dtype diff --git a/utensor_cgen/frontend/tensorflow.py b/utensor_cgen/frontend/tensorflow.py index 263faa92..9082a333 100644 --- a/utensor_cgen/frontend/tensorflow.py +++ b/utensor_cgen/frontend/tensorflow.py @@ -4,63 +4,74 @@ import numpy as np import six - -import tensorflow as tf +import tensorflow.compat.v1 as tf from google.protobuf import text_format + from utensor_cgen.frontend import FrontendSelector from utensor_cgen.frontend.base import Parser from utensor_cgen.ir.base import OperationInfo, TensorInfo, uTensorGraph from utensor_cgen.legalizer import Legalizer +from utensor_cgen.logger import logger from utensor_cgen.utils import random_str, topologic_order_graph -@FrontendSelector.register(target_exts=['.pb', '.pbtxt']) +@FrontendSelector.register(target_exts=[".pb", ".pbtxt"]) class GraphDefParser(Parser): - @classmethod - def parse(cls, pb_file, output_nodes=None): - graph_def, graph_name = cls._load_graph_def(pb_file) - if not cls._tf_is_freeze_graph(graph_def): - raise ValueError('Given graph_def is not freezed') + def parse(self, pb_file, output_nodes=None, model_name=None): + graph_def, graph_name = self._load_graph_def(pb_file) + if model_name: + graph_name = model_name + if not self._is_freeze_graph(graph_def): + raise ValueError("Given graph_def is not freezed") if output_nodes is None: output_nodes = [node.name for node in graph_def.node] - + logger.warning( + 'output_nodes is not given, use all nodes instead (may cause unexpected behaviour)' + ) + graph = tf.Graph() with graph.as_default(): - tf.import_graph_def(graph_def, name='') + tf.import_graph_def(graph_def, name="") ugraph = uTensorGraph( - name=graph_name, - output_nodes=output_nodes, - lib_name="tensorflow", + name=graph_name, output_nodes=output_nodes, lib_name="tensorflow", ) for node in graph_def.node: op = graph.get_operation_by_name(node.name) - in_tensors = [TensorInfo(name=tensor.name, - ugraph=ugraph, - op_name=tensor.op.name, - dtype=np.dtype(tensor.dtype.as_numpy_dtype), - shape=cls._tf_parse_tshape(tensor.shape), - ) - for tensor in op.inputs] - out_tensors = [TensorInfo(name=tensor.name, - ugraph=ugraph, - op_name=op.name, - dtype=np.dtype(tensor.dtype.as_numpy_dtype), - shape=cls._tf_parse_tshape(tensor.shape), - ) - for tensor in op.outputs] + in_tensors = [ + TensorInfo( + name=tensor.name, + ugraph=ugraph, + op_name=tensor.op.name, + dtype=np.dtype(tensor.dtype.as_numpy_dtype), + shape=self._tf_parse_tshape(tensor.shape), + ) + for tensor in op.inputs + ] + out_tensors = [ + TensorInfo( + name=tensor.name, + ugraph=ugraph, + op_name=op.name, + dtype=np.dtype(tensor.dtype.as_numpy_dtype), + shape=self._tf_parse_tshape(tensor.shape), + ) + for tensor in op.outputs + ] op_type = node.op op_attr = node.attr - op_info = OperationInfo(name=node.name, - input_tensors=in_tensors, - n_inputs=len(in_tensors), - output_tensors=out_tensors, - n_outputs=len(out_tensors), - op_type=op_type, - lib_name='tensorflow', - op_attr=op_attr, - ugraph=ugraph) - op_info.op_attr['tensorflow__device'] = node.device + op_info = OperationInfo( + name=node.name, + input_tensors=in_tensors, + n_inputs=len(in_tensors), + output_tensors=out_tensors, + n_outputs=len(out_tensors), + op_type=op_type, + lib_name="tensorflow", + op_attr=op_attr, + ugraph=ugraph, + ) + op_info.op_attr["tensorflow__device"] = node.device ugraph.ops_info[node.name] = op_info topologic_order_graph(ugraph) ugraph = Legalizer.legalize(ugraph, {}) @@ -69,18 +80,18 @@ def parse(cls, pb_file, output_nodes=None): @staticmethod def _load_graph_def(pb_file): if isinstance(pb_file, tf.GraphDef): - return pb_file, 'tf_graph_{}'.format(random_str(6)) + return pb_file, "tf_graph_{}".format(random_str(6)) assert isinstance(pb_file, six.string_types) - graph_name, _ = os.path.splitext(os.path.basename(pb_file)) + graph_name, ext = os.path.splitext(os.path.basename(pb_file)) graph_def = tf.GraphDef() - if pb_file[-3:] == ".pb": - with open(pb_file, 'rb') as fid: + if ext == ".pb": + with open(pb_file, "rb") as fid: graph_def.ParseFromString(fid.read()) - elif pb_file[-6:] == ".pbtxt": - with open(pb_file, 'r') as fid: + elif ext == ".pbtxt": + with open(pb_file, "r") as fid: text_format.Parse(fid.read(), graph_def) else: - raise ValueError('unknown file format: %s' % pb_file) + raise ValueError("unknown file format: %s" % pb_file) return graph_def, graph_name @staticmethod @@ -91,7 +102,7 @@ def _tf_parse_tshape(t_shape): shape = None return shape - @classmethod - def _tf_is_freeze_graph(self, graph_def): - is_frozen = all(node.op not in ['VariableV2'] for node in graph_def.node) + @staticmethod + def _is_freeze_graph(graph_def): + is_frozen = all(node.op not in ["VariableV2"] for node in graph_def.node) return is_frozen diff --git a/utensor_cgen/frontend/tflite.py b/utensor_cgen/frontend/tflite.py new file mode 100644 index 00000000..cf169a1e --- /dev/null +++ b/utensor_cgen/frontend/tflite.py @@ -0,0 +1,413 @@ +import os +import re + +import numpy as np + +from utensor_cgen.frontend import FrontendSelector +from utensor_cgen.frontend.base import Parser +from utensor_cgen.ir.base import OperationInfo, TensorInfo, uTensorGraph +from utensor_cgen.ir.converter import (AttrValueConverter, + GenericTensorConverterMixin) +from utensor_cgen.third_party.tflite.ActivationFunctionType import \ + ActivationFunctionType +from utensor_cgen.third_party.tflite.BuiltinOperator import BuiltinOperator +from utensor_cgen.third_party.tflite.CustomOptionsFormat import \ + CustomOptionsFormat +from utensor_cgen.third_party.tflite.FullyConnectedOptionsWeightsFormat import \ + FullyConnectedOptionsWeightsFormat +from utensor_cgen.third_party.tflite.Model import Model +from utensor_cgen.utils import topologic_order_graph + +_CUSTOM_OPTION_FORMAT_MAP = {v: k for k, v in CustomOptionsFormat.__dict__.items()} + +def class_option2str(obj, idx): + names_lookup = {v: k for k, v in obj.__dict__.items()} + name = names_lookup[idx] + return str(idx) + " (" + name + ")" + +def fully_connected_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.FullyConnectedOptions import FullyConnectedOptions + + option = FullyConnectedOptions() + builtin_data = op.BuiltinOptions() + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict["FusedActivationFunction"] = class_option2str( + ActivationFunctionType, option.FusedActivationFunction() + ) + option_dict["w_formats"] = class_option2str( + FullyConnectedOptionsWeightsFormat, option.WeightsFormat() + ) + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + return option_dict + +def depthwise_conv2d_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.DepthwiseConv2DOptions import DepthwiseConv2DOptions + + option = DepthwiseConv2DOptions() + builtin_data = op.BuiltinOptions() + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict["Padding"] = option.Padding() + option_dict["StrideW"] = option.StrideW() + option_dict["StrideH"] = option.StrideH() + option_dict["DepthMultiplier"] = option.DepthMultiplier() + option_dict["FusedActivationFunction"] = class_option2str( + ActivationFunctionType, option.FusedActivationFunction() + ) + option_dict["DilationWFactor"] = option.DilationWFactor() + option_dict["DilationHFactor"] = option.DilationHFactor() + + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + +def reshape_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.ReshapeOptions import ReshapeOptions + + option = ReshapeOptions() + builtin_data = op.BuiltinOptions() + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict["NewShape"] = option.NewShapeAsNumpy() + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + +def dequantize_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.DequantizeOptions import DequantizeOptions + + option = DequantizeOptions() + builtin_data = op.BuiltinOptions() + if builtin_data is None: + return option_dict + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict['builtin'] = option + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + +def quantize_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.QuantizeOptions import QuantizeOptions + + option = QuantizeOptions() + builtin_data = op.BuiltinOptions() + if builtin_data is None: + return option_dict + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict['builtin'] = option + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + +def pool2d_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.Pool2DOptions import Pool2DOptions + + option = Pool2DOptions() + builtin_data = op.BuiltinOptions() + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict["Padding"] = option.Padding() + option_dict["StrideW"] = option.StrideW() + option_dict["StrideH"] = option.StrideH() + option_dict["FilterWidth"] = option.FilterWidth() + option_dict["FilterHeight"] = option.FilterHeight() + option_dict["FusedActivationFunction"] = class_option2str( + ActivationFunctionType, option.FusedActivationFunction() + ) + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + +def argmax_op_data(op): + option_dict = {} + if op.CustomOptionsLength() < 1: + from utensor_cgen.third_party.tflite.ArgMaxOptions import ArgMaxOptions + + option = ArgMaxOptions() + builtin_data = op.BuiltinOptions() + option.Init(builtin_data.Bytes, builtin_data.Pos) + option_dict["OutputType"] = option.OutputType() + else: + option_dict[ + _CUSTOM_OPTION_FORMAT_MAP[op.CustomOptionsFormat()] + ] = op.CustomOptionsAsNumpy() + + return option_dict + + +_OP_DATA_FUNC_MAP = dict() +_OP_DATA_FUNC_MAP["QUANTIZE"] = quantize_op_data +_OP_DATA_FUNC_MAP["DEPTHWISE_CONV_2D"] = depthwise_conv2d_op_data +_OP_DATA_FUNC_MAP["MAX_POOL_2D"] = pool2d_op_data +_OP_DATA_FUNC_MAP["RESHAPE"] = reshape_op_data +_OP_DATA_FUNC_MAP["FULLY_CONNECTED"] = fully_connected_op_data +_OP_DATA_FUNC_MAP["DEQUANTIZE"] = dequantize_op_data +_OP_DATA_FUNC_MAP["ARG_MAX"] = argmax_op_data + + +@FrontendSelector.register(target_exts=[".tflite"]) +class TFLiteParser(Parser): + _TENSOR_NP_TYPE = { + 0:np.dtype("float32"), + 1: np.dtype("float16"), + 2: np.dtype("int32"), + 3: np.dtype("uint8"), + 4: np.dtype("uint64"), + 5: np.dtype("str"), + 6: np.dtype("bool"), + 7: np.dtype("int16"), + 8: np.dtype("cdouble"), + 9: np.dtype("int8"), + } + _BUILTIN_OPS = {v: k for k, v in BuiltinOperator.__dict__.items()} + + def parse(self, tflite_file, output_nodes=None, model_name=None): + if output_nodes is None: + output_nodes = [] + if model_name: + graph_name = model_name + else: + graph_name, _ = os.path.splitext( + os.path.basename(tflite_file) + ) + with open(tflite_file, "rb") as fid: + buf = bytearray(fid.read()) + fb_model = Model.GetRootAsModel(buf, 0) + + ugraph = uTensorGraph( + name=graph_name, + output_nodes=output_nodes, + lib_name="tflite", + ops_info={}, + ) + self._build_graph(fb_model, ugraph) + _OpRenaming.apply(ugraph) + return ugraph + + def _build_graph(self, fb_model, ugraph): + self.tensor_names_map = {} # addresseed by indexi + self._build_tensor_map(fb_model, ugraph) + + self._build_param_ops(fb_model, ugraph) + # find and set input nodes + self._build_input_ops(fb_model, ugraph) + self._build_intermediate_ops(fb_model, ugraph) + self._set_output_ops(fb_model, ugraph) + + topologic_order_graph(ugraph) + + def _set_output_ops(self, fb_model, ugraph): + """identfy output nodes in fb_mdel + sets output_nodes in ugraph + Note this method will update ugraph **inplace** + """ + subgraph = self._get_tflm_get_subgraph(fb_model) + subgraph_outputs_indexi = subgraph.OutputsAsNumpy() # tensor indexi + output_node_names = set() + for index in subgraph_outputs_indexi: + output_node_names.add(self.tensor_names_map[index].op_name) + + ugraph.output_nodes = list(output_node_names) + + def _build_tensor_map(self, fb_model, ugraph): + subgraph = self._get_tflm_get_subgraph(fb_model) + + for idx in range(0, subgraph.TensorsLength()): + tensor = subgraph.Tensors(idx) + tensor_name = tensor.Name().decode('utf8') + if tensor_name is "" or None: + tensor_name = "tensor_" + str(idx) + + dtype = self._TENSOR_NP_TYPE[tensor.Type()] + attributes = dict() + quant_params = tensor.Quantization() + if quant_params is not None: + zp = quant_params.ZeroPointAsNumpy() + if zp.dtype == np.dtype(' 0 else None for i in range(len(value.dim)) + ] list_view = tensor_shape.TensorShape(value).as_list() except ValueError: # unknown shape diff --git a/utensor_cgen/ir/misc/graph_viz.py b/utensor_cgen/ir/misc/graph_viz.py index befe3780..7b18578f 100644 --- a/utensor_cgen/ir/misc/graph_viz.py +++ b/utensor_cgen/ir/misc/graph_viz.py @@ -1,7 +1,12 @@ -from graphviz import Digraph +from random import random, seed +import matplotlib.pyplot as plt +from graphviz import Digraph +from matplotlib import cm as _cm from utensor_cgen.logger import logger +plt.style.use('ggplot') + def viz_graph(ugraph, out_fname=None, view=False, cleanup=True, colored_nodes=None, suffix=''): if colored_nodes is None: @@ -65,3 +70,82 @@ def viz_graph(ugraph, out_fname=None, view=False, cleanup=True, colored_nodes=No dot.render(out_fname, view=view, cleanup=cleanup) logger.info('graph visualization file generated: %s', out_fname) return dot + + +class _MemallcVisualizer(object): + + @classmethod + def viz_memalloc( + cls, + ugraph, + split_on_large_graph=True, + num_tensors_per_split=20, + figsize=None, + fontsize=12, + lw=12, + cmap=_cm.BrBG_r, + rand_seed=1111 + ): + seed(rand_seed) + if TensorAllocationPlanner.KWARGS_NAMESCOPE not in ugraph.attributes: + logger.info('No tensor allocation plan to visualize: %s', ugraph.name) + return plt.Figure() + alloc_plan = ugraph.attributes[TensorAllocationPlanner.KWARGS_NAMESCOPE] + topo_tensors = sorted( + [tensor_name for tensor_name in alloc_plan.plan], + key=lambda tensor_name: alloc_plan.plan[tensor_name].time_slot_start + ) + return cls._draw_figs(topo_tensors, alloc_plan, cmap, figsize, fontsize, lw, split_on_large_graph, num_tensors_per_split) + + @classmethod + def _draw_figs(cls, topo_tensors, alloc_plan, cmap, figsize, fontsize, lw, split_on_large_graph, num_tensors_per_split): + xmins = [alloc_plan.plan[tensor_name].offset_start for tensor_name in topo_tensors] + xmaxs = [alloc_plan.plan[tensor_name].offset_end for tensor_name in topo_tensors] + colors = [cmap(random()) for _ in alloc_plan.plan] + labels = topo_tensors[:] + sizes = [alloc_plan.plan[tensor_name].size for tensor_name in topo_tensors] + if split_on_large_graph: + xmin_chunks = [xmins[i:i+num_tensors_per_split] for i in range(0, len(xmins), num_tensors_per_split)] + xmax_chunks = [xmaxs[i:i+num_tensors_per_split] for i in range(0, len(xmaxs), num_tensors_per_split)] + color_chunks = [colors[i:i+num_tensors_per_split] for i in range(0, len(colors), num_tensors_per_split)] + label_chunks = [labels[i:i+num_tensors_per_split] for i in range(0, len(labels), num_tensors_per_split)] + size_chunks = [sizes[i:i+num_tensors_per_split] for i in range(0, len(sizes), num_tensors_per_split)] + else: + xmin_chunks = [xmins] + xmax_chunks = [xmaxs] + color_chunks = [colors] + label_chunks = [labels] + size_chunks = [sizes] + figs = [] + for i, (xmins, xmaxs, colors, labels, sizes) in enumerate(zip(xmin_chunks, xmax_chunks, color_chunks, label_chunks, size_chunks)): + fig, _ = plt.subplots(1, 1) + ys = [len(xmins)-i for i in range(len(xmins))] + for y, xmin, xmax, color, size in zip(ys, xmins, xmaxs, colors, sizes): + plt.hlines(y, xmin, xmax, lw=lw, colors=color) + plt.text(xmax+lw*10, y-0.01*lw, '{} bytes'.format(size), fontdict={'fontsize': fontsize}) + plt.xlabel('Offset (bytes)', fontdict={'fontsize': fontsize}) + plt.yticks(ys, labels, fontsize=fontsize) + plt.xticks(fontsize=fontsize) + plt.ylabel( + 'Tensor Names (Topological Ordered, Top to Bottom)', + fontdict={'fontsize':fontsize} + ) + if i: + title = 'Optimal Tensor Allocation: {} bytes in total (Cont.)'.format(alloc_plan.total_size) + else: + title = 'Optimal Tensor Allocation: {} bytes in total'.format(alloc_plan.total_size) + plt.title( + title, + fontdict={'fontsize': fontsize} + ) + if figsize is None: + figsize = (num_tensors_per_split, num_tensors_per_split / 2) + fig.set_size_inches(*figsize) + fig.tight_layout() + figs.append(fig) + return figs + +viz_memalloc = _MemallcVisualizer.viz_memalloc + +# FIXME: cyclic import +from utensor_cgen.backend.graph_lower.generic_graph_lower import TensorAllocationPlanner # isort:skip diff --git a/utensor_cgen/legalizer/__init__.py b/utensor_cgen/legalizer/__init__.py index 15e82b78..824a36b3 100644 --- a/utensor_cgen/legalizer/__init__.py +++ b/utensor_cgen/legalizer/__init__.py @@ -1,5 +1,6 @@ from .base import LegalizerBase from .tensorflow import GraphDefLegalizer +from .onnx import OnnxLegalizer class Legalizer(object): @@ -32,3 +33,4 @@ def legalize(cls, ugraph, config=None): return legalizer.legalize(ugraph) Legalizer.register(GraphDefLegalizer) +Legalizer.register(OnnxLegalizer) diff --git a/utensor_cgen/legalizer/base.py b/utensor_cgen/legalizer/base.py index e401eb8b..10903ed3 100644 --- a/utensor_cgen/legalizer/base.py +++ b/utensor_cgen/legalizer/base.py @@ -1,9 +1,9 @@ -from utensor_cgen.utils import MUST_OVERWRITEN +from utensor_cgen.utils import MUST_OVERWRITE class LegalizerBase(object): - TARGET = MUST_OVERWRITEN + TARGET = MUST_OVERWRITE COMPONET = 'legalizer' def __new__(cls, config): @@ -11,7 +11,7 @@ def __new__(cls, config): raise TypeError( 'expecting dict as config, get {}'.format(type(config)) ) - if cls.TARGET is MUST_OVERWRITEN: + if cls.TARGET is MUST_OVERWRITE: raise ValueError('cls.TARGET must be overwriten') self = object.__new__(cls) self._config = config diff --git a/utensor_cgen/legalizer/onnx.py b/utensor_cgen/legalizer/onnx.py new file mode 100644 index 00000000..170ad828 --- /dev/null +++ b/utensor_cgen/legalizer/onnx.py @@ -0,0 +1,61 @@ +from utensor_cgen.ir import OperationInfo, TensorInfo +from utensor_cgen.utils import topologic_order_graph + +from .base import LegalizerBase + + +class OnnxLegalizer(LegalizerBase): + TARGET = 'onnx' + + def legalize(self, ugraph): + self._visit_all(ugraph) + return ugraph + + def _visit_all(self, ugraph): + for op_info in list(ugraph.ops_info.values()): + visitor = getattr( + self, + '_visit_{}'.format(op_info.op_type.lower()), + lambda op_info: op_info + ) + visitor(op_info) + topologic_order_graph(ugraph) + + # _visit_ methods will be invoked when an op_info of + # type is encountered during graph traversal + # ex: _visit_gemm -> will be invoked with an op_info with type Gemm + def _visit_gemm(self, op_info): + # op_info.op_type == "Gemm" + ugraph = op_info.ugraph + op_info.op_type = 'MatMul' + tensor_a, tensor_w, tensor_bias = op_info.input_tensors + out_tensor = TensorInfo( + name='{}_MatMul:0'.format(op_info.name), + op_name='{}_MatMul'.format(op_info.name), + dtype=op_info.output_tensors[0].dtype, + shape=op_info.output_tensors[0].shape[:], + ugraph=ugraph, + ) + OperationInfo( + name='{}_MatMul'.format(op_info.name), + input_tensors=[tensor_a, tensor_w], + output_tensors=[out_tensor], + op_type='MatMul', + lib_name=op_info.lib_name, + ugraph=ugraph, + op_attr=op_info.op_attr + ) + add_op = OperationInfo( + name='{}_AddBias'.format(op_info.name), + input_tensors=[out_tensor, tensor_bias], + output_tensors=op_info.output_tensors[:], + op_type='Add', + lib_name=op_info.lib_name, + ugraph=ugraph, + op_attr={} + ) + op_info.output_tensors[0].op_name = add_op.name + + def _visit_id(self, op_info): + # identity op should be op_info.op_type == 'Identity' + return op_info diff --git a/utensor_cgen/legalizer/tensorflow.py b/utensor_cgen/legalizer/tensorflow.py index e2850c68..08f2a28f 100644 --- a/utensor_cgen/legalizer/tensorflow.py +++ b/utensor_cgen/legalizer/tensorflow.py @@ -4,6 +4,17 @@ class GraphDefLegalizer(LegalizerBase): TARGET = 'tensorflow' + class _OpTypeRenamePostProcessing(object): + _RENAME_MAP = { + 'BatchMatMulV2': 'MatMul' + } + + @classmethod + def apply(cls, ugraph): + for op_type, new_op_type in cls._RENAME_MAP.items(): + for op_info in ugraph.get_ops_by_type(op_type): + op_info.op_type = new_op_type + def legalize_ops(self, ugraph): '''Legalize ops to generic ops in given graph ''' @@ -11,6 +22,7 @@ def legalize_ops(self, ugraph): raise ValueError( 'expecting tensorflow graph, get {}'.format(ugraph.lib_name) ) + self._OpTypeRenamePostProcessing.apply(ugraph) return ugraph def legalize_dtype(self, ugraph): diff --git a/utensor_cgen/matcher/__init__.py b/utensor_cgen/matcher/__init__.py index 2522277b..58e0737b 100644 --- a/utensor_cgen/matcher/__init__.py +++ b/utensor_cgen/matcher/__init__.py @@ -1,2 +1,2 @@ -from ._matcher_impl import (OpEqualityDelegate, uTensorGraphMatch, +from ._matcher_impl import (OpEqualityDelegateBase, uTensorGraphMatch, uTensorGraphMatcher) diff --git a/utensor_cgen/matcher/_matcher_impl.py b/utensor_cgen/matcher/_matcher_impl.py index 4eca39e8..dc37f27c 100644 --- a/utensor_cgen/matcher/_matcher_impl.py +++ b/utensor_cgen/matcher/_matcher_impl.py @@ -5,6 +5,7 @@ import attr from attr.validators import instance_of from click import style +from six import with_metaclass from utensor_cgen.ir import (MetaOperationInfo, OperationInfo, uTensorGraph, uTensorGraphView) @@ -15,16 +16,30 @@ __all__ = ["uTensorGraphMatcher", "uTensorGraphMatch"] +class _OpEqualityDelegateMeta(type): + def __new__(mcls, name, bases, attribs): + if name != 'OpEqualityDelegateBase': + # op_type -> list[tuple] (permutations) + attribs['_association_map'] = {} + # op_type -> dict[op_type] -> morphism + attribs['_compatibility_map'] = {} + + cls = type.__new__(mcls, name, bases, attribs) + return cls + @attr.s(frozen=True, slots=True) -class OpEqualityDelegate(object): +class OpEqualityDelegateBase(with_metaclass(_OpEqualityDelegateMeta)): - # op_type -> list[tuple] (permutations) - _association_map = {} - # op_type -> dict[op_type] -> morphism - _compatibility_map = {} + def __new__(cls, *args, **kwargs): + if cls is OpEqualityDelegateBase: + raise RuntimeError('OpEqualityDelegateBase is abstract base class and should not be instantiated') + self = object.__new__(cls) + return self @classmethod def is_associative(cls, permutations): + if cls is OpEqualityDelegateBase: + raise RuntimeError('You should use OpEqualityDelegateBase directly, please create a subclass') def deco(op): if op.op_type in cls._association_map: raise ValueError( @@ -40,6 +55,8 @@ def deco(op): @classmethod def is_compatible_with(cls, other_op_type, morphism_type, **kwargs): + if cls is OpEqualityDelegateBase: + raise RuntimeError('You should use OpEqualityDelegateBase directly, please create a subclass') if not issubclass(morphism_type, Morphism): raise ValueError( 'expecting Morphism for `morphism`, get {}'.format(morphism_type) @@ -73,7 +90,8 @@ def query(cls, sub_op, patrn_op): equivalent_ops : List[OperationInfo] a list of equivalent ops derieved from `sub_op` """ - cls._setup() + if cls is OpEqualityDelegateBase: + raise RuntimeError('You should use OpEqualityDelegateBase directly, please create a subclass') is_eq = False equivalent_ops = [] if sub_op is None or patrn_op is None: @@ -105,11 +123,6 @@ def query(cls, sub_op, patrn_op): equivalent_ops = [MetaOperationInfo(op_info=sub_op, morphism=morphism)] return is_eq, equivalent_ops - - @classmethod - def _setup(cls): - # to activate all configurations - import utensor_cgen.backend.utensor.code_generator.legacy._operators @attr.s class uTensorGraphMatcher(object): @@ -126,7 +139,10 @@ class uTensorGraphMatcher(object): patrn_ugraph = ... # load the pattern uTensorGraph # create a matcher - matcher = uTensorGraphMatcher(pattern_ugraph=patrn_ugraph) + matcher = uTensorGraphMatcher( + pattern_ugraph=patrn_ugraph, + op_equality_delegate=delegate, # a subclass of :py:class:`OpEqualityDelegateBase` + ) # define a callback def callback(match): @@ -152,6 +168,11 @@ def callback(match): """ pattern_ugraph = attr.ib(validator=instance_of(uTensorGraph)) + _op_equality_delegate = attr.ib() + + def __attrs_post_init__(self): + assert issubclass(self._op_equality_delegate, OpEqualityDelegateBase), \ + 'expecting subclass of %s, get %s' % (OpEqualityDelegateBase, self._op_equality_delegate) def _match(self, other_ugraph): outputs_pool = [] @@ -227,7 +248,7 @@ def _visit(self, states): match = state.match sub_op = state.sub_bfs_queue.popleft() patrn_op = state.patrn_bfs_queue.popleft() - is_eq, eq_ops = OpEqualityDelegate.query(sub_op, patrn_op) + is_eq, eq_ops = self._op_equality_delegate.query(sub_op, patrn_op) if is_eq: for eq_op in eq_ops: new_sub_bfs_queue = deque(state.sub_bfs_queue) diff --git a/utensor_cgen/matcher/_morphism.py b/utensor_cgen/matcher/_morphism.py index 4013e7f1..9b537fcc 100644 --- a/utensor_cgen/matcher/_morphism.py +++ b/utensor_cgen/matcher/_morphism.py @@ -3,7 +3,7 @@ import attr -from utensor_cgen.utils import MUST_OVERWRITEN +from utensor_cgen.utils import MUST_OVERWRITE class Morphism(object): @@ -16,10 +16,10 @@ def apply(self, from_op): @attr.s class TypedMorphism(Morphism): - from_op_type = MUST_OVERWRITEN + from_op_type = MUST_OVERWRITE def __attrs_post_init__(self): - if self.from_op_type is MUST_OVERWRITEN: + if self.from_op_type is MUST_OVERWRITE: raise ValueError( "must overwrite {}.from_op_type".format(type(self).__name__) ) diff --git a/utensor_cgen/third_party/__init__.py b/utensor_cgen/third_party/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utensor_cgen/third_party/flatbuffers/__init__.py b/utensor_cgen/third_party/flatbuffers/__init__.py new file mode 100644 index 00000000..d14872ae --- /dev/null +++ b/utensor_cgen/third_party/flatbuffers/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .builder import Builder +from .table import Table +from .compat import range_func as compat_range diff --git a/utensor_cgen/third_party/flatbuffers/builder.py b/utensor_cgen/third_party/flatbuffers/builder.py new file mode 100644 index 00000000..478cdcd5 --- /dev/null +++ b/utensor_cgen/third_party/flatbuffers/builder.py @@ -0,0 +1,764 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import number_types as N +from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags) + +from . import encode +from . import packer + +from . import compat +from .compat import range_func +from .compat import memoryview_type +from .compat import import_numpy, NumpyRequiredForThisFeature + +np = import_numpy() +## @file +## @addtogroup flatbuffers_python_api +## @{ + +## @cond FLATBUFFERS_INTERNAL +class OffsetArithmeticError(RuntimeError): + """ + Error caused by an Offset arithmetic error. Probably caused by bad + writing of fields. This is considered an unreachable situation in + normal circumstances. + """ + pass + + +class IsNotNestedError(RuntimeError): + """ + Error caused by using a Builder to write Object data when not inside + an Object. + """ + pass + + +class IsNestedError(RuntimeError): + """ + Error caused by using a Builder to begin an Object when an Object is + already being built. + """ + pass + + +class StructIsNotInlineError(RuntimeError): + """ + Error caused by using a Builder to write a Struct at a location that + is not the current Offset. + """ + pass + + +class BuilderSizeError(RuntimeError): + """ + Error caused by causing a Builder to exceed the hardcoded limit of 2 + gigabytes. + """ + pass + +class BuilderNotFinishedError(RuntimeError): + """ + Error caused by not calling `Finish` before calling `Output`. + """ + pass + + +# VtableMetadataFields is the count of metadata fields in each vtable. +VtableMetadataFields = 2 +## @endcond + +class Builder(object): + """ A Builder is used to construct one or more FlatBuffers. + + Typically, Builder objects will be used from code generated by the `flatc` + compiler. + + A Builder constructs byte buffers in a last-first manner for simplicity and + performance during reading. + + Internally, a Builder is a state machine for creating FlatBuffer objects. + + It holds the following internal state: + - Bytes: an array of bytes. + - current_vtable: a list of integers. + - vtables: a hash of vtable entries. + + Attributes: + Bytes: The internal `bytearray` for the Builder. + finished: A boolean determining if the Builder has been finalized. + """ + + ## @cond FLATBUFFERS_INTENRAL + __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd", + "vtables", "nested", "forceDefaults", "finished") + + """Maximum buffer size constant, in bytes. + + Builder will never allow it's buffer grow over this size. + Currently equals 2Gb. + """ + MAX_BUFFER_SIZE = 2**31 + ## @endcond + + def __init__(self, initialSize): + """Initializes a Builder of size `initial_size`. + + The internal buffer is grown as needed. + """ + + if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE): + msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes." + raise BuilderSizeError(msg) + + self.Bytes = bytearray(initialSize) + ## @cond FLATBUFFERS_INTERNAL + self.current_vtable = None + self.head = UOffsetTFlags.py_type(initialSize) + self.minalign = 1 + self.objectEnd = None + self.vtables = {} + self.nested = False + self.forceDefaults = False + ## @endcond + self.finished = False + + def Output(self): + """Return the portion of the buffer that has been used for writing data. + + This is the typical way to access the FlatBuffer data inside the + builder. If you try to access `Builder.Bytes` directly, you would need + to manually index it with `Head()`, since the buffer is constructed + backwards. + + It raises BuilderNotFinishedError if the buffer has not been finished + with `Finish`. + """ + + if not self.finished: + raise BuilderNotFinishedError() + + return self.Bytes[self.Head():] + + ## @cond FLATBUFFERS_INTERNAL + def StartObject(self, numfields): + """StartObject initializes bookkeeping for writing a new object.""" + + self.assertNotNested() + + # use 32-bit offsets so that arithmetic doesn't overflow. + self.current_vtable = [0 for _ in range_func(numfields)] + self.objectEnd = self.Offset() + self.nested = True + + def WriteVtable(self): + """ + WriteVtable serializes the vtable for the current object, if needed. + + Before writing out the vtable, this checks pre-existing vtables for + equality to this one. If an equal vtable is found, point the object to + the existing vtable and return. + + Because vtable values are sensitive to alignment of object data, not + all logically-equal vtables will be deduplicated. + + A vtable has the following format: + + + * N, where N is the number of fields + in the schema for this type. Includes deprecated fields. + Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide. + + An object has the following format: + + + + """ + + # Prepend a zero scalar to the object. Later in this function we'll + # write an offset here that points to the object's vtable: + self.PrependSOffsetTRelative(0) + + objectOffset = self.Offset() + + vtKey = [] + trim = True + for elem in reversed(self.current_vtable): + if elem == 0: + if trim: + continue + else: + elem = objectOffset - elem + trim = False + + vtKey.append(elem) + + vtKey = tuple(vtKey) + vt2Offset = self.vtables.get(vtKey) + if vt2Offset is None: + # Did not find a vtable, so write this one to the buffer. + + # Write out the current vtable in reverse , because + # serialization occurs in last-first order: + i = len(self.current_vtable) - 1 + trailing = 0 + trim = True + while i >= 0: + off = 0 + elem = self.current_vtable[i] + i -= 1 + + if elem == 0: + if trim: + trailing += 1 + continue + else: + # Forward reference to field; + # use 32bit number to ensure no overflow: + off = objectOffset - elem + trim = False + + self.PrependVOffsetT(off) + + # The two metadata fields are written last. + + # First, store the object bytesize: + objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd) + self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize)) + + # Second, store the vtable bytesize: + vBytes = len(self.current_vtable) - trailing + VtableMetadataFields + vBytes *= N.VOffsetTFlags.bytewidth + self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes)) + + # Next, write the offset to the new vtable in the + # already-allocated SOffsetT at the beginning of this object: + objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) + encode.Write(packer.soffset, self.Bytes, objectStart, + SOffsetTFlags.py_type(self.Offset() - objectOffset)) + + # Finally, store this vtable in memory for future + # deduplication: + self.vtables[vtKey] = self.Offset() + else: + # Found a duplicate vtable. + objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) + self.head = UOffsetTFlags.py_type(objectStart) + + # Write the offset to the found vtable in the + # already-allocated SOffsetT at the beginning of this object: + encode.Write(packer.soffset, self.Bytes, self.Head(), + SOffsetTFlags.py_type(vt2Offset - objectOffset)) + + self.current_vtable = None + return objectOffset + + def EndObject(self): + """EndObject writes data necessary to finish object construction.""" + self.assertNested() + self.nested = False + return self.WriteVtable() + + def growByteBuffer(self): + """Doubles the size of the byteslice, and copies the old data towards + the end of the new buffer (since we build the buffer backwards).""" + if len(self.Bytes) == Builder.MAX_BUFFER_SIZE: + msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes" + raise BuilderSizeError(msg) + + newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE) + if newSize == 0: + newSize = 1 + bytes2 = bytearray(newSize) + bytes2[newSize-len(self.Bytes):] = self.Bytes + self.Bytes = bytes2 + ## @endcond + + def Head(self): + """Get the start of useful data in the underlying byte buffer. + + Note: unlike other functions, this value is interpreted as from the + left. + """ + ## @cond FLATBUFFERS_INTERNAL + return self.head + ## @endcond + + ## @cond FLATBUFFERS_INTERNAL + def Offset(self): + """Offset relative to the end of the buffer.""" + return UOffsetTFlags.py_type(len(self.Bytes) - self.Head()) + + def Pad(self, n): + """Pad places zeros at the current offset.""" + for i in range_func(n): + self.Place(0, N.Uint8Flags) + + def Prep(self, size, additionalBytes): + """ + Prep prepares to write an element of `size` after `additional_bytes` + have been written, e.g. if you write a string, you need to align + such the int length field is aligned to SizeInt32, and the string + data follows it directly. + If all you need to do is align, `additionalBytes` will be 0. + """ + + # Track the biggest thing we've ever aligned to. + if size > self.minalign: + self.minalign = size + + # Find the amount of alignment needed such that `size` is properly + # aligned after `additionalBytes`: + alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1 + alignSize &= (size - 1) + + # Reallocate the buffer if needed: + while self.Head() < alignSize+size+additionalBytes: + oldBufSize = len(self.Bytes) + self.growByteBuffer() + updated_head = self.head + len(self.Bytes) - oldBufSize + self.head = UOffsetTFlags.py_type(updated_head) + self.Pad(alignSize) + + def PrependSOffsetTRelative(self, off): + """ + PrependSOffsetTRelative prepends an SOffsetT, relative to where it + will be written. + """ + + # Ensure alignment is already done: + self.Prep(N.SOffsetTFlags.bytewidth, 0) + if not (off <= self.Offset()): + msg = "flatbuffers: Offset arithmetic error." + raise OffsetArithmeticError(msg) + off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth + self.PlaceSOffsetT(off2) + ## @endcond + + def PrependUOffsetTRelative(self, off): + """Prepends an unsigned offset into vector data, relative to where it + will be written. + """ + + # Ensure alignment is already done: + self.Prep(N.UOffsetTFlags.bytewidth, 0) + if not (off <= self.Offset()): + msg = "flatbuffers: Offset arithmetic error." + raise OffsetArithmeticError(msg) + off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth + self.PlaceUOffsetT(off2) + + ## @cond FLATBUFFERS_INTERNAL + def StartVector(self, elemSize, numElems, alignment): + """ + StartVector initializes bookkeeping for writing a new vector. + + A vector has the following format: + - + - +, where T is the type of elements of this vector. + """ + + self.assertNotNested() + self.nested = True + self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems) + self.Prep(alignment, elemSize*numElems) # In case alignment > int. + return self.Offset() + ## @endcond + + def EndVector(self, vectorNumElems): + """EndVector writes data necessary to finish vector construction.""" + + self.assertNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = False + ## @endcond + # we already made space for this, so write without PrependUint32 + self.PlaceUOffsetT(vectorNumElems) + return self.Offset() + + def CreateString(self, s, encoding='utf-8', errors='strict'): + """CreateString writes a null-terminated byte string as a vector.""" + + self.assertNotNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = True + ## @endcond + + if isinstance(s, compat.string_types): + x = s.encode(encoding, errors) + elif isinstance(s, compat.binary_types): + x = s + else: + raise TypeError("non-string passed to CreateString") + + self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth) + self.Place(0, N.Uint8Flags) + + l = UOffsetTFlags.py_type(len(s)) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond + self.Bytes[self.Head():self.Head()+l] = x + + return self.EndVector(len(x)) + + def CreateByteVector(self, x): + """CreateString writes a byte vector.""" + + self.assertNotNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = True + ## @endcond + + if not isinstance(x, compat.binary_types): + raise TypeError("non-byte vector passed to CreateByteVector") + + self.Prep(N.UOffsetTFlags.bytewidth, len(x)*N.Uint8Flags.bytewidth) + + l = UOffsetTFlags.py_type(len(x)) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond + self.Bytes[self.Head():self.Head()+l] = x + + return self.EndVector(len(x)) + + def CreateNumpyVector(self, x): + """CreateNumpyVector writes a numpy array into the buffer.""" + + if np is None: + # Numpy is required for this feature + raise NumpyRequiredForThisFeature("Numpy was not found.") + + if not isinstance(x, np.ndarray): + raise TypeError("non-numpy-ndarray passed to CreateNumpyVector") + + if x.dtype.kind not in ['b', 'i', 'u', 'f']: + raise TypeError("numpy-ndarray holds elements of unsupported datatype") + + if x.ndim > 1: + raise TypeError("multidimensional-ndarray passed to CreateNumpyVector") + + self.StartVector(x.itemsize, x.size, x.dtype.alignment) + + # Ensure little endian byte ordering + if x.dtype.str[0] == "<": + x_lend = x + else: + x_lend = x.byteswap(inplace=False) + + # Calculate total length + l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond + + # tobytes ensures c_contiguous ordering + self.Bytes[self.Head():self.Head()+l] = x_lend.tobytes(order='C') + + return self.EndVector(x.size) + + ## @cond FLATBUFFERS_INTERNAL + def assertNested(self): + """ + Check that we are in the process of building an object. + """ + + if not self.nested: + raise IsNotNestedError() + + def assertNotNested(self): + """ + Check that no other objects are being built while making this + object. If not, raise an exception. + """ + + if self.nested: + raise IsNestedError() + + def assertStructIsInline(self, obj): + """ + Structs are always stored inline, so need to be created right + where they are used. You'll get this error if you created it + elsewhere. + """ + + N.enforce_number(obj, N.UOffsetTFlags) + if obj != self.Offset(): + msg = ("flatbuffers: Tried to write a Struct at an Offset that " + "is different from the current Offset of the Builder.") + raise StructIsNotInlineError(msg) + + def Slot(self, slotnum): + """ + Slot sets the vtable key `voffset` to the current location in the + buffer. + + """ + self.assertNested() + self.current_vtable[slotnum] = self.Offset() + ## @endcond + + def __Finish(self, rootTable, sizePrefix, file_identifier=None): + """Finish finalizes a buffer, pointing to the given `rootTable`.""" + N.enforce_number(rootTable, N.UOffsetTFlags) + + if file_identifier is not None: + self.Prep(N.UOffsetTFlags.bytewidth, N.Uint8Flags.bytewidth*4) + + # Convert bytes object file_identifier to an array of 4 8-bit integers, + # and use big-endian to enforce size compliance. + # https://docs.python.org/2/library/struct.html#format-characters + file_identifier = N.struct.unpack(">BBBB", file_identifier) + for i in range(encode.FILE_IDENTIFIER_LENGTH-1, -1, -1): + # Place the bytes of the file_identifer in reverse order: + self.Place(file_identifier[i], N.Uint8Flags) + + self.PrependUOffsetTRelative(rootTable) + if sizePrefix: + size = len(self.Bytes) - self.Head() + N.enforce_number(size, N.Int32Flags) + self.PrependInt32(size) + self.finished = True + return self.Head() + + def Finish(self, rootTable, file_identifier=None): + """Finish finalizes a buffer, pointing to the given `rootTable`.""" + return self.__Finish(rootTable, False, file_identifier=file_identifier) + + def FinishSizePrefixed(self, rootTable, file_identifier=None): + """ + Finish finalizes a buffer, pointing to the given `rootTable`, + with the size prefixed. + """ + return self.__Finish(rootTable, True, file_identifier=file_identifier) + + ## @cond FLATBUFFERS_INTERNAL + def Prepend(self, flags, off): + self.Prep(flags.bytewidth, 0) + self.Place(off, flags) + + def PrependSlot(self, flags, o, x, d): + N.enforce_number(x, flags) + N.enforce_number(d, flags) + if x != d or self.forceDefaults: + self.Prepend(flags, x) + self.Slot(o) + + def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args) + + def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args) + + def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args) + + def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args) + + def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args) + + def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args) + + def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args) + + def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args) + + def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args) + + def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args) + + def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags, + *args) + + def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags, + *args) + + def PrependUOffsetTRelativeSlot(self, o, x, d): + """ + PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at + vtable slot `o`. If value `x` equals default `d`, then the slot will + be set to zero and no other data will be written. + """ + + if x != d or self.forceDefaults: + self.PrependUOffsetTRelative(x) + self.Slot(o) + + def PrependStructSlot(self, v, x, d): + """ + PrependStructSlot prepends a struct onto the object at vtable slot `o`. + Structs are stored inline, so nothing additional is being added. + In generated code, `d` is always 0. + """ + + N.enforce_number(d, N.UOffsetTFlags) + if x != d: + self.assertStructIsInline(x) + self.Slot(v) + + ## @endcond + + def PrependBool(self, x): + """Prepend a `bool` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.BoolFlags, x) + + def PrependByte(self, x): + """Prepend a `byte` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Uint8Flags, x) + + def PrependUint8(self, x): + """Prepend an `uint8` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Uint8Flags, x) + + def PrependUint16(self, x): + """Prepend an `uint16` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Uint16Flags, x) + + def PrependUint32(self, x): + """Prepend an `uint32` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Uint32Flags, x) + + def PrependUint64(self, x): + """Prepend an `uint64` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Uint64Flags, x) + + def PrependInt8(self, x): + """Prepend an `int8` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Int8Flags, x) + + def PrependInt16(self, x): + """Prepend an `int16` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Int16Flags, x) + + def PrependInt32(self, x): + """Prepend an `int32` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Int32Flags, x) + + def PrependInt64(self, x): + """Prepend an `int64` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Int64Flags, x) + + def PrependFloat32(self, x): + """Prepend a `float32` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Float32Flags, x) + + def PrependFloat64(self, x): + """Prepend a `float64` to the Builder buffer. + + Note: aligns and checks for space. + """ + self.Prepend(N.Float64Flags, x) + + def ForceDefaults(self, forceDefaults): + """ + In order to save space, fields that are set to their default value + don't get serialized into the buffer. Forcing defaults provides a + way to manually disable this optimization. When set to `True`, will + always serialize default values. + """ + self.forceDefaults = forceDefaults + +############################################################## + + ## @cond FLATBUFFERS_INTERNAL + def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x) + + def Place(self, x, flags): + """ + Place prepends a value specified by `flags` to the Builder, + without checking for available space. + """ + + N.enforce_number(x, flags) + self.head = self.head - flags.bytewidth + encode.Write(flags.packer_type, self.Bytes, self.Head(), x) + + def PlaceVOffsetT(self, x): + """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking + for space. + """ + N.enforce_number(x, N.VOffsetTFlags) + self.head = self.head - N.VOffsetTFlags.bytewidth + encode.Write(packer.voffset, self.Bytes, self.Head(), x) + + def PlaceSOffsetT(self, x): + """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking + for space. + """ + N.enforce_number(x, N.SOffsetTFlags) + self.head = self.head - N.SOffsetTFlags.bytewidth + encode.Write(packer.soffset, self.Bytes, self.Head(), x) + + def PlaceUOffsetT(self, x): + """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking + for space. + """ + N.enforce_number(x, N.UOffsetTFlags) + self.head = self.head - N.UOffsetTFlags.bytewidth + encode.Write(packer.uoffset, self.Bytes, self.Head(), x) + ## @endcond + +## @cond FLATBUFFERS_INTERNAL +def vtableEqual(a, objectStart, b): + """vtableEqual compares an unwritten vtable to a written vtable.""" + + N.enforce_number(objectStart, N.UOffsetTFlags) + + if len(a) * N.VOffsetTFlags.bytewidth != len(b): + return False + + for i, elem in enumerate(a): + x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth) + + # Skip vtable entries that indicate a default value. + if x == 0 and elem == 0: + pass + else: + y = objectStart - elem + if x != y: + return False + return True +## @endcond +## @} diff --git a/utensor_cgen/third_party/flatbuffers/compat.py b/utensor_cgen/third_party/flatbuffers/compat.py new file mode 100644 index 00000000..2fc9cca0 --- /dev/null +++ b/utensor_cgen/third_party/flatbuffers/compat.py @@ -0,0 +1,81 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" A tiny version of `six` to help with backwards compability. Also includes + compatibility helpers for numpy. """ + +import sys +import imp + +PY2 = sys.version_info[0] == 2 +PY26 = sys.version_info[0:2] == (2, 6) +PY27 = sys.version_info[0:2] == (2, 7) +PY275 = sys.version_info[0:3] >= (2, 7, 5) +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = (str,) + binary_types = (bytes,bytearray) + range_func = range + memoryview_type = memoryview + struct_bool_decl = "?" +else: + string_types = (unicode,) + if PY26 or PY27: + binary_types = (str,bytearray) + else: + binary_types = (str,) + range_func = xrange + if PY26 or (PY27 and not PY275): + memoryview_type = buffer + struct_bool_decl = " {'start': op_idx, 'end': op_idx} - use_def_table = self._create_resource_table(new_ugraph) - allocate_table = dict() - allocate_success = self.allocate_graph(new_ugraph, allocate_table, use_def_table, self.buff_size, self.unit_size) - - if allocate_success: - for node_name in new_ugraph.topo_order: - in_t_infos = new_ugraph.ops_info[node_name].input_tensors - for in_o in in_t_infos: - if in_o.name in allocate_table: - new_ugraph.data_manager.address = (in_o.name, allocate_table[in_o.name]['offsetstart']) - out_t_infos = new_ugraph.ops_info[node_name].output_tensors - for out_o in out_t_infos: - if out_o.name in allocate_table: - new_ugraph.data_manager.address = (out_o.name, allocate_table[out_o.name]['offsetstart']) - return new_ugraph - return ugraph - - def _query_offset_fromallocate_table(self, allocate_table, start, end): - new_start = start - new_end = end - for key in allocate_table: - if allocate_table[key]['offsetstart'] >= start and allocate_table[key]['offsetend'] <= end: - continue - elif allocate_table[key]['offsetstart'] <= start and allocate_table[key]['offsetend'] >= start: - new_start = allocate_table[key]['offsetstart'] - if allocate_table[key]['offsetend'] >= end: - new_end = max(new_end, allocate_table[key]['offsetend']) - else: - new_end = max(end, new_end) - elif allocate_table[key]['offsetstart'] >= start and allocate_table[key]['offsetend'] >= start: - if allocate_table[key]['offsetend'] >= end: - new_end = max(new_end, allocate_table[key]['offsetend']) - else: - new_end = max(end, new_end) - return new_start, new_end - - def _query_time_fromallocate_table(self, allocate_table, start, end): - time_start = start - time_end = end - for key in allocate_table: - if allocate_table[key]['start'] >= start and allocate_table[key]['end'] <= end: - continue - elif allocate_table[key]['start'] <= start and allocate_table[key]['end'] >= start: - if allocate_table[key]['end'] >= end: - time_end = max(time_end, allocate_table[key]['end']) - else: - time_end = max(end, time_end) - elif allocate_table[key]['start'] >= start and allocate_table[key]['end'] >= start: - if allocate_table[key]['end'] >= end: - time_end = max(time_end, allocate_table[key]['end']) - else: - time_end = max(end, time_end) - return time_start, time_end - - def _query_result(self, allocate_table, offset, length, timestart, timeend): - for key in allocate_table: - mem_occupied = ( - (allocate_table[key]['offsetstart'] >= offset and allocate_table[key]['offsetstart'] <= offset + length) or - (allocate_table[key]['offsetstart'] <= offset and allocate_table[key]['offsetend'] >= offset) - ) - life_span_occupied = ( - (allocate_table[key]['start'] >= timestart and allocate_table[key]['start'] <= timeend) or - (allocate_table[key]['start'] <= timestart and allocate_table[key]['end'] >= timestart) - ) - if mem_occupied and life_span_occupied: - return True - return False - - def allocate_tensor(self, tensors, tensor_index, allocate_table, use_def_table, buffer_size, unit_size): - if tensor_index == len(tensors): - return True - if tensors[tensor_index].name in allocate_table: - return self.allocate_tensor(tensors, tensor_index + 1, allocate_table, use_def_table, buffer_size, unit_size) - - tensor = tensors[tensor_index] - candidates = self._get_candidates(allocate_table, use_def_table, buffer_size, unit_size, tensor) - if not candidates: - return False - success = False - for candidate in candidates: - self._update_allocation_table(allocate_table, use_def_table, tensor, candidate, candidate + tensor.size) - success = self.allocate_tensor(tensors, tensor_index + 1, allocate_table, use_def_table, buffer_size, unit_size) - if success: - break - else: - self._remove_allocate_table(allocate_table, tensor) - return success - - def allocate_graph(self, ugraph, allocate_table, use_def_table, buffer_size, unit_size): - tensors = [] - - for node_name in ugraph.topo_order: - in_t_infos = [ - tensor - for tensor in ugraph.ops_info[node_name].input_tensors - if tensor.op.op_type != 'Inline' - ] - out_t_infos = [ - tensor - for tensor in ugraph.ops_info[node_name].output_tensors - if tensor.op.op_type != 'Inline' - ] - tensors.extend(in_t_infos) - tensors.extend(out_t_infos) - - succ = self.allocate_tensor(tensors, 0, allocate_table, use_def_table, buffer_size, unit_size) - return succ - - def _check(self, allocate_table, use_def_table, tensor, tensor_offset_start, tensor_offset_end): - valid = False - timestart = use_def_table[tensor.name]['start'] - timeend = use_def_table[tensor.name]['end'] - offset, length = self._query_offset_fromallocate_table(allocate_table, tensor_offset_start, tensor_offset_end) - timestart, timeend = self._query_time_fromallocate_table(allocate_table, timestart, timeend) - occupied = self._query_result(allocate_table, offset, length, timestart, timeend) - if not occupied: - valid = True - return valid - - def _get_candidates(self, allocate_table, use_def_table, buffer_size, unit_size, in_o): - ret = [] - for i in range(0, buffer_size, unit_size): - if self._check(allocate_table, use_def_table, in_o, i, i + in_o.size): - ret.append(i) - return ret - - def _update_allocation_table( - self, - allocate_table, - use_def_table, - tensor, - offset_start, - offset_end - ): - time_start = use_def_table[tensor.name]['start'] - time_end = use_def_table[tensor.name]['end'] - attribute = dict() - attribute['start'] = time_start - attribute['end'] = time_end - attribute['offsetstart'] = offset_start - attribute['offsetend'] = offset_end - allocate_table[tensor.name] = attribute - return allocate_table - - def _remove_allocate_table(self, allocate_table, tensor): - del allocate_table[tensor.name] - - def _create_resource_table(self, ugraph): - resource_table = dict() - len_map = { - op_name: idx - for idx, op_name in enumerate(ugraph.topo_order) - } - for node_name in ugraph.topo_order: - for tensor_info in ugraph.ops_info[node_name].input_tensors: - if tensor_info.name not in resource_table: - lifetime = dict() - lifetime['start'] = len_map[node_name] - lifetime['end'] = len_map[node_name] - resource_table[tensor_info.name] = lifetime - resource_table[tensor_info.name]['end']= len_map[node_name] - - for outtensor in ugraph.ops_info[node_name].output_tensors: - if outtensor.name not in resource_table: - lifetime = dict() - lifetime['start'] = len_map[node_name] - lifetime['end'] = len_map[node_name] - resource_table[outtensor.name] = lifetime - - return resource_table +__all__ = [ + "DropoutTransformer", + "BatchNormTransformer", + "InlineTransformer", +] @TransformerPipeline.register_transformer class BiasAddTransformer(Transformer): METHOD_NAME = 'biasAdd' KWARGS_NAMESCOPE = '_utensor_biasAdd' + APPLICABLE_LIBS = GENERIC_SENTINEL def transform(self, ugraph): for node_name in ugraph.topo_order: @@ -235,6 +53,10 @@ def transform(self, ugraph): class InlineTransformer(Transformer): METHOD_NAME = 'inline' KWARGS_NAMESCOPE = '_utensor_inline' + APPLICABLE_LIBS = GENERIC_SENTINEL + + def __init__(self): + super(InlineTransformer, self).__init__(prune_graph=False) def transform(self, ugraph): for node_name in ugraph.topo_order: @@ -263,81 +85,52 @@ class DropoutTransformer(Transformer): """ METHOD_NAME = 'dropout' KWARGS_NAMESCOPE = '_utensor_dropout' - TARGET_NODENAME_PATTERN = re.compile(r'(dropout[_\w\d]*)/.*') + APPLICABLE_LIBS = GENERIC_SENTINEL def __init__(self, name_pattern=r'(dropout[_\w\d]*)/.*'): + super(DropoutTransformer, self).__init__(prune_graph=True) self._op_name_pattern = re.compile(name_pattern) def transform(self, ugraph): - new_graph = uTensorGraph(name=ugraph.name, output_nodes=ugraph.output_nodes) - dropout_input_map = self._find_input(ugraph) - new_ops_info = {} - for node_name in ugraph.ops_info: - match = self._op_name_pattern.match(node_name) - if match: - # ignore all dropout nodes - continue - # replace inputs with dropout inputs - op_info = ugraph.ops_info[node_name] - in_t_infos = [deepcopy(t_info, {'ugraph': new_graph}) - for t_info in op_info.input_tensors] - out_t_infos = [deepcopy(t_info, {'ugraph': new_graph}) - for t_info in op_info.output_tensors] - op_attr = deepcopy(op_info.op_attr) - for i, t_info in enumerate(in_t_infos): - op_name = parse_tensor_name(t_info.name)[0] - match = self._op_name_pattern.match(op_name) - if match: - name_scope = match.group(1) - # assume there should be only on input except keep_prob - dropout_in_tensor = dropout_input_map[name_scope] - in_t_infos.pop(i) - in_t_infos.insert(i, dropout_in_tensor) - new_op_info = OperationInfo(name=op_info.name, - input_tensors=in_t_infos, - n_inputs=len(in_t_infos), - output_tensors=out_t_infos, - n_outputs=len(out_t_infos), - op_type=op_info.op_type, - lib_name=op_info.lib_name, - op_attr=op_attr, - ugraph=new_graph) - new_ops_info[node_name] = new_op_info - new_graph.ops_info = new_ops_info - new_graph._lib_name = ugraph._lib_name - return new_graph + new_ugraph = deepcopy(ugraph) + ( + dropout_input_map, + dropout_output_map, + clusters + ) = self._find_dropout_clusters(ugraph) + for name_scope, cluster in clusters.items(): + input_tensor = list(dropout_input_map[name_scope])[0].output_tensors[0] + output_tensor = list(dropout_output_map[name_scope])[0].output_tensors[0] + for node in output_tensor.op.output_nodes: + new_input = new_ugraph.ops_info[input_tensor.op.name].output_tensors[0] + new_node = new_ugraph.ops_info[node.name] + for i, tensor in enumerate(new_node.input_tensors): + if tensor.name == output_tensor.name: + new_node.input_tensors[i] = new_input + for op_info in cluster: + if op_info.name in new_ugraph.ops_info: + del new_ugraph.ops_info[op_info.name] + return new_ugraph def _find_dropout_clusters(self, ugraph): - clusters = defaultdict(lambda: []) - for node_name in ugraph.topo_order: - match = self._op_name_pattern.match(node_name) + clusters = defaultdict(set) + for op_info in ugraph.ops_info.values(): + match = self._op_name_pattern.match(op_info.name) if match: name_scope = match.group(1) - clusters[name_scope].append(node_name) - return dict(clusters) - - def _find_input(self, ugraph): - """dropout_name --> input_tensor_info - - input_tensor_info := the tensor info of a tensor which is not generated - in the dropout namescope but is consumed by ops in - dropout namescope with name not starts with 'keep_prob' - """ - clusters = self._find_dropout_clusters(ugraph) - input_map = {} - for node_name in ugraph.topo_order: - match = self._op_name_pattern.match(node_name) - if match: - name_scope = match.group(1) - cluster = clusters[name_scope] - op_info = ugraph.ops_info[node_name] - for in_tensor_info in op_info.input_tensors: - in_op_name = in_tensor_info.op.name - if in_op_name not in cluster and not in_op_name.startswith('keep_prob'): - input_map[name_scope] = in_tensor_info - # assuming there is only one input for dropout - break - return input_map + clusters[name_scope].add(op_info) + clusters = dict(clusters) + input_map = defaultdict(set) + output_map = defaultdict(set) + for name_scope, cluster in clusters.items(): + for op_info in cluster: + for tensor in op_info.input_tensors: + if not re.match(r"^(rate|keep_prob).*", tensor.op.name) and tensor.op not in cluster: + input_map[name_scope].add(tensor.op) + for out_node in op_info.output_nodes: + if out_node not in cluster: + output_map[name_scope].add(op_info) + return dict(input_map), dict(output_map), clusters @TransformerPipeline.register_transformer @@ -359,6 +152,7 @@ class DropoutTransformerV2(Transformer): """ METHOD_NAME = 'dropout_v2' KWARGS_NAMESCOPE = '_utensor_dropout_v2' + APPLICABLE_LIBS = set(["tensorflow"]) @property def pattern_ugraph(self): @@ -367,7 +161,7 @@ def pattern_ugraph(self): dummy_x = tf.constant(np.random.rand(10, 10), dtype=tf.float32, name='dummy_x') dummy_rate = tf.placeholder(dtype=tf.float32, name='dummy_rate') dropout = tf.nn.dropout(dummy_x, rate=dummy_rate, name='dropout') - patrn_ugraph = GraphDefParser.parse(graph.as_graph_def(), output_nodes=[dropout.op.name]) + patrn_ugraph = GraphDefParser(config={}).parse(graph.as_graph_def(), output_nodes=[dropout.op.name]) # replace dummy_x patrn_ugraph['dropout/truediv'].replace_with_null_input_tensor(0) # # replace dummy_rate @@ -389,7 +183,11 @@ def transform(self, ugraph): return new_ugraph def _transform_tf(self, ugraph): - matcher = uTensorGraphMatcher(pattern_ugraph=self.pattern_ugraph) + # FIXME: should use a generic op_equality_delegate + matcher = uTensorGraphMatcher( + pattern_ugraph=self.pattern_ugraph, + op_equality_delegate=uTensorOpEqualityDelegate + ) matches = matcher.match(ugraph, n=1) while matches: match = matches[0] @@ -425,6 +223,7 @@ class BatchNormTransformer(Transformer): """ METHOD_NAME = 'batch_norm' KWARGS_NAMESCOPE = '_batch_norm' + APPLICABLE_LIBS = GENERIC_SENTINEL def transform(self, ugraph): # TODO: implement this! @@ -437,6 +236,7 @@ class FakeGatherV2Transformer(Transformer): """ METHOD_NAME = 'fake_gather_v2' KWARGS_NAMESCOPE = '_fake_gatherv2' + APPLICABLE_LIBS = GENERIC_SENTINEL def transform(self, ugraph): logger.warning( diff --git a/utensor_cgen/transformer/optimizer.py b/utensor_cgen/transformer/optimizer.py index e83d081c..3b0facc1 100644 --- a/utensor_cgen/transformer/optimizer.py +++ b/utensor_cgen/transformer/optimizer.py @@ -1,7 +1,7 @@ from collections import defaultdict from copy import deepcopy -from .base import Transformer +from .base import GENERIC_SENTINEL, Transformer from .pipeline import TransformerPipeline __all__ = ['RefCntOptimizer'] @@ -12,9 +12,10 @@ class RefCntOptimizer(Transformer): METHOD_NAME = 'refcnt' KWARGS_NAMESCOPE = '_utensor_refcnt' + APPLICABLE_LIBS = GENERIC_SENTINEL - def __init__(self, **kwargs): - self.prune_graph = False + def __init__(self): + super(RefCntOptimizer, self).__init__(prune_graph=False) def transform(self, ugraph): """Optimization with reference count @@ -49,17 +50,12 @@ class IdOpRemoveOptimizer(Transformer): METHOD_NAME = 'remove_id_op' KWARGS_NAMESCOPE = '_utensor_remove_id_op' - - def __init__(self, **kwargs): - self.prune_graph = True + APPLICABLE_LIBS = GENERIC_SENTINEL def transform(self, ugraph): - if ugraph.lib_name == 'tensorflow': - return self._transform_tf(ugraph) - else: - raise RuntimeError('unsupported lib_name: {}'.format(ugraph.lib_name)) + return self._transform(ugraph) - def _transform_tf(self, ugraph): + def _transform(self, ugraph): ops_to_remove = [ op for op_name, op in ugraph.ops_info.items() diff --git a/utensor_cgen/transformer/pipeline.py b/utensor_cgen/transformer/pipeline.py index 500f58a0..1279f325 100644 --- a/utensor_cgen/transformer/pipeline.py +++ b/utensor_cgen/transformer/pipeline.py @@ -1,5 +1,4 @@ import re -from ast import literal_eval from .base import Transformer @@ -74,10 +73,4 @@ def _parse_expr(cls, expr): @classmethod def _get_kwargs(cls, kws_str): - kw_arg_strs = [s.strip() for s in kws_str.split(',')] - kwargs = {} - for kw_str in kw_arg_strs: - name, v_str = kw_str.split('=') - value = literal_eval(v_str) - kwargs[name] = value - return kwargs + return eval('dict({})'.format(kws_str), {}, {}) diff --git a/utensor_cgen/transformer/quantize.py b/utensor_cgen/transformer/quantize.py index d876ed69..d2a91536 100644 --- a/utensor_cgen/transformer/quantize.py +++ b/utensor_cgen/transformer/quantize.py @@ -1,22 +1,37 @@ -from tensorflow.tools.graph_transforms import TransformGraph +"""Legacy, DON'T USE +""" from utensor_cgen.frontend.tensorflow import GraphDefParser +from utensor_cgen.logger import logger from .base import Transformer from .pipeline import TransformerPipeline +try: + from tensorflow.tools.graph_transforms import TransformGraph +except ImportError: + logger.warning("trying to import deprecated quantization transformer") + TransformGraph = None + + __all__ = ['QuantizeTransformer'] @TransformerPipeline.register_transformer class QuantizeTransformer(Transformer): - METHOD_NAME = 'quantize' KWARGS_NAMESCOPE = '_quantize' + APPLICABLE_LIBS = set(["tensorflow"]) def transform(self, ugraph): + if ugraph.lib_name != 'tensorflow': + raise ValueError('only support tensorflow graph') graph_def = ugraph.graph_def + if TransformGraph is None: + raise RuntimeError("quantization is temporary not supported") quant_graph_def = TransformGraph(input_graph_def=graph_def, inputs=[], outputs=ugraph.output_nodes, transforms=["quantize_weights", "quantize_nodes"]) - return GraphDefParser.parse(quant_graph_def, - output_nodes=ugraph.output_nodes) + return GraphDefParser(config={}).parse( + quant_graph_def, + output_nodes=ugraph.output_nodes + ) diff --git a/utensor_cgen/transformer/tflite_exporter.py b/utensor_cgen/transformer/tflite_exporter.py new file mode 100644 index 00000000..0277f010 --- /dev/null +++ b/utensor_cgen/transformer/tflite_exporter.py @@ -0,0 +1,412 @@ +# -*- coding:utf8 -*- +r"""Graph Visualization Transformer + +Transformers that export a flatbuffer file presenting the Tensorflow Lite's model. +""" +import re +from collections import OrderedDict +import numpy as np +from copy import deepcopy + +from utensor_cgen.ir import OperationInfo, uTensorGraph +import utensor_cgen.third_party.flatbuffers as flatbuffers +import utensor_cgen.third_party.tflite as tflite +from utensor_cgen.third_party.tflite import * +from utensor_cgen.third_party.tflite.BuiltinOperator import BuiltinOperator +from utensor_cgen.third_party.tflite.BuiltinOptions import BuiltinOptions +from utensor_cgen.third_party.tflite.ActivationFunctionType import ActivationFunctionType +from utensor_cgen.third_party.tflite.TensorType import TensorType +from utensor_cgen.logger import logger +from utensor_cgen.utils import parse_tensor_name + +from .base import Transformer + +__all__ = ["TFLiteExporter"] + +def rename_ugraph_ops_(ugraph, name_old, name_new): + op = ugraph.ops_info.pop(name_old) + op.name = name_new + for out_tensor in op.output_tensors: + pass # renaming tensors + for out_node in op.output_nodes: + for in_tensor in out_node.input_tensors: + if in_tensor.op_name == name_old: + in_tensor.op_name = name_new + ugraph.ops_info[name_new] = op + for i, op_name in enumerate(ugraph.topo_order): + if op_name == name_old: + ugraph.topo_order[i] = name_new + break + for i, op_name in enumerate(ugraph.output_nodes): + if op_name == name_old: + ugraph.output_nodes[i] = name_new + break + + op.name = name_new + for out_tensor in op.output_tensors: + pass # renaming tensors + for out_node in op.output_nodes: + for in_tensor in out_node.input_tensors: + if in_tensor.op_name == name_old: + in_tensor.op_name = name_new + ugraph.ops_info[name_new] = op + for i, op_name in enumerate(ugraph.topo_order): + if op_name == name_old: + ugraph.topo_order[i] = name_new + break + for i, op_name in enumerate(ugraph.output_nodes): + if op_name == name_old: + ugraph.output_nodes[i] = name_new + break + +def get_fullyconnected_builtin_option(fbuilder, op_info): + + weight_format = tflite.FullyConnectedOptionsWeightsFormat.FullyConnectedOptionsWeightsFormat.DEFAULT + + tflite.FullyConnectedOptions.FullyConnectedOptionsStart(fbuilder) + #FIXME: node fusion and select an activation function here + tflite.FullyConnectedOptions.FullyConnectedOptionsAddFusedActivationFunction(fbuilder, ActivationFunctionType.NONE) + tflite.FullyConnectedOptions.FullyConnectedOptionsAddWeightsFormat(fbuilder, weight_format) + #FIXME: check the parameter here + tflite.FullyConnectedOptions.FullyConnectedOptionsAddKeepNumDims(fbuilder, False) + obj = tflite.FullyConnectedOptions.FullyConnectedOptionsEnd(fbuilder) + + return obj, BuiltinOptions.FullyConnectedOptions + +def get_add_builtin_option(fbuilder, op_info): + + tflite.AddOptions.AddOptionsStart(fbuilder) + #FIXME: node fusion and select an activation function here + tflite.AddOptions.AddOptionsAddFusedActivationFunction(fbuilder, ActivationFunctionType.NONE) + obj = tflite.AddOptions.AddOptionsEnd(fbuilder) + + return obj, BuiltinOptions.AddOptions + +def tensor_type_lookup(numpy_dtype): + TensorType = tflite.TensorType.TensorType + lookup_map = dict() + lookup_map[np.dtype('float32')] = TensorType.FLOAT32 + lookup_map[np.dtype('int8')] = TensorType.INT8 + + return lookup_map[numpy_dtype] + + +class FlatbufferOpManager: + op_list = list() + code_name_lookup = {v: k for k, v in BuiltinOperator.__dict__.items()} + + def regsiter_op(self, op_type_str): + if op_type_str not in BuiltinOperator.__dict__: + assert("invalid op str") + assert "op already been registered", not self.is_registered(op_type_str) + self.op_list.append(op_type_str) + return self.op_code(op_type_str) + + def is_registered(self, op_type_str): + return op_type_str in self.op_list + + def op_index(self, op_type_str): + return self.op_list.index(op_type_str) + def op_code(self, op_type_str): + return BuiltinOperator.__dict__[op_type_str] + def code2name(self, _op_code): + return self.code_name_lookup[_op_code] + def code2index(self, _op_code): + return self.op_list.index(self.code2name(_op_code)) + def index2code(self, _index): + op_type = self.op_list[_index] + return BuiltinOperator.__dict__[op_type] + def index2name(self, _index): + code = self.index2code(_index) + return self.code2name(code) + +class TFLiteExporter(Transformer): + METHOD_NAME = 'tflite_export' + KWARGS_NAMESCOPE = '_tflite_export' + __Max_fbuff_size = 1024 * 10 + static_op_types = ["Inline", "Const"] + schema_version = 3 + file_ident = "TFL3" + + def __init__(self, input_tensors, output_tensors): + """ + input_tensors: a list of input tensor names + output_tensors: alist of output tensor names + """ + + self.prune_graph = False #what is this? + self.op_manager = FlatbufferOpManager() + self.fbuilder = flatbuffers.Builder(self.__Max_fbuff_size) + self.tensor_buffer_index = OrderedDict() # out_name : data buff vec + # added to the Model object + self.tensor_index = OrderedDict() # out_name : ref tensor object + #added to the Subgraph object + self.input_tensors = input_tensors + self.output_tensors = output_tensors + + + def transform(self, ugraph): + self.__tflm_graph_legalize_(ugraph) + # create tensor data buffer + # create const tensors + # update tensor_index + self.__create_static_tensor(ugraph) + + # create intermediate tensors + # update tensor_index + self.__create_variable_tensors(ugraph) + + # interacts with FlatbufferOpManager + op_codes_vec = self.__create_op_codes(ugraph) # to be added into the Model + + # create subgraph + tensors_vec = self.__fb_vector(tflite.SubGraph.SubGraphStartTensorsVector, + [f_obj for t_name, f_obj in self.tensor_index.items()]) + + subgraph_input_t_names = [list(self.tensor_index.keys()).index(t_name) for t_name in self.input_tensors] + subgraph_input_vec = self.__fb_vector(tflite.SubGraph.SubGraphStartInputsVector, subgraph_input_t_names) + + subgraph_output_t_names = [list(self.tensor_index.keys()).index(t_name) for t_name in self.output_tensors] + subgraph_output_vec = self.__fb_vector(tflite.SubGraph.SubGraphStartOutputsVector, subgraph_output_t_names) + + # traverse ugraph + # compile a list of static ops : fb_ops_list + fb_ops_list = list() + for op_name in ugraph.topo_order: + op_info = ugraph.ops_info[op_name] + if not op_info.op_type in self.static_op_types: + fb_ops_list.append(self.add_Op(op_info)) + + # build the operators vector + ops_vec = self.__fb_vector(tflite.SubGraph.SubGraphStartOperatorsVector, fb_ops_list) + + # construct the subgraph + tflite.SubGraph.SubGraphStart(self.fbuilder) + tflite.SubGraph.SubGraphAddTensors(self.fbuilder, tensors_vec) + tflite.SubGraph.SubGraphAddOperators(self.fbuilder, ops_vec) + tflite.SubGraph.SubGraphAddInputs(self.fbuilder, subgraph_input_vec) + tflite.SubGraph.SubGraphAddOutputs(self.fbuilder, subgraph_output_vec) + subgraph = tflite.SubGraph.SubGraphEnd(self.fbuilder) + + #TFLM runtime only support 1 subgraph + subgraph_vec = self.__fb_vector(tflite.Model.ModelStartSubgraphsVector, [subgraph]) + + #model + buff_vec = self.__fb_vector(tflite.Model.ModelStartBuffersVector, [f_obj for t_name, f_obj in self.tensor_buffer_index.items()]) + + tflite.Model.ModelStart(self.fbuilder) + tflite.Model.ModelAddVersion(self.fbuilder, self.schema_version) + tflite.Model.ModelAddOperatorCodes(self.fbuilder, op_codes_vec) + tflite.Model.ModelAddSubgraphs(self.fbuilder, subgraph_vec) + tflite.Model.ModelAddBuffers(self.fbuilder, buff_vec) + + model = tflite.Model.ModelEnd(self.fbuilder) + + self.fbuilder.Finish(model, file_identifier=bytearray(self.file_ident, 'utf-8')) + + #output = self.fbuilder.Output() #do something with the output here + + return ugraph + + def __create_static_tensor(self, ugraph): + export_tensor_name = True + for op_name in ugraph.topo_order: + + op_info = ugraph.ops_info[op_name] + op_type = op_info.op_type + if op_type in self.static_op_types: #TODO: check if op data is empty + out_tensor_info = op_info.output_tensors[0] + out_tname, out_dtype, tensor_shape = (out_tensor_info.name, + out_tensor_info.dtype, + out_tensor_info.shape) + #weight + #value = op_info.op_attr['value'].value.np_array.flatten() #TODO: specify endianness here + value = op_info.op_attr['value'].value.np_array.flatten() #FIXME: deal with the proper attribute type here + raw_data = value.tobytes() + data_vec = self.__fb_vector(tflite.Buffer.BufferStartDataVector, raw_data) + + + tflite.Buffer.BufferStart(self.fbuilder) + tflite.Buffer.BufferAddData(self.fbuilder, data_vec) + tensor_buff = tflite.Buffer.BufferEnd(self.fbuilder) + + self.tensor_buffer_index[out_tname] = tensor_buff + + #shape + shape_vec = self.__fb_vector(tflite.Tensor.TensorStartShapeVector, tensor_shape) + + #name + if export_tensor_name: + tensor_name = self.fbuilder.CreateString(self.__legalize_name(out_tname)) + + #quantization + ## only per-layer supported + (q_scale, q_zero) = self.__sym_quantization_info(op_info) + q_param = self.__create_quantization_param([q_scale], [q_zero]) + + #tensor object + tflite.Tensor.TensorStart(self.fbuilder) + tflite.Tensor.TensorAddShape(self.fbuilder, shape_vec) + tflite.Tensor.TensorAddType(self.fbuilder, tensor_type_lookup(out_dtype)) + if export_tensor_name: + tflite.Tensor.TensorAddName(self.fbuilder, tensor_name) + tflite.Tensor.TensorAddQuantization(self.fbuilder, q_param) + tflite.Tensor.TensorAddIsVariable(self.fbuilder, False) + + tflite.Tensor.TensorAddBuffer(self.fbuilder, list(self.tensor_buffer_index.keys()).index(out_tname)) + new_tensor = tflite.Tensor.TensorEnd(self.fbuilder) + self.tensor_index[out_tname] = new_tensor + + #construct the output tensor vector for an op + def __output_vector(self, tensor_infos): + tensor_indexi = [list(self.tensor_index.keys()).index(tensor_info.name) for tensor_info in tensor_infos] + return self.__fb_vector(tflite.Operator.OperatorStartOutputsVector, tensor_indexi) + + #construct the input tensor vector for an op + def __input_vector(self, tensor_infos): + tensor_indexi = [list(self.tensor_index.keys()).index(tensor_info.name) for tensor_info in tensor_infos] + return self.__fb_vector(tflite.Operator.OperatorStartInputsVector, tensor_indexi) + + def __create_op_codes(self, ugraph): + #scan, translation and register + op_codes = list() + for op_name in ugraph.topo_order: + op_type = ugraph.ops_info[op_name].op_type + if op_type in self.static_op_types: + continue + + tflite.OperatorCode.OperatorCodeStart(self.fbuilder) + + if self.op_manager.is_registered(op_type): + continue + + opcode_index = self.op_manager.regsiter_op(op_type) + tflite.OperatorCode.OperatorCodeAddBuiltinCode(self.fbuilder, opcode_index) + op_codes.append(tflite.OperatorCode.OperatorCodeEnd(self.fbuilder)) + + op_code_vec = self.__fb_vector(tflite.Model.ModelStartOperatorCodesVector, op_codes) + return op_code_vec + + def add_Op(self, op_info): + op_inputs = self.__input_vector(op_info.input_tensors) + op_outputs = self.__output_vector(op_info.output_tensors) + + ##TODO: add op factory to deal with op options + op_option_factory = dict() + op_option_factory["FULLY_CONNECTED"] = get_fullyconnected_builtin_option + op_option_factory["ADD"] = get_add_builtin_option + + builtin_opt_func = op_option_factory[op_info.op_type] + + builtin_opt, builtin_opt_type = builtin_opt_func(self.fbuilder, op_info) + + tflite.Operator.OperatorStart(self.fbuilder) + tflite.Operator.OperatorAddOpcodeIndex(self.fbuilder, self.op_manager.op_index(op_info.op_type)) + tflite.Operator.OperatorAddInputs(self.fbuilder, op_inputs) + tflite.Operator.OperatorAddOutputs(self.fbuilder, op_outputs) + tflite.Operator.OperatorAddBuiltinOptionsType(self.fbuilder, builtin_opt_type) + tflite.Operator.OperatorAddBuiltinOptions(self.fbuilder, builtin_opt) + op = tflite.Operator.OperatorEnd(self.fbuilder) + + return op #to be added into SubGraphStartOperatorsVector + + def __sym_quantization_info(self, op_info): + """ + https://www.tensorflow.org/lite/performance/quantization_spec + C++: TfLiteAffineQuantization struct + zero-point = 0 : symmetric quantization + Returns (scale, zero) + + TODO: per-axis quantization + """ + + #values = op_info.op_attr['value'].value.np_array.flatten() + values = op_info.op_attr['value'].value.np_array.flatten() #FIXME: deal with the proper attribute type here + abs_max = np.absolute(values).max() + #based on quantizted int8 dtype + scale = 127 / abs_max + + return (scale, 0) + + def __create_quantization_param(self, scales, zeros): + assert len(scales) == len(zeros), "scales and zero-points length mismatch" + + q_scales_vec = self.__fb_vector(tflite.QuantizationParameters.QuantizationParametersStartScaleVector, scales) + q_zeros_vec = self.__fb_vector(tflite.QuantizationParameters.QuantizationParametersStartZeroPointVector, zeros) + + tflite.QuantizationParameters.QuantizationParametersStart(self.fbuilder) + tflite.QuantizationParameters.QuantizationParametersAddScale(self.fbuilder, q_scales_vec) + tflite.QuantizationParameters.QuantizationParametersAddZeroPoint(self.fbuilder, q_zeros_vec) + q_param = tflite.QuantizationParameters.QuantizationParametersEnd(self.fbuilder) + + return q_param + + # These are often the intermediate output tensors with online quantization + # Allocation them as Float32 to use online quantization + def __create_variable_tensors(self, ugraph): + tensor_infos = set() + for op_name in ugraph.topo_order: + tensor_infos.update(ugraph.ops_info[op_name].input_tensors) + tensor_infos.update(ugraph.ops_info[op_name].output_tensors) + for tensor_info in tensor_infos: + if ugraph.ops_info[tensor_info.op_name].op_type in self.static_op_types: + continue + + shape_vec = self.__fb_vector(tflite.Tensor.TensorStartShapeVector, tensor_info.shape) + + tensor_name = self.fbuilder.CreateString(self.__legalize_name(tensor_info.name)) + + #q_param = self.__create_quantization_param([-20],[200]) #TODO: workout how to treat the quantization here + + tflite.Tensor.TensorStart(self.fbuilder) + tflite.Tensor.TensorAddShape(self.fbuilder, shape_vec) + tflite.Tensor.TensorAddType(self.fbuilder, tensor_type_lookup(tensor_info.dtype)) + tflite.Tensor.TensorAddName(self.fbuilder, tensor_name) + #tflite.Tensor.TensorAddQuantization(self.fbuilder, q_param) + tflite.Tensor.TensorAddIsVariable(self.fbuilder, True) + #tflite.Tensor.TensorAddIsVariable(self.fbuilder, False) #FIXME: TOCO outputs False here, no idea why + + self.tensor_index[tensor_info.name] = tflite.Tensor.TensorEnd(self.fbuilder) + + def __legalize_name(self, str): + #TODO: legalize the name + return str + + def output(self): + return self.fbuilder.Output() + + def __fb_vector(self, vector_constructor, arr_list): + prepend_method_table = dict() + prepend_method_table[tflite.SubGraph.SubGraphStartTensorsVector] = self.fbuilder.PrependUOffsetTRelative + prepend_method_table[tflite.SubGraph.SubGraphStartOperatorsVector] = self.fbuilder.PrependUOffsetTRelative + prepend_method_table[tflite.Model.ModelStartSubgraphsVector] = self.fbuilder.PrependUOffsetTRelative + prepend_method_table[tflite.Model.ModelStartBuffersVector] = self.fbuilder.PrependUOffsetTRelative + prepend_method_table[tflite.Model.ModelStartOperatorCodesVector] = self.fbuilder.PrependUOffsetTRelative + prepend_method_table[tflite.Buffer.BufferStartDataVector] = self.fbuilder.PrependUint8 + prepend_method_table[tflite.Tensor.TensorStartShapeVector] = self.fbuilder.PrependInt32 + prepend_method_table[tflite.Operator.OperatorStartOutputsVector] = self.fbuilder.PrependInt32 + prepend_method_table[tflite.Operator.OperatorStartInputsVector] = self.fbuilder.PrependInt32 + prepend_method_table[tflite.QuantizationParameters.QuantizationParametersStartScaleVector] = self.fbuilder.PrependFloat32 + prepend_method_table[tflite.QuantizationParameters.QuantizationParametersStartZeroPointVector] = self.fbuilder.PrependFloat32 + prepend_method_table[tflite.SubGraph.SubGraphStartOutputsVector] = self.fbuilder.PrependInt32 + prepend_method_table[tflite.SubGraph.SubGraphStartInputsVector] = self.fbuilder.PrependInt32 + + preprend_method = prepend_method_table[vector_constructor] + + num_items = len(arr_list) + #assert num_items > 0 + + vector_constructor(self.fbuilder, num_items) + # reversed to handle prepend + for it in reversed(arr_list): + preprend_method(it) + + return self.fbuilder.EndVector(num_items) + + def __tflm_graph_legalize_(self, ugraph): + for _, op in ugraph.ops_info.items(): + assert op.op_type.startswith("TFLM_") or op.op_type in self.static_op_types + #TODO: strip the leading TFLM_ only + op.op_type = op.op_type.replace("TFLM_", '') +# How to construct the quantization parameter for intermediate tensors? +## zero point and scale diff --git a/utensor_cgen/utils.py b/utensor_cgen/utils.py index 006bf0dd..d4f2a666 100644 --- a/utensor_cgen/utils.py +++ b/utensor_cgen/utils.py @@ -6,19 +6,21 @@ from ast import literal_eval from collections import deque from copy import deepcopy +from functools import wraps from random import choice from string import ascii_letters, digits +from time import time import attr +import idx2numpy as idx2np import numpy as np from click.types import ParamType from toml import loads as _parse -import idx2numpy as idx2np from utensor_cgen.logger import logger __all__ = ["save_idx", "save_consts", "save_graph", "log_graph", - "NamescopedKWArgsParser", "NArgsParam", "MUST_OVERWRITEN"] + "NamescopedKWArgsParser", "NArgsParam", "MUST_OVERWRITE"] class LazyLoader(types.ModuleType): @@ -64,7 +66,9 @@ def attrib(self): def log_graph(graph_or_graph_def, logdir): - if isinstance(graph_or_graph_def, tf.GraphDef): + from tensorflow.compat.v1 import GraphDef + + if isinstance(graph_or_graph_def, GraphDef): graph = tf.Graph() with graph.as_default(): tf.import_graph_def(graph_or_graph_def, name='') @@ -197,7 +201,7 @@ class NamescopedKWArgsParser: TODO: replace it with a better data manager """ - def __init__(self, namespace, kwargs, data_manager=None, op_info=None): + def __init__(self, namespace, kwargs): ns_pattern = re.compile(r'^([^\d\W][\w\d_]*)__([^\d\W][\w\d_]*)') self._namespace = namespace self._private_kwargs = {} @@ -211,17 +215,7 @@ def __init__(self, namespace, kwargs, data_manager=None, op_info=None): self._private_kwargs[argname] = value else: self._shared_kwargs[key] = value - if op_info is not None and data_manager is not None: - outputs = [tensor_info.name for tensor_info in op_info.output_tensors] - for tensor in outputs: - values = data_manager.group(tensor) - for key, value in values.items(): - if key not in self._private_kwargs: - self._private_kwargs[key] = [] - self._private_kwargs[key].append(value) - else: - self._private_kwargs[key].append(value) - + def get(self, argname, default=None): """ Get value of given name in the namespace @@ -323,7 +317,7 @@ def __new__(cls, *args, **kwargs): return cls._obj -MUST_OVERWRITEN = _MustOverwrite() +MUST_OVERWRITE = _MustOverwrite() def topologic_order_graph(ugraph): @@ -337,7 +331,6 @@ def topologic_order_graph(ugraph): """ ugraph.topo_order = get_topologic_order(ugraph, ugraph.output_nodes)[::-1] - def get_topologic_order(ugraph, init_nodes=None): """ return list of op names in topological order @@ -352,10 +345,6 @@ def get_topologic_order(ugraph, init_nodes=None): - `Topological Sorting (wiki) `_ """ - if ugraph.lib_name != "tensorflow": - raise ValueError( - "topologic_order_graph works only on tensorflow graph" - ) if init_nodes is None: init_nodes = ugraph.output_nodes queue = deepcopy(init_nodes) @@ -388,7 +377,6 @@ def visit(node_name): visit(node_name) return ops_torder - def ops_bfs_queue(ugraph, init_nodes=None): if init_nodes is None: init_nodes = [ @@ -409,8 +397,7 @@ def ops_bfs_queue(ugraph, init_nodes=None): bfs_deck.append(op) return bfs_deck - -def prune_graph(ugraph): +def prune_graph(ugraph, output_nodes=None): """ Remove nodes that is no longer needed *in-place* @@ -420,11 +407,17 @@ def prune_graph(ugraph): :param ugraph: the graph to be pruned :type ugraph: :class:`.uTensorGraph` + :param output_nodes: the output nodes + :type output_nodes: List[String] """ new_ugraph = deepcopy(ugraph) + if output_nodes is None: + output_nodes = ugraph.output_nodes[:] + else: + new_ugraph.output_nodes = output_nodes[:] # BFS to find all ops you need - ops_in_need = set(ugraph.output_nodes) - queue = [name for name in ugraph.output_nodes] + ops_in_need = set(output_nodes) + queue = [name for name in output_nodes] visited = set([]) while queue: op_name = queue.pop(0) @@ -451,14 +444,38 @@ def prune_graph(ugraph): ops_to_remove.add(op_name) for op_name in ops_to_remove: new_ugraph.ops_info.pop(op_name) + topologic_order_graph(new_ugraph) return new_ugraph - def random_str(length=8): letters = ascii_letters+digits chars = [choice(letters) for _ in range(length)] return ''.join(chars) +def parse_toml(file_or_path): + if isinstance(file_or_path, str): + fid = open(file_or_path, 'r') + doc = _parse(fid.read()) + fid.close() + return doc + +def timed(func): + + @wraps(func) + def wrapped(*args, **kwargs): + start_time = time() + ret = func(*args, **kwargs) + end_time = time() + logger.info('collapsed time of calling %s: %0.4f seconds', func.__name__, end_time - start_time) + return ret + + return wrapped + +def is_abstract(func): + if isinstance(func, types.MethodType): + func = func.__func__ + return getattr(func, '__isabstractmethod__', False) + class class_property(object): @@ -489,16 +506,15 @@ def __getitem__(self, slice_obj): return cls(funcs=self._funcs[slice_obj]) -def parse_toml(file_or_path): - if isinstance(file_or_path, str): - fid = open(file_or_path, 'r') - doc = _parse(fid.read()) - fid.close() - return doc - - class Configuration(object): def __init__(self, defaults=None, user_config=None): + """ + Note + ---- + - any value that is in user_config should be in defaults + - any value that is not in defaults should not be in user_config + """ + # TODO: write a check on the inputs? if defaults is None: defaults = {} if user_config is None: @@ -521,16 +537,30 @@ def get(self, key, default=None): elif key in self._defaults: value = self._defaults[key] return value - + + def keys(self): + return self.to_dict().keys() + + def values(self): + return self.to_dict().values() + + def items(self): + config = self.to_dict() + return config.items() + + def to_dict(self): + config = deepcopy(self._defaults) + config.update(self._user_config) + return config def __getitem__(self, key): if key not in self: raise KeyError('invalid key: {}'.format(key)) value = self._user_config.get( key, self._defaults[key] - ) + ) if isinstance(value, dict): - value = type(self)(value, {}) + value = type(self)(self._defaults[key], value) return value def __contains__(self, key): @@ -543,3 +573,39 @@ def __repr__(self): ' user_config={} \n' ')' ).format(self._defaults, self._user_config) + + +class must_return_type(object): + + def __init__(self, type_): + self._expected_type = type_ + + def __call__(self, func): + @wraps(func) + def wrapped(*args, **kwargs): + ret = func(*args, **kwargs) + ret_cls = type(ret) + if not issubclass(ret_cls, self._expected_type): + raise TypeError( + "expecting {} to return value of type {}, get {}".format( + func, + self._expected_type, + ret_cls + ) + ) + return ret + wrapped._has_return_type_check = True + wrapped._expecting = self._expected_type + return wrapped + + @staticmethod + def get_expect_type(wrapped): + if isinstance(wrapped, classmethod): + wrapped = wrapped.__func__ + return wrapped._expecting + + @staticmethod + def return_type_is_ensured(wrapped): + if isinstance(wrapped, classmethod): + wrapped = wrapped.__func__ + return getattr(wrapped, '_has_return_type_check', False)