├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── dat ├── ars.txt ├── cfc.txt ├── file_1.txt ├── file_2.txt ├── gen.txt ├── lee.txt ├── mih.txt └── suz.txt ├── docker-compose.yml ├── ex_01_remo_func.ipynb ├── ex_02_remo_func.ipynb ├── ex_02_remo_objs.ipynb ├── ex_03_remo_meth.ipynb ├── ex_04_mult_pool.ipynb ├── ex_05_job_lib.ipynb ├── images ├── object_resolution.png ├── shared_memory.png ├── task_life.png └── task_ownership.png ├── pi.ipynb ├── requirements-dev.txt ├── requirements.txt ├── slides └── slides.pdf └── snekviz.png /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | prof_cpu.txt 3 | Running 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | default_stages: [commit, push] 4 | default_language_version: 5 | python: python3 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v3.4.0 9 | hooks: 10 | - id: check-executables-have-shebangs 11 | - id: check-merge-conflict 12 | - id: check-yaml 13 | - id: detect-private-key 14 | - id: no-commit-to-branch 15 | args: ["--branch", "main"] 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.12 2 | 3 | ENV LANGUAGE en_US.UTF-8 4 | ENV LC_ALL en_US.UTF-8 5 | ENV LANG en_US.UTF-8 6 | 7 | RUN apt-get update 8 | RUN apt-get install -y graphviz 9 | 10 | COPY requirements.txt requirements.txt 11 | RUN python3 -m pip install -U pip 12 | RUN python3 -m pip install -r requirements.txt 13 | 14 | ENV WORKDIR /ray_tutorial 15 | WORKDIR ${WORKDIR} 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Guided Tour of Ray Core 2 | 3 | An introductory tutorial about leveraging [Ray](https://docs.ray.io/) 4 | core features for distributed patterns. 5 | 6 | These examples have been tested in the following environments: 7 | 8 | - Ubuntu 18.04 LTS 9 | - macOS 11.6, Big Sur 10 | 11 | Using: 12 | 13 | - Ray versions 1.7+ 14 | - Python versions: 3.6, 3.7, 3.8 15 | 16 | See the `slides.pdf` file for the presentation slide deck that 17 | accompanies this tutorial. 18 | 19 | 20 | ## Getting Started 21 | 22 | To get started use `git` to clone this public repository: 23 | ```bash 24 | git clone https://github.com/DerwenAI/ray_tutorial.git 25 | cd ray_tutorial 26 | ``` 27 | 28 | ### Getting Started with a Virtual Environment 29 | 30 | Set up a local [*virtual environment*](https://docs.python.org/3/library/venv.html) 31 | and activate it: 32 | ```bash 33 | python3 -m venv venv 34 | source venv/bin/activate 35 | ``` 36 | 37 | Then use `pip` to install the required dependencies: 38 | ```bash 39 | python3 -m pip install -U pip 40 | python3 -m pip install -r requirements.txt 41 | python3 -m ipykernel install 42 | ``` 43 | 44 | Alternatively, if you use `conda` for installing Python packages: 45 | ```bash 46 | conda create -n ray_tutorial python=3.7 47 | conda activate ray_tutorial 48 | python3 -m pip install -r requirements.txt 49 | conda install ipykernel --name Python3 50 | ``` 51 | 52 | Note: if you run into any problems on Python 3.8 with "wheels" 53 | during a `pip` installation, you may need to use the `conda` 54 | approach instead. 55 | 56 | For some of the visualizations in `pi.ipynb` you also need to 57 | [install graphviz](https://graphviz.org/download/#executable-packages) 58 | 59 | 60 | Then launch the [JupyterLab](https://jupyterlab.readthedocs.io/) 61 | environment to run examples in this repo: 62 | ```bash 63 | jupyter-lab 64 | ``` 65 | 66 | Browse to to continue. 67 | 68 | 69 | ### Getting started with Docker-Compose 70 | 71 | First, install [docker](https://docs.docker.com/engine/install/) 72 | and [docker-compose](https://docs.docker.com/compose/install/), 73 | then: 74 | ```bash 75 | docker-compose up -d 76 | ``` 77 | 78 | Docker compose will start a JupyterLab service without requiring use 79 | of a security token. 80 | 81 | Browse to to continue. 82 | 83 | To stop this container: 84 | ```bash 85 | docker-compose stop 86 | ``` 87 | 88 | 89 | ## Syllabus 90 | 91 | ### Overview 92 | 93 | *A Guided Tour of Ray Core* covers an introductory, hands-on coding 94 | tour through the core features of Ray, which provide powerful yet 95 | easy-to-use design patterns for implementing distributed systems in 96 | Python. This training includes a brief talk to provide overview of 97 | concepts, then coding for remote functions, tasks, object references and 98 | resolutions, actors, and so on. 99 | 100 | Then we'll follow with Q&A. All code is available in notebooks in the GitHub repo. 101 | 102 | ### Intended Audience 103 | 104 | * Python developers who want to learn how to parallelize their application code 105 | 106 | Note: this material is not intended as an introduction to the higher 107 | level components in Ray, such as RLlib and Ray Tune. 108 | 109 | ### Prerequisites 110 | 111 | * Some prior experience developing code in Python 112 | * Basic understanding of distributed systems 113 | 114 | ### Key Takeaways 115 | 116 | * What are the Ray core features and how to use them? 117 | * In which contexts are the different approaches indicated? 118 | * Profiling methods, to decide when to make trade-offs (compute cost, memory, I/O, etc.) ? 119 | 120 | ### Course Outline 121 | 122 | 1. Introduction to Ray core features as a *pattern language* for distributed systems 123 | 2. Overview of the main Ray core features and their intended usage 124 | 3. Background, primary sources, and closely related resources about distributed systems 125 | 4. Code samples: 126 | * Remote Functions: [`ex_01_remo_func.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_01_remo_func.ipynb) 127 | * Remote Functions: [`ex_02_remo_func.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_02_remo_func.ipynb) 128 | * Remote Objects: [`ex_02_remo_objs.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_02_remo_objs.ipynb) 129 | * Remote Methods: [`ex_03_remo_meth.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_03_remo_meth.ipynb) 130 | * Multiprocessing Pool: [`ex_04_mult_pool.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_04_mult_pool.ipynb) 131 | * JobLib: [`ex_05_job_lib.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/ex_05_job_lib.ipynb) 132 | 5. Profiling: comparing trade-offs and overhead 133 | * Estimate Pi: [`pi.ipynb`](https://github.com/DerwenAI/ray_tutorial/blob/main/pi.ipynb) 134 | 6. Ray Summit, Anyscale Connect, developer forums, and other resources 135 | 7. Q&A 136 | 137 | 138 | ## Other Recommended Reading 139 | 140 | * [*Ray Design Patterns*](https://docs.google.com/document/d/167rnnDFIVRhHhK4mznEIemOtj63IOhtIPvSYaPgI4Fg/edit#heading=h.crt5flperkq3) 141 | * UC Berkeley EECS ["Our Pattern Language"](https://patterns.eecs.berkeley.edu/) 142 | * ["Futures and Promises"](http://dist-prog-book.com/chapter/2/futures.html) 143 | * ["Ray Distributed Library Patterns"](https://www.anyscale.com/blog/ray-distributed-library-patterns) 144 | * [`python-patterns`](https://github.com/faif/python-patterns) 145 | * [*Python Data Science*](https://jakevdp.github.io/PythonDataScienceHandbook/01.07-timing-and-profiling.html) 146 | * ["Profiling and Optimizing Jupyter Notebooks"](https://towardsdatascience.com/speed-up-jupyter-notebooks-20716cbe2025) 147 | * [Anyscale Academy](https://github.com/anyscale/academy) 148 | * [Ray Tutorial](https://github.com/ray-project/tutorial) 149 | 150 | 151 | ## Kudos 152 | 153 | [@dmatrix](https://github.com/dmatrix), 154 | [@penolove](https://github.com/penolove), 155 | [@deanwampler](https://github.com/deanwampler), 156 | [@ceteri](https://github.com/ceteri). 157 | 158 | -------------------------------------------------------------------------------- /dat/ars.txt: -------------------------------------------------------------------------------- 1 | Scientists have just discovered a bizarre pattern in global weather. Extreme heat waves like the one that hit the Eastern US in 2012, leaving at least 82 dead, don't just come out of nowhere. A new study, published today in Nature Geoscience, reveals that heat waves arise in a predictable pattern roughly 40 to 50 days after an event called the Pacific Extreme Pattern. During a Pacific Extreme Pattern, a large area of the Pacific north of Hawaii experiences unusual temperatures both at the water's surface and far above it in the atmosphere. Specifically, the southern part of the region gets far hotter than is typical, and the northeastern part of the region gets much colder. These unusual temperature patterns create a wave of weather effects that sweep over most of the US, then stop over the humid inland eastern region, creating a high pressure zone that brings clear skies and oppressive heat. The effect is intensified if there has been little rain in the east as well. The researchers examined weather data from sensors in both the Pacific and throughout the Eastern US between 1982 to 2015, finding that the Pacific Extreme Pattern was a strong predictor of heat waves. When the pattern emerges, there is a 1 in 4 chance that the Eastern US will experience extreme heat in 50 days. There's a 1 in 2 chance that they'll experience it in 40 days. Given that the Eastern US has a high population as well as many agricultural regions that are breadbaskets for the nation, this kind of long-range prediction could be lifesaving. What's remarkable about this study is that it might pave the way for other kinds of extreme weather prediction as well. Tornado forecasting researcher Victor Gensini, who was not involved in the study, told the Associated Press: The physical mechanisms underpinning the results of their study make sense. It is exciting that there is some skill demonstrated as far out as 50 days as this also gives us hope for linkages to other extreme weather events. The authors of the study emphasize that the heat waves are a combination of heat in the Pacific with low rain in the east. They write, "The results presented here link two previously identified precursors to hot days - precipitation deficits and an anomalous atmospheric wave train - with the occurrence of [Pacific Extreme Pattern]." That's one train you don't want to be riding when summer comes. Now, at least, you may have seven weeks to prepare for it. 2 | -------------------------------------------------------------------------------- /dat/cfc.txt: -------------------------------------------------------------------------------- 1 | Chelsea 'opted against' signing Salomon Rondón on deadline day. 2 | 3 | Chelsea reportedly opted against signing Salomón Rondón on deadline day despite their long search for a new centre forward. With Olivier Giroud expected to leave, the Blues targeted Edinson Cavani, Dries Mertens and Moussa Dembele – only to end up with none of them. According to Telegraph Sport, Dalian Yifang offered Rondón to Chelsea only for them to prefer keeping Giroud at the club. Manchester United were also linked with the Venezuela international before agreeing a deal for Shanghai Shenhua striker Odion Ighalo. Manager Frank Lampard made no secret of his transfer window frustration, hinting that to secure top four football he ‘needed’ signings. Their draw against Leicester on Saturday means they have won just four of the last 13 Premier League matches. -------------------------------------------------------------------------------- /dat/file_1.txt: -------------------------------------------------------------------------------- 1 | 0, 48, 72, 75, 54, 89, 84, 68, 9, 38 2 | 3, 40, 73, 58, 10, 35, 96, 6, 65, 33 3 | 55, 67, 12, 97, 52, 73, 7, 76, 39, 50 4 | 59, 34, 20, 40, 92, 55, 11, 39, 93, 38 5 | 89, 89, 37, 52, 48, 86, 49, 3, 19, 50 6 | 84, 37, 68, 11, 20, 36, 46, 61, 52, 77 7 | 70, 90, 56, 55, 49, 76, 94, 28, 32, 23 8 | 5, 44, 92, 15, 53, 63, 87, 75, 61, 25 9 | 51, 58, 29, 30, 93, 94, 52, 72, 80, 27 10 | 1, 28, 82, 35, 89, 36, 10, 84, 85, 65 11 | -------------------------------------------------------------------------------- /dat/file_2.txt: -------------------------------------------------------------------------------- 1 | 101, 31, 83,124, 41, 73, 0,121, 14, 98 2 | 137, 3, 77, 38,117, 79,104, 96, 62,117 3 | 99, 92,139, 29, 59, 30,116, 30, 74, 12 4 | 130,105,145,124, 39, 50, 66, 9,126, 24 5 | 32, 33, 34, 90, 43,140,136,149,140, 22 6 | 14, 8,138,101,136, 60, 41,110, 92,105 7 | 44, 76,104, 6,121,135, 41,132,131, 26 8 | 1, 1,112, 45,146, 29, 44,104, 75,122 9 | 132, 63, 70,109, 49, 75, 33, 36, 38,105 10 | 131, 71, 51, 79,109,146, 71, 27, 65,126 11 | -------------------------------------------------------------------------------- /dat/gen.txt: -------------------------------------------------------------------------------- 1 | First, a quick description of some popular algorithms & implementations for text summarization that exist today: the summarization module in gensim implements TextRank, an unsupervised algorithm based on weighted-graphs from a paper by Mihalcea et al. It was added by another incubator student Olavur Mortensen – see his previous post on this blog. It is built on top of the popular PageRank algorithm that Google used for ranking webpages. TextRank works as follows: Pre-process the text: remove stop words and stem the remaining words. Create a graph where vertices are sentences. Connect every sentence to every other sentence by an edge. The weight of the edge is how similar the two sentences are. Run the PageRank algorithm on the graph. Pick the vertices(sentences) with the highest PageRank score. In original TextRank the weights of an edge between two sentences is the percentage of words appearing in both of them. Gensim’s TextRank uses Okapi BM25 function to see how similar the sentences are. It is an improvement from a paper by Barrios et al. 2 | -------------------------------------------------------------------------------- /dat/lee.txt: -------------------------------------------------------------------------------- 1 | After more than four hours of tight play and a rapid-fire endgame, Google's artificially intelligent Go-playing computer system has won a second contest against grandmaster Lee Sedol, taking a two-games-to-none lead in their historic best-of-five match in downtown Seoul. The surprisingly skillful Google machine, known as AlphaGo, now needs only one more win to claim victory in the match. The Korean-born Lee Sedol will go down in defeat unless he takes each of the match's last three games. Though machines have beaten the best humans at chess, checkers, Othello, Scrabble, Jeopardy!, and so many other games considered tests of human intellect, they have never beaten the very best at Go. Game Three is set for Saturday afternoon inside Seoul's Four Seasons hotel. The match is a way of judging the suddenly rapid progress of artificial intelligence. One of the machine-learning techniques at the heart of AlphaGo has already reinvented myriad online services inside Google and other big-name Internet companies, helping to identify images, recognize commands spoken into smartphones, improve search engine results, and more. Meanwhile, another AlphaGo technique is now driving experimental robotics at Google and places like the University of California at Berkeley. This week's match can show how far these technologies have come - and perhaps how far they will go. Created in Asia over 2,500 year ago, Go is exponentially more complex than chess, and at least among humans, it requires an added degree of intuition. Lee Sedol is widely-regarded as the top Go player of the last decade, after winning more international titles than all but one other player. He is currently ranked number five in the world, and according to Demis Hassabis, who leads DeepMind, the Google AI lab that created AlphaGo, his team chose the Korean for this all-important match because they wanted an opponent who would be remembered as one of history's great players. Although AlphaGo topped Lee Sedol in the match's first game on Wednesday afternoon, the outcome of Game Two was no easier to predict. In his 1996 match with IBM's Deep Blue supercomputer, world chess champion Gary Kasparov lost the first game but then came back to win the second game and, eventually, the match as a whole. It wasn't until the following year that Deep Blue topped Kasparov over the course of a six-game contest. The thing to realize is that, after playing AlphaGo for the first time on Wednesday, Lee Sedol could adjust his style of play - just as Kasparov did back in 1996. But AlphaGo could not. Because this Google creation relies so heavily on machine learning techniques, the DeepMind team needs a good four to six weeks to train a new incarnation of the system. And that means they can't really change things during this eight-day match. "This is about teaching and learning," Hassabis told us just before Game Two. "One game is not enough data to learn from - for a machine - and training takes an awful lot of time." 2 | -------------------------------------------------------------------------------- /dat/mih.txt: -------------------------------------------------------------------------------- 1 | Compatibility of systems of linear constraints over the set of natural numbers. Criteria of compatibility of a system of linear Diophantine equations, strict inequations, and nonstrict inequations are considered. Upper bounds for components of a minimal set of solutions and algorithms of construction of minimal generating sets of solutions for all types of systems are given. These criteria and the corresponding algorithms for constructing a minimal supporting set of solutions can be used in solving all the considered types systems and systems of mixed types. 2 | -------------------------------------------------------------------------------- /dat/suz.txt: -------------------------------------------------------------------------------- 1 | A series of novel xanthine-based tricyclic heterocycles in 1H-imidazo[4,5-c]quinolin-4(5H)-ones was designed, synthesized, and tested as potential active bronchodilators. Inhibition of the Schulz-Dale (SD) reaction-induced contraction in trachea and inhibition of antigen inhalation-induced bronchospasm in passively sensitized guinea pigs served as primary in vitro and in vivo assays, respectively. Simultaneous measurement of acute lethal toxicity (minimum lethal dose; MLD, po) in mice allowed determination of a safety margin. The bronchodilatory activity of these heterocycles was considerably varied with the nature of substituents at the 5-position. The most active substituents at the 2- and 5-positions and on the aromatic ring were found to be hydrogen, n-butyl, and hydrogen, respectively. There was a bulk tolerance for lipophilic substituents at the 1-position. 5-Butyl-substituted compounds appeared to be less toxic than theophylline on the basis of MLD data. Thus 5-butyl-1-methyl-1H-imidazo[4,5-c]quinolin-4(5H)-one (10) (IC50 value of the SD assay = 0.25 microM, MLD > 300 mg/kg) was selected for further studies. Compound 10 (KF15570) reduced bronchoconstriction produced by antigen (Konzett-Rössler preparation in anesthetized guinea pigs, ED50 = 0.42 mg/kg, iv) more effectively than aminophylline (ethylenediamine salt of theophylline, ED50 = 7.8 mg/kg, iv) but had fewer side effects on the heart and CNS than theophylline. Compound 10 and its derivatives showed weak adenosine antagonism and phosphodiesterase (PDE) inhibition which could not account for their potent bronchodilation. Although their precise mechanism of action remains unclear, this series of novel tricyclic heterocycles represents a new class of bronchodilator. 2 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | services: 3 | jupyter-lab: 4 | build: . 5 | volumes: 6 | - .:/ray_tutorial 7 | command: jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.token='' 8 | network_mode: "host" -------------------------------------------------------------------------------- /ex_01_remo_func.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Guided Tour of Ray Core: Remote Functions" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "[*Remote Functions*](https://docs.ray.io/en/latest/walkthrough.html#remote-functions-tasks)\n", 15 | "involve using a `@ray.remote` decorator on a function. \n", 16 | "\n", 17 | "This implements a [*task parallelism*](https://patterns.eecs.berkeley.edu/?page_id=208) pattern, with properties: *data independence*, *stateless*\n", 18 | "\n", 19 | "But first the concept of tasks and ownership.\n", 20 | "\n", 21 | "Most of the system metadata is managed according to a decentralized concept called ownership: Each worker process manages and owns the tasks that it submits and the `ObjectRef`s returned by those tasks. The owner is responsible for ensuring execution of the task and facilitating the resolution of an `ObjectRef` to its underlying value. Similarly, a worker owns any objects that it created through a `ray.put` call.\n", 22 | "\n", 23 | "\n", 24 | "\n", 25 | "\n", 26 | "---" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": { 32 | "jp-MarkdownHeadingCollapsed": true, 33 | "tags": [] 34 | }, 35 | "source": [ 36 | "### Lifetime of a Ray Task\n", 37 | "\n", 38 | "The owner is responsible for ensuring execution of a submitted task and facilitating the resolution of the returned `ObjectRef` to its underlying value.\n", 39 | "The process that submits a task is considered to be the owner of the result and is responsible for acquiring resources from the raylet to execute the task. Here, the driver owns the result of `A`, and `Worker 1` owns the result of `B`.\n", 40 | "\n", 41 | "" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "### Launch a Ray locally, a single head node" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "First, let's start Ray… \n", 56 | "\n", 57 | "This will start Ray on the local host, with headnode and workers for each core or CPU available.\n", 58 | "You can check the resources being used." 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 19, 64 | "metadata": {}, 65 | "outputs": [ 66 | { 67 | "data": { 68 | "text/plain": [ 69 | "{'CPU': 12.0,\n", 70 | " 'memory': 13590429696.0,\n", 71 | " 'node:127.0.0.1': 1.0,\n", 72 | " 'object_store_memory': 6795214848.0}" 73 | ] 74 | }, 75 | "execution_count": 19, 76 | "metadata": {}, 77 | "output_type": "execute_result" 78 | } 79 | ], 80 | "source": [ 81 | "import logging\n", 82 | "import ray\n", 83 | "\n", 84 | "ray.init(\n", 85 | " ignore_reinit_error=True, # Don't print error messages if a Ray instance is already running. Attach to it\n", 86 | " logging_level=logging.ERROR, \n", 87 | ")\n", 88 | "ray.cluster_resources() # get the cluster resources" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "Ray Dashboard accessible at URI: [http://127.0.0.1:8265](http://127.0.0.1:8265)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "## Remote Functions example" 103 | ] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "metadata": {}, 108 | "source": [ 109 | "The following is just a regular Python function..." 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": 20, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "def my_function ():\n", 119 | " return 42" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "When called, it simply returns an integer:" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": 21, 132 | "metadata": {}, 133 | "outputs": [ 134 | { 135 | "data": { 136 | "text/plain": [ 137 | "42" 138 | ] 139 | }, 140 | "execution_count": 21, 141 | "metadata": {}, 142 | "output_type": "execute_result" 143 | } 144 | ], 145 | "source": [ 146 | "my_function()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "markdown", 151 | "metadata": {}, 152 | "source": [ 153 | "If you were to iterate through a sequence of calls to a function such as that, these calls would be performed *sequentially*." 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "However, by adding the `@ray.remote` decorator, a regular Python function becomes a Ray remote function:" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 22, 166 | "metadata": {}, 167 | "outputs": [], 168 | "source": [ 169 | "@ray.remote\n", 170 | "def my_function ():\n", 171 | " return 42" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "To invoke this remote function, use the `remote` method. This will immediately return an object ref (a *future* in Python) and then create a task that will be executed on a worker process." 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 23, 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "name": "stdout", 188 | "output_type": "stream", 189 | "text": [ 190 | "CPU times: user 1.42 ms, sys: 1.11 ms, total: 2.53 ms\n", 191 | "Wall time: 1.79 ms\n" 192 | ] 193 | }, 194 | { 195 | "data": { 196 | "text/plain": [ 197 | "ObjectRef(bbde8638d39a1245ffffffffffffffffffffffff0100000001000000)" 198 | ] 199 | }, 200 | "execution_count": 23, 201 | "metadata": {}, 202 | "output_type": "execute_result" 203 | } 204 | ], 205 | "source": [ 206 | "%%time\n", 207 | "\n", 208 | "obj_ref = my_function.remote()\n", 209 | "obj_ref" 210 | ] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "metadata": {}, 215 | "source": [ 216 | "The result can be retrieved with `ray.get`, which is a blocking call if the task is still not finished" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": 24, 222 | "metadata": {}, 223 | "outputs": [ 224 | { 225 | "name": "stdout", 226 | "output_type": "stream", 227 | "text": [ 228 | "CPU times: user 205 µs, sys: 99 µs, total: 304 µs\n", 229 | "Wall time: 286 µs\n" 230 | ] 231 | }, 232 | { 233 | "data": { 234 | "text/plain": [ 235 | "42" 236 | ] 237 | }, 238 | "execution_count": 24, 239 | "metadata": {}, 240 | "output_type": "execute_result" 241 | } 242 | ], 243 | "source": [ 244 | "%%time\n", 245 | "\n", 246 | "ray.get(obj_ref)" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "Invocations of Ray *remote functions* happen in parallel, and all computation gets performed in the background, driven by Ray's internal event loop. Remote calls return immediately, with a Python Future Object reference.\n", 254 | "\n", 255 | "To illustrate this, first let's define a relatively \"slow\" function, by adding a 10 second delay..." 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": 25, 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [ 264 | "import time\n", 265 | "import random\n", 266 | "\n", 267 | "@ray.remote\n", 268 | "def slow_function ():\n", 269 | " time.sleep(10)\n", 270 | " return random.randint(0, 9)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "Now we'll iterate through multiple calls, showing that this does not block:" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 26, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | "0\n", 290 | "1\n", 291 | "2\n", 292 | "3\n", 293 | "[ObjectRef(44ed5e1383be6308ffffffffffffffffffffffff0100000001000000), ObjectRef(d56d800cbde6ca14ffffffffffffffffffffffff0100000001000000), ObjectRef(38db0ab51c6b6cfbffffffffffffffffffffffff0100000001000000), ObjectRef(4e2ab276f14c37c2ffffffffffffffffffffffff0100000001000000)]\n", 294 | "CPU times: user 2.31 ms, sys: 1.84 ms, total: 4.15 ms\n", 295 | "Wall time: 2.31 ms\n" 296 | ] 297 | } 298 | ], 299 | "source": [ 300 | "%%time\n", 301 | "\n", 302 | "futures_list = []\n", 303 | "\n", 304 | "for i in range(4):\n", 305 | " future = slow_function.remote()\n", 306 | " futures_list.append(future)\n", 307 | " print(i)\n", 308 | "print(futures_list)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 27, 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "name": "stdout", 318 | "output_type": "stream", 319 | "text": [ 320 | "5\n", 321 | "9\n", 322 | "9\n", 323 | "5\n", 324 | "CPU times: user 288 ms, sys: 184 ms, total: 472 ms\n", 325 | "Wall time: 8.77 s\n" 326 | ] 327 | } 328 | ], 329 | "source": [ 330 | "%%time\n", 331 | "\n", 332 | "for future in futures_list:\n", 333 | " print(ray.get(future))" 334 | ] 335 | }, 336 | { 337 | "cell_type": "code", 338 | "execution_count": null, 339 | "metadata": {}, 340 | "outputs": [], 341 | "source": [ 342 | "%%time\n", 343 | "\n", 344 | "futures_list = []\n", 345 | "\n", 346 | "for i in range(4):\n", 347 | " future = slow_function.remote()\n", 348 | " futures_list.append(future)\n", 349 | "print(futures_list)" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": null, 355 | "metadata": {}, 356 | "outputs": [], 357 | "source": [ 358 | "%%time\n", 359 | "\n", 360 | "for future in futures_list:\n", 361 | " print(ray.get(future))" 362 | ] 363 | }, 364 | { 365 | "cell_type": "markdown", 366 | "metadata": {}, 367 | "source": [ 368 | "Note the difference between CPU times and wall clock?" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "metadata": { 374 | "tags": [] 375 | }, 376 | "source": [ 377 | "## Another way to do this is using list comprehension" 378 | ] 379 | }, 380 | { 381 | "cell_type": "code", 382 | "execution_count": 30, 383 | "metadata": {}, 384 | "outputs": [ 385 | { 386 | "name": "stdout", 387 | "output_type": "stream", 388 | "text": [ 389 | "CPU times: user 1.14 ms, sys: 524 µs, total: 1.66 ms\n", 390 | "Wall time: 838 µs\n" 391 | ] 392 | }, 393 | { 394 | "data": { 395 | "text/plain": [ 396 | "[ObjectRef(d5a75db31f99bd73ffffffffffffffffffffffff0100000001000000),\n", 397 | " ObjectRef(d68fec326c8433c9ffffffffffffffffffffffff0100000001000000),\n", 398 | " ObjectRef(8e088f779f48acd6ffffffffffffffffffffffff0100000001000000),\n", 399 | " ObjectRef(65a1a3aaa614cba3ffffffffffffffffffffffff0100000001000000)]" 400 | ] 401 | }, 402 | "execution_count": 30, 403 | "metadata": {}, 404 | "output_type": "execute_result" 405 | } 406 | ], 407 | "source": [ 408 | "%%time\n", 409 | "futures_list = [slow_function.remote() for _ in range(4)]\n", 410 | "futures_list" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": 31, 416 | "metadata": {}, 417 | "outputs": [ 418 | { 419 | "data": { 420 | "text/plain": [ 421 | "[7, 6, 2, 1]" 422 | ] 423 | }, 424 | "execution_count": 31, 425 | "metadata": {}, 426 | "output_type": "execute_result" 427 | } 428 | ], 429 | "source": [ 430 | "values = [ray.get(future) for future in futures_list]\n", 431 | "values" 432 | ] 433 | }, 434 | { 435 | "cell_type": "code", 436 | "execution_count": null, 437 | "metadata": {}, 438 | "outputs": [], 439 | "source": [ 440 | "%%time\n", 441 | "\n", 442 | "futures_list = [slow_function.remote() for _ in range(4)]\n", 443 | "futures_list" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": null, 449 | "metadata": {}, 450 | "outputs": [], 451 | "source": [ 452 | "%%time\n", 453 | "\n", 454 | "values_list = [ray.get(future) for future in futures_list]\n", 455 | "values_list" 456 | ] 457 | }, 458 | { 459 | "cell_type": "markdown", 460 | "metadata": {}, 461 | "source": [ 462 | "Note the difference between CPU times and wall clock in comprehensions? \n", 463 | "Comprehensions seems faster." 464 | ] 465 | }, 466 | { 467 | "cell_type": "markdown", 468 | "metadata": {}, 469 | "source": [ 470 | "Finally, shutdown Ray" 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": 34, 476 | "metadata": {}, 477 | "outputs": [], 478 | "source": [ 479 | "ray.shutdown()" 480 | ] 481 | }, 482 | { 483 | "cell_type": "markdown", 484 | "metadata": {}, 485 | "source": [ 486 | "---\n", 487 | "## References" 488 | ] 489 | }, 490 | { 491 | "cell_type": "markdown", 492 | "metadata": {}, 493 | "source": [ 494 | "[*Patterns for Parallel Programming*](https://www.goodreads.com/book/show/85053.Patterns_for_Parallel_Programming) \n", 495 | "Timothy G. Mattson, Beverly A. Sanders, Berna L. Massingill \n", 496 | "Addison-Wesley (2004)\n", 497 | "\n", 498 | "[Ray Core Walkthrough](https://docs.ray.io/en/latest/walkthrough.html)\n", 499 | "Ray Documentation and Gettting started materials\n", 500 | "\n", 501 | "[Ray Architecture Reference](https://docs.google.com/document/d/1lAy0Owi-vPz2jEqBSaHNQcy2IBSDEHyXNOQZlGuj93c/preview#)\n", 502 | "Ray 1.x Architecture Technical Paper\n", 503 | "\n", 504 | "[Ray Internals: A peek at ray,get](https://www.youtube.com/watch?v=a1kNnQu6vGw)\n", 505 | "\n", 506 | "[Ray Internals: Object management with Ownership Model](https://www.anyscale.com/events/2021/06/22/ray-internals-object-management-with-the-ownership-model)" 507 | ] 508 | } 509 | ], 510 | "metadata": { 511 | "kernelspec": { 512 | "display_name": "Python 3 (ipykernel)", 513 | "language": "python", 514 | "name": "python3" 515 | }, 516 | "language_info": { 517 | "codemirror_mode": { 518 | "name": "ipython", 519 | "version": 3 520 | }, 521 | "file_extension": ".py", 522 | "mimetype": "text/x-python", 523 | "name": "python", 524 | "nbconvert_exporter": "python", 525 | "pygments_lexer": "ipython3", 526 | "version": "3.8.12" 527 | } 528 | }, 529 | "nbformat": 4, 530 | "nbformat_minor": 4 531 | } 532 | -------------------------------------------------------------------------------- /ex_02_remo_func.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a4ad1455-57a4-439f-aeb8-d58d8b48fb89", 6 | "metadata": {}, 7 | "source": [ 8 | "# A Guided Tour of Ray Core: Remote Functions" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "c50a00c6-a6d4-48fc-a823-4d3d10417912", 14 | "metadata": {}, 15 | "source": [ 16 | "[*Remote Functions*](https://docs.ray.io/en/latest/walkthrough.html#remote-functions-tasks)\n", 17 | "involve using a `@ray.remote` decorator on a function. \n", 18 | "\n", 19 | "This implements a [*task parallelism*](https://patterns.eecs.berkeley.edu/?page_id=208) pattern, with properties: *data independence*, *stateless*" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "id": "902059f3-959a-418e-a8d8-670fb11fff5a", 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import numpy as np\n", 30 | "from numpy import loadtxt\n", 31 | "import ray" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "id": "d968d45e-7162-4116-834d-a4f9a29598c6", 37 | "metadata": {}, 38 | "source": [ 39 | "Ray converts decorated functions into stateless tasks, scheduled anywhere onto a ray worker in the cluster.\n", 40 | "\n", 41 | "This remote task reads a file and returns its contents as a numpy array." 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "2ddb7b82-1b20-4bbd-96e0-6b9e98afd1e8", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "@ray.remote\n", 52 | "def read_array(fn: str) -> np.array:\n", 53 | " arr = loadtxt(fn, comments=\"#\", delimiter=\",\", unpack=False)\n", 54 | " return arr.astype('int')" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "id": "fe1111fe-5b85-4a84-969c-ea5402c7f1a4", 60 | "metadata": {}, 61 | "source": [ 62 | "Given two numpy arrays, this remote task returns add the two numpy arrays" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "id": "3c509fad-96dd-473c-bec3-c5a6bbce64c8", 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "@ray.remote\n", 73 | "def add_array(a1: np.array, a2: np.array) -> np.array:\n", 74 | " return np.add(a1, a2)" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "b6283055-3ef4-4d6e-9959-4ffc050e9a4d", 80 | "metadata": {}, 81 | "source": [ 82 | "Given a numpy array, add its contents" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "id": "9de6e60c-867d-43d0-b913-f4044c4e5d11", 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "@ray.remote\n", 93 | "def sum_arr(a1: np.array) -> int:\n", 94 | " return a1.sum()" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "id": "57f40e79-53f0-4dac-a703-cb9e303c9a76", 100 | "metadata": {}, 101 | "source": [ 102 | "Ray executes all remote tasks on an assigned node and returns future immediately. Futures enable asynchonrous calls, which enables concurrency and parallelism.\n", 103 | "\n", 104 | "**Note**: I did not start the Ray iwth `ray.init(..)` as in previous excercise; the first call to Ray, automatically launched \n", 105 | "a Ray on my local host. Since this is a local mode, I'll only have a headnode running." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "id": "5ac942a0-2d9b-4fd1-a8ec-8f1cedb23b47", 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "obj_ref_arr1 = read_array.remote(\"dat/file_1.txt\")\n", 116 | "print(f\"array 1: {obj_ref_arr1}\")" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "id": "a69772fc-5808-4d20-9ce0-b4006e587d63", 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "obj_ref_arr2 = read_array.remote(\"dat/file_2.txt\")\n", 127 | "print(f\"array 2: {obj_ref_arr2}\")" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "id": "e4204e60-16ca-4b49-bb30-4e1561253e03", 133 | "metadata": {}, 134 | "source": [ 135 | "Ray executes the remote task to add two arrays stored in the object references and immediately \n", 136 | "returns an object reference. \n", 137 | "\n", 138 | "**Note**: That you can send object references or futures to the function. Ray will resolve it at the time\n", 139 | "of executing the task, to resolve them." 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "id": "8ae81b41-7a5a-487b-87ba-dcf688d6ff06", 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "result_obj_ref = add_array.remote(obj_ref_arr1, obj_ref_arr2)\n", 150 | "result_obj_ref" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "id": "4b74801f-fdf3-47f5-abb8-35e2fd5e6e7b", 156 | "metadata": {}, 157 | "source": [ 158 | "Fetch the result: this will block if not finished" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "id": "2c611019-dcfd-465e-9d60-97c48cd3c92b", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "result = ray.get(result_obj_ref)\n", 169 | "print(f\"Result: add arr1 + arr2: {result}\")" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "id": "209c4756-ea6a-47c9-bcf9-8e28493059de", 175 | "metadata": {}, 176 | "source": [ 177 | "Add the array elements in each numpy array and get the sum" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": null, 183 | "id": "de2b1350-2d29-4a31-a0d3-b307e5c0f646", 184 | "metadata": {}, 185 | "outputs": [], 186 | "source": [ 187 | "sum_1 = ray.get(sum_arr.remote(obj_ref_arr1))\n", 188 | "sum_2 = ray.get(sum_arr.remote(obj_ref_arr2))\n", 189 | "\n", 190 | "print(f'Sum of arr1: {sum_1}')\n", 191 | "print(f'Sum of arr2: {sum_2}')" 192 | ] 193 | } 194 | ], 195 | "metadata": { 196 | "kernelspec": { 197 | "display_name": "Python 3 (ipykernel)", 198 | "language": "python", 199 | "name": "python3" 200 | }, 201 | "language_info": { 202 | "codemirror_mode": { 203 | "name": "ipython", 204 | "version": 3 205 | }, 206 | "file_extension": ".py", 207 | "mimetype": "text/x-python", 208 | "name": "python", 209 | "nbconvert_exporter": "python", 210 | "pygments_lexer": "ipython3", 211 | "version": "3.7.11" 212 | } 213 | }, 214 | "nbformat": 4, 215 | "nbformat_minor": 5 216 | } 217 | -------------------------------------------------------------------------------- /ex_02_remo_objs.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Guided Tour of Ray Core: Remote Objects" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "[*Remote Objects*](https://docs.ray.io/en/latest/walkthrough.html#objects-in-ray)\n", 15 | "implement a [*shared-memory object store*](https://en.wikipedia.org/wiki/Shared_memory) pattern.\n", 16 | "\n", 17 | "Objects are immutable, and can be accessed from anywhere on the cluster, as they are stored in the cluster shared memory.\n", 18 | "\n", 19 | "\n", 20 | "\n", 21 | "In general, small objects are stored in their owner’s **in-process store** while large objects are stored in the **distributed object store**. This decision is meant to reduce the memory footprint and resolution time for each object. Note that in the latter case, a placeholder object is stored in the in-process store to indicate the object has been promoted to shared memory.\n", 22 | "\n", 23 | "[Ray Architecture Reference](https://docs.google.com/document/d/1lAy0Owi-vPz2jEqBSaHNQcy2IBSDEHyXNOQZlGuj93c/preview#)\n", 24 | "\n", 25 | "---" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "First, let's start Ray…" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "import logging\n", 42 | "import ray\n", 43 | "\n", 44 | "ray.init(\n", 45 | " ignore_reinit_error=True,\n", 46 | " logging_level=logging.ERROR,\n", 47 | ")" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "## Remote Objects example" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "To start, we'll define a remote object..." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "%%time\n", 71 | "\n", 72 | "num_list = [ 23, 42, 93 ]\n", 73 | "\n", 74 | "obj_ref = ray.put(num_list)\n", 75 | "obj_ref" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "Then retrieve the value of this object reference. This follows an object resolution protocol.\n", 83 | "\n", 84 | "\n", 85 | "\n", 86 | "Small objects are resolved by copying them directly from the owner’s **in-process store**. For example, if the owner calls `ray.get`, the system looks up and deserializes the value from the local in-process store. If the owner submits a dependent task, it inlines the object by copying the value directly into the task description. Note that these objects are local to the owner process: if a borrower attempts to resolve the value, the object is promoted to shared memory, where it can be retrieved through the distributed object resolution protocol described next.\n", 87 | "\n", 88 | "Resolving a large object. The object x is initially created on Node 2, e.g., because the task that returned the value ran on that node. This shows the steps when the owner (the caller of the task) calls `ray.get`: \n", 89 | "\n", 90 | " 1) Lookup object’s locations at the owner. \n", 91 | " 2) Select a location and send a request for a copy of the object. \n", 92 | " 3) Receive the object.\n", 93 | "\n" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "%%time\n", 103 | "\n", 104 | "ray.get(obj_ref)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "Let's combine use of a remote function with a remote object, to illustrate *composable futures*:" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": null, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "@ray.remote\n", 121 | "def my_function (num_list):\n", 122 | " return sum(num_list)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "In other words, the remote function `myfunction()` will sum the list of integers in the remote object `num_list`:" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": null, 135 | "metadata": {}, 136 | "outputs": [], 137 | "source": [ 138 | "%%time\n", 139 | "\n", 140 | "calc_ref = my_function.remote(obj_ref)" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "%%time\n", 150 | "\n", 151 | "ray.get(calc_ref)" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "You can gather the values of multiple object references in parallel using collections:" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": null, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "%%time\n", 168 | "\n", 169 | "ray.get([ray.put(i) for i in range(3)])" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "Now let's set a timeout to return early from attempted access of a remote object that is blocking for too long..." 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": {}, 183 | "outputs": [], 184 | "source": [ 185 | "import time\n", 186 | "\n", 187 | "@ray.remote\n", 188 | "def long_running_function ():\n", 189 | " time.sleep(10)\n", 190 | " return 42" 191 | ] 192 | }, 193 | { 194 | "cell_type": "code", 195 | "execution_count": null, 196 | "metadata": {}, 197 | "outputs": [], 198 | "source": [ 199 | "%%time\n", 200 | "\n", 201 | "from ray.exceptions import GetTimeoutError\n", 202 | "\n", 203 | "obj_ref = long_running_function.remote()\n", 204 | "\n", 205 | "try:\n", 206 | " ray.get(obj_ref, timeout=6)\n", 207 | "except GetTimeoutError:\n", 208 | " print(\"`get` timed out\")" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "Then shutdown Ray" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": null, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "ray.shutdown()" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "metadata": {}, 230 | "source": [ 231 | "## References\n", 232 | "\n", 233 | "[Ray Architecture Reference](https://docs.google.com/document/d/1lAy0Owi-vPz2jEqBSaHNQcy2IBSDEHyXNOQZlGuj93c/preview#)\n", 234 | "Ray 1.x Architecture Technical Paper\n", 235 | "\n", 236 | "[Ray Internals: A peek at ray,get](https://www.youtube.com/watch?v=a1kNnQu6vGw)\n", 237 | "\n", 238 | "[Ray Internals: Object management with Ownership Model](https://www.anyscale.com/events/2021/06/22/ray-internals-object-management-with-the-ownership-model)" 239 | ] 240 | } 241 | ], 242 | "metadata": { 243 | "kernelspec": { 244 | "display_name": "Python 3 (ipykernel)", 245 | "language": "python", 246 | "name": "python3" 247 | }, 248 | "language_info": { 249 | "codemirror_mode": { 250 | "name": "ipython", 251 | "version": 3 252 | }, 253 | "file_extension": ".py", 254 | "mimetype": "text/x-python", 255 | "name": "python", 256 | "nbconvert_exporter": "python", 257 | "pygments_lexer": "ipython3", 258 | "version": "3.8.12" 259 | } 260 | }, 261 | "nbformat": 4, 262 | "nbformat_minor": 4 263 | } 264 | -------------------------------------------------------------------------------- /ex_03_remo_meth.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Guided Tour of Ray Core: Remote Classes" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "[*Remote Classes*](https://docs.ray.io/en/latest/walkthrough.html#remote-classes-actors)\n", 15 | "involve using a `@ray.remote` decorator on a class. \n", 16 | "\n", 17 | "This implements an [*actor*](https://patterns.eecs.berkeley.edu/?page_id=258) pattern, with properties: *stateful*, *message-passing semantics*\n", 18 | "\n", 19 | "---" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "First, let's start Ray…" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "import logging\n", 36 | "import ray\n", 37 | "\n", 38 | "ray.init(\n", 39 | " ignore_reinit_error=True,\n", 40 | " logging_level=logging.ERROR,\n", 41 | ")" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "metadata": {}, 47 | "source": [ 48 | "## Remote Classes example" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "To start, we'll define a class and use the decorator:" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "@ray.remote\n", 65 | "class Counter:\n", 66 | " def __init__ (self):\n", 67 | " self.value = 0\n", 68 | "\n", 69 | " def increment (self):\n", 70 | " self.value += 1\n", 71 | " return self.value" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "Now use this class `Counter` to create an actor:" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "%%time\n", 88 | "\n", 89 | "counter = Counter.remote()" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "Then call the actor:" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "%%time\n", 106 | "\n", 107 | "obj_ref = counter.increment.remote()\n", 108 | "ray.get(obj_ref)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "Use list comprehension to show the state being maintained in the actor" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "%%time\n", 125 | "\n", 126 | "f_list = [counter.increment.remote() for _ in range(3)]" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": {}, 133 | "outputs": [], 134 | "source": [ 135 | "%%time \n", 136 | "\n", 137 | "print(ray.get(f_list))" 138 | ] 139 | }, 140 | { 141 | "cell_type": "markdown", 142 | "metadata": {}, 143 | "source": [ 144 | "\n", 145 | "Let's use another Actor class and create multiple instances associated with a distinct attribute, such as a name." 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "from random import randint\n", 155 | "\n", 156 | "@ray.remote\n", 157 | "class GoalsScored:\n", 158 | " def __init__ (self, player) -> None:\n", 159 | " self._goals = 0\n", 160 | " self._player = player\n", 161 | "\n", 162 | " def score (self) -> object:\n", 163 | " self._goals += randint(1, 5)\n", 164 | " return self._goals\n", 165 | " \n", 166 | " def player(self) -> str:\n", 167 | " return self._player" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "Define three Actors: Rolando, Neymar, Messi" 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "metadata": {}, 181 | "outputs": [], 182 | "source": [ 183 | "%%time \n", 184 | "\n", 185 | "ronaldo = GoalsScored.remote(\"Ronaldo\")\n", 186 | "neymar = GoalsScored.remote(\"Neymar\")\n", 187 | "messi = GoalsScored.remote(\"Messi\")" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "metadata": {}, 194 | "outputs": [], 195 | "source": [ 196 | "%%time\n", 197 | "\n", 198 | "ronaldo_ref = ronaldo.score.remote()\n", 199 | "neymar_ref = neymar.score.remote()\n", 200 | "messi_ref = messi.score.remote()" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "\n", 208 | "Again, use list comprehension to iterate over each Actor instances, along with object_ref\n", 209 | "for their scores, maintained by each distincgive actor." 210 | ] 211 | }, 212 | { 213 | "cell_type": "code", 214 | "execution_count": null, 215 | "metadata": {}, 216 | "outputs": [], 217 | "source": [ 218 | "for ref, ref_obj in [(ronaldo, ronaldo_ref), (neymar, neymar_ref), (messi, messi_ref) ]:\n", 219 | " print(f\"Player: {ray.get(ref.player.remote())} and goals scored: {ray.get(ref_obj)}\")" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "Finally, shutdown Ray" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": {}, 233 | "outputs": [], 234 | "source": [ 235 | "ray.shutdown()" 236 | ] 237 | }, 238 | { 239 | "cell_type": "markdown", 240 | "metadata": {}, 241 | "source": [ 242 | "---\n", 243 | "## References" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "[\"A Universal Modular Actor Formalism for Artificial Intelligence\"](https://www.ijcai.org/Proceedings/73/Papers/027B.pdf) \n", 251 | "Carl Hewitt, Peter Bishop, Richard Steiger \n", 252 | "*IJCAI* (1973)" 253 | ] 254 | } 255 | ], 256 | "metadata": { 257 | "kernelspec": { 258 | "display_name": "Python 3", 259 | "language": "python", 260 | "name": "python3" 261 | }, 262 | "language_info": { 263 | "codemirror_mode": { 264 | "name": "ipython", 265 | "version": 3 266 | }, 267 | "file_extension": ".py", 268 | "mimetype": "text/x-python", 269 | "name": "python", 270 | "nbconvert_exporter": "python", 271 | "pygments_lexer": "ipython3", 272 | "version": "3.8.8" 273 | } 274 | }, 275 | "nbformat": 4, 276 | "nbformat_minor": 4 277 | } 278 | -------------------------------------------------------------------------------- /ex_04_mult_pool.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Guided Tour of Ray Core: Multiprocessing Pool" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "[*Distributed multiprocessing.Pool*](https://docs.ray.io/en/latest/multiprocessing.html) make it easy to scale existing Python applications that use [`multiprocessing.Pool`](https://docs.python.org/3/library/multiprocessing.html) by leveraging *actors*.\n", 15 | "\n", 16 | "---" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "First, let's start Ray…" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import logging\n", 33 | "import ray\n", 34 | "\n", 35 | "ray.init(\n", 36 | " ignore_reinit_error=True,\n", 37 | " logging_level=logging.ERROR,\n", 38 | ")" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "## Multiprocessing Pool example" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "The following is a simple Python function with a slight delay added (to make it behave like a more complex calculation)..." 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "import time\n", 62 | "\n", 63 | "def my_function (x):\n", 64 | " time.sleep(1)\n", 65 | " return x ** 2" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "%%time\n", 75 | "\n", 76 | "my_function(11)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "First, let's try using this with Python's built-in [multiprocessing.Pool](https://docs.python.org/3/library/multiprocessing.html):" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": null, 89 | "metadata": {}, 90 | "outputs": [], 91 | "source": [ 92 | "%%time\n", 93 | "\n", 94 | "from multiprocessing import Pool\n", 95 | "\n", 96 | "pool = Pool()\n", 97 | "\n", 98 | "for result in pool.map(my_function, range(50)):\n", 99 | " print(result)" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "Now we'll create a *Pool* using and distribute its tasks across a cluster (or across the available cores on a laptop):" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "%%time\n", 116 | "\n", 117 | "from ray.util.multiprocessing import Pool\n", 118 | "\n", 119 | "pool = Pool()\n", 120 | "\n", 121 | "for result in pool.map(my_function, range(50)):\n", 122 | " print(result)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "The distributed version has the trade-off of increased overhead, although now it can scale-out horizontally across a cluster. The benefits would be more pronounced with a more computationally expensive calculation." 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "Finally, shutdown Ray" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": null, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "ray.shutdown()" 146 | ] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.8.8" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 4 170 | } 171 | -------------------------------------------------------------------------------- /ex_05_job_lib.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# A Guided Tour of Ray Core: JobLib" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "[*Distributed scikit-learn*](https://docs.ray.io/en/latest/joblib.html) provides a drop-in replacement to parallelize the [`JobLib`](https://joblib.readthedocs.io/en/latest/) backend for [`scikit-learn`](https://scikit-learn.org/stable/)\n", 15 | "\n", 16 | "\n", 17 | "---" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "First, let's start Ray…" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "import logging\n", 34 | "import ray\n", 35 | "\n", 36 | "ray.init(\n", 37 | " ignore_reinit_error=True,\n", 38 | " logging_level=logging.ERROR,\n", 39 | ")" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## JobLib example" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "Set up for this example..." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "from ray.util.joblib import register_ray\n", 63 | "from sklearn.datasets import load_digits\n", 64 | "from sklearn.model_selection import RandomizedSearchCV\n", 65 | "from sklearn.svm import SVC\n", 66 | "import numpy as np\n", 67 | "import joblib" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "First, let's register Ray as the parallelized [*joblib*](https://scikit-learn.org/stable/modules/generated/sklearn.utils.parallel_backend.html) backend for `scikit-learn`, using Ray actors instead of local processes.\n", 75 | "This makes it easy to scale existing applications running on a single node to running on a cluster.\n", 76 | "\n", 77 | "See: " 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": {}, 84 | "outputs": [], 85 | "source": [ 86 | "%%time\n", 87 | "\n", 88 | "register_ray()" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "Next, load a copy of the UCI machine learning data repository's hand-written *digits* dataset.\n", 96 | "See: " 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "%%time\n", 106 | "\n", 107 | "digits = load_digits()" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "We'll define the hyper-parameter space for training a *support vector machines* model:" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "%%time\n", 124 | "\n", 125 | "param_space = {\n", 126 | " \"C\": np.logspace(-6, 6, 30),\n", 127 | " \"gamma\": np.logspace(-8, 8, 30),\n", 128 | " \"tol\": np.logspace(-4, -1, 30),\n", 129 | " \"class_weight\": [None, \"balanced\"],\n", 130 | "}\n", 131 | "\n", 132 | "model = SVC(kernel=\"rbf\")" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "Then use a randomized search to optimize these hyper-parameters. See: \n", 140 | "\n", 141 | "We'll use 10 cross-validation splits and 50 iterations, which will result in a total of 500 \"fits\". This is enough to illustrate the `joblib` being parallelized, although in practice you'd probably use more iterations." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "%%time\n", 151 | "\n", 152 | "clf = RandomizedSearchCV(model, param_space, cv=10, n_iter=50, verbose=True)\n", 153 | "clf" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "Run the cross-validation fits (i.e., the random search for hyper-parameter optimization) using Ray to parallelize the backend processes.\n", 161 | "\n", 162 | "NB: **While this runs, check out the performance metrics on the Ray dashboard**" 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": null, 168 | "metadata": { 169 | "scrolled": true, 170 | "tags": [] 171 | }, 172 | "outputs": [], 173 | "source": [ 174 | "%%time\n", 175 | "\n", 176 | "with joblib.parallel_backend(\"ray\"):\n", 177 | " search = clf.fit(digits.data, digits.target)" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "So far, what is the best set of hyper-parameters found?" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [ 193 | "search.best_params_" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "metadata": {}, 199 | "source": [ 200 | "Finally, shutdown Ray" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": {}, 207 | "outputs": [], 208 | "source": [ 209 | "ray.shutdown()" 210 | ] 211 | } 212 | ], 213 | "metadata": { 214 | "kernelspec": { 215 | "display_name": "Python 3", 216 | "language": "python", 217 | "name": "python3" 218 | }, 219 | "language_info": { 220 | "codemirror_mode": { 221 | "name": "ipython", 222 | "version": 3 223 | }, 224 | "file_extension": ".py", 225 | "mimetype": "text/x-python", 226 | "name": "python", 227 | "nbconvert_exporter": "python", 228 | "pygments_lexer": "ipython3", 229 | "version": "3.8.8" 230 | } 231 | }, 232 | "nbformat": 4, 233 | "nbformat_minor": 4 234 | } 235 | -------------------------------------------------------------------------------- /images/object_resolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/images/object_resolution.png -------------------------------------------------------------------------------- /images/shared_memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/images/shared_memory.png -------------------------------------------------------------------------------- /images/task_life.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/images/task_life.png -------------------------------------------------------------------------------- /images/task_ownership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/images/task_ownership.png -------------------------------------------------------------------------------- /pi.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Profiling in Python\n", 8 | "\n", 9 | "There are several different ways to measure resource usage in Python applications, which help identify where an application should be parallelized and what kinds of design patterns from Ray core to use.\n", 10 | "to show how to determine where to parallelize.\n", 11 | "We'll introduce profiling methods to measure memory allocation, objects creation, deterministic profiling (call stack), sampling, etc., plus different means of visualization.\n", 12 | "\n", 13 | "The following example uses a Monte Carlo method for approximating the value of π, based on the tutorial [\"Ray Crash Course - Tasks\"](https://github.com/anyscale/academy/blob/main/ray-crash-course/01-Ray-Tasks.ipynb) by Dean Wampler.\n", 14 | "\n", 15 | "We'll compare/contrast the serial implementation versus use of *remote functions* to help parallelize this application.\n", 16 | "Then we'll analysze the per-CPU speedup, and also consider the overhead costs of using Ray core features -- to understand more about the trade-offs being made." 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "%load_ext watermark\n", 26 | "%watermark -v -m" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Launch Ray\n", 34 | "\n", 35 | "First, start Ray and open its dashboard in another browser tab:" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": {}, 42 | "outputs": [], 43 | "source": [ 44 | "import logging\n", 45 | "import ray\n", 46 | "\n", 47 | "ray.init(\n", 48 | " ignore_reinit_error=True,\n", 49 | " logging_level=logging.ERROR,\n", 50 | ")" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [ 57 | "## Application Code\n", 58 | "\n", 59 | "Define a simple function that uses a stochastic method of approximating π, repeated through some number of samples:" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": null, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "from IPython.core.display import display, HTML\n", 69 | "import matplotlib.pyplot as plt\n", 70 | "import numpy as np\n", 71 | "import pandas as pd\n", 72 | "import statistics\n", 73 | "import time\n", 74 | "\n", 75 | "def estimate_pi (num_samples):\n", 76 | " xs = np.random.uniform(low=-1.0, high=1.0, size=num_samples) # generate num_samples random samples for the x coordinate\n", 77 | " ys = np.random.uniform(low=-1.0, high=1.0, size=num_samples) # generate num_samples random samples for the y coordinate\n", 78 | " xys = np.stack((xs, ys), axis=-1) # similar to Python's \"zip(a,b)\"; creates np.array([(x1,y1), (x2,y2), ...]).\n", 79 | "\n", 80 | " inside = (xs**2.0 + ys**2.0) <= 1.0 # create a predicate over all the array elements\n", 81 | " xys_inside = xys[inside] # select only those elements inside the circle\n", 82 | " in_circle = xys_inside.shape[0] # return the number of elements inside the circle\n", 83 | " approx_pi = 4.0 * in_circle / num_samples # the Pi estimate\n", 84 | "\n", 85 | " return approx_pi" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "Since this approximation requires many samples, which are independent, a *task-parallel* pattern can be applied. We'll create a *remote function* version of the sampling function:" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": {}, 99 | "outputs": [], 100 | "source": [ 101 | "@ray.remote\n", 102 | "def distrib_estimate_pi (num_samples):\n", 103 | " return estimate_pi(num_samples)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "Another function will collect measures for one epoch, i.e., some number of trials:" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "def run_epoch (num_samples, num_trials, distrib=False):\n", 120 | " start = time.time()\n", 121 | " \n", 122 | " if distrib:\n", 123 | " refs = [distrib_estimate_pi.remote(num_samples) for _ in range(num_trials)]\n", 124 | " pis = ray.get(refs)\n", 125 | " else:\n", 126 | " pis = [estimate_pi(num_samples) for _ in range(num_trials)]\n", 127 | "\n", 128 | " # measure CPU time for the code section parallelized as a remote function\n", 129 | " duration = time.time() - start\n", 130 | "\n", 131 | " approx_pi = statistics.mean(pis)\n", 132 | " stdev = statistics.stdev(pis)\n", 133 | " error = 100.0 * abs(approx_pi - np.pi) / np.pi\n", 134 | "\n", 135 | " return num_samples, duration, approx_pi, stdev, error" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "Define class to manage the simulation:" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": { 149 | "scrolled": true 150 | }, 151 | "outputs": [], 152 | "source": [ 153 | "class Sim:\n", 154 | " DF_COL_NAMES = [\"n\", \"duration\", \"approx_pi\", \"stdev\", \"error\"]\n", 155 | " NUM_SAMPLES = 1000000\n", 156 | " NUM_TRIALS = 20\n", 157 | " STEP_SIZE = int(NUM_SAMPLES / 25)\n", 158 | "\n", 159 | " def __init__ (self, distrib=False, num_samples=NUM_SAMPLES, num_trials=NUM_TRIALS, step_size=STEP_SIZE):\n", 160 | " self.distrib = distrib\n", 161 | " self.num_samples = num_samples\n", 162 | " self.num_trials = num_trials\n", 163 | " self.step_size = step_size\n", 164 | " self.df = None\n", 165 | "\n", 166 | "\n", 167 | " def run (self):\n", 168 | " # use a minimum of 2 trials, to be able to calculate standard deviation\n", 169 | " results = [\n", 170 | " run_epoch(n_samp, self.num_trials, distrib=self.distrib)\n", 171 | " for n_samp in range(2, self.num_samples, self.step_size)\n", 172 | " ]\n", 173 | " \n", 174 | " self.df = pd.DataFrame(results, columns=self.DF_COL_NAMES)\n", 175 | " return self\n", 176 | "\n", 177 | "\n", 178 | " def plot (self):\n", 179 | " plt.plot(\"n\", \"duration\", data=self.df, color=\"green\", linewidth=1, linestyle=\"dashed\")\n", 180 | " plt.plot(\"n\", \"error\", data=self.df, color=\"red\", linewidth=0.5, linestyle=\"dashed\")\n", 181 | " plt.plot(\"n\", \"stdev\", data=self.df, color=\"blue\", linewidth=2)\n", 182 | "\n", 183 | " plt.yscale(\"log\")\n", 184 | " plt.legend()\n", 185 | " plt.show()\n", 186 | " return self" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "metadata": {}, 192 | "source": [ 193 | "## Profiling Tools\n", 194 | "\n", 195 | "Next, we'll set up to use the following tools for profiling in Python:\n", 196 | "\n", 197 | " * [`objgraph`](https://mg.pov.lt/objgraph/)\n", 198 | " * [`tracemalloc`](https://docs.python.org/3/library/tracemalloc.html)\n", 199 | " * [`prun`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-prun)\n", 200 | " * [`snakeviz`](https://jiffyclub.github.io/snakeviz/)\n", 201 | " * [`pyinstrument`](https://github.com/joerick/pyinstrument/)" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": null, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "import objgraph\n", 211 | "import tracemalloc\n", 212 | "import pyinstrument\n", 213 | "\n", 214 | "%load_ext snakeviz" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "### Object Creation\n", 222 | "\n", 223 | "The first method of profiling shown here uses\n", 224 | "[`objgraph`](https://mg.pov.lt/objgraph/)\n", 225 | "to examine a before/after contrast of what objects are getting generated by the application.\n", 226 | "While other profiling methods can be repeated, this kind of analysis requires some special handling (isolation) and should be run first -- before other tools make the runtime environment confusing to analyze.\n", 227 | "The objectives here are to understand:\n", 228 | "\n", 229 | " 1. Which kinds of objects are growing (by count)\n", 230 | " 2. How the different objects refer to each other\n", 231 | "\n", 232 | "That can help identify if there are problems with allocating many objects, which might require the application code to be reworked.\n", 233 | "\n", 234 | "First we'll measure a baseline of the top 10 kinds of objects that have been created so far:" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "objgraph.show_growth(limit=10)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "metadata": {}, 249 | "source": [ 250 | "Then run the simulation serially **once**:" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": null, 256 | "metadata": {}, 257 | "outputs": [], 258 | "source": [ 259 | "%%time\n", 260 | "\n", 261 | "sim_s = Sim(distrib=False, num_samples=Sim.NUM_SAMPLES)\n", 262 | "sim_s.run();" 263 | ] 264 | }, 265 | { 266 | "cell_type": "markdown", 267 | "metadata": {}, 268 | "source": [ 269 | "Now we can measure the object counts again, and examine the growth (by contrast):" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "objgraph.show_growth()" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": null, 284 | "metadata": {}, 285 | "outputs": [], 286 | "source": [ 287 | "objgraph.show_most_common_types() " 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": {}, 293 | "source": [ 294 | "The top few categories are function calls, dictionaries, and tuples – which is expected, given how our π approximations create lots of NumPy arrays.\n", 295 | "As expected, there's nothing much there to worry about – although in applications which large memory use we might need to parallelize so that each unit of work had more available memory.\n", 296 | "\n", 297 | "Next, let's examine a graph of how object make reference to other objects.\n", 298 | "This can be useful for tracing potential memory leaks..." 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "metadata": {}, 305 | "outputs": [], 306 | "source": [ 307 | "roots = objgraph.get_leaking_objects()\n", 308 | "objgraph.show_refs(roots[:3], refcounts=True)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "metadata": {}, 314 | "source": [ 315 | "### Memory Allocation\n", 316 | "\n", 317 | "The next profiling method uses\n", 318 | "[`tracemalloc`](https://docs.python.org/3/library/tracemalloc.html)\n", 319 | "to trace the memory blocks allocated by Python.\n", 320 | "This analysis provides the following information:\n", 321 | "\n", 322 | " * traceback to where an object got allocated\n", 323 | " * statistics about allocated memory blocks per filename, per line: total size, number and average size\n", 324 | " * computing the differences between two snapshots to detect memory leaks\n", 325 | " \n", 326 | "We'll run the application serially to capture a snapshot:" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": null, 332 | "metadata": {}, 333 | "outputs": [], 334 | "source": [ 335 | "tracemalloc.start()\n", 336 | "\n", 337 | "Sim(distrib=False, num_samples=Sim.NUM_SAMPLES).run()\n", 338 | "\n", 339 | "snapshot = tracemalloc.take_snapshot()" 340 | ] 341 | }, 342 | { 343 | "cell_type": "code", 344 | "execution_count": null, 345 | "metadata": { 346 | "scrolled": true 347 | }, 348 | "outputs": [], 349 | "source": [ 350 | "top_stats = snapshot.statistics('traceback')\n", 351 | "\n", 352 | "# pick the biggest memory block\n", 353 | "for stat in top_stats:\n", 354 | " print(\"%s memory blocks: %.1f KiB\" % (stat.count, stat.size / 1024))\n", 355 | "\n", 356 | " for line in stat.traceback.format():\n", 357 | " print(line)" 358 | ] 359 | }, 360 | { 361 | "cell_type": "markdown", 362 | "metadata": {}, 363 | "source": [ 364 | "There should not be any *memory leaks* in this application." 365 | ] 366 | }, 367 | { 368 | "cell_type": "markdown", 369 | "metadata": {}, 370 | "source": [ 371 | "### Deterministic Profiling\n", 372 | "\n", 373 | "The\n", 374 | "[`%%prun` magic](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-prun)\n", 375 | "invokes the\n", 376 | "[cProfile](https://docs.python.org/3/library/profile.html)\n", 377 | "*deterministic* profiler in Python to trace how often and for what duration the different functions get called.\n", 378 | "In other words, we'll track of the call stack statistics to understand more about CPU use.\n", 379 | "This configures the profiler to save data to the file `prof_cpu.txt` as text.\n", 380 | "\n", 381 | "The [`%%snakeviz` magic](https://jiffyclub.github.io/snakeviz/)\n", 382 | "then analyzes that profiling data and generates an interactive report.\n", 383 | "\n", 384 | "Note that *deterministic profiling* creates some overhead which can distort the measurements in relatively small code blocks or short-running code, although it's generally find for long-running programs." 385 | ] 386 | }, 387 | { 388 | "cell_type": "code", 389 | "execution_count": null, 390 | "metadata": { 391 | "tags": [] 392 | }, 393 | "outputs": [], 394 | "source": [ 395 | "%%prun -q -T prof_cpu.txt\n", 396 | "%%snakeviz\n", 397 | "\n", 398 | "Sim(distrib=False, num_samples=Sim.NUM_SAMPLES).run()" 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "metadata": {}, 404 | "source": [ 405 | "The *icicle* interactive chart illustrates how so very much of the overall CPU cost is in the `estimate_pi()` method.\n", 406 | "\n", 407 | "In some environments, there may be security checks that limit use of `SnakeViz` (pending updates) so here's a screenshot:" 408 | ] 409 | }, 410 | { 411 | "cell_type": "markdown", 412 | "metadata": {}, 413 | "source": [ 414 | "" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": {}, 420 | "source": [ 421 | "### Sampling Profiler\n", 422 | "\n", 423 | "The\n", 424 | "[`pyinstrument`](https://github.com/joerick/pyinstrument/)\n", 425 | "library provides a *sampling profiler* – an alternative way to measure the call stack and CPU use.\n", 426 | "While not *exact*, this approach creates less overhead:" 427 | ] 428 | }, 429 | { 430 | "cell_type": "code", 431 | "execution_count": null, 432 | "metadata": {}, 433 | "outputs": [], 434 | "source": [ 435 | "profiler = pyinstrument.Profiler()\n", 436 | "profiler.start()\n", 437 | "\n", 438 | "Sim(distrib=False, num_samples=Sim.NUM_SAMPLES).run()\n", 439 | "\n", 440 | "profiler.stop()\n", 441 | "display(HTML(profiler.output_html()))" 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "metadata": {}, 447 | "source": [ 448 | "Again, the `estimate_pi()` function is where most of the CPU cost occurs, although we can get clearer measures for how much overhead there is among the rest of code.\n", 449 | "In this case the cumulative overhead is relatively small, probably measured in milliseconds." 450 | ] 451 | }, 452 | { 453 | "cell_type": "markdown", 454 | "metadata": {}, 455 | "source": [ 456 | "## Results of Serial Execution\n", 457 | "\n", 458 | "Now let's look at a visualization of the *number of samples* plotted versus *duration*, *error*, and *standard deviation*:" 459 | ] 460 | }, 461 | { 462 | "cell_type": "code", 463 | "execution_count": null, 464 | "metadata": {}, 465 | "outputs": [], 466 | "source": [ 467 | "sim_s.plot();" 468 | ] 469 | }, 470 | { 471 | "cell_type": "markdown", 472 | "metadata": {}, 473 | "source": [ 474 | "Note how the *standard deviation* drops quickly.\n", 475 | "We can calculate that measure without even knowing an \"exact\" value of π (the *error* measure), so we could potentially rework this application to have an *early termination* by setting a threshold on the *stdev* measure." 476 | ] 477 | }, 478 | { 479 | "cell_type": "code", 480 | "execution_count": null, 481 | "metadata": {}, 482 | "outputs": [], 483 | "source": [ 484 | "sim_s.df.describe()" 485 | ] 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "metadata": {}, 490 | "source": [ 491 | "## Results of Parallel Execution\n", 492 | "\n", 493 | "Now let's run again and this time parallelize the application using *remote functions*, by setting the `distrib=True` flag.\n", 494 | "We'll use the `%%time` magic to show the \"wall clock\" CPU time:" 495 | ] 496 | }, 497 | { 498 | "cell_type": "code", 499 | "execution_count": null, 500 | "metadata": {}, 501 | "outputs": [], 502 | "source": [ 503 | "%%time\n", 504 | "\n", 505 | "sim_d = Sim(distrib=True, num_samples=Sim.NUM_SAMPLES)\n", 506 | "sim_d.run();" 507 | ] 508 | }, 509 | { 510 | "cell_type": "code", 511 | "execution_count": null, 512 | "metadata": {}, 513 | "outputs": [], 514 | "source": [ 515 | "sim_d.plot();" 516 | ] 517 | }, 518 | { 519 | "cell_type": "code", 520 | "execution_count": null, 521 | "metadata": {}, 522 | "outputs": [], 523 | "source": [ 524 | "sim_d.df.describe()" 525 | ] 526 | }, 527 | { 528 | "cell_type": "markdown", 529 | "metadata": {}, 530 | "source": [ 531 | "While the *duration* of `estimate_pi()` CPU use scales pretty much the same as in serial execution, we'll show in the next section how the parallel run improves the application speed dramatically – depending on the number of available CPUs." 532 | ] 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "metadata": {}, 537 | "source": [ 538 | "## Visualizing Aggregate Measures\n", 539 | "\n", 540 | "Now let's build a pipeline in `scikit-learn` to run a \n", 541 | "[*polynomial regression*](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html)\n", 542 | "of the *duration* measures.\n", 543 | "In other words, we'll perform some \"curve fitting\" to calculate the speedup of using *remote functions*.\n", 544 | "\n", 545 | "The `degree` parameter is set to the value `1`, which means we'll look at a linear regression.\n", 546 | "This pipeline allows you to evaluate fitting to higher order polynomials, by changing this parameter." 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "execution_count": null, 552 | "metadata": {}, 553 | "outputs": [], 554 | "source": [ 555 | "from sklearn.linear_model import LinearRegression\n", 556 | "from sklearn.pipeline import make_pipeline\n", 557 | "from sklearn.preprocessing import PolynomialFeatures\n", 558 | "\n", 559 | "DEGREE = 1\n", 560 | "polyreg = make_pipeline(PolynomialFeatures(DEGREE), LinearRegression())\n", 561 | "\n", 562 | "X = sim_s.df.iloc[:, 0].values.reshape(-1, 1)\n", 563 | "Y_s = sim_s.df.iloc[:, 1].values.reshape(-1, 1)\n", 564 | "Y_d = sim_d.df.iloc[:, 1].values.reshape(-1, 1)\n", 565 | "\n", 566 | "polyreg.fit(X, Y_s)\n", 567 | "Y_s_pred = polyreg.predict(X)\n", 568 | "\n", 569 | "polyreg.fit(X, Y_d)\n", 570 | "Y_d_pred = polyreg.predict(X)" 571 | ] 572 | }, 573 | { 574 | "cell_type": "code", 575 | "execution_count": null, 576 | "metadata": {}, 577 | "outputs": [], 578 | "source": [ 579 | "plt.scatter(X, Y_s, color=\"lightgray\")\n", 580 | "plt.scatter(X, Y_d, color=\"gray\")\n", 581 | "\n", 582 | "plt.plot(X, Y_s_pred, color=\"green\", linewidth=1, label=\"serial\")\n", 583 | "plt.plot(X, Y_d_pred, color=\"red\", linewidth=0.5, label=\"distrib\")\n", 584 | "\n", 585 | "plt.legend()\n", 586 | "plt.show()" 587 | ] 588 | }, 589 | { 590 | "cell_type": "markdown", 591 | "metadata": {}, 592 | "source": [ 593 | "The distributed processing shows less time required for the tasks in aggregate.\n", 594 | "Let's calculate how much speedup, based on a ratio of the *slope* values for the two fitted regressions:" 595 | ] 596 | }, 597 | { 598 | "cell_type": "code", 599 | "execution_count": null, 600 | "metadata": { 601 | "scrolled": true, 602 | "tags": [] 603 | }, 604 | "outputs": [], 605 | "source": [ 606 | "m = (Y_s_pred / Y_d_pred)\n", 607 | "m" 608 | ] 609 | }, 610 | { 611 | "cell_type": "markdown", 612 | "metadata": {}, 613 | "source": [ 614 | "Now we can plot these ratios, and take their median value:" 615 | ] 616 | }, 617 | { 618 | "cell_type": "code", 619 | "execution_count": null, 620 | "metadata": {}, 621 | "outputs": [], 622 | "source": [ 623 | "plt.plot(m, color=\"red\", linewidth=1, label=\"speedup {:.2f}\".format(np.median(m)))\n", 624 | "plt.legend()\n", 625 | "plt.show()" 626 | ] 627 | }, 628 | { 629 | "cell_type": "markdown", 630 | "metadata": {}, 631 | "source": [ 632 | "Depending on the number of available CPUs, you should see an asymptotically linear increase in performance." 633 | ] 634 | }, 635 | { 636 | "cell_type": "code", 637 | "execution_count": null, 638 | "metadata": {}, 639 | "outputs": [], 640 | "source": [ 641 | "ray.shutdown()" 642 | ] 643 | }, 644 | { 645 | "cell_type": "markdown", 646 | "metadata": {}, 647 | "source": [ 648 | "## Exercise\n", 649 | "\n", 650 | "Try running the `Sim` simulation again in its parallelized mode, with a much larger number of samples.\n", 651 | "While that's running, check the Ray dashboard to see how processing for the *remote tasks* gets distributed across the available CPUs." 652 | ] 653 | } 654 | ], 655 | "metadata": { 656 | "kernelspec": { 657 | "display_name": "Python 3", 658 | "language": "python", 659 | "name": "python3" 660 | }, 661 | "language_info": { 662 | "codemirror_mode": { 663 | "name": "ipython", 664 | "version": 3 665 | }, 666 | "file_extension": ".py", 667 | "mimetype": "text/x-python", 668 | "name": "python", 669 | "nbconvert_exporter": "python", 670 | "pygments_lexer": "ipython3", 671 | "version": "3.8.8" 672 | } 673 | }, 674 | "nbformat": 4, 675 | "nbformat_minor": 4 676 | } 677 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | codespell 2 | pre-commit 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | graphviz >= 0.14 2 | jupyterlab >= 3.0 3 | matplotlib >= 3.3 4 | mistune >= 2.0.1 # not directly required, pinned by Snyk to avoid a vulnerability 5 | numpy >= 1.19 6 | objgraph >= 3.5 7 | pandas >= 1.1 8 | pyinstrument >= 3.4 9 | ray[default] 10 | scikit-image >= 0.15 11 | scikit-learn >= 0.20 12 | snakeviz >= 2.1 13 | watermark >= 2.2 14 | -------------------------------------------------------------------------------- /slides/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/slides/slides.pdf -------------------------------------------------------------------------------- /snekviz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DerwenAI/ray_tutorial/0e27b7aa6fa8b34a01dd7b8b556d86461c37bc2c/snekviz.png --------------------------------------------------------------------------------