├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── THIRD-PARTY-LICENSES ├── cdk ├── cdk.json ├── index.ts ├── package.json └── tsconfig.json ├── lambda └── main.py └── notebook ├── lambda_efs_ml_demo.ipynb └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode,node 2 | # Edit at https://www.gitignore.io/?templates=osx,linux,python,windows,pycharm,visualstudiocode,node 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### Node ### 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | lerna-debug.log* 27 | 28 | # Diagnostic reports (https://nodejs.org/api/report.html) 29 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 30 | 31 | # Runtime data 32 | pids 33 | *.pid 34 | *.seed 35 | *.pid.lock 36 | 37 | # Directory for instrumented libs generated by jscoverage/JSCover 38 | lib-cov 39 | 40 | # Coverage directory used by tools like istanbul 41 | coverage 42 | *.lcov 43 | 44 | # nyc test coverage 45 | .nyc_output 46 | 47 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 48 | .grunt 49 | 50 | # Bower dependency directory (https://bower.io/) 51 | bower_components 52 | 53 | # node-waf configuration 54 | .lock-wscript 55 | 56 | # Compiled binary addons (https://nodejs.org/api/addons.html) 57 | build/Release 58 | 59 | # Dependency directories 60 | node_modules/ 61 | jspm_packages/ 62 | 63 | # TypeScript v1 declaration files 64 | typings/ 65 | 66 | # TypeScript cache 67 | *.tsbuildinfo 68 | 69 | # Optional npm cache directory 70 | .npm 71 | 72 | # Optional eslint cache 73 | .eslintcache 74 | 75 | # Optional REPL history 76 | .node_repl_history 77 | 78 | # Output of 'npm pack' 79 | *.tgz 80 | 81 | # Yarn Integrity file 82 | .yarn-integrity 83 | 84 | # dotenv environment variables file 85 | .env 86 | .env.test 87 | 88 | # parcel-bundler cache (https://parceljs.org/) 89 | .cache 90 | 91 | # next.js build output 92 | .next 93 | 94 | # nuxt.js build output 95 | .nuxt 96 | 97 | # vuepress build output 98 | .vuepress/dist 99 | 100 | # Serverless directories 101 | .serverless/ 102 | 103 | # FuseBox cache 104 | .fusebox/ 105 | 106 | # DynamoDB Local files 107 | .dynamodb/ 108 | 109 | ### OSX ### 110 | # General 111 | .DS_Store 112 | .AppleDouble 113 | .LSOverride 114 | 115 | # Icon must end with two \r 116 | Icon 117 | 118 | # Thumbnails 119 | ._* 120 | 121 | # Files that might appear in the root of a volume 122 | .DocumentRevisions-V100 123 | .fseventsd 124 | .Spotlight-V100 125 | .TemporaryItems 126 | .Trashes 127 | .VolumeIcon.icns 128 | .com.apple.timemachine.donotpresent 129 | 130 | # Directories potentially created on remote AFP share 131 | .AppleDB 132 | .AppleDesktop 133 | Network Trash Folder 134 | Temporary Items 135 | .apdisk 136 | 137 | ### PyCharm ### 138 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 139 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 140 | 141 | # User-specific stuff 142 | .idea/**/workspace.xml 143 | .idea/**/tasks.xml 144 | .idea/**/usage.statistics.xml 145 | .idea/**/dictionaries 146 | .idea/**/shelf 147 | 148 | # Generated files 149 | .idea/**/contentModel.xml 150 | 151 | # Sensitive or high-churn files 152 | .idea/**/dataSources/ 153 | .idea/**/dataSources.ids 154 | .idea/**/dataSources.local.xml 155 | .idea/**/sqlDataSources.xml 156 | .idea/**/dynamic.xml 157 | .idea/**/uiDesigner.xml 158 | .idea/**/dbnavigator.xml 159 | 160 | # Gradle 161 | .idea/**/gradle.xml 162 | .idea/**/libraries 163 | 164 | # Gradle and Maven with auto-import 165 | # When using Gradle or Maven with auto-import, you should exclude module files, 166 | # since they will be recreated, and may cause churn. Uncomment if using 167 | # auto-import. 168 | .idea/*.xml 169 | .idea/*.iml 170 | .idea 171 | # .idea/modules 172 | # *.iml 173 | # *.ipr 174 | 175 | # CMake 176 | cmake-build-*/ 177 | 178 | # Mongo Explorer plugin 179 | .idea/**/mongoSettings.xml 180 | 181 | # File-based project format 182 | *.iws 183 | 184 | # IntelliJ 185 | out/ 186 | 187 | # mpeltonen/sbt-idea plugin 188 | .idea_modules/ 189 | 190 | # JIRA plugin 191 | atlassian-ide-plugin.xml 192 | 193 | # Cursive Clojure plugin 194 | .idea/replstate.xml 195 | 196 | # Crashlytics plugin (for Android Studio and IntelliJ) 197 | com_crashlytics_export_strings.xml 198 | crashlytics.properties 199 | crashlytics-build.properties 200 | fabric.properties 201 | 202 | # Editor-based Rest Client 203 | .idea/httpRequests 204 | 205 | # Android studio 3.1+ serialized cache file 206 | .idea/caches/build_file_checksums.ser 207 | 208 | ### PyCharm Patch ### 209 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 210 | 211 | # *.iml 212 | # modules.xml 213 | # .idea/misc.xml 214 | # *.ipr 215 | 216 | # Sonarlint plugin 217 | .idea/sonarlint 218 | 219 | ### Python ### 220 | # Byte-compiled / optimized / DLL files 221 | __pycache__/ 222 | *.py[cod] 223 | *$py.class 224 | 225 | # C extensions 226 | *.so 227 | 228 | # Distribution / packaging 229 | .Python 230 | build/ 231 | develop-eggs/ 232 | dist/ 233 | downloads/ 234 | eggs/ 235 | .eggs/ 236 | lib64/ 237 | parts/ 238 | sdist/ 239 | var/ 240 | wheels/ 241 | pip-wheel-metadata/ 242 | share/python-wheels/ 243 | *.egg-info/ 244 | .installed.cfg 245 | *.egg 246 | MANIFEST 247 | 248 | # PyInstaller 249 | # Usually these files are written by a python script from a template 250 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 251 | *.manifest 252 | *.spec 253 | 254 | # Installer logs 255 | pip-log.txt 256 | pip-delete-this-directory.txt 257 | 258 | # Unit test / coverage reports 259 | htmlcov/ 260 | .tox/ 261 | .nox/ 262 | .coverage 263 | .coverage.* 264 | nosetests.xml 265 | coverage.xml 266 | *.cover 267 | .hypothesis/ 268 | .pytest_cache/ 269 | 270 | # Translations 271 | *.mo 272 | *.pot 273 | 274 | # Django stuff: 275 | local_settings.py 276 | db.sqlite3 277 | db.sqlite3-journal 278 | 279 | # Flask stuff: 280 | instance/ 281 | .webassets-cache 282 | 283 | # Scrapy stuff: 284 | .scrapy 285 | 286 | # Sphinx documentation 287 | docs/_build/ 288 | 289 | # PyBuilder 290 | target/ 291 | 292 | # Jupyter Notebook 293 | .ipynb_checkpoints 294 | 295 | # IPython 296 | profile_default/ 297 | ipython_config.py 298 | 299 | # pyenv 300 | .python-version 301 | 302 | # pipenv 303 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 304 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 305 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 306 | # install all needed dependencies. 307 | #Pipfile.lock 308 | 309 | # celery beat schedule file 310 | celerybeat-schedule 311 | 312 | # SageMath parsed files 313 | *.sage.py 314 | 315 | # Environments 316 | .venv 317 | env/ 318 | venv/ 319 | ENV/ 320 | env.bak/ 321 | venv.bak/ 322 | 323 | # Spyder project settings 324 | .spyderproject 325 | .spyproject 326 | 327 | # Rope project settings 328 | .ropeproject 329 | 330 | # mkdocs documentation 331 | /site 332 | 333 | # mypy 334 | .mypy_cache/ 335 | .dmypy.json 336 | dmypy.json 337 | 338 | # Pyre type checker 339 | .pyre/ 340 | 341 | ### VisualStudioCode ### 342 | .vscode 343 | 344 | ### VisualStudioCode Patch ### 345 | # Ignore all local history of files 346 | .history 347 | 348 | ### Windows ### 349 | # Windows thumbnail cache files 350 | Thumbs.db 351 | Thumbs.db:encryptable 352 | ehthumbs.db 353 | ehthumbs_vista.db 354 | 355 | # Dump file 356 | *.stackdump 357 | 358 | # Folder config file 359 | [Dd]esktop.ini 360 | 361 | # Recycle Bin used on file shares 362 | $RECYCLE.BIN/ 363 | 364 | # Windows Installer files 365 | *.cab 366 | *.msi 367 | *.msix 368 | *.msm 369 | *.msp 370 | 371 | # Windows shortcuts 372 | *.lnk 373 | 374 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode,node 375 | 376 | ### CDK-specific ignores ### 377 | *.swp 378 | cdk.context.json 379 | package-lock.json 380 | .cdk.staging 381 | cdk.out 382 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deep Learning inference with AWS Lambda and Amazon EFS 2 | 3 | ## Introduction 4 | 5 | [Amazon EFS support for AWS Lambda](https://aws.amazon.com/about-aws/whats-new/2020/06/aws-lambda-support-for-amazon-elastic-file-system-now-generally-/?nc1=h_ls) enables storing large deep learning (DL) framework libraries and models on Amazon EFS and load from Lambda. 6 | 7 | This repository shows and example of how to use such capability to host a pre-trained Tensorflow 2 object detection model (SSD) from TensorFlow Hub and execute inferences. 8 | 9 | Using EFS and Lambda for deep learning inference requires to execute two steps: 10 | 11 | 1. Storing the deep learning libraries and model on EFS 12 | 2. Creating a Lambda function for inference, which loads the libraries and model from the EFS file system 13 | 14 | The steps above are executed by deploying the project via the [AWS CDK](https://aws.amazon.com/cdk/) and then using [AWS CodeBuild](https://aws.amazon.com/codebuild/) for installing the required libraries to EFS. 15 | 16 | ## Running the example 17 | 18 | To run this example: 19 | 20 | ``` 21 | # Install the AWS CDK and bootstrap the target account (if this was never done before) 22 | $ npm install -g aws-cdk 23 | $ cdk bootstrap aws://{account_id}/{region} 24 | 25 | ``` 26 | 27 | ``` 28 | # clone repository 29 | $ git clone https://github.com/giuseppeporcelli/lambda-efs-ml-demo.git 30 | $ cd lambda-efs-ml-demo 31 | ``` 32 | 33 | ``` 34 | # Install packages for the project, build and deploy 35 | $ cd cdk/ 36 | $ npm install 37 | $ npm run build 38 | $ cdk deploy 39 | ``` 40 | 41 | After deployment, note the output: 42 | 43 | ``` 44 | Outputs: 45 | LambdaEFSMLDemo.LambdaFunctionName = 46 | LambdaEFSMLDemo-LambdaEFSMLExecuteInference17332C2-0546aa45dfXXXXXX 47 | ``` 48 | 49 | It takes a few minutes for AWS CodeBuild to deploy the libraries and framework to EFS. To test the Lambda function, run this command, replacing the function name: 50 | 51 | ``` 52 | $ aws lambda invoke \ 53 | --function-name LambdaEFSMLDemo-LambdaEFSMLExecuteInference17332C2-0546aa45dfXXXXXX \ 54 | --region us-east-1 \ 55 | --cli-binary-format raw-in-base64-out \ 56 | --payload '{"url": "https://images.pexels.com/photos/310983/pexels-photo-310983.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"}' \ 57 | --region us-east-1 \ 58 | /tmp/return.json 59 | ``` 60 | 61 | This is the output: 62 | 63 | ``` 64 | { 65 | "StatusCode": 200, 66 | "ExecutedVersion": "$LATEST" 67 | } 68 | ``` 69 | Here you can check the inference’s result: 70 | 71 | ``` 72 | $ tail /tmp/return.json 73 | 74 | {"statusCode": 200, "body": "{\"detection_boxes\": [[0.4908023476600647, 0.29575252532958984, 75 | 0.9392691254615784, 0.7548272609710693], [0.2890659272670746, 0.44450390338897705, 76 | 0.8515050411224365, 0.6895579099655151], [0.700944721698761, 0.5776023864746094, 77 | 0.9346526265144348, 0.763191819190979], [0.6840880513191223, 0.3130854070186615, 78 | 0.9327453970909119, 0.4774819314479828], [0.6840880513191223, 0.3130854070186615, 79 | 0.9327453970909119, 0.4774819314479828], [0.700944721698761, 0.5776023864746094, 80 | 0.9346526265144348, 0.763191819190979], [0.2874983847141266, 0.4483768939971924, 81 | 0.8595395088195801, 0.6886227130889893], [0.7860985994338989, 0.06227143853902817, 82 | 0.8351912498474121, 0.1968214511871338], [0.7888965010643005, 0.5248408913612366, 83 | 0.861912190914154, 0.5945348143577576], ... 84 | 85 | ``` 86 | 87 | Additionally, you can use the Jupyter notebook in the [notebook](./notebook) folder to print bounding boxes on images. 88 | 89 | ### Destroy the stack 90 | 91 | ``` 92 | # Afterwards, you can destroy the stack with 93 | $ cdk destroy 94 | ``` 95 | 96 | ## Security 97 | 98 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 99 | 100 | ## License 101 | 102 | The contents of this repository are licensed under the [MIT-0 License](./LICENSE). 103 |
104 | Third party content is licensed under the [Apache-2.0 License](./THIRD-PARTY-LICENSES). 105 | 106 | ## Authors 107 | 108 | [Giuseppe A. Porcelli](https://it.linkedin.com/in/giuporcelli) - Principal, ML Specialist Solutions Architect - Amazon Web Services EMEA
109 | [Diego Natali](https://www.linkedin.com/in/diego-natali) - Sr. Solutions Architect - Amazon Web Services EMEA 110 | -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES: -------------------------------------------------------------------------------- 1 | ** notebook/utils.py 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. -------------------------------------------------------------------------------- /cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "node index" 3 | } -------------------------------------------------------------------------------- /cdk/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | import cdk = require('@aws-cdk/core'); 5 | import ec2 = require('@aws-cdk/aws-ec2'); 6 | import iam = require('@aws-cdk/aws-iam'); 7 | import efs = require('@aws-cdk/aws-efs'); 8 | import codebuild = require('@aws-cdk/aws-codebuild'); 9 | import cr = require('@aws-cdk/custom-resources'); 10 | import lambda = require('@aws-cdk/aws-lambda'); 11 | import path = require('path'); 12 | import { Arn, Size, RemovalPolicy } from '@aws-cdk/core'; 13 | 14 | interface LambdaEFSMLStackProps extends cdk.StackProps { 15 | readonly installPackages?: string; 16 | } 17 | 18 | export class LambdaEFSMLStack extends cdk.Stack { 19 | constructor(scope: cdk.App, id: string, props: LambdaEFSMLStackProps) { 20 | super(scope, id, props); 21 | 22 | // VPC definition. 23 | const vpc = new ec2.Vpc(this, 'LambdaEFSMLVPC', { 24 | maxAzs: 2, 25 | natGateways: 1, 26 | }); 27 | 28 | // Security Group definitions. 29 | const ec2SecurityGroup = new ec2.SecurityGroup(this, 'LambdaEFSMLEC2SG', { 30 | vpc, 31 | securityGroupName: "LambdaEFSMLEC2SG", 32 | }); 33 | 34 | const lambdaSecurityGroup = new ec2.SecurityGroup(this, 'LambdaEFSMLLambdaSG', { 35 | vpc, 36 | securityGroupName: "LambdaEFSMLLambdaSG", 37 | }); 38 | 39 | const efsSecurityGroup = new ec2.SecurityGroup(this, 'LambdaEFSMLEFSSG', { 40 | vpc, 41 | securityGroupName: "LambdaEFSMLEFSSG", 42 | }); 43 | 44 | ec2SecurityGroup.connections.allowTo(efsSecurityGroup, ec2.Port.tcp(2049)); 45 | lambdaSecurityGroup.connections.allowTo(efsSecurityGroup, ec2.Port.tcp(2049)); 46 | 47 | // Elastic File System file system. 48 | // For the purpose of cost saving, provisioned troughput has been kept low. 49 | const fs = new efs.FileSystem(this, 'LambdaEFSMLEFS', { 50 | vpc: vpc, 51 | securityGroup: efsSecurityGroup, 52 | throughputMode: efs.ThroughputMode.PROVISIONED, 53 | provisionedThroughputPerSecond: Size.mebibytes(10), 54 | removalPolicy: RemovalPolicy.DESTROY 55 | }); 56 | 57 | const EfsAccessPoint = new efs.AccessPoint(this, 'EfsAccessPoint', { 58 | fileSystem: fs, 59 | path: '/lambda', 60 | posixUser: { 61 | gid: '1000', 62 | uid: '1000' 63 | }, 64 | createAcl: { 65 | ownerGid: '1000', 66 | ownerUid: '1000', 67 | permissions: '777' 68 | } 69 | }) 70 | 71 | // Lambda function to execute inference. 72 | const executeInferenceFunction = new lambda.Function(this, 'LambdaEFSMLExecuteInference', { 73 | runtime: lambda.Runtime.PYTHON_3_8, 74 | handler: 'main.lambda_handler', 75 | code: lambda.Code.fromAsset(path.join(__dirname, '..', 'lambda')), 76 | vpc, 77 | vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE }), 78 | securityGroup: lambdaSecurityGroup, 79 | timeout: cdk.Duration.minutes(2), 80 | memorySize: 3008, 81 | reservedConcurrentExecutions: 10, 82 | filesystem: lambda.FileSystem.fromEfsAccessPoint(EfsAccessPoint, '/mnt/python') 83 | }); 84 | executeInferenceFunction.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonElasticFileSystemClientFullAccess")); 85 | 86 | // Leveraging on AWS CodeBuild to install Python libraries to EFS. 87 | const codeBuildProject = new codebuild.Project(this, 'LambdaEFSMLCodeBuildProject', { 88 | projectName: "LambdaEFSMLCodeBuildProject", 89 | description: "Installs Python libraries to EFS.", 90 | vpc, 91 | buildSpec: codebuild.BuildSpec.fromObject({ 92 | version: '0.1', 93 | phases: { 94 | build: { 95 | commands: [ 96 | 'echo "Downloading and copying model..."', 97 | 'mkdir -p $CODEBUILD_EFS1/lambda/model', 98 | 'curl https://storage.googleapis.com/tfhub-modules/google/openimages_v4/ssd/mobilenet_v2/1.tar.gz --output /tmp/1.tar.gz', 99 | 'tar zxf /tmp/1.tar.gz -C $CODEBUILD_EFS1/lambda/model', 100 | 'echo "Installing virtual environment..."', 101 | 'mkdir -p $CODEBUILD_EFS1/lambda', 102 | 'python3 -m venv $CODEBUILD_EFS1/lambda/tensorflow', 103 | 'echo "Installing Tensorflow..."', 104 | 'source $CODEBUILD_EFS1/lambda/tensorflow/bin/activate && pip3 install ' + 105 | (props.installPackages ? props.installPackages : "tensorflow"), 106 | 'echo "Changing folder permissions..."', 107 | 'chown -R 1000:1000 $CODEBUILD_EFS1/lambda/' 108 | ] 109 | } 110 | }, 111 | }), 112 | 113 | environment: { 114 | buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('lambci/lambda:build-python3.8'), 115 | computeType: codebuild.ComputeType.LARGE, 116 | privileged: true, 117 | }, 118 | securityGroups: [ec2SecurityGroup], 119 | subnetSelection: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE }), 120 | timeout: cdk.Duration.minutes(30), 121 | }); 122 | 123 | // Configure EFS for CodeBuild. 124 | const cfnProject = codeBuildProject.node.defaultChild as codebuild.CfnProject; 125 | cfnProject.fileSystemLocations = [{ 126 | type: "EFS", 127 | //location: fs.mountTargetsAvailable + ".efs." + cdk.Stack.of(this).region + ".amazonaws.com:/", 128 | location: fs.fileSystemId + ".efs." + cdk.Stack.of(this).region + ".amazonaws.com:/", 129 | mountPoint: "/mnt/python", 130 | identifier: "efs1", 131 | mountOptions: "nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2" 132 | }] 133 | cfnProject.logsConfig = { 134 | cloudWatchLogs: { 135 | status: "ENABLED" 136 | } 137 | } 138 | 139 | // Triggers the CodeBuild project to install the python packages and model to the EFS file system 140 | const triggerBuildProject = new cr.AwsCustomResource(this, 'TriggerCodeBuild', { 141 | onCreate: { 142 | service: 'CodeBuild', 143 | action: 'startBuild', 144 | parameters: { 145 | projectName: codeBuildProject.projectName 146 | }, 147 | physicalResourceId: cr.PhysicalResourceId.fromResponse('build.id'), 148 | }, 149 | policy: cr.AwsCustomResourcePolicy.fromSdkCalls({ resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE }) 150 | }); 151 | 152 | // Create dependenct between EFS and Codebuild 153 | codeBuildProject.node.addDependency(EfsAccessPoint); 154 | 155 | // Output Lambda function name. 156 | new cdk.CfnOutput(this, 'LambdaFunctionName', { value: executeInferenceFunction.functionName }); 157 | } 158 | } 159 | 160 | const app = new cdk.App(); 161 | 162 | var props: LambdaEFSMLStackProps = { 163 | installPackages: undefined, 164 | env: { 165 | region: 'us-east-1' 166 | } 167 | } 168 | 169 | new LambdaEFSMLStack(app, 'LambdaEFSMLDemo', props); 170 | app.synth(); 171 | -------------------------------------------------------------------------------- /cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lambda-efs-ml-demo", 3 | "version": "1.0.0", 4 | "description": "Host Machine Learning models with AWS Lambda and Amazon EFS", 5 | "private": true, 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc -w", 9 | "cdk": "cdk" 10 | }, 11 | "author": { 12 | "name": "Amazon Web Services", 13 | "url": "https://aws.amazon.com", 14 | "organization": true 15 | }, 16 | "license": "Apache-2.0", 17 | "devDependencies": { 18 | "@types/node": "^8.10.40", 19 | "typescript": "~3.7.2", 20 | "aws-cdk": "*" 21 | }, 22 | "dependencies": { 23 | "@aws-cdk/core": "*", 24 | "@aws-cdk/aws-ec2": "*", 25 | "@aws-cdk/aws-iam": "*", 26 | "@aws-cdk/aws-efs": "*", 27 | "@aws-cdk/aws-codebuild": "*", 28 | "@aws-cdk/custom-resources": "*", 29 | "@aws-cdk/aws-cloudformation": "*", 30 | "@aws-cdk/aws-lambda": "*", 31 | "source-map-support": "^0.5.9" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target":"ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2016", "es2017.object", "es2017.string"], 6 | "strict": true, 7 | "noImplicitAny": true, 8 | "strictNullChecks": true, 9 | "noImplicitThis": true, 10 | "alwaysStrict": true, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": false, 15 | "inlineSourceMap": true, 16 | "inlineSources": true, 17 | "experimentalDecorators": true, 18 | "strictPropertyInitialization":false 19 | }, 20 | "exclude": ["cdk.out"] 21 | } -------------------------------------------------------------------------------- /lambda/main.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import sys 5 | import os 6 | 7 | # Setting library paths. 8 | efs_path = "/mnt/python" 9 | python_pkg_path = os.path.join(efs_path, "tensorflow/lib/python3.8/site-packages") 10 | sys.path.append(python_pkg_path) 11 | 12 | import json 13 | import string 14 | import time 15 | import io 16 | import requests 17 | 18 | # Importing TensorFlow 19 | import tensorflow as tf 20 | 21 | # Loading model 22 | model_path = os.path.join(efs_path, 'model/') 23 | loaded_model = tf.saved_model.load(model_path) 24 | detector = loaded_model.signatures['default'] 25 | 26 | def lambda_handler(event, context): 27 | r = requests.get(event['url']) 28 | img = tf.image.decode_jpeg(r.content, channels=3) 29 | 30 | # Executing inference. 31 | converted_img = tf.image.convert_image_dtype(img, tf.float32)[tf.newaxis, ...] 32 | start_time = time.time() 33 | result = detector(converted_img) 34 | end_time = time.time() 35 | 36 | obj = { 37 | 'detection_boxes' : result['detection_boxes'].numpy().tolist(), 38 | 'detection_scores': result['detection_scores'].numpy().tolist(), 39 | 'detection_class_entities': [el.decode('UTF-8') for el in result['detection_class_entities'].numpy()] 40 | } 41 | 42 | return { 43 | 'statusCode': 200, 44 | 'body': json.dumps(obj) 45 | } -------------------------------------------------------------------------------- /notebook/utils.py: -------------------------------------------------------------------------------- 1 | # The following code is licensed under: https://www.apache.org/licenses/LICENSE-2.0 2 | # and has been adapted from the TensorFlow object detection tutorial at: 3 | # https://www.tensorflow.org/hub/tutorials/object_detection 4 | 5 | # For downloading the image. 6 | import matplotlib.pyplot as plt 7 | import tempfile 8 | from six.moves.urllib.request import Request 9 | from six.moves.urllib.request import urlopen 10 | from six import BytesIO 11 | 12 | # For drawing onto the image. 13 | import numpy as np 14 | import tensorflow as tf 15 | from PIL import Image 16 | from PIL import ImageColor 17 | from PIL import ImageDraw 18 | from PIL import ImageFont 19 | from PIL import ImageOps 20 | 21 | def load_img(path): 22 | img = tf.io.read_file(path) 23 | img = tf.image.decode_jpeg(img, channels=3) 24 | return img 25 | 26 | def display_image(image): 27 | fig = plt.figure(figsize=(15, 12)) 28 | plt.grid(False) 29 | plt.imshow(image) 30 | 31 | def download_and_resize_image(url, new_width=256, new_height=256, 32 | display=False): 33 | _, filename = tempfile.mkstemp(suffix=".jpg") 34 | 35 | req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) 36 | 37 | response = urlopen(req) 38 | image_data = response.read() 39 | image_data = BytesIO(image_data) 40 | pil_image = Image.open(image_data) 41 | pil_image = ImageOps.fit(pil_image, (new_width, new_height), Image.ANTIALIAS) 42 | pil_image_rgb = pil_image.convert("RGB") 43 | pil_image_rgb.save(filename, format="JPEG", quality=90) 44 | print("Image downloaded to %s." % filename) 45 | if display: 46 | display_image(pil_image) 47 | return filename 48 | 49 | 50 | def draw_bounding_box_on_image(image, 51 | ymin, 52 | xmin, 53 | ymax, 54 | xmax, 55 | color, 56 | font, 57 | thickness=2, 58 | display_str_list=()): 59 | """Adds a bounding box to an image.""" 60 | draw = ImageDraw.Draw(image) 61 | im_width, im_height = image.size 62 | (left, right, top, bottom) = (xmin * im_width, xmax * im_width, 63 | ymin * im_height, ymax * im_height) 64 | draw.line([(left, top), (left, bottom), (right, bottom), (right, top), 65 | (left, top)], 66 | width=thickness, 67 | fill=color) 68 | 69 | # If the total height of the display strings added to the top of the bounding 70 | # box exceeds the top of the image, stack the strings below the bounding box 71 | # instead of above. 72 | display_str_heights = [font.getsize(ds)[1] for ds in display_str_list] 73 | # Each display_str has a top and bottom margin of 0.05x. 74 | total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights) 75 | 76 | if top > total_display_str_height: 77 | text_bottom = top 78 | else: 79 | text_bottom = top + total_display_str_height 80 | # Reverse list and print from bottom to top. 81 | for display_str in display_str_list[::-1]: 82 | text_width, text_height = font.getsize(display_str) 83 | margin = np.ceil(0.05 * text_height) 84 | draw.rectangle([(left, text_bottom - text_height - 2 * margin), 85 | (left + text_width, text_bottom)], 86 | fill=color) 87 | draw.text((left + margin, text_bottom - text_height - margin), 88 | display_str, 89 | fill="black", 90 | font=font) 91 | text_bottom -= text_height - 2 * margin 92 | 93 | 94 | def draw_boxes(image, boxes, class_names, scores, max_boxes=10, min_score=0.1): 95 | """Overlay labeled boxes on an image with formatted scores and label names.""" 96 | colors = list(ImageColor.colormap.values()) 97 | 98 | try: 99 | font = ImageFont.truetype("/usr/share/fonts/dejavu/DejaVuSansMono.ttf", 100 | 12) 101 | except IOError: 102 | print("Font not found, using default font.") 103 | font = ImageFont.load_default() 104 | 105 | for i in range(min(boxes.shape[0], max_boxes)): 106 | if scores[i] >= min_score: 107 | ymin, xmin, ymax, xmax = tuple(boxes[i]) 108 | display_str = "{}: {}%".format(class_names[i], 109 | int(100 * scores[i])) 110 | color = colors[hash(class_names[i]) % len(colors)] 111 | image_pil = Image.fromarray(np.uint8(image)).convert("RGB") 112 | draw_bounding_box_on_image( 113 | image_pil, 114 | ymin, 115 | xmin, 116 | ymax, 117 | xmax, 118 | color, 119 | font, 120 | display_str_list=[display_str]) 121 | np.copyto(image, np.array(image_pil)) 122 | return image --------------------------------------------------------------------------------