From 95798a0891a737f76929c954dc15671ae59aa61c Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 4 Jul 2024 22:49:35 -0600 Subject: [PATCH] refactor: content and organization for RADIUSS 2024 I am starting preliminary changes for RADIUSS 2024. So far: - the launcher is fixed to load the tutorials via buttons - I am moving "core" flux tutorial content separate from what we can consider external or plugins, like dyad. The reason is because these are experimental and change from year to year, and having it alongside core content makes a promise about consistency that I am not sure we can keep. I have not done this yet, but I am going to separate the files (images and notebooks) cleanly as well. - top level: everything is there for the tutorial user when the notebook opens, no need to navigate into many sub- directories. - terminal button: instead of "click this long list of annoying paths to open a terminal" I figured out how to make a button in the notebook directly. - marginal content changes: I am starting to tweak / update content, this will be a multi-step process. Signed-off-by: vsoch --- .github/workflows/docker-builds.yaml | 5 +- 2024-RADIUSS-AWS/JupyterNotebook/README.md | 10 +- .../JupyterNotebook/docker/Dockerfile.spawn | 25 +- .../docker/jupyter-launcher.yaml | 37 +- .../JupyterNotebook/flux-tree/flux-tree | 2 +- .../{notebook => }/01_flux_tutorial.ipynb | 481 ++++++------- .../{notebook => }/02_flux_framework.ipynb | 12 +- ...ynb => 03_flux_tutorial_conclusions.ipynb} | 18 +- .../tutorial/{notebook => }/Flux-logo.svg | 0 .../{notebook => }/dyad/dyad_example1.svg | 0 .../{notebook => }/dyad/dyad_example2.svg | 0 .../03_dyad_dlio.ipynb => dyad_dlio.ipynb} | 0 .../JupyterNotebook/tutorial/flux-icon.png | Bin 0 -> 117673 bytes .../job-watch/job-watch.sh | 8 +- .../tutorial/{notebook => }/hello-batch.sh | 0 .../{notebook => }/img/dl-training-io.png | Bin .../img/dyad-software-stack.png | Bin .../img/dyad-unet3d-results.svg | 0 .../{notebook => }/img/dyad_design.png | Bin .../{notebook => }/img/flux-broker-design.png | Bin .../img/flux-instance-pre-tbon.png | Bin .../img/flux-instance-w-tbon.png | Bin .../tutorial/{notebook => }/img/flux-tree.png | Bin .../{notebook => }/img/instance-submit.png | Bin .../{notebook => }/img/scaled-submit.png | Bin .../{notebook => }/img/single-submit.png | Bin .../notebook/old/02_flux_scheduling.ipynb | 598 ---------------- .../tutorial/notebook/old/06_supplement.ipynb | 321 --------- .../notebook/old/X01_flux_tutorial.ipynb | 115 --- .../tutorial/notebook/old/dyad.ipynb | 671 ------------------ .../tutorial/{notebook => }/sleep_batch.sh | 0 .../tutorial/{notebook => }/sub_job1.sh | 0 .../tutorial/{notebook => }/sub_job2.sh | 0 33 files changed, 273 insertions(+), 2030 deletions(-) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/01_flux_tutorial.ipynb (88%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/02_flux_framework.ipynb (96%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook/04_flux_tutorial_conclusions.ipynb => 03_flux_tutorial_conclusions.ipynb} (78%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/Flux-logo.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/dyad/dyad_example1.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/dyad/dyad_example2.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook/03_dyad_dlio.ipynb => dyad_dlio.ipynb} (100%) create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-icon.png rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/hello-batch.sh (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/dl-training-io.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/dyad-software-stack.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/dyad-unet3d-results.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/dyad_design.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/flux-broker-design.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/flux-instance-pre-tbon.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/flux-instance-w-tbon.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/flux-tree.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/instance-submit.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/scaled-submit.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/img/single-submit.png (100%) delete mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/02_flux_scheduling.ipynb delete mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb delete mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb delete mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/sleep_batch.sh (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/sub_job1.sh (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{notebook => }/sub_job2.sh (100%) diff --git a/.github/workflows/docker-builds.yaml b/.github/workflows/docker-builds.yaml index fea9573..2f1b545 100644 --- a/.github/workflows/docker-builds.yaml +++ b/.github/workflows/docker-builds.yaml @@ -10,10 +10,13 @@ jobs: strategy: fail-fast: false matrix: -# Tutorial is over - these builds are disabled +# Tutorials are over - these builds are disabled # test: [["2023-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.hub", "ghcr.io/flux-framework/flux-jupyter-hub:2023"], # ["2023-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.init", "ghcr.io/flux-framework/flux-jupyter-init:2023"], # ["2023-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.spawn", "ghcr.io/flux-framework/flux-jupyter-spawn:2023"]] +# ["2024-RIKEN-AWS/JupyterNotebook", "docker/Dockerfile.hub", "ghcr.io/flux-framework/flux-jupyter-hub:riken-2024"], +# ["2024-RIKEN-AWS/JupyterNotebook", "docker/Dockerfile.init", "ghcr.io/flux-framework/flux-jupyter-init:riken-2024"], +# ["2024-RIKEN-AWS/JupyterNotebook", "docker/Dockerfile.spawn", "ghcr.io/flux-framework/flux-jupyter-spawn:riken-2024"]] test: [["2024-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.hub", "ghcr.io/flux-framework/flux-jupyter-hub:radiuss-2024"], ["2024-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.init", "ghcr.io/flux-framework/flux-jupyter-init:radiuss-2024"], ["2024-RADIUSS-AWS/JupyterNotebook", "docker/Dockerfile.spawn", "ghcr.io/flux-framework/flux-jupyter-spawn:radiuss-2024"]] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/README.md b/2024-RADIUSS-AWS/JupyterNotebook/README.md index 8e3d00f..cb39c04 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/README.md @@ -3,8 +3,8 @@ This set of tutorials provides: - [Building Base Images](#build-images) - - [Deploy A Cluster to AWS or Google Cloud Using](#deploy-to-kubernetes) using Google Cloud or AWS - [Local Development or Usage](#local-usage) + - [Deploy A Cluster to AWS or Google Cloud Using](#deploy-to-kubernetes) using Google Cloud or AWS Pre-requisites: @@ -19,6 +19,7 @@ For AWS Tutorial Day users: ## Build Images Let's build a set of images - one spawner and one hub, and an init. You can customize the tag to your liking. +Remember that if you just want to test locally, you can jump to the [local usage](#local-usage) section. ```bash docker build -t ghcr.io/flux-framework/flux-jupyter-hub:radiuss-2024 -f docker/Dockerfile.hub . @@ -30,12 +31,11 @@ Note that these are available under the flux-framework organization GitHub packa to build them unless you are developing or changing them. If you do build (and use a different name) be sure to push your images to a public registry (or load them locally to your development cluster). -Remember that if you just want to test locally, you can jump to the [local usage](#local-usage) section. -## Local Deploy -While the tutorial here is intended for deployment on AWS or Google Cloud, you can also give it a try on your local machine with a single container! You will need to [install Docker](https://docs.docker.com/engine/install/). -When you have Docker available, you can build and run the tutorial with: +## Local Usage + +While the tutorial here is intended for deployment on AWS or Google Cloud, you can also give it a try on your local machine with a single container! You will need to [install Docker](https://docs.docker.com/engine/install/). When you have Docker available, you can build and run the tutorial with: ```bash docker build -t flux-tutorial -f docker/Dockerfile.spawn . diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn index bdc27d6..55f51e1 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn @@ -1,4 +1,4 @@ -FROM fluxrm/flux-sched:focal +FROM fluxrm/flux-sched:jammy # Based off of https://github.com/jupyterhub/zero-to-jupyterhub-k8s/tree/main/images/singleuser-sample # Local usage @@ -26,8 +26,8 @@ RUN apt-get update \ ca-certificates \ dnsutils \ iputils-ping \ - python3.9 \ - python3.9-dev \ + python3 \ + python3-dev \ python3-pip \ python3-venv \ openmpi-bin \ @@ -47,8 +47,6 @@ RUN python3 -m pip install -r requirements.txt && \ python3 -m pip install ipython==7.34.0 && \ python3 -m IPython kernel install -COPY ./tutorial /home/jovyan/flux-tutorial-2024 - # This is code to install DYAD # This was added to the RADIUSS 2023 tutorials on AWS RUN git clone https://github.com/openucx/ucx.git \ @@ -76,22 +74,31 @@ RUN git clone https://github.com/flux-framework/dyad.git \ && cd ../.. \ && rm -rf dyad - # This adds the flux-tree command, which is provided in flux-sched source # but not installed alongside production flux-core COPY ./flux-tree/* /usr/libexec/flux/cmd/ RUN chmod +x /usr/libexec/flux/cmd/flux-tree* +RUN apt-get update && apt-get install -y nodejs && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN wget https://nodejs.org/dist/v20.15.0/node-v20.15.0-linux-x64.tar.xz && \ + apt-get update && apt-get install -y xz-utils && rm -rf /var/lib/apt/lists/* && \ + xz -d -v node-v20.15.0-linux-x64.tar.xz && \ + tar -C /usr/local --strip-components=1 -xvf node-v20.15.0-linux-x64.tar + # This customizes the launcher UI # https://jupyter-app-launcher.readthedocs.io/en/latest/usage.html RUN python3 -m pip install jupyter_app_launcher && \ python3 -m pip install --upgrade jupyter-server && \ + python3 -m pip install jupyter-launcher-shortcuts && \ mkdir -p /usr/local/share/jupyter/lab/jupyter_app_launcher -COPY ./docker/jupyter-launcher.yaml /usr/local/share/jupyter/lab/jupyter_app_launcher/config.yaml -ENV JUPYTER_APP_LAUNCHER_PATH /usr/local/share/jupyter/lab/jupyter_app_launcher + +COPY ./tutorial /home/jovyan/ +COPY ./docker/jupyter-launcher.yaml /usr/local/share/jupyter/lab/jupyter_app_launcher/jp_app_launcher.yaml +ENV JUPYTER_APP_LAUNCHER_PATH=/usr/local/share/jupyter/lab/jupyter_app_launcher/ # Give jovyan user permissions to tutorial materials -RUN chmod -R 777 ~/flux-tutorial-2024 +RUN chmod -R 777 ~/ /home/jovyan WORKDIR $HOME COPY ./docker/flux-icon.png $HOME/flux-icon.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml index becbc80..1121928 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml @@ -1,19 +1,26 @@ -# These don't work, but we can try again next year. -#- title: Flux Tutorial Notebook -# description: This is the main Flux Framework Tutorial -# source: /home/jovyan/flux-tutorial/notebook/flux.ipynb -# cwd: /home/jovyan/flux-tutorial/notebook/ -# type: notebook -# catalog: Notebook -# icon: /home/jovyan/flux-icon.png +- title: Flux Tutorial Notebook + description: This is the main Flux Framework Tutorial + type: jupyterlab-commands + icon: ./flux-icon.png + source: + - label: Flux Tutorial + id: 'filebrowser:open-path' + args: + path: 01_flux_tutorial.ipynb + icon: ./flux-icon.png + catalog: Notebook -# - title: Dyad Notebook Tutorial -# description: This is a tutorial for using Dyad -# source: /home/jovyan/flux-tutorial/notebook/dyad.ipynb -# cwd: /home/jovyan/flux-tutorial/notebook/ -# type: notebook -# catalog: Notebook -# icon: /home/jovyan/flux-icon.png +- title: Dyad Notebook Tutorial + description: This is a tutorial for using Dyad + type: jupyterlab-commands + icon: flux-icon.png + source: + - label: Dyad Tutorial + id: 'filebrowser:open-path' + args: + path: dyad_dlio.ipynb + icon: ./flux-icon.png + catalog: Notebook - title: Flux Framework Portal description: Flux Framework portal for projects, releases, and publication. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree index f12a9b8..5eb5002 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree +++ b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree @@ -212,7 +212,7 @@ jsonify() { if [[ "${avail}" = "yes" ]] then - avg=$(flux ion-resource stat | grep "Avg" | awk '{print $4}') + avg=$(flux ion-resource stats | grep "Avg" | awk '{print $4}') el_match=$(awk "BEGIN {print ${avg}*${njobs}*1000000.0}") fi diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb similarity index 88% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index 98def89..b9bd3b0 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -27,23 +27,25 @@ "\n", "> I'm ready! How do I do this tutorial? 😁️\n", "\n", - "To step through examples in this notebook you need to execute cells. To run a cell, press Shift+Enter on your keyboard. If you prefer, you can also paste the shell commands in the JupyterLab terminal and execute them there. This notebook provides the main Flux tutorial, and we have several other modules available:\n", + "To step through examples in this notebook you need to execute cells. To run a cell, press Shift+Enter on your keyboard. If you prefer, you can also paste the shell commands in the and execute them there. This notebook provides the main Flux tutorial, and we have several other modules available:\n", "\n", "## I'm ready! How do I do this tutorial? 😁️\n", "\n", - "This tutorial is split into 5 modules, each of which has a notebook:\n", - "* [Module 1: Getting started with Flux](./01_flux_tutorial.ipynb) (the rest of this notebook)\n", - "* [Module 2: Using Flux to manage and deploy distributed services](./02_flux_framework.ipynb)\n", - "* [Module 3: Using DYAD to accelerate distributed Deep Learning (DL) training](./03_dyad_dlio.ipynb)\n", - "* [Module 4: Lessons learned, next steps, and discussion](./04_flux_tutorial_conclusions.ipynb)\n", + "This tutorial is split into 3 chapters, each of which has a notebook:\n", + "* [Chapter 1: Getting started with Flux](./01_flux_tutorial.ipynb) (you're already here, it's this notebook!)\n", + "* [Chapter 2: Using Flux to manage and deploy distributed services](./02_flux_framework.ipynb)\n", + "* [Chapter 3: Lessons learned, next steps, and discussion](./03_flux_tutorial_conclusions.ipynb)\n", "\n", + "And if you have some extra time and interest, we have supplementary chapters to teach you about advanced (often experimental, or under development) features:\n", + "\n", + "* [Supplementary Chapter 1: Using DYAD to accelerate distributed Deep Learning (DL) training](./dyad_dlio.ipynb)\n", "\n", "Let's get started! To provide some brief, added background on Flux and a bit more motivation for our tutorial, \"Shift+Enter\" the cell below to watch our YouTube video!" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "id": "d71ecd22-8552-4b4d-9bc4-61d86f8d33fe", "metadata": { "tags": [] @@ -86,7 +88,7 @@ "source": [ "# Getting started with Flux\n", "\n", - "The code and examples that this tutorial is based on can be found at [flux-framework/Tutorials](https://github.com/flux-framework/Tutorials/tree/master/2023-RADIUSS-AWS). You can also find the examples one level up in the flux-workflow-examples directory in this JupyterLab instance.\n", + "The code and examples that this tutorial is based on can be found at [flux-framework/Tutorials](https://github.com/flux-framework/Tutorials/tree/master/2024-RADIUSS-AWS). You can also find python examples in the `flux-workflow-examples` directory from the sidebar navigation in this JupyterLab instance.\n", "\n", "## Resources\n", "\n", @@ -194,12 +196,8 @@ "tags": [] }, "source": [ - "### You can run any of the commands and examples that follow in the JupyterLab terminal. You can find the terminal in the JupyterLab launcher.\n", - "If you do `File -> New -> Terminal` you can open a raw terminal to play with Flux. You'll see a prompt like this: \n", - "\n", - "`ƒ(s=4,d=0) fluxuser@6e0f43fd90eb:~$`\n", - "\n", - "`s=4` indicates the number of running Flux brokers, `d=0` indicates the Flux hierarchy depth. `@6e0f43fd90eb` references the host, which is a Docker container for our tutorial." + "### What does the terminal prompt mean?\n", + "For cases when you need a terminal, we will ! However, you can also select `File -> New -> Terminal` to open one on the fly. Let's next talk about flux instances." ] }, { @@ -213,9 +211,14 @@ "\n", "A Flux instance is a fully functional set of services which manage compute resources under its domain with the capability to launch jobs on those resources. A Flux instance may be running as the default resource manager on a cluster, a job in a resource manager such as Slurm, LSF, or Flux itself, or as a test instance launched locally.\n", "\n", - "When run as a job in another resource manager, Flux is started like an MPI program, e.g., under Slurm we might run `srun [OPTIONS] flux start [SCRIPT]`. Flux is unique in that a test instance which mimics a multi-node instance can be started locally with simply `flux start --test-size=N`. This offers users to a way to learn and test interfaces and commands without access to an HPC cluster.\n", + "When run as a job in another resource manager, Flux is started like an MPI program, e.g., under Slurm we might run `srun [OPTIONS] flux start [SCRIPT]`. Flux is unique in that a test instance that mimics a multi-node instance can be started locally with simply:\n", "\n", - "To start a Flux session with 4 brokers in your container, run:" + "```bash\n", + "flux start --test-size=4\n", + "```\n", + "\n", + "This offers users to a way to learn and test interfaces and commands without access to an HPC cluster.\n", + "To start a Flux session with 4 brokers in your notebook container here, run:" ] }, { @@ -241,7 +244,7 @@ "id": "e693f2d9-651f-4f58-bf53-62528caa83d9", "metadata": {}, "source": [ - "The output indicates the number of brokers started successfully." + "When you run `flux start` without a command, it will give you an interactive shell to the instance. When you provide a command (as we do above) it will run it and exit. This is what happens for the command above! The output indicates the number of brokers started successfully. As soon as we get and print the size, we exit." ] }, { @@ -250,27 +253,28 @@ "metadata": {}, "source": [ "## Flux uptime\n", - "Flux provides an `uptime` utility to display properties of the Flux instance such as state of the current instance, how long it has been running, its size and if scheduling is disabled or stopped. The output shows how long the instance has been up, the instance owner, the instance depth (depth in the Flux hierarchy), and the size of the instance (number of brokers)." + "\n", + "Did someone say... [uptime](https://youtu.be/SYRlTISvjww?si=zDlvpWbBljUmZw_Q)? ☝️🕑️\n", + "\n", + "Don't worry, we are going to insert tidbits of fun throughout the tutorial! Don't be afraid to pause and dance! 🕺️ Flux provides an `uptime` utility to display properties of the Flux instance such as state of the current instance, how long it has been running, its size and if scheduling is disabled or stopped. The output shows how long the instance has been up, the instance owner, the instance depth (depth in the Flux hierarchy), and the size of the instance (number of brokers)." ] }, { "cell_type": "code", - "execution_count": 6, - "id": "6057ce25-d1b3-4cc6-b26a-4b05a1639616", - "metadata": { - "tags": [] - }, + "execution_count": 9, + "id": "1268ed06-e8f4-47a0-af4b-1b93fe3fa1b1", + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " 05:01:16 run 54m, owner jovyan, depth 0, size 4\n" + " 04:18:52 run 27m, owner jovyan, depth 0, size 4\n" ] } ], "source": [ - "!flux uptime" + "! flux uptime" ] }, { @@ -280,9 +284,9 @@ "tags": [] }, "source": [ - "# Submitting Jobs to Flux\n", + "# Submitting Jobs to Flux 💼️\n", "\n", - "How to submit jobs to flux? Let us count the ways! Here are how flux commands map to other schedulers you are familiar with.\n", + "How to submit jobs to Flux? Let us count the ways! Here are how Flux commands map to other schedulers you are familiar with.\n", "\n", "\n", " \n", @@ -322,7 +326,7 @@ " \n", "
\n", "\n", - "## Submission CLI\n", + "## Submission Client\n", "### `flux`: the Job Submission Tool\n", "\n", "To submit jobs to Flux, you can use the `flux` `submit`, `run`, `bulksubmit`, `batch`, and `alloc` commands. The `flux submit` command submits a job to Flux and prints out the jobid. " @@ -330,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", "metadata": {}, "outputs": [ @@ -338,7 +342,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRovbySzK\n" + "ƒF91H7Gc7\n" ] } ], @@ -356,7 +360,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "571d8c3d-b24a-415e-b9ac-f58b99a7e92c", "metadata": {}, "outputs": [ @@ -364,7 +368,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRp4rqT1h\n" + "ƒFCA1pkFH\n" ] } ], @@ -374,7 +378,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", "metadata": {}, "outputs": [ @@ -389,7 +393,7 @@ "positional arguments:\n", " command Job command and arguments\n", "\n", - "optional arguments:\n", + "options:\n", " -h, --help show this help message and exit\n", " -q, --queue=NAME Submit a job to a specific named queue\n", " -t, --time-limit=MIN|FSD Time limit in minutes when no units provided,\n", @@ -415,7 +419,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", "metadata": {}, "outputs": [ @@ -423,7 +427,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "993a4f746854\n" + "7db0bdd6f967\n" ] } ], @@ -441,7 +445,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", "metadata": {}, "outputs": [ @@ -475,7 +479,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "id": "02032748", "metadata": {}, "outputs": [ @@ -483,10 +487,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "3: 993a4f746854\n", - "2: 993a4f746854\n", - "1: 993a4f746854\n", - "0: 993a4f746854\n" + "3: 7db0bdd6f967\n", + "2: 7db0bdd6f967\n", + "1: 7db0bdd6f967\n", + "0: 7db0bdd6f967\n" ] } ], @@ -515,7 +519,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "id": "f0e82702", "metadata": {}, "outputs": [ @@ -523,12 +527,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRptxz9Nf\n", - "ƒRptxz9Ng\n", - "ƒRptxz9Nh\n", + "ƒFSHgbfxs\n", + "ƒFSHgbfxt\n", + "ƒFSHgbfxu\n", + "baz\n", "bar\n", - "foo\n", - "baz\n" + "foo\n" ] } ], @@ -546,7 +550,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "0ea1962b-1831-4bd2-8dab-c61fd710df9c", "metadata": {}, "outputs": [ @@ -554,26 +558,26 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRq7bN2b1\n", - "ƒRq7cr1sM\n", - "ƒRq7cr1sN\n", - "ƒRq7cr1sP\n", - "ƒRq7cr1sQ\n", - "ƒRq7eL19h\n", - "ƒRq7eL19i\n", - "ƒRq7eL19j\n", - "ƒRq7eL19k\n", - "ƒRq7eL19m\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n", - "993a4f746854\n" + "ƒFVMZdW4X\n", + "ƒFVMZdW4Y\n", + "ƒFVMZdW4Z\n", + "ƒFVMZdW4a\n", + "ƒFVMb7VLs\n", + "ƒFVMb7VLt\n", + "ƒFVMb7VLu\n", + "ƒFVMb7VLv\n", + "ƒFVMb7VLw\n", + "ƒFVMb7VLx\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n", + "7db0bdd6f967\n" ] } ], @@ -603,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 18, "id": "brazilian-former", "metadata": {}, "outputs": [ @@ -611,8 +615,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRqJMWpEB\n", - "ƒRqTXkNPy\n" + "ƒFZ7C25NP\n", + "ƒFZDMK6HV\n" ] } ], @@ -642,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 20, "id": "5ad231c2-4cdb-4d18-afc2-7cb3a74759c2", "metadata": {}, "outputs": [ @@ -650,7 +654,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRqcDpADD\n", + "ƒGA8B8Cf1\n", "25 blueberry pancakes on the table... 25 blueberry pancakes! 🥞️\n", "Eat a stack, for a snack, 15 blueberry pancakes on the table! 🥄️\n", "15 blueberry pancakes on the table... 15 blueberry pancakes! 🥞️\n", @@ -660,7 +664,7 @@ } ], "source": [ - "!flux submit ../flux-workflow-examples/job-watch/job-watch.sh\n", + "!flux submit ./flux-workflow-examples/job-watch/job-watch.sh\n", "!flux watch $(flux job last)" ] }, @@ -676,7 +680,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 21, "id": "institutional-vocabulary", "metadata": {}, "outputs": [ @@ -685,9 +689,9 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - " ƒRqTXkNPy jovyan analysis R 1 1 10.74s 993a4f746854\n", - " ƒRqJMWpEB jovyan simulation R 2 2 11.09s 993a4f[746854,746854]\n", - " ƒRp4rqT1h jovyan sleep R 2 1 13.89s 993a4f746854\n" + " ƒFZDMK6HV jovyan analysis R 1 1 1.492m 7db0bdd6f967\n", + " ƒFZ7C25NP jovyan simulation R 2 2 1.496m 7db0bdd6f[967,967]\n", + " ƒFCA1pkFH jovyan sleep R 2 1 2.288m 7db0bdd6f967\n" ] } ], @@ -705,7 +709,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "id": "46dd8ec8-6c64-4d8d-9a00-949f5f58c07b", "metadata": {}, "outputs": [ @@ -729,7 +733,7 @@ "id": "544aa0a9", "metadata": {}, "source": [ - "We can use the `flux batch` command to easily created nested flux instances. When `flux batch` is invoked, Flux will automatically create a nested instance that spans the resources allocated to the job, and then Flux runs the batch script passed to `flux batch` on rank 0 of the nested instance. \"Rank\" refers to the rank of the Tree-Based Overlay Network (TBON) used by the Flux brokers: https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man1/flux-broker.html\n", + "We can use the `flux batch` command to easily created nested flux instances. When `flux batch` is invoked, Flux will automatically create a nested instance that spans the resources allocated to the job, and then Flux runs the batch script passed to `flux batch` on rank 0 of the nested instance. \"Rank\" refers to the rank of the Tree-Based Overlay Network (TBON) used by the [Flux brokers](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man1/flux-broker.html).\n", "\n", "While a batch script is expected to launch parallel jobs using `flux run` or `flux submit` at this level, nothing prevents the script from further batching other sub-batch-jobs using the `flux batch` interface, if desired.\n", "\n", @@ -738,7 +742,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, "id": "blank-carpet", "metadata": {}, "outputs": [ @@ -746,8 +750,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRvcKDdSf\n", - "ƒRvkLjjvw\n" + "ƒGYBWWT7d\n", + "ƒGYGiwvby\n" ] } ], @@ -766,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 24, "id": "381a3f6c-0da1-4923-801f-486ca5226d3c", "metadata": {}, "outputs": [ @@ -898,7 +902,7 @@ "flux run -N 2 -n 2 sleep 30\n" ] }, - "execution_count": 21, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -910,7 +914,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 25, "id": "edff8993-3c39-4f46-939d-4c8be5739fbc", "metadata": {}, "outputs": [ @@ -918,20 +922,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒRvuBhTSF\n", + "ƒGbBf664w\n", " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;34m ƒRvkLjjvw jovyan ./sleep_b+ R 2 2 0.660s 993a4f[746854,746854]\n", - "\u001b[0;0m\u001b[01;34m ƒRvcKDdSf jovyan ./sleep_b+ R 2 2 0.972s 993a4f[746854,746854]\n", + "\u001b[01;34m ƒGYGiwvby jovyan ./sleep_b+ R 2 2 6.797s 7db0bdd6f[967,967]\n", + "\u001b[0;0m\u001b[01;34m ƒGYBWWT7d jovyan ./sleep_b+ R 2 2 6.996s 7db0bdd6f[967,967]\n", "\u001b[0;0m JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;34m ƒRvkLjjvw jovyan ./sleep_b+ R 2 2 0.966s 993a4f[746854,746854]\n", - "\u001b[0;0m\u001b[01;34m ƒRvcKDdSf jovyan ./sleep_b+ R 2 2 1.278s 993a4f[746854,746854]\n", + "\u001b[01;34m ƒGYGiwvby jovyan ./sleep_b+ R 2 2 6.977s 7db0bdd6f[967,967]\n", + "\u001b[0;0m\u001b[01;34m ƒGYBWWT7d jovyan ./sleep_b+ R 2 2 7.176s 7db0bdd6f[967,967]\n", "\u001b[0;0m\n", - "ƒRvkLjjvw:\n", - " ƒKZL1bM jovyan sleep R 2 2 0.060s 993a4f[746854,746854]\n", + "ƒGYGiwvby:\n", + " ƒJZWVbu jovyan sleep R 2 2 6.123s 7db0bdd6f[967,967]\n", "\n", - "ƒRvcKDdSf:\n", - " ƒK5AFEo jovyan sleep R 2 2 0.370s 993a4f[746854,746854]\n", - "{\"version\": 1, \"execution\": {\"R_lite\": [{\"rank\": \"3\", \"children\": {\"core\": \"7\"}}], \"nodelist\": [\"993a4f746854\"], \"starttime\": 1712898092, \"expiration\": 4866494812}}\n", + "ƒGYBWWT7d:\n", + " ƒJnrP91 jovyan sleep R 2 2 6.302s 7db0bdd6f[967,967]\n", + "{\"version\": 1, \"execution\": {\"R_lite\": [{\"rank\": \"3\", \"children\": {\"core\": \"7\"}}], \"nodelist\": [\"7db0bdd6f967\"], \"starttime\": 1720153582, \"expiration\": 4873751530}}\n", "0: stdout redirected to /tmp/cheese.txt\n", "0: stderr redirected to /tmp/cheese.txt\n" ] @@ -1025,7 +1029,7 @@ "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️" ] }, - "execution_count": 22, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -1066,7 +1070,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 26, "id": "df8a8b7c-f475-4a51-8bc6-9983dc9d78ab", "metadata": {}, "outputs": [ @@ -1075,30 +1079,31 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;34m ƒRvkLjjvw jovyan ./sleep_b+ R 2 2 1.705s 993a4f[746854,746854]\n", - "\u001b[0;0m\u001b[01;34m ƒRvcKDdSf jovyan ./sleep_b+ R 2 2 2.017s 993a4f[746854,746854]\n", - "\u001b[0;0m\u001b[01;32m ƒRvuBhTSF jovyan echo CD 1 1 0.013s 993a4f746854\n", - "\u001b[0;0m\u001b[37m ƒRp4rqT1h jovyan sleep CA 2 1 14.18s 993a4f746854\n", - "\u001b[0;0m\u001b[37m ƒRqJMWpEB jovyan simulation CA 2 2 11.38s 993a4f[746854,746854]\n", - "\u001b[0;0m\u001b[37m ƒRqTXkNPy jovyan analysis CA 1 1 11.02s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRqcDpADD jovyan job-watch+ CD 1 1 10.05s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7eL19k jovyan hostname CD 1 1 0.044s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7eL19j jovyan hostname CD 1 1 0.043s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7eL19h jovyan hostname CD 1 1 0.043s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7cr1sQ jovyan hostname CD 1 1 0.045s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7cr1sP jovyan hostname CD 1 1 0.043s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7cr1sN jovyan hostname CD 1 1 0.042s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7eL19m jovyan hostname CD 1 1 0.035s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7eL19i jovyan hostname CD 1 1 0.036s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7bN2b1 jovyan hostname CD 1 1 0.038s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRq7cr1sM jovyan hostname CD 1 1 0.034s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRptxz9Nh jovyan echo CD 1 1 0.083s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRptxz9Nf jovyan echo CD 1 1 0.083s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRptxz9Ng jovyan echo CD 1 1 0.073s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRpheDdXu jovyan hostname CD 4 1 0.049s 993a4f746854\n", - "\u001b[0;0m\u001b[01;31m ƒRpW95Cij jovyan false F 1 1 0.051s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRpKtaAiT jovyan hostname CD 1 1 0.033s 993a4f746854\n", - "\u001b[0;0m\u001b[01;32m ƒRovbySzK jovyan hostname CD 1 1 0.028s 993a4f746854\n", + "\u001b[01;34m ƒGYGiwvby jovyan ./sleep_b+ R 2 2 13.68s 7db0bdd6f[967,967]\n", + "\u001b[0;0m\u001b[01;34m ƒGYBWWT7d jovyan ./sleep_b+ R 2 2 13.88s 7db0bdd6f[967,967]\n", + "\u001b[0;0m\u001b[01;32m ƒGbBf664w jovyan echo CD 1 1 0.033s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[37m ƒFCA1pkFH jovyan sleep CA 2 1 2.291m 7db0bdd6f967\n", + "\u001b[0;0m\u001b[37m ƒFZ7C25NP jovyan simulation CA 2 2 1.499m 7db0bdd6f[967,967]\n", + "\u001b[0;0m\u001b[37m ƒFZDMK6HV jovyan analysis CA 1 1 1.495m 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒGA8B8Cf1 jovyan job-watch+ CD 1 1 10.05s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFf72VaCo jovyan job-watch+ CD 1 1 10.06s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLx jovyan hostname CD 1 1 0.027s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLv jovyan hostname CD 1 1 0.027s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLu jovyan hostname CD 1 1 0.026s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLw jovyan hostname CD 1 1 0.026s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLs jovyan hostname CD 1 1 0.023s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMb7VLt jovyan hostname CD 1 1 0.022s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMZdW4a jovyan hostname CD 1 1 0.021s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMZdW4Z jovyan hostname CD 1 1 0.020s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMZdW4X jovyan hostname CD 1 1 0.019s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFVMZdW4Y jovyan hostname CD 1 1 0.018s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFSHgbfxs jovyan echo CD 1 1 0.016s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFSHgbfxt jovyan echo CD 1 1 0.013s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFSHgbfxu jovyan echo CD 1 1 0.012s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFQ98A1iX jovyan hostname CD 4 1 0.044s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;31m ƒFKYd97bM jovyan false F 1 1 0.053s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒFH2K3zXZ jovyan hostname CD 1 1 0.034s 7db0bdd6f967\n", + "\u001b[0;0m\u001b[01;32m ƒF91H7Gc7 jovyan hostname CD 1 1 0.045s 7db0bdd6f967\n", "\u001b[0;0m" ] } @@ -1117,7 +1122,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 27, "id": "032597d2-4b02-47ea-a5e5-915313cdd7f9", "metadata": {}, "outputs": [ @@ -1126,7 +1131,7 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;31m ƒRpW95Cij jovyan false F 1 1 0.051s 993a4f746854\n", + "\u001b[01;31m ƒFKYd97bM jovyan false F 1 1 0.053s 7db0bdd6f967\n", "\u001b[0;0m" ] } @@ -1140,7 +1145,7 @@ "id": "04b405b1-219f-489c-abfc-e2983e82124a", "metadata": {}, "source": [ - "# The Flux Hierarchy\n", + "# The Flux Hierarchy 🍇️\n", "\n", "One feature of the Flux Framework scheduler that is unique is its ability to submit jobs within instances, where an instance can be thought of as a level in a graph. Let's start with a basic image - this is what it might look like to submit to a scheduler that is not graph-based,\n", "where all jobs go to a central job queue or database. Note that our maximum job throughput is one job per second.\n", @@ -1165,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 28, "id": "2735b1ca-e761-46be-b509-a86b771628fc", "metadata": {}, "outputs": [ @@ -1173,19 +1178,35 @@ "name": "stdout", "output_type": "stream", "text": [ - "TreeID Elapsed(sec) Begin(Epoch) End(Epoch) Match(usec) NJobs NNodes CPN GPN\n", - "tree 59.617500 1712898094.388196 1712898154.005696 1691.590000 4 1 4 0\n", - "tree.2 3.144260 1712898094.970127 1712898098.114384 8733.710000 2 1 2 0\n", - "tree.2.2 0.208135 1712898096.299190 1712898096.507325 0.000000 1 1 1 0\n", - "tree.2.1 0.203801 1712898095.883411 1712898096.087211 0.000000 1 1 1 0\n", - "tree.1 3.162000 1712898094.891970 1712898098.053968 622.539000 2 1 2 0\n", - "tree.1.2 0.110160 1712898096.142613 1712898096.252773 0.000000 1 1 1 0\n", - "tree.1.1 0.110111 1712898095.736442 1712898095.846553 0.000000 1 1 1 0\n" + "flux-job: task(s) exited with exit code 1\n", + "flux-job: task(s) exited with exit code 1\n", + "7db0bdd6f967\n", + "usage: flux-ion-resource.py [-h] [-v]\n", + " {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}\n", + " ...\n", + "flux-ion-resource.py: error: argument {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}: invalid choice: 'stat' (choose from 'match', 'update', 'info', 'stats', 'stats-cancel', 'cancel', 'find', 'status', 'set-status', 'set-property', 'get-property', 'ns-info', 'params')\n", + "awk: line 1: syntax error at or near *\n", + "flux-tree-helper: ERROR: Expecting value: line 1 column 160 (char 159)\n", + "Jul 05 04:27:34.087814 UTC broker.err[0]: rc2.0: flux tree -N1 -c1 --leaf --prefix=tree.1.1 --njobs=1 -- hostname Exited (rc=1) 0.5s\n", + "usage: flux-ion-resource.py [-h] [-v]\n", + " {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}\n", + " ...\n", + "flux-ion-resource.py: error: argument {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}: invalid choice: 'stat' (choose from 'match', 'update', 'info', 'stats', 'stats-cancel', 'cancel', 'find', 'status', 'set-status', 'set-property', 'get-property', 'ns-info', 'params')\n", + "awk: line 1: syntax error at or near *\n", + "flux-tree-helper: ERROR: Expecting value: line 1 column 157 (char 156)\n", + "Jul 05 04:27:35.284158 UTC broker.err[0]: rc2.0: flux tree -N1 -c2 --topology=2 --queue-policy=fcfs --prefix=tree.1 --njobs=2 -- hostname Exited (rc=1) 2.3s\n", + "usage: flux-ion-resource.py [-h] [-v]\n", + " {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}\n", + " ...\n", + "flux-ion-resource.py: error: argument {match,update,info,stats,stats-cancel,cancel,find,status,set-status,set-property,get-property,ns-info,params}: invalid choice: 'stat' (choose from 'match', 'update', 'info', 'stats', 'stats-cancel', 'cancel', 'find', 'status', 'set-status', 'set-property', 'get-property', 'ns-info', 'params')\n", + "awk: line 1: syntax error at or near *\n", + "flux-tree-helper: ERROR: Expecting value: line 1 column 155 (char 154)\n", + "cat: ./tree.out: No such file or directory\n" ] } ], "source": [ - "!flux tree -T2x2 -J 4 -N 1 -c 4 -o ./tree.out -Q easy:fcfs hostname \n", + "!flux tree -T2x2 -J 4 -N 1 -c 4 -o ./tree.out -Q easy:fcfs hostname\n", "! cat ./tree.out" ] }, @@ -1218,7 +1239,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 29, "id": "e82863e5-b2a1-456b-9ff1-f669b3525fa1", "metadata": {}, "outputs": [ @@ -1332,7 +1353,7 @@ "flux job wait --all" ] }, - "execution_count": 26, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -1359,7 +1380,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 30, "id": "72358a03-6f1f-4c5e-91eb-cab71883a232", "metadata": {}, "outputs": [ @@ -1367,12 +1388,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSQP2KMFd\n", - "ƒSQP2KMFd\n", - "Hello job 1 from 993a4f746854 💛️\n", - "Hello job 2 from 993a4f746854 💚️\n", - "Hello job 3 from 993a4f746854 💙️\n", - "Hello job 4 from 993a4f746854 💜️\n" + "ƒJVmi8uzX\n", + "ƒJVmi8uzX\n", + "Hello job 1 from 7db0bdd6f967 💛️\n", + "Hello job 2 from 7db0bdd6f967 💚️\n", + "Hello job 3 from 7db0bdd6f967 💙️\n", + "Hello job 4 from 7db0bdd6f967 💜️\n" ] } ], @@ -1400,7 +1421,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 31, "id": "2e6976f8-dbb6-405e-a06b-47c571aa1cdf", "metadata": {}, "outputs": [ @@ -1502,7 +1523,7 @@ "flux queue drain\n" ] }, - "execution_count": 28, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -1513,7 +1534,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 32, "id": "a0719cc9-6bf2-4285-b5d7-6cc534fc364c", "metadata": {}, "outputs": [ @@ -1612,7 +1633,7 @@ "flux run -N1 sleep 30\n" ] }, - "execution_count": 29, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -1651,7 +1672,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 33, "id": "2d2b1f0b-e6c2-4583-8068-7c76fa341884", "metadata": {}, "outputs": [ @@ -1660,15 +1681,14 @@ "output_type": "stream", "text": [ ".\n", - "├── ./sub_job1.sh\n", "├── ./hello-batch.sh:CD\n", + "├── 2*[flux-tree-Tpb37xfIP23YjqCChAoJjWshyEHYJob1:F]\n", "├── 2*[./sleep_batch.sh:CD]\n", - "├── 2*[flux-tree-LGb1X8A4sLe0CTuB5DXfLON8BpytAldt:CD]\n", "├── 4*[echo:CD]\n", "├── sleep:CA\n", "├── simulation:CA\n", "├── analysis:CA\n", - "├── job-watch.sh:CD\n", + "├── 2*[job-watch.sh:CD]\n", "├── 13*[hostname:CD]\n", "└── false:F\n" ] @@ -1691,13 +1711,13 @@ "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", "metadata": {}, "source": [ - "# Flux Process and Job Utilities\n", - "## Flux top\n", + "# Flux Process and Job Utilities ⚙️\n", + "## Flux top \n", "Flux provides a feature-full version of `top` for nested Flux instances and jobs. In the JupyterLab terminal, invoke `flux top` to see the \"sleep\" jobs. If they have already completed you can resubmit them. \n", "\n", "We recommend not running `flux top` in the notebook as it is not designed to display output from a command that runs continuously.\n", "\n", - "## Flux pstree\n", + "## Flux pstree \n", "In analogy to `top`, Flux provides `flux pstree`. Try it out in the JupyterLab terminal or here in the notebook.\n", "\n", "## Flux proxy\n", @@ -1747,13 +1767,13 @@ "id": "997faffc", "metadata": {}, "source": [ - "## Python Submission API\n", + "## Python Submission API 🐍️\n", "Flux also provides first-class python bindings which can be used to submit jobs programmatically. The following script shows this with the `flux.job.submit()` call:" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 35, "id": "third-comment", "metadata": {}, "outputs": [], @@ -1767,7 +1787,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 36, "id": "selective-uganda", "metadata": {}, "outputs": [ @@ -1775,7 +1795,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSRjxR7UT\n" + "ƒKKdeYAGo\n" ] } ], @@ -1798,7 +1818,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 37, "id": "ed65cb46-8d8a-41f0-bec1-92b9a89e6db2", "metadata": {}, "outputs": [ @@ -1806,9 +1826,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "🎉️ Hooray, we just submitted ƒSRkkNjD9!\n", + "🎉️ Hooray, we just submitted ƒKLZWG53M!\n", "{\n", - " \"t_depend\": 1712898157.9034483,\n", + " \"t_depend\": 1720153943.7848454,\n", " \"t_run\": 0.0,\n", " \"t_cleanup\": 0.0,\n", " \"t_inactive\": 0.0,\n", @@ -1828,8 +1848,8 @@ " \"success\": \"\",\n", " \"result\": \"\",\n", " \"waitstatus\": \"\",\n", - " \"id\": 56141966999552,\n", - " \"t_submit\": 1712898157.8924408,\n", + " \"id\": 40488354709504,\n", + " \"t_submit\": 1720153943.7735925,\n", " \"t_remaining\": 0.0,\n", " \"state\": \"SCHED\",\n", " \"username\": \"jovyan\",\n", @@ -1890,7 +1910,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 38, "id": "efa06478", "metadata": {}, "outputs": [ @@ -1955,7 +1975,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 40, "id": "industrial-privacy", "metadata": {}, "outputs": [ @@ -1963,8 +1983,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSRuYNU9m\n", - "ƒSRv8UBdh\n" + "ƒKf7Aiz4P\n", + "ƒKf7rkefh\n" ] } ], @@ -1972,19 +1992,19 @@ "compute_jobreq = JobspecV1.from_command(\n", " command=[\"./compute.py\", \"120\"], num_tasks=4, num_nodes=2, cores_per_task=2\n", ")\n", - "compute_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\")\n", + "compute_jobreq.cwd = os.path.expanduser(\"~/flux-workflow-examples/job-submit-api/\")\n", "print(JobID(flux.job.submit(f, compute_jobreq)))\n", "\n", "io_jobreq = JobspecV1.from_command(\n", " command=[\"./io-forwarding.py\", \"120\"], num_tasks=1, num_nodes=1, cores_per_task=1\n", ")\n", - "io_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\")\n", + "io_jobreq.cwd = os.path.expanduser(\"~/flux-workflow-examples/job-submit-api/\")\n", "print(JobID(flux.job.submit(f, io_jobreq)))" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 41, "id": "pregnant-creativity", "metadata": {}, "outputs": [ @@ -1992,9 +2012,10 @@ "name": "stdout", "output_type": "stream", "text": [ - " ƒSRuYNU9m jovyan compute.py F 4 2 0.012s 993a4f[746854,746854]\n", - " ƒSRkkNjD9 jovyan compute.py F 1 1 0.014s 993a4f746854\n", - " ƒSRjxR7UT jovyan compute.py F 1 1 0.019s 993a4f746854\n" + " ƒKf7Aiz4P jovyan compute.py R 4 2 2.727s 7db0bdd6f[967,967]\n", + " ƒKNequHnF jovyan compute.py F 4 2 0.012s 7db0bdd6f[967,967]\n", + " ƒKLZWG53M jovyan compute.py F 1 1 0.037s 7db0bdd6f967\n", + " ƒKKdeYAGo jovyan compute.py F 1 1 0.012s 7db0bdd6f967\n" ] } ], @@ -2028,7 +2049,7 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 42, "id": "cleared-lawsuit", "metadata": {}, "outputs": [ @@ -2036,106 +2057,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "bulksubmit_executor: submitted 200 jobs in 0.24s. 816.77job/s\n", - "bulksubmit_executor: First job finished in about 0.245s\n", - "|██████████████████████████████████████████████████████████| 100.0% (263.6 job/s)\n", - "bulksubmit_executor: Ran 200 jobs in 0.9s. 221.6 job/s\n" + "bulksubmit_executor: submitted 200 jobs in 0.28s. 721.87job/s\n", + "bulksubmit_executor: First job finished in about 0.328s\n", + "|██████████████████████████████████████████████████████████| 100.0% (174.4 job/s)\n", + "bulksubmit_executor: Ran 200 jobs in 1.3s. 153.7 job/s\n" ] } ], "source": [ "# Submit a FluxExecutor based script.\n", - "%run ../flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py -n200 /bin/sleep 0" - ] - }, - { - "cell_type": "markdown", - "id": "e1f041b1-ebe3-49d7-b522-79013e29acfa", - "metadata": {}, - "source": [ - "# Flux Archive\n", - "\n", - "As Flux is more increasingly used in cloud environments, you might find yourself in a situation of having a cluster without a shared filesystem! Have no fear, the `flux archive` command is here to help!\n", - "At a high level, `flux archive` is allowing you to save named pieces of data (text or data files) to the Flux key value store (KVS) for later retrieval.\n", - "Since this tutorial is running on one node it won't make a lot of sense, but we will show you how to use it. The first thing you'll want to do is make a named archive." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "0114079f-26a3-4614-a8b2-6422ee2170a2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "flux-archive: archive.myshare: key exists\n" - ] - } - ], - "source": [ - "! touch shared-file.txt\n", - "! flux archive create --name myshare --directory $(pwd) shared-file.txt" - ] - }, - { - "cell_type": "markdown", - "id": "e33173df-adbf-4028-8795-7f68d7dc66ba", - "metadata": {}, - "source": [ - "We would then want to send this file to the other nodes, and from the same node. We can combine two commands to do that:\n", - "\n", - "- `flux exec` executes commands on instance nodes, optionally excluding ranks with `-x`\n", - "- `flux archive extract` does the extraction\n", - "\n", - "So we might put them together to look like this - asking for all ranks, but excluding (`-x`) rank 0 where we are currently sitting and the file already exists." - ] - }, - { - "cell_type": "code", - "execution_count": 41, - "id": "05769493-54a9-453c-9c5e-516123a274c2", - "metadata": {}, - "outputs": [], - "source": [ - "! flux exec --rank all -x 0 flux archive extract --name myshare --directory $(pwd) shared-file.txt" - ] - }, - { - "cell_type": "markdown", - "id": "4df4ee23-4cce-4df8-9c99-e5cd3a4ae277", - "metadata": {}, - "source": [ - "If the extraction directory doesn't exist on the other nodes yet? No problem! We can use `flux exec` to execute a command to the other nodes to create it, again with `-x 0` to exclude rank 0 (where the directory already exists). Note that you'd run this _before_ `flux archive extract` and we are using `-r` as a shorthand for `--rank`." - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "id": "351415e0-4644-49bc-b4b1-b3ab3544d527", - "metadata": {}, - "outputs": [], - "source": [ - "! flux exec -r all -x 0 mkdir -p $(pwd)" - ] - }, - { - "cell_type": "markdown", - "id": "781bb105-4977-4022-a0bf-0bc53d73b2e4", - "metadata": {}, - "source": [ - "When you are done, it's good practice to clean up and remove the archive. Also note that for larger files, you can use `--mmap` to memory map content." - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "id": "acde2ba8-ade9-450e-8ff9-2b0f094166b9", - "metadata": {}, - "outputs": [], - "source": [ - "! flux archive remove --name myshare" + "%run ./flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py -n200 /bin/sleep 0" ] }, { @@ -2143,8 +2074,6 @@ "id": "ec052119", "metadata": {}, "source": [ - "Finally, note that older versions of flux used `flux filemap` instead of flux archive. It's largely the same command with a rename.\n", - "\n", "# Diving Deeper Into Flux's Internals\n", "\n", "Flux uses [hwloc](https://github.com/open-mpi/hwloc) to detect the resources on each node and then to populate its resource graph.\n", @@ -2548,7 +2477,7 @@ "id": "c9c3e767-0459-4218-a8cf-0f98bd32d6bf", "metadata": {}, "source": [ - "# This concludes Module 1.\n", + "# This concludes Chapter 1! 📗️\n", "\n", "In this module, we covered:\n", "1. Submitting jobs with Flux\n", @@ -2556,7 +2485,7 @@ "3. Flux Process and Job Utilities\n", "4. Deeper Dive into Flux Internals\n", "\n", - "To continue with the tutorial, open [Module 2](./02_flux_framework.ipynb)" + "To continue with the tutorial, open [Chapter 2](./02_flux_framework.ipynb)" ] } ], @@ -2576,7 +2505,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb similarity index 96% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb index bc4ff66..15dfe6f 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb @@ -8,7 +8,7 @@ "
\n", "\n", "\n", - "# Module 2: Using Flux to manage and deploy distributed services\n", + "# Chapter 2: Using Flux to manage and deploy distributed services\n", "\n", "Now that we have learned about hierarchical scheduling and its benefits, let's dive deeper into the structure of the individual Flux instances that comprise a hierarchy and examine how that structure enables the management and deployment of distributed services. In this module, we cover:\n", "1. The structure of Flux instances\n", @@ -17,7 +17,7 @@ "\n", "## The structure of Flux instances\n", "\n", - "As mentioned in [Module 1](./01_flux_tutorial.ipynb), a Flux instance is comprised of one or more Flux brokers. A high-level depiction of the design of a Flux broker is shown in the figure below.\n", + "As mentioned in [Chapter 2](./01_flux_tutorial.ipynb), a Flux instance is comprised of one or more Flux brokers. A high-level depiction of the design of a Flux broker is shown in the figure below.\n", "\n", "
\n", "\n", @@ -189,7 +189,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### flux archive\n", + "### flux archive 📚️\n", "\n", "As Flux is used more in cloud environments, we might find ourselves in a situation where we have a cluster without a shared filesystem. The `flux archive` command helps with this situation. At a high level, `flux archive` allows us to save named pieces of data (e.g., files) to the Flux KVS for later retrieval.\n", "\n", @@ -271,14 +271,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# This concludes Module 2.\n", + "# This concludes Chapter 2.\n", "\n", "In this module, we covered:\n", "1. The structure of Flux instances and how that structure enables distributed services (including traditional and hierarchical scheduling)\n", "2. How to start and stop services in Flux\n", "3. Two useful services for users of Flux (i.e., `flux kvs` and `flux archive`)\n", "\n", - "To continue with the tutorial, open [Module 3](./03_dyad_dlio.ipynb)." + "To finish the tutorial, open [Chapter 3](./03_flux_tutorial.ipynb)." ] } ], @@ -298,7 +298,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb similarity index 78% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb index 0bb171c..0f65f03 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb @@ -8,7 +8,7 @@ "
\n", "\n", "\n", - "# Module 4: Lessons learned, next steps, and discussion\n", + "# Chapter 3: Lessons learned, next steps, and discussion\n", "# This concludes the Flux tutorial! 😄️\n", "\n", "In this tutorial, we:\n", @@ -19,16 +19,18 @@ "* Described the structure of Flux instances and how that structure supports distributed services\n", "* Explained how to manage services with Flux\n", "* Showed examples of Flux services\n", - "* Described the design of DYAD, a Flux service for runtime data movement\n", - "* Introduced distributed Deep Learning (DL) training\n", - "* Introduced Argonne National Laboratory's Deep Learning I/O (DLIO) benchmark\n", - "* Used DLIO to show how DYAD accelerates distributed DL training\n", "\n", - "Don't worry, you'll have more opportunities for using Flux! We hope you reach out to us on any of our [project repositories](https://flux-framework.org) and ask any questions that you have. We'd love your contribution to code, documentation, or just saying hello! 👋️ If you have feedback on the tutorial, please let us know so we can improve it for next year. \n", + "If you are ready for advanced content, you can do the [DYAD and DLIO tutorial](./dyad_dlio.ipynb) and learn about:\n", + "* Describing the design of DYAD, a Flux service for runtime data movement\n", + "* Introducing distributed Deep Learning (DL) training\n", + "* Introducing Argonne National Laboratory's Deep Learning I/O (DLIO) benchmark\n", + "* Using DLIO to show how DYAD accelerates distributed DL training\n", + "\n", + "And don't worry, you'll have more opportunities for using Flux! We hope you reach out to us on any of our [project repositories](https://flux-framework.org) and ask any questions that you have. We'd love your contribution to code, documentation, or just saying hello! 👋️ If you have feedback on the tutorial, please let us know so we can improve it for next year. \n", "\n", "> But what do I do now?\n", "\n", - "Feel free to experiment more with Flux here, or (for more freedom) in the terminal. You can try more of the examples in the flux-workflow-examples directory one level up in the window to the left. If you're using a shared system like the one on the RADIUSS AWS tutorial please be mindful of other users and don't run compute intensive workloads. If you're running the tutorial in a job on an HPC cluster... compute away! ⚾️\n", + "Feel free to experiment more with Flux here, or (for more freedom) in the terminal. You can try more of the examples in the `flux-workflow-examples` directory in the window to the left. If you're using a shared system like the one on the RADIUSS AWS tutorial please be mindful of other users and don't run compute intensive workloads. If you're running the tutorial in a job on an HPC cluster... compute away! ⚾️\n", "\n", "> Where can I learn to set this up on my own?\n", "\n", @@ -82,7 +84,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.12" } }, "nbformat": 4, diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/Flux-logo.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/Flux-logo.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/Flux-logo.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/Flux-logo.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example1.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example1.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example2.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example2.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/03_dyad_dlio.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad_dlio.ipynb similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/03_dyad_dlio.ipynb rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad_dlio.ipynb diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-icon.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d50aa52d32a78c54ffcbd597b84d489fa3d80ae7 GIT binary patch literal 117673 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

1EHvr9mpbNpSP2MBiWfYkH?TkxkzQ z>V&tYvj#Z9o=!M9S)E<<_<81ns5~QTmjl`JspG*Xzm(N8(ONibSPBA9 zd;Juq{7iJ{uG4QKB2F0*MJzAhPDGxIGn9qPWr`^hhG9jnQ8(K)Fmmx@PJNSIpSh`2 z{a|@G*+W}4i4(nWLlO~n(HPYKCFmnFYlB4$GvWu|zOB5qgyhbb6c;$;b?^Bo88J#* z$pPY=&qcW|alYtiM)X})lzI0s!*Y#PgxVNc+8^JTNuPw&!=6ofp|6TuXMU8Efm^w{ z-eb`4Ah)#(a6BgJNW0gkUHt9-mrky#T`dY`w+?^%o=%?=D1*dk7gByU{5=oGToQC} z|AG+=KCr4?Z*?5VK%mfGr_E4rsjWnC5ERwa0q@F6Anmq88la`Tj_Ne+duA{ zc%oz2^7s4^&4FEB`2Su|Q&w@3M-hL|MLLwqoSj13`LVXl@D$Q&!2{ebm226WnD`Ve z(p-u9cNYV;^eaH@gY29jEsN>8)!B27Ryk-5G7J^t2TrxQ-p(}#9Y@GtoD zlokeiCM056>K!~Zh~8lfQ5f}Z1fVb|4*VTRW$Zm7L&5vL`Wanrp~-gp+f9O?4Wh8j zRr#$|Vv2MQ{zPn<*bg2@Rw1aRuL;F0)(^1E1S-C3eHIb)d1i@dkPfuTmTY;{{9)iy zfHz~QWo4d6@2EqEDDPI?{Ga(n?7%jJ3TA6<;7mjh=8~n(m*C8pCjzyIlp9kz@B`@5 zI|Xdf^%4SC<@5Umn77wUKGjpxZ?aQ`>n!@ZX$uA;G;`5^D2v;tWr@3?;rQ;H*u=o4 zwIXq>Z%NKqf(pJryl)Z%v6uv<3MWUe5w zRC$Sk@k4?quYhw|W4;zYwFwjrt%8ix1bxthg5~RiPf}bOv{lG-1T>SAfv;{c577%s zrLz@Q9DsIWDw14|>Su=k#04$=BfjWk;3W-Z5EzA-wjAbK(1R(~c#ReknU?-AAj7ux z3J3d92xwY9a=&UC&2@0fF-@&sPBHf))|GL3a-R1tO+h(&5vH*hLOcIu z`~&^|PjidDOaqM`#^?ij8L%vbFS2O1pO=fQr0UJ2Owiks^gs0P=2!)`6qIK?=e2vgNi39?m$Fo8lVb7-QwPxmF;j%5 zD&e(uvbad9_{2DAZv$f(-P2}PA{idHQ`y7Th^w!KQdO?0ya=?Rp*hw~tCG2a7U@Y5 z4ol0)Ap)(wuXWVSkYcR&$X8Ygr&G9)UZga55;i;7uCi1k60L6Wo77%H&FZ)Z_RX|x zC}4?wO=IFE5qUEvWu!rj0|T}}_IkS-Jy`lhV`qQO!j~j(X>ZjdyjPgi{sNi-=&4cj z^Z;T;J*&BRYT?j3#e`YR1E-!z!esCg<>g6!Mx^?lMg|Hi@yRD1NwoH?*`gVBkdblX zXfWb$kocZOla9Y$pM{uqrp`r^^*<@=t^+C`_hjAnR;j6SD<+Cdpy40{Vs{cw$5@N( zl^u+m&tc-fHgAK=Ms8*)V;Ow>jOVc}5J?Yu1Hh)uIRpk9#^CKN-=sxx(-^_-tZrk5 zn(988i^9exLu|i1dN2wyGNmL;FSdtTwp;q()ne7G5naG?8+#d zvojdEtm9!&Fg>aaSr}cAuTK5N&=-TlIXqhNu^eIQ_`Y`$uS&YUp9FxTTnt41NT2>f z=wLdCQXq(}OH_q{X&~>6_&0^xmngsShz2|)*D5c<%Ix#x>6{W8o+_QbvT=U!Y(;%G zzbo3$wa`04^y8~ML>h$3HeK+7P;?B10|50a_a2mf9}v;vi{C0j9TGl zJ!^3)BflCbzVbhQwFVe-R47lAt*uSWNTQn0C*`i=-s(f2-_f=craC&&-bvigwQB?Z zrY_Amx_37Z{>^ABT^o6(gG$EqQ)|9c2I$OQqz+vaX=A+l8$(By(Z;K4sHYt=WB~i3 zIzjfV-%<-C?^>1rI14vJ84$#@p0foTIo%~$kX!vad!0|I(L+5sZ$MU;g_=}@UrPt@ z3#^Fbu-Ied84CYIQ7Oubly!ukp7rPxE25$-kUqX<$=rzQ{*(bx9E8)7p>t4Q#NIKz zgkkQ3QO`HsY*?*`F%@bp!Q(*k_8jX_IdejsJ#MY2KW$;Qu02`<9Lt13>AiKx{__O{ zq^I|hGH6k3PssJ3qoA8)slWF}=5Fj`bt(7QACFBGRAe3qo|NM% zbfqH$je$0x^+;ya3ylQeo6Fstnen?PAoQ`i<;yn_*HY~OIrLdZ{9;3a;JJwr)w0&l z1hKy;dL5_a@8123z|dM#zU_TIL<7(RDV-ZhM<2?EN=hN&lw{Lio*M&=T6d;FeUn{p zxE<0bZ`VP>waihrn_!> z0euuM=UZm4`e)8F7xLX-Nr4=NOL5-~aO3jEVdW>Y(>itqTUMRM8)Aqw_)h^$p|=H- zdJLs2m3i1f;5;&sMdN!g{r(6D()-o$WMC_~+x^$k15<>ahy;Ij60ZpUoFaMH_qW_i zlIO>)$R6z4=dInCNEr4|#IX*wD%F`u@3S&`dN5Pe=fYM#{_J9SE7~5TW@-2l^=DGX z6ck#71!|hvigk5!+?T6T5vJv+M`>EtTW~JsL zm6>-{v--Jr{j|lM%W$v(J2X6ucxon=S*H8>aDf>u`i%K@HC%i);Y>#G{h^$)dlJxm zawaw|(v+>W3=KtwN7G@nDoz*JHv>HH&0SF@4fEqNCww2-55Om%)#{&DGnvP*lrc2P z%?ok;`AGD$A9bA^m<5GsYN1Z@Fe_U%LXBUUB+O62jeJ6llATlm*O^!EQlj69NALQq zxv#tZ?`$RIdw(WiDjpdXbOQ6y5o^lyo)n|;#Wxt-9-_`O7C0nW`0^_#QsttfH+2^A z#E{-lAcOd}=7d)wqTMZA z0!Wn}H4J6RvEGg;i!6?)&B@u#$Oq9cgDHXrDA>3n(I45eP_gXS!RGWv7+8bqjY_ZY zb-dpmrT9>|I_~PDeUgM|!E&o0k z3xS3Lf|DU3sCBmk1oO#mV&_Ku#OvAT&PN8Pqal~oX1;a>-SzQ2#m9~77APRlF>a(e zj5>Bw_PAjb(=b7bNdLq0LxIex%^MzRFbK4T!Tjhf^B7bbz3PtwfFn};}1Q<8FV}b*YGk(Q=Y-unY-w98mluoKrqVjgt(gxbxNze1p zmz0>BFj+L&>X(@&qABlkSOoOw;T5ob0Y1zI0+|~?3Begf2ZaCVe((os90-&uYtdd9 zR=yA^#F(n*(qK6L1xMHu0a7nCvlt z=h^gvxbPldMFJ)~To5Q+8UYQhUHtu38RL~X2m}EkfVhVGyOy*U8X5==r)Pw~FAp1d z{}6bG0(_zchK8a0p1%(^n>7a#FZd;;C~N6AcQg=oj5R6s;WhA zG!Qra(MUzyC3J4m#LopE7|+i?_kVhpO%42UG+0uSKid*R0EYcIClYW?UEN#Qg<6P` zxjzHhI8qO9LjrycJ#>FExlC$V$=|#3={~DI;3lwXxX%WH3eksdqBwfKoY!C63ZR(U}}=uoRH@wyl$rNeSvb{}}X+I9QZb;c~{jxmCXZ#ym@-xEeS z;QPp$(67TrzqJX%4I&HcXZ*I4yFD7uGX7PgT3D3+s>YHo0QO#i_>$We#Q34h4L+z*>v z>mKSGTh{4L1A?o^^@R@rEIqMo*`sHk$DGSCtSE!=kW7E0#MF}c3}^xf;{f3q%2apd|sLrOT7Hl|xhOHQsp)*7l@&K9)h$yfEKa}&I zAIQHk;y2D?9_HXuh6{2|qXs(I^2yqd zC18c=QQwE!lnkzUc#-`>C47{Fl&V?o+>e!2S_lfbBOwYQJKe0IIN1%O8|tGzY`j0z zMJiLu0d`xE^5w^k&MU;1zd#fm-`+-by+9C8& z<0sxWYDqZ+L;)l49h$?(9&YT+Zh(+VG6DUBz~NDIeE|iT>A$aoy$HOTVj2E=IL_7Z zp!>4JpX;;lS@l*^{AyNW!r%yTOo7g7yveJQq;10F|KtE+UH#hd0sho}wKa{m>Xab( z;r9pqpz-<-_g7MW7t=O!i9*+14^KnR&I;}$r0&KYpS=v6UJm2i0+2gymJwT{82rFT zjxv6`#{4M%rcvveSd=v_p%>awezs~Jh073gG3gE4my6Owe-_|Q_5ZX4Ne}6zc~zqR zarvWQLoBlrK6-203jo*xqPpsN^>bO_Iq>+GNz&oBQnx(C;rai`H(Uc3QK%ftjpJl< z0R&J;-RIkYYB#XpBC)Sg9{I`Gs}N*foz0Ql$A)G+NyDGUA!;P?_h$uv06qBbKl5_3 z=Jnb&R0|I2iSzw4Q|YkjK_!Y@{*Qfy2jAKONkplBcV!=L7`2;+!8|w)u0dHEg0in#Td@y;roI z`wgfn=o-Ka9!}S+UT#Zu#P?eG?f}Bjy}y_zB}udRDgeF00?}REOa2I}@&wpuV-V`2l2|mP zdAtT5P1|Fm6iWSP-?mQ%)^}tKpe(3B{UIi(>Oc8_12$wz4FhP&1Pue>Vm>ZUq)Nqg zkJCa#MFAH4VVuyH!`2Az$Bt#Y*Jp>qA@pC~ptjEq^ z#W@FpJj%PgXYW*jeV)!KW5>VD!H}yt*ZbRUV9N|b{$~>Uwsv{!&Hxm5`1{T(v=bEX zxGLoNtKz|(1+YJGYJmN{MLQpB=A=G9q~p=maNKERlxa?5jg=;fYLkUNt;C?2cO+j` zP3pSTJE2<>epnt45{u{uN}{0QaB8F7Us95Z`occ9Iy5RR-NL1V8g#~)M#BXdw*pMyytF3NjHE*sq#(S`5$h!%9kj#H86G#~IS0Bz{7y`X=VjWv zk1>e~SadIgOn;PtVFn%8MJIdckXZS{URg*1@>vB)nZw03On;8Tcs4nSEMGmqN@95i3s_Y0*edKG-Y^VckpGYGYnMo9q`A*sZ6+(@%n zpfccQN?yX@=^KNoenqSsbV6xO?BBnAsPX6ywGvrB3jK>qBI;WZb@S2jgQ&}M zOMfPQ8JS*f>jvX~4j=@58ngV0coAKIU~rhkVDwo4$$pFPP^hSONVBiJ26ZqulK7_- zZ_<%tm=QnDTr3L5`lAcM**(`Jly{Nw@7OGOtETfFy}?xQ` zNg#qWR0y&218T*F0U(1U#5DP53-{H97;UJ?F8zR!G_q$Oo=D#sD-;+k-xEhYUG{k> z=tmieEtudPX_Z{b_2L&@_9yFZK;0h$%oIN$Hi63VDq=YFa_6Ynh@Tb43Fk1%fn zIOuUySJO;PO1y<@$_4d#B?VW9L$Tjaw{fJWA3vs}uY?Ki>A^B|GSCRXdA!}s*WrSO z6ZUI@ z(hIR*29;c(AD|N_f7xr)BIs1n5)f^>?GTN5EH9KEAyjuk#MZn|ymrRLmjgH1zRn(I z^hAC@>#T^W_o$+fU`S#-iE8IK#Lc+cS|)~ z^z0q(M%u#v@I-m#R%Re(5}FMVq1)t&cN}J;3(~xMI@O!-2(mmX*?|ga9tR>)?$pk& zwfMU4Hz4+n;TPy8?54nF;E`!mlReX~N+iFmX(F^8ZFfZcJ0ynRh)kKpD(dFp>5VGBg#GnK^DLdtDrf}<%!KoCEbRXK78ozXF z0x(4Sn7Rys>VcN6;*CBWeNO6eTS%3Qj)Z2A;a70;g!wfRXWkNM1Z~qjd=K#(*a`SZ za{RcykM#=RFqC@${g6lZ4qf=g%jDRU?5|2wveu3|BYzb2qKr=LdgDdJbG9snb~!n$ zkhPnxo$;=}FU%f-$z9*Kh3X+L@-td?LLzP948SOe9WRua^4(R~R7A(g0!>m5_xSDp z&cp4sg5*Eo1{17Fct|-$WB1(`;gAQ7uGi~h>m6A?b>q%;A*tQ9tVsi8KpMouJ#etdJ~l3cm4ett;+fYQM2 z9f^pc;o&C>cnPPjpxvYR15^(LCQZYsUC72&N&yIVQlds5OF=ic@{yTt(@k#Yz`+QK&uRR$y< zzS%bQh?_q>7i^Ad23LF|n4th9o3|Gu6gvz`5*g#73F(2jcYF>Ft73USmi`;F3<=Vd z0CF(LP;>+yy_>T{BSv1N7Z3sJMloUJg=P1FbjQVxDURiRJUSRUz1LBTEwY-8BoZqz)1qCyhwq#Xo4#Ohdy+ZCc_k*4%xE$|QH}ymyIU0OB5Vu}^O~Ykr%%!9WNC z#_*AeJ$`cAx^|0eta1@Pu9!hpI)~Abi(7kFpfVOD35W3Yy=kGE>gl1xFZO%H$Jix{ zq9SvM)LjH(&6Np3s&I!atZNiUW3Wpo1T5q}q54;K&Y{GxA|gen??8Vg9}Y(C!9;+c z%HshC>$cwiD(PuuU8DX!DJ}5E-s^4cQ>ub*VK%s+4mhG##%nplnXnau5qb$%z)Uvu zuRLp0p#^iI3PNu|Jg9`dac<_LVMy%qNI(UDVcKzZ`pirg1DL0TM7cIlI0%x05>|m| zu4*|@Tj1dEkDA1?US7v>QL+YPpF34+8F{j4IDB@bF=9|a?4ig)J5H%BO-D3?A}T4` z26t7Xm+}!VQ1^A3Z{eg&Ev!2s&X@oM#DhjCtwEw%?CcI@gu2Q4M7Y$t8}Q0uctLrC+ligb}tbA ziNYNgcpp(7<`~XS9PM5>N%OW%%sbW^LH-xhkqfmz8RXwz1X2l&JRsbPby;_XIfu&f z<+RL|HG`#c*G=DihGH>`qj@Q-%-jIcY~|Fg)@voIvrPuB4@<28cI?hp6^Dz#i4j2- zHtf`LEYa)~4FY9KBPIxi;*DnRK#e%wT~<_9lPrBpPa-p)ZFx0*wgA+|SV2W!U(C?VL| zEAV>;#A>Qx(8dI{lHG+vey8H;zX7sG12gOoU7bY5(H{d=WHxo7P4#sMG$5Nmhw!8V z1p>{Ha)d|@2A0WJD`*Ki9gc}pY)t0E3s4=qGm#egJCp^=;l z7WymcQ1f%K978*$4vwX!6Pg~7D0gDE!U2T;7!ycilR2XLC|2J5#TzIfoZero3cPQn zPC@5wHMV3@@2!$pEfI(-P0C3D< zxxfO5ucVD&Ai!!;nrne;)?be6kv_6cLTpn!;FB(GQyo3Vpx68m5gr0)M$x00gH+dA zf`-+`a1^#HYcO2zWXJ-px%?Y@(SC|BNyJCu?9N?431@Nf`*Cvrm^LwmPlMs&NC?qH zzSoxunuY!mi|73;<>?ZugwAq{; zV{z%*Oinf)fgB+{7j7tZ3MF@#I#9E2MDK=D&KhzWSGLSLa&YW4=fIYazV3n6+i05M z%=23}u?!yw$Us}6NOLSsl17DRsM$C@(`{mgv+Vi637^d_0=UAve^*1)L+gGOubQBK zb}s_r0f`J{f`-eY652;Mhd%$l<(9stLs-(UP3i3=V3ZBj;IV&y!nZNnNcML6Ods%j zmajsW{^wGop=!_$ewR@E!P7&B+v@8c)-J%lJ>aY3eA;7M02=)+J3Cnuu5F41puS1+fToN;13Ri zhSLCA{mSA3sP|v}wY8RI;W)DbDkhrkI?)|})r9DK?>Ar-c_RQsOQtH}l^+IFy z`&PkB*__fHTt z0|-!xUess~i)XCudYjTbYZlrJ&9<*DdX@w&4W8}Zijf%$xIH2i^91)7fLhY0_^ipc zhrKc}XJ$OEl_e}^42`Ept&|g7-#y)Xjfq*WYGP;PL|LRW+8*28zZaQn#2;Uic2{>Z z{~-8iGNGZ+cl?R0#1R&a>gesYy$eO?^iw?vkLAYK0iEN$T6r_XvI!INlP}o2WnCR! zuD7{l(md@BSlq13;|CQ%gB%uQo^bB!ma#|;x)kCVK$E_JjVMqQ61UqIdS-JBNp1Ht zJic!jd7KH;Dj2n*naJ~(h5We?H<4F+p*>&aP$e3bD71?iS!k#%vYVZgRHlQ_QlVh1 z*_TyA&z8PuJ3F#;Y6^=3s^b-d56pxtX-fRM$^3pid3U8|A*w`_fhC6h22xBG9<7yB zC>Dvg(|(Sc*Mnkib^n??fzuYZZJ|1d&2eUG?28I=W;(;D8TT?ZW3hifClfOqk;`H~y<=tWd z5@Skl6NsYQ-OLDL*rN(92A+PU=)-3eN21Dyqa8`t^He^00(1ON-M$i0|^Y_48V3iM-%I562TTHB;YnscnQFcwhK);3^Z93u!TR6wZ#^d}e0$REBMXy=G~Ib!Dz!O}(%)L`V0w5N_IRk^$Ph&g zoOph3e6cYxlVdo-0>z}D?&tith_;a|IKDfMd2WFl%P8n%AL(dwjl#z14`w9Ay|%1# zsrAZPP}o$gQe%87@1i#Lu#}|Sz;O^*i+0JBO1n)pLI|cgs4GqV%SO@8#ye3cpAF0bAbniTk|#olqhpLM>h8Q|?l& z1_T-l;nqb=$V~9CP|rY~$1_4PCPO0!@z08c88I$G!&d)&jaoYg;K%*zAZi2S#kII| zdU10;vN~liX7}({5;ktX41QvnKp0t^w7}8gTIiR(#V~47BaLdE!;7F#g8bzFGUfhX zJaTwyYQ8x4Eq&U5Ajfmvm<2G%Qw;$ynpC2nm8EgcO5mqpJRPmx_mR$}TL9y@IOYAK z|K9N*6a1ziO?XauIT<6&5)k4vGJF>r3+gCVD3>$7FaDH`rpg$Ngo?6SGbl>bnRj_* zBIrdU!n~bgdo6_72X3V2kAg?i5UEtJEH)QM@_3-3Sp`2Z@2;rJTV<)jCkUmnrQb(Z zFB?RBOsm4~Z0|CANHFAH>MD-kwG$&^S>(MWiI_3!b?I)O8yOoEFM0hiAo2=TMYwVD zn17DILnQq&7rtLdea$cSvWOj_6KfMr93Cy4N#ACH=foce2NP+2X>L9<6ylbAE|)?nkPwHe}F_A8fmAGW+XefBniW) zt=CB7E)r>a(22@MK|M2g#7P^SNy~~xhdqO*ebNP#08zIabe;@o+P_Q|NBk){cYdJd z;o#x!_UvH3yC5*K4>0QLV>z&u0**CPeH2o)KV$k^$Vwg!7V9pW9D3rrVA58vS z%N`0>0+VToPxnC%WrhF7u_tQ`bN+SQk^k0s$d%c>Y4^Wl7pJUl0=r% z9LhpuQv6Xj*lc0)YQ=dlR1|x>4+J(iuiocZ6xF{u1&&unZB5NOV#F{On9585P9K%W zT)Z@am!%T37%;>NsrK!#w-pVV6~1xOUqucCgno-Ql9DD!aJ@T)hSLh^)uP8jG;BnV zX%5$Ds*=jYXD|mE-3aYxhADC(x`a4)6EP64e)Xmd-Y{s%K-q#wEn!S zS290W`nUsxk$)Ukkt)C!6+db4kH8YJj4BjjV_~@3E_P&86ODXpEKYrHKY94QkMVW> zWcY-`^&n4PLsc?-nRnzFaQ2WKZ$%PT_Lo?zY1rTD*Eb#mH55be(=1;t3JfmD2O6$E zDq6Ez8jcE;aei*w?(JT(6RT(7Kn7yXW4oTjOR{ZUOlo zkP<>#*Uu9`o1EvpoO$dMwBM|uYq^mI0Wf6Pt8yUG$Iu0w4h#0-Y9%>E6Vf-QePX{^ zi{YKUG6Ck*8v(x&4A-TrMfe)c)N#}an&UIoKEn3Yod^~MEe32ROAhy2d^mJ(j{ z*UZ%Pc})m<1EoU4kH_RrxFRgS-^ZSmBo&9UZ(yRizf4x91z4vYW$u;N7hN`~<%_Nl zMHepvlV9{%IrBHpr}erDSDE-UOUPm_Y7{ALVE}u7lRF1TDs(6&8SYMiJE!wZ_55=c z_&caT!Z{KbCHHmvmkH_(UTk*BdZYDAajR#~`USLJNH1x;l!2`D0FXh;^I57<#=!@D zsSt#oMvo>|&xbDua;RXyG=d^{ZlP8E~CiP`^( zl`U1`sx1v6^|eP67fCl~<5@c%sjs+b^#o4Y4^{13u|WgdRSe1yc}hG1tO!?#-;0l8 zLnd>*&Kf{)g&!jIR8`_e*^-2&@$>V2y#S>B8g*u*IZN4ue*=H-d)wErbVc#1gG2@> z<{FPEOpQ2JfB~i_Fv0Ve(5o||qUbeUy4~`AR{K|RIYZ*KgDD<4ap3IAeAZJ)?{iLw z^iHn!-E;L_d5#xzLV865>nI6I2Z<$1z3d|&fU_}*C1HwKK5K3lF<2NT$GQPZaUD4b zo^}NKj)~*5ybz}6r|5J>1{fGJ6N}!Zjvp>4uRkiwVfAc>w+4IlF?*A?Q z;2(~PU!4dMk}ACYcrSL>YU9rJ*>g+G?%~TWjc~`+BQOP=+OYI;Aq&MU`SRw-eBzvs z5w}#W3|Su2F(`Wyz5*1Ca{msDL9uvn7<-jfC?% zImw@)-5sm!tS)>+6QzpRKD_)ue_D)trVo5q*N^`Jrg|LKuuJXLY%L(3%ZxT3D z3+>C*sMP=)%A?@4?420x|N@<10bH?w@)~k-cMaMB#(Qd zr9TLJ3AD_Oc>KtOQi*M^Pb9SCegtpd!#eS3etrlijv$IO7np?0ZtfOh8_{^xV3-{n zlP2MPjES>{ITfA*C44LI-9Blpgng5Rj(jlFt@R}S7_}0aA3@VNK^6It;r4Tkkca4d zcAcS2eii9z21nqi?CYnzl!|ve;gk$^i(4=)xJIigIRWwSGE=Fmqp=|o$+N@QZ;q32 z1D;sy*F?*mlWw|>Y(SClA8(j0FAtX{IF-th9yOqzhH-+SC-|k*FXo|3uL!g zGRgP~KMUYcbD^zKs5JkYcZv4zJ#~!934LBEs*pyIhr>@ zI8B?(yxV3_uxNIzi$ypIWRyY$*hN2Cmw(2>FzchY#WE%do5Y2@MfNXFJeSkxsW~d0 z>U!4U!R>c4@}HLqtKv+NdB(7SBcbm3HsY1C4OUmhz{Z`@mM5|jn!rM7@2vk3f&1Gd z+$bJa`?GGInB~$`@F8j^_XGe7FiivANrfhmT)DI>2)s6X7_KA_emeILv+zkEb}yq)r{N9MIyQ*~_)Pq8 z_>J`&3%7N>ZCsi@HaSaFXjs{s5gIq$E{@N%zQ#TXJ5u(>OMY_yf||W^o+RW zM}gCxk*<1I?MLWGU^ddfRHqpf0goFj95(c&!o2oM9MFX-EG)li>WZ}|VWdVDZebAO z(DW%{5?2WF&tBbhe2u`Q2Ypn=A?SN=;SLF>)Uzwf&GNo}Cx=L()J|fjWPftEj1Igq zfcJtp;of)qponf9>|!Gk?C?g|U#n6!ZM`LR1;ikMRW+|C$b8)&E1&TLwhceed4`3@|i9Hw+EZ zC=D`nw{(Yemy|MeOG$TkOM^5>Hwe<*C?KJDKgaL$|GnTH=Pa(N!Z08dXENk@F=~3A%dI94ApicaBy(_=@pL^w5ab6?tDLS5eUCN8- zIsTY+5gm4L^rJ^~JfY5wU6P4jgg49~!G_11=?#$1ll7oWeg-#YE*%I9EDRe-S5Pj_ zu%l+d)$D&a@utx`G*aAhMP=Vi}g}D6pDsBw7uy9y3OLp=G;bm1g&BQTVDUkfX8b z)h|>~0lk{pbNuLk<@ls#qC)zyxM=ifZ#MQ`-rf1fnH=c6Wd+J+><*31laAnNBIYT- z)HPNM4~t^qAsRq+`4D3*|$WWMdWIwaN%!3XR%;s4z_~uBV`N zTv>Dmc`@}G_hlr|*>8Reh%;43ljG`E+CE3WW;4OAl9NBhs?M7;ek0T1M7Fq;=#)-H z|Hq|rPO>Ud+Z%D+g#wDQ_-F0`*bde}dGJXX&{3oDYqxQrGB{Aq9H%7rpm`TFeEw+h z=99KOdnJ@{e?fK$|2XQXw5dKtpQZducgd{4De?v&?&*Uv6*#$FTzvQ^D_D3CB>rf7 zn?Q-EWs3bG{*Sf6*W~r}TwT$pJl?;B`uAei1J`*X6&+7ye$8p-u?ggD?XT9R=5SPj z=o|(wEOkW{EJ~uUbxEfwW4Tw~9*tJGO?pUv^MN8+p`}A9Pb``*!{birsbd@WhZ%Wh z5nj9sL0q~;C{_3&Hd`9sNke`z;Zxv7z%JzyqyIk1-;6+xe< zwMW+^ka((x4n0mwDpgL~YHIg60kjrU5~|zyZ%_m!1hT#$2`gFfny`L4YStBvR>XIJ zHjjm<7gUiGv7s&T0uLweF27c^T|EL;4~2*LZbO|{StFu~5Q`#^A96~!)P_tjOyi988G_RG-qw9B!*2GO#LTSTx*vA%~x z{j1FQyEZ=Jy~f_he}~1`_a|SRyGaHgkv(-to#0@X@+e6r(A^*&eS;{w)Vth;9E0@Z zt3-SG9|kc8RD>13MAV6aoMQBLyEmGgca5m1Bb8Z2JFGm^@D!wC`|FmKxx4w|XPWS;M1^eip%qOu zhM*#nS?X#p{^-Tb80KHug7Q>;7w znJ=pK{dG;hY)SO%1%=`M_3TN?RHIp*^KCzx*v9dA9Y2?290f*cy%*q^W(X;iM@bQ) z&tvq|mq9_`bxsM&$}|Z2y>y$sJDDRg6esglbdR1TFF?s-DWiI+z}Ym?a>7cF)f!}9 ze96dkxvoJm<`h;ahRwJviHz_n!<=KC&jxbi{O1qPGzZ6o*3)W_9IRBg8m0YXe?nnG*T+@(Vi;t>B5WAdR_B={osvcyPdYX$Rt{P;Sk z6tey|2=up&9chXCx+}jBu4_E{2vhm0-qNLJGRVTa{6y3P6BQ(W7Ns%55D#V{_ z=8B^20NY7xnUv*m*Eu89zDq<2@lM?c4@Bk8Kc5gy|9tY+)Hn*6g8c6IClDar6tAf;VRRP$9u6`;=(nJc7yH5EG=?@Ob&nsA=isg&`;=`*9p&L#JtM_xVPP z)l#Ee)feBa*Hk|ygMgT$82)5w0wE|?ER2Ql+FP8eg7LthhbEVUE+?9G2^E~CSl~^6 z_1Qw0Fquru310#FiJn;wwKmFx6bOzl6?G^B4H{o?I7x}2U0t(%`8C|!_0CIC|j0V=RS+{^YN z?S9QiBwYl_8uQ_}FmU1uy<6pQ!ZFTFydSQ@Z`qii%;2;9Y6vfPl;USab$8#LL=D_^ z5waA)BT1E>;WxF0CE|yXEm9_i4yBtl1qyMoQ4_b!ky(k-A3L$~k&YkkhpT=IM!Wtr(7<0XDagjPQyRhD6>#pUf~QmISjqs#R4`A6?ZL(3 zjn-x`*@hl(r^Ud_Hymv2%Xp_E!0f$GxOEwrCXQ0G<}I-;l&a*8REit3sm!$Y^fX|l z+RQ?=&*(!5I|tWj`_iyCD$L*o`xK%^B3HPj+pxm%S{x*enk}7yxk$&yQt5cZ+d&Hn z=wQ+LtvD-xpsAkCZGysIhF=daS~BAUeGX_nX(~N8rw<$9hDQwqo`QnkdWQXqGwv1- ze`?FP_i1Z~@Fn`+;6HQmqWjDQ;udlui_skPw`iK@$)OId^dXIl(Vy7HYf5l(AWT`Y ziu52bSf+r=i|{LS7l}Ki_i+V@F^0^P*=|?C00*9T{8kx)_F609`T&|lB49^=sN?|c zm=Ct$zLW9w)E?f}&2Dz6?(4HN93M)WKLT7bB`qC|_^E$w*x{AbI%^fQ6;HvDVe^1| zg3@6s&Nz!e#PN!Z{198PfLVh%!tz=J)XLYz#$ALa87Re(9gyr1B|smtj7H1P@=#%m zp%PRJYQPA*^mn^Dj)8x$iR7u*o<$Ur5jnrG!gos|02RKkD>dh}Ob{jT!6RzY&Gv%Y zo4o+FOA2J+N!>+K^8BE_f9o$^4$=45@U^*-DMcEDn~POY zfY1sg8qfg?^Tyv%-nfKVt>*nT`q?zO{H!yPTAgqUm`%JAoH%Gn+5GiHtPo@ zSX$#~Ig}~Z5$iAdh|~xoA<>y7=bUfH#w@jB`vx~*P(W>Aur#dT?=bHxvM>?e@GKI! zTU0lcR<%Kth2yAyZPKBUVR5ZzRGgxanjQs{^2iA}u$2-9%aDuAc*2n2aTA!%d}MUl zC+|CvO7R{8fVHz%|2pG8&MzdxJyp0cD2>%$v*bfSU7R4}9^e1i^jC`f9er|S4$3z2 z%b44;W{Fuz!2hX{Enkv`fEI@kgfL9!^-E*GO|)CT&G9D66}=1g@BQveq9gCUii+e? zAb}seQ?UA8kqhO;#R~;@$PescV}DlO*G@7TPDaA0gWNKS-r=2_V2~O>PC(D%rDQ>l`hc#0FebmG`mN?l z>LvuW0tF3i&0iK=eT~1{um|zZF{ZaDqX!!DeJ2Cb3Rs~?$4?#eatj2M@==tqOOXPv zg&8^$NI&N5bETlbCS6e(X+@9}YCOHks@Hw@)PKBOqp0uTx1Wqhf{-IQGxBXuZm$bA zBmJZ_0;YAC$Er>pmMX8!X*n8wq>%81qq0KYr@Dv%){B}x;ti>HIO|VRmX*3&n~~X4 ziC?L9s^-%RKTh*k`V~x8fkE%el!NHx#u0h=lz2jGJ$K=DCnRr13dx{idOPkqa@#(< z!pxeRbT~~2V}Oa8RLAADh(7QW0~>{joz=Zh=Hv{P_VV343~VsDR$ZVf4m32dx&$Hc zJ{(B@zhAYC?~}BQ#$;BMs*K)udM*8f@chx>3&W0}`+!k)=k^a_8=m-g^gZBq*wXj_ z<&e7$om$98Oo9nUq4LPuWsE~8To^6S6Dgzg#70(PQXgfRMNixGZv`M2Tq68I#b>@&Yf{n-# zY!FZHQ8Q%XgV79K5;?_?7coEX%Hq68 zY7ZAul@Jrv=U!Y~khZMAimSS#mH{$7k>d(Hv9XR;=&i*BD&52))3J;E zK|$g{%)iiBlHoA}e?Z_cfklCny4h}@P)NC~`LWrenv+^IAP(mMthF*-PoehoeUOb8 z_#&`R>iYI%Fbp`G-TjS{Oo5uL-j7~^Vg#n+i600XUaWvleTUGe)xdZoeifBE{gO}Z zgZ6Szf5cif{&|Hk)kxczPKVyfd($`T9nOw9tXYo1kRl53Yi!^uo)72(tMCWv9B17_ zcuSIn0gFOQjNvSg*4f|tC#%yR^4lwsuI9b#k1F7PnT+rCM({2BUymmoy5O37M{`Q-_f`%C8%!U+p!?p@_0*ys`^BHs_xo=z|u1WMlMa_UJ*^k=doE0b;LMNPb~frk-nRp2i|mhVW!<_tp$#sXe^>sXdLBfM$|Jf}e3uK57%7Vn1LeRy29$QOSyi@7GlLd7vwGszvz5qw z>4yA+-_Irb1ki-&Z*kC09bWa9Mcf#C?L7Vz%GyFhe49fM5lLBrxrpw=Bhi|+^dtDX zKtwiUZNhITMp+R87B^s=ldbPyJ?Cj55q}H#)7W;*7w%I%5TOl{r_G^KLE92squ^k< zp*y9^I<$u(8V|(QOX}StZ1z%2kw$o zY^LFmA?hj3tZt2g`;$EY-Yy=2#?S%%4*ebjB+yMEc#RGmiyv$0>T~K{lIeSEuWaVx zkpqx)3q|n(ZXDsu7%zY>r?Hd&|19lxZy5WJIdGR*@oo z#5F(I$HK*O7-bzz!zq7@hf=^H4_Vm>ON@^p(AW)1dc*TSB=h;9UqCFx<5y=<29vf( zLdCHc2W7EZ2~abnqhLkgurX238I3UlnCP&shK`|avY72%qyl8XGCsC-OSVk=5KMN9 z54B-190TjMVZ+{`w9;}c7-D58qZ}<}k?U1QjI#oP)(9Hx0CAazhHc{ry$DR3A_u^| z6?ASmLu%*@|3JS$z0e&@#6+bDvg>LsoEb0Pv1lFvB_Vlx0vhq$YM#8Xrs1#fq`24@2f&Z zdM@-l+b2kkH$Iux3gi1QuO^Kt_|xmJF}3V{!lqM(^G)z)F+tlCPJm9m#t|v|6@2 z+3@Y$+=~ma0KZY`pJ!7kA`L;v2`Q4E(?K&PaXKR(P`Kbv&7$W@T9P_ck@(mN-=gRo zz;N6cfTMTaz}8@_PKc01BM$B8-ho#`!Sn@U3eW?TU>M%i5_%JrAVv{lJ`Mz91wr76 zp}=HK4|B$zL?Fra5M{)N0nxpc1*sc|r^2O!e?dZ^zlaTZ5MAO}U#I6kwKxc*cHUpk zJIWdB3ed16@D;$&_elRUZD|W^1zFXwodQPvy8n3!%i=!9?-nD0g#NjM`$B+sJ@M%h zNWTsOgXw<^^H&R z0?}ro5Cl3TOwJa~BKSWofamxr7}{@+!ApfF`7-Vl@WKsX%(-we9{tZgZ9WMP6p|Eb z$R-R{hea(myk^DWsV>dqNSylp;Rf$N%D^yBjxTeZIR%e~l(N^cc+eY1&wMS%-{*MAdiiXHn}8Br@y|8&Fw}+oA7G0% zP!Pd__ETzzWV?8}(}9MD^!}cn$cX8vjt{&d{P0rqW9&$Q96y za@F|gY0yGI*ZnoJ#BU&IOgYGRXCj9G*x~V=^4o-D%%u9&qvJq5%7Qr(+@~reYNLVi zBZn2BsK=>2Y8yhB6+>{TWCu75;{n`~7G0nEd4nOqQuOd7Uyj2gM!_Id z3@M(|xhg-6nKod(a#$^#&Y3|4jp%0rz~k-s_@!7A?8Y<{Wo`f?v+ksna>IRxfxI0J zfRG2ZNLR&d8G{nO!jNVa+2&FOPJzzyt$>>%s#KzFddWbXTKJza&;T@th1Rm13(q|nNSK{AudWG@6idantf$<2eV zBOG#%qMO!E4{v#|q<9pQF+%UXR8JNG$AhN?Eok)z(A9m=!0c0?07Lj9{=$5{Q^E@n ze1}ekD)wYMT;TgpL5Qjy4^LJ)9}U{}L``u+TmX&E=XajXj(m&6wchQ2!(UZo5awCV zSt=LleFRwdv}LFr+Jj%r#x3T~Q~w3MD{%tJOZhsV|8o6=32z0W-dk3b?L>eoIuy8r z%#(~J#mWTET$w+R;RMI#L&NBf0e2qyD_s2R?GEkw-4s?0o@Cs)Ud%A*s6BZL=2~9j zkdhFM!;Z&!wKib0?6~ADT*M#{haeqCcdD5JHhGeYzK2!TY%3(tP;@1JNyG=?6%0+S zmo;a3QQjeE)Kr*JlrLf4N0Vp5q$(7dxgaOr9sy;)4g?Db-#p1Wadn11 zNrD~*mkA;hZE|ft*)oDPSLiXvAFAk%*h3s7gSG>gr@xK-|Gr6|Z*io*HKT`qzMP&J zY&zoSj*5H>RR$>I_mw|VMdW1Mf{As!$nOw0$nr10)M_mC%oo6`QWbdEhCJahjrfc{-_S7bMc@ve>dOv$7N~Lmz9;4# zqP-Pln}>a|0Pk3kVs|F7aN$s`Buu4?VWT!7nBNR$Yk(P!0gavxY64JWU&rghmtgA8 z1yrS{br!AOA;6ar7QnE2r?8vdVTT^)ck*ZJ52i%^V;qOcz=<13KR7trK*wiX>^v_u zNPv4xYLy@)l{t)Y_A$JMx1s8tL^2a{+M~MJ^F;nYmJe{q4CbHNUVy^cV5_xM!)f@~ zpVboieD$SPGn0R%ZGVE0{Ujy*i;E+VsvGw`>p%3>V@i$ZA$7mC>^|4Gvf!IbxpKsHbbw?K3kYw%d97>L;yN@=u{JO)qyT9{@J>C~c zX~;Ds0@eMQ|FD7Op$6K}hUn8B31UMEwgwlA-Z(XYigFQC)lET1r4qA>*3!XbZFad z6R!0iQ;(&hw9+jQvbBcOb$I7^ZWxT6?6hbKDxNf7JfJNV(eG;4yR~j2gZA|mFq(l} zTr*}4eO+qS+S=I7o)LC99}U-W`*^Si+Hv~>H21FEeb9lAjbq42UK)mqzosu7!(us; z+X2w$^;yO?L#gOd~TwO3rGKXi@iTfww5b=qn z_C-k3Xi1|KBvnLVm>t#|-A4$xS^i7{@bB-`z-H?v^&2$?qMKpP!^9Nn*ih{Bn5hSQ z7xpBDB$t-@ZIB*Z@-?H6QBloMjEF9Oin~C<4GBnFc|_^)Uvx6h4smM`l&Lzx7uA2Q z2Y2F!Fnfo5|B^v8L8M5!PI8JR%`Jd7SXhMk8Xrxs&kG_0JZ~!u6&N|g$y4NAklu?! z!!xPGj*p4ZMSCj1PPzQtMS!iXdj^b5fF;DW^#AOzqdP17Lub7BY!KU*!P6>iWjJoJ z)HwJio=%vXk?xbyNkh;`zk9o7NB@;9^hz{P_&-8%9wAv2mkggC2t-H0g>P6fH!K`js_gk%QC8k zrw=|*lHQepWNn^Hl45%7g56OGEhf>7SY8&xaySu#mi{kW7H;X@J() zoaDHzQ9S<3+frq)Oe!msUsL*FL2iRpmewNDFwU4g?*bDoPKWrPUK z{*m8ht_4$BW2{9Fb>Rhy{`yjg7sE<&I`?TL-OSA->559xp*ypJ-%;}rEYrq)n8@4Y zQGMBDztKD*^l63g%cW+e{8zs!t`GD97g+ZK_wvQyHFQ$ zOf0tv6_Lv4{TvFlFrsd)w=a}$z4&;ubk6}E0fzD@a@B!Wo9_pljR7nmM^*SM&#dVU z>I5Ds=DeQ%f*=*%rKl)8m|ZeWtAzH901#pc1n1{VJGIx2n^gdYiIa`>0{4^K?Qs1_ zuNW4q3K857?1qRp=v=P2_PZEok5rYJJy;VV4JkXZxKw4JP}0%RL3ii7H^`C`0LUcc z)*5>=fGs;ND4HZuFb2}`5y=ABy4!?*Z35+Loakw^R}V@!*$;D#_b7Z(m@$r?Z5rGe zlGx+Y0V%^GK(;)ck#?~<nRtk)q~rE!sDBj@a*qwv z3rAnCL3}XR!;A;!>&VCUhZ5Jfq(|U9e;3-{>k-=SWE-ks+Y?Y$In53DIXXq1^ z`KMFR>Z$ef&s8CYtPVEye=B=Kw0s3b=DP+5>is$PVLJqK8~qg+FaPD6vEn{-l%ERc zLuT#xhy+_`YiEvZ4nChtjE}behD?WceTs|l+BqT05xQOFpzNA?!y{KqGJVtk-*dz5kNG*fYbYE^*9``aFX62{S=V zd%rZZC1$aqQs3N6xr~xcVPs;SIf1B=1I<^?n#hnmT!&|b-=IXfIBLx9b)q_IKjEI2 z!%i=w#bga(U;^GP7Z}G$*CS0a3X_72+x1fxz1n$fzm3S7$tm&I5U-uja|wjNhYuyO z)F3W8o%sy1w5mR&UlM0z&K)SNs4}o#yS~%vU0wXvZK*xb4 z8?zFjtw-jUa9el3MPJ1$KIb)~+8!i?@D?$Ah2o;S8XY);PhDBf%pp%yq3E+71J4qWZ|js*4#$pZ5X2(x{T ze+yPVy6Y#NbUgaK*)`J7{P(Y;go;Bo;jey;ZXambLT9A6R8$Z!Xz3p}_7Cwj)Dqyn9zy9a=m?rOw`l6IZtfU@v4)LwI ziX}E9;fn!t#b8>F9G1-=!?lX~vS}+CNiIme@@0D~9ER#-0%BJ5VETAjb?ndci-b_g$0kB$`s1a5snyAIZ98SL*c~*J{t+s?f3#Q>5bSGDUA{40fRn7rOU4H&JABq7d;9{z>H*tb@UH&((e);u1e=Po83^G^yjueE;I;zEO8j8igf|L!TARLY=)2So9+d*!gs_e;jKTA2daxAqAmlhtw3Y zaOSpLi!!}3eM)Em^rZQ`7*4m}_gSz5!J>^8FX&apGA8#-%6t+1nhgZ2(A`H~%Lx!v zXs#nA*MB&=yv7bFV6u8Y@JOPkHNudc4LTnOe6v1#rH~e%>zc(<=t^nG|BTD$OCDJ| z#$9GW`vPQc8Fse6P2a!iiwg3WQkbFqQuoc+t+~hRjzYKh-<+i;ZsF6X3`r{ez)knt ztLBLCKB-DRnvGfd0hv4_nM5bKZ~ZjY=m%sb+}8BLp(LTv0!0yYEmB;d-x6Y8Rh8o- z)S7X(w45tAUU(=*`hJ^=F=zy69FuB_Y+w?dUq529i@PwY>ki*bldNLEr$XigY?1`h zrb0ltz-*iw)EyVJNQUT*K}esqWX~MnoQd7IK_p`uVFdLrAoMiNNsi zcF+o$;N%cJ8e#7Atdwn%MXikj)x=7PN~diGml&BMw{@UXYT#5S)myxf(eWGAf*YA{ zH#AjT8ny>%_s}runR@P$f{y6Xx*1^ThlY9%&q&%rC-?32u#igqkhxQCbk(w!S)ikF z*zZjK+M`vjBAG?+DI9toFn0F$RaJ7(pT-B{Y|Q7q%cK02gDX=m8Hg`w-C2;$d-a|B zgOj5L8?&>hx~Ug6f#5C+woWFO*)CbLii*hZXHgTHY#T;Qnu3+iW%*YPmw$jzp)B$D z7ArwbmSYrPQ8`ESro*T3&|MlIobZoq6&tK9UafcSp3ni$sVR;H| z+9~vHGHcT~u2=&y2-+7VA4`3yX%`19k~TZCV*1Vo6YTsi7s*rFFh6~BoX{BS7otw> zU%pYX95kuK5odvB5NVAN2kdiIklNBhRnBEm)nCBPckbT}@8U}BKX%E*?wj-;z9G;U zxC_3Bf-+SFH+o_ye>6DEtd*l!~ycZvvSUVpQ&%^SW;A9)Tz$m z1(`J+T(H$qA>5o5DbkPLV8;-Je2otmaTyGIb2_!`;S68&4lT3$jZCaLk?sCeN;`d$ zFXQW!Tg~@Vnp&m)56bK34(<+N*22Ks9o2CK|II-;b-x0NKfkE4@peawD8bd89To** z|1pZ61$`_cd;um5AeGue`ke!$6~A!{(JU+s&HpYovn1=8eTj0FMF1FtXo1r_JddGw3Nw5$^`aSnu@rZ zqp73`FvK?O%J{rx-eNKgD|T@FygF)ZG!eLs*i0`dL_hVDt5)@ zD7vi8{-zT+Vb1P@zf@gwp-jv-?R@t1PICRvdS>ICzI@$u-X%6wv02cA>YY&WQzPZVqcE#ubxG06Q*bt`DfzZzMnaI{M&1`CDG>ZteJLZ zC&^73a=Ik=tug2D>*mq#BKhgypLMZ9i8t9$>^TdA z@_{)~VdspBmz~249%7fcbI#7@%NVc2ley@`zy5YWv|+)S!jpxAUR*hPFb0hUSkIy+ zl9gU{+#FV?|5kSvj2atB$|DSL@ZF=I+VnhQIdb3n zZb~BJ=S9qxe33a=?$?7))Zvr`*}>7%(jxqVy)qGU?ahyA5owGj;Emk;rE)sLe~vlb zX|3V=2SXoQ+vO}KwV_=)(Xg((hvpDUF$uSx62xYl-Q?cJznp82n8W_#5FP_V%SzF$Y4?Cu< zGtS3TN#x{Te}+n|O*9g(+4m-Bys4-8XgYveKi|iJq@=}0X^;(W#@Bfy8A0>>Rr~t; zL3_q|>t+Q^Y{@E#+wfQr%K+pPV}EadduNa8#;ga45%JwBjVUBYXE z#sHL6H)WJalcV0=erP*-^<=g$qM^xaUSHpFH6JD#Y_;FT->ym>igebJE8j4dp%Y( zivG!bEJpoKe1Fkh)a8-4B%rO>v;Ay9BNPk&KyC=Wyz};q@^@CTYP$;Zq~Qt-;QGP) zsNX}yyMkd5xbI=WIaJmeu^A`g-qz9)oyAxtdo^a_{YUHOY~9{0aYkclb&0E-F1Deaw;}omv2v7bLiZd$NK`j(*@gac4hOpD(U@N@;gOIID;nqCz`G0 zsl4DXUlGfyN8f`5e)TH#88Ms3x^>@~%Up+rCvPRSjLy!1*^}S0YOx&>`DcStEG%e# zSAWc2?cBo|?ae<#Ab|o(Wb=6Yxxq`0vK1YRrs40|m}jg$g+41)^3pn(S-0IPM8J0a zvd`XvtsL2gu(W^_OW3;+W~g|bW5cN6ZQXoq&StR) z7kaR8*{PuPKcsE??KRaZ+7?$@SO{1|{rPu)1HF7^zeAuA zVhphZzVJkii8?7lygXfY87|ibyvF%&s1t0+x;Y?%B&(?u^@5zSU*Zk8l4`utp2Au3 zqyILzcD>VH|FC7%qqg$7M7UR;de%AC4bNYgjw-uvlVN&(vS@?2E%Q{^gTu zXZjBV;MN-y6H8c>)&w6br@WhCoX7#HV`9!P4rM7SEKMO*e@H{|Mup7u1TWL}R#`;- zlk)AqA$2p?=UTm_Gd0Pc7_fDsIQ^me6k;R5TC4EB?I-)F(04N0sPinXzAkL)>7ZW0 zSeGjc_~oQV`$g_4 z-8RLX1krJ7^A#C3|7+qlIPHG`#GrSW30_rcB=dAYm`<;r&`0RkfvuC;3oQU}e%4^s6r=F)k96Y>C9ei4@ z*S^2p?D$UYw7xK*J8xlUIou24mVc{dj<-Zdx_41V;ypI8m(&=!kJCC**@W*jC@DSr zEivZ3YLZ|)l+aU{bG14(i^sde_f-!Uk);b`(Ep1h6Mye*xbW`%ciROkQ-LMyJj{2t zj^M;7{8l#WtZxL$AX#9tIIyRv(1&o%%rcR25&J4mk3~}FUHmderTITCKy^wlo58KY zm$;-I>_EjytbSrHEN+9#c#*;E4~O?CgVCSrOsJzy#3%ZQ3yH&xm0fP`2O{F`igjuE zvHIVTt=b!`u4c@BEt21Q!r>yK#Of)19Ohc7v91^v=3EZnu0u>L)*+m$pIz72nYs<; zi@QmX#S_j?XCup4nGsnG>J5IfCBn4ZeP_uPqi>On(#$*1m#^UtdEoLS}K> zZZa;0)wG>|L?JeoHewFNC8YU!&#yk6HKp3LH$B+ER#BvDDed?}6ZZA-eB`E#YJm0N z#>#J<&u(+{mP#hera&xL%H>|@&~#a4%iZCOQQ7GUCRZlSu4lXB}&q<>*x1Is8?n_2y8j+CwFR#qSw+A>a%@DQlDy5hrI zTLC>!GFDr6*fyd~Q&#CCsB{+jv_S8+#TVPhdiJKv1YEVkh1AA$qF(>XA8(vF;=AJ7 z>L|9`URfzFPnqntN=a-V$6X`yGOqZG4lmn4hFY0rGNJWeQ;7%rSA@JMU6Ohf--xEr z^#%`BDQ~x2j(5oF-;rx^ck(f!`V)pG;mLQ*H_TZ+n~xPa4RGp>_+L~siXU}px0tF= z9vM4IkCg7@jNxv}V+Z}VJ5@0+$$YH3v*k4<`FkwtqD9+&5((I!?2H<>3`S%FVcA&L z@Kw*OP^LLGi?6Ga-_G14U{3h<|L$>2t^O07-G2Ksqs76TCvbZohj|9bep<6XbvkUU z&C_%^{`PN=eX)!_5xws85$_pU(J*elGK@p$H>RC1VV2wrG7`jw(RCnnHGc@z*;j=rheb7jPtJd?3OC2aog0|nJn=WPr zPu*R2RXK*~R_|O)BwkBcRGGR7Cof`?3Y89_o0^SG62|taDE2hJN@5I&uz4On(a@>I zlrpzNTGi(6#QAbf`;qGPqsL2cOVL(m-8M7dC2~qzzD1k5PKB|xU!%Vw%Ncqgv1Qz6 zcpYL!-FCu(X~cPMM{HIXtqZEd1`v{x0%b0dC7^9Fq54zKV^+ic{fuiy<~D4Q^99zq9k$PV@Ns5BDi4H56hjco#N{>;#PMWkmr&@OK|B(+3F!*kme1e&knjQ& zj#j7bc?G%TKk>x3e-0$vFDaAr22fcKRcWU3mz#nKj>I*EMYPDm9Ip7Umw91*?5A= z%Lz^g4jb&s1oOj8yP0t|D4X19J7r=Lky85LDhL;bU<1CFhGF&D1Vx*O$IPwTK8696 zTt~Lre5@wQg{kg>wrGahrz-o+;`5x(H!++J$EOpby){`oCMe$p7BpQpAx+tS4g<~mhd0-lV1cp2e z-rbbpoIbZ1uiqWqVbjK$JadqQHn3S0?=K)5g5QeMY{tW^VaG|xZ^=k)4Bt=dXz$$j zi=7hKAj+)~a45wEL9+Vb0%JfxoZ+()3PmGzV0I`6hPl9P7J(_bZ zU#yr^{(LYQ)4E}x?CE^=^9wAXO3R&*-1{svLf_CsrPlkTve8DL2tnhjUAhzMK(~60 zi|W}KsZ*JCn1ENG04yu=0H@3zJXV(DbuOy$qw|GM=P&@yF& zk$Jvj?Vj;k&wxLfEn zV?Vr$rJU4L<^LPfP;1bKSVjkq#W#Jkmc?qizUlOHJ;v(qy*pb3;su5MbS|uL!b_3! z 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 literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh index 7784c52..df379bc 100755 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh @@ -1,11 +1,11 @@ #!/bin/bash -echo "25 blueberry pancakes on the table... 25 blueberry pancakes! 🥞️" +echo "25 chocolate chip pancakes on the table... 25 chocolate chip pancakes! 🥞️" sleep 3 -echo "Eat a stack, for a snack, 15 blueberry pancakes on the table! 🥄️" +echo "Eat a stack, for a snack, 15 chocolate chip pancakes on the table! 🥄️" sleep 3 -echo "15 blueberry pancakes on the table... 15 blueberry pancakes! 🥞️" +echo "15 chocolate chip pancakes on the table... 15 chocolate chip pancakes! 🥞️" sleep 2 -echo "Throw a stack... it makes a smack! 15 blueberry pancakes on the wall! 🥞️" +echo "Throw a stack... it makes a smack! 15 chocolate chip pancakes on the wall! 🥞️" sleep 2 echo "You got some cleaning to do 🧽️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/hello-batch.sh similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/hello-batch.sh diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dl-training-io.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dl-training-io.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dl-training-io.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dl-training-io.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-software-stack.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-software-stack.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-software-stack.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-software-stack.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-unet3d-results.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-unet3d-results.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad_design.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad_design.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad_design.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad_design.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-broker-design.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-broker-design.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-broker-design.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-broker-design.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-pre-tbon.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-instance-pre-tbon.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-pre-tbon.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-instance-pre-tbon.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-w-tbon.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-instance-w-tbon.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-w-tbon.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-instance-w-tbon.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-tree.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-tree.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-tree.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-tree.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/instance-submit.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/instance-submit.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/instance-submit.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/instance-submit.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/scaled-submit.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/scaled-submit.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/scaled-submit.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/scaled-submit.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/single-submit.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/single-submit.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/single-submit.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/single-submit.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/02_flux_scheduling.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/02_flux_scheduling.ipynb deleted file mode 100644 index 453be69..0000000 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/02_flux_scheduling.ipynb +++ /dev/null @@ -1,598 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "

\n", - "
\n", - "
\n", - "\n", - "# Module 2: Using Flux for traditional and hierarchical schedulinng\n", - "\n", - "Flux provides powerful and advanced scheduling capabilities that are important for exascale systems like El Capitan. In this module, we demonstrate:\n", - "1. Traditional batch scheduling with Flux (similar to what is provided by other schedulers like Slurm)\n", - "2. Hierarchical scheduling with Flux to achieve higher throughput (novel capability of Flux)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Traditional batch scheduling with Flux\n", - "\n", - "In traditional batch scheduling (e.g., what Slurm provides), users send requests for resources and jobs to a centralized service (i.e., the scheduler), which stores the requests in a queue and fulfills them as possible.\n", - "\n", - "
\n", - "\n", - "
\n", - "Image created by Vanessa Sochat for Flux Framework Components documentation
\n", - "
\n", - "\n", - "Traditional schedulers provide 3 main operations:\n", - "1. Submitting jobs\n", - "2. Running distributed applications within a job\n", - "3. Querying the status of jobs or canceling running jobs\n", - "\n", - "We use Flux to perform these traditional batch scheduling operations in the order shown in this table:\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
OperationSlurmFlux
Submitting jobssbatchflux batch
Submiting interactive jobssallocflux alloc
Running distributed applications with waiting for completionsrunflux run
Running distrubted applications without waiting for completionN/Aflux submit
Querying the status of jobssqueue/scontrol show job job_idflux jobs/flux job info job_id
Cancelling running jobsscancelflux cancel
\n", - "\n", - "For a more comprehensive cross-reference between Slurm, Flux, and other schedulers, check out LLNL's [Batch System Cross-Reference Guides](https://hpc.llnl.gov/banks-jobs/running-jobs/batch-system-cross-reference-guides)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submitting jobs\n", - "\n", - "Similar to Slurm's `sbatch`, users submit non-interactive, batch script-based jobs using `flux batch`. To see how `flux batch` works, let's start by looking at the batch script `sleep_batch.sh`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Code\n", - "Code(filename='sleep_batch.sh', language='bash')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similar to a Slurm batch script, a Flux batch script consists of two main sections:\n", - "1. A set of Flux directives defining the arguments that should be passed to `flux batch`\n", - "2. The commands defining the job\n", - "\n", - "In `sleep_batch.sh`, there are 3 directives:\n", - "1. `#FLUX: --nodes=2`: tells Flux to create an allocation of 2 nodes for this job\n", - "2. `#FLUX: --nslots=2`: tells Flux to reserve 2 slots total for this job\n", - "3. `#FLUX: --cores-per-slot=1`: tells Flux to reserve 1 core per slot for this job\n", - "\n", - "The rest of this batch script contains several `echo` commands follwed by 2 `flux run` commands that will sleep for 30 seconds each.\n", - "\n", - "Let's try to run our batch job with `flux batch`. Note that we provide two extra flags to `flux batch`. Similar to Slurm, flags passed on the command line are added to the set of flags specified in the Flux directives. In this case, the `--output=kvs` and `--error=kvs` flags redirect `stdout` and `stderr` to the Flux key-value store (which will be covered in [Module 3](./03_flux_framework.ipynb)), which allows it to be tracked by the `flux watch` command." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux batch --output=kvs --error=kvs ./sleep_batch.sh\n", - "!flux watch $(flux job last)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Submitting interactive jobs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similar to Slurm's `salloc`, users can submit interactive jobs using `flux alloc`. When launching an interactive job, you can request resources using the same flags that you would pass to `flux batch` (e.g., `-N` for requesting a number of nodes).\n", - "\n", - "Due to Jupyter's lack of a pseudo-terminal, we cannot show `flux alloc` in this notebook. So, we will open a terminal in Jupyter. To do so, click on `FILE -> NEW -> TERMINAL`. Then, copy and paste the following commands into the terminal:\n", - "\n", - "```bash\n", - "$ flux alloc --nodes=2 --nslots=2 --cores-per-slot=1\n", - "$ ./hello-batch.sh\n", - "$ cat /tmp/hello-batch-1.out\n", - "$ cat /tmp/hello-batch-2.out\n", - "$ cat /tmp/hello-batch-3.out\n", - "$ cat /tmp/hello-batch-4.out\n", - "```\n", - "\n", - "The `hello-batch.sh` script (shown below) runs 4 `flux submit` commands that print output to the 4 files that we run `cat` on. It then runs `flux job wait --all`, which waits for all 4 `flux submit` commands to finish." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Code\n", - "Code(filename='hello-batch.sh', language='bash')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Optional: connecting to an existing Flux instance using flux proxy\n", - "\n", - "TODO check if this text or original example should be put in supplement\n", - "\n", - "One cool feature that Flux provides is the ability to connect to an existing Flux instance/allocation from any other node of the system using `flux proxy`. To use this command, we first need to get the ID of the Flux instance we want to connect to. Assuming the interactive job we just launched is still running, we can get the job ID (which is the same as the instance ID) using `flux jobs`. Once we have that ID, we can run `flux proxy ` to connect to that Flux instance.\n", - "\n", - "Once we're connected to the interactive allocation, we can run the following job in one terminal:\n", - "```bash\n", - "$ flux run --nodes=1 sleep inf\n", - "```\n", - "\n", - "Then, in the other terminal, we should be able to see that `flux run` by running `flux jobs`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Running distributed applications with waiting for completion\n", - "\n", - "Similar to Slurm's `srun`, users can run distributed (e.g., MPI) applications and wait for completion using `flux run`. To see how `flux run` works, let's run the following command." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux run -n 4 --label-io --time-limit=5s --env-remove=LD_LIBRARY_PATH hostname" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This command does the following:\n", - "1. Remove `LD_LIBRARY_PATH` from the environment of each `hostname` program (specified by `--env-remove=LD_LIBRARY_PATH`)\n", - "2. Launch 4 copies of the `hostname` program and waits for all of them to complete before finishing (specified by `-n 4`)\n", - "3. Prepend the task rank to each line of `stdout` and `stderr` (specified by `--label-io`)\n", - "4. Kill the job automatically after 5 seconds (specified by `--time-limit=5s`)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Running distributed applications without waiting for completion\n", - "\n", - "Unlike Slurm, Flux provides the `flux submit` command to run distributed (e.g., MPI) applications **without** waiting for the application to complete. This allows users to easily run multiple distributed applications in parallel *under the same job*, which is important for many modern HPC applications such as workflows.\n", - "\n", - "To see how `flux submit` works, let's look at `hello-batch.sh` again:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Code\n", - "Code(filename='hello-batch.sh', language='bash')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see, this script runs 4 different `flux submit` commands, each of which prints a message to a different file. If this script were to use `flux run`, these commands would run one after the other. Instead, by using `flux submit` instead of `flux run`, Flux can run all of these `echo` programs in parallel (assuming there are enough resources to do so). This means the job that runs this script can (theoretically) complete **4 times faster** than it could using `flux run`.\n", - "\n", - "Because `flux submit` does not wait for jobs, batch scripts that use this command must use another approach for waiting on job completion. To help with this scenario, Flux provides the `flux job wait` command, which waits for the specified job/program (or all of them if the `--all` flag is provided) to complete. *Note that, to use `flux job wait`, you must pass the `--flags=waitable` flag to your Flux command.*\n", - "\n", - "To see `flux submit` in action, let's run `hello-batch.sh` through `flux batch`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux batch --flags=waitable --out /tmp/flux-batch.out -N2 ./hello-batch.sh\n", - "!flux job wait $(flux job last)\n", - "!cat /tmp/hello-batch-1.out\n", - "!cat /tmp/hello-batch-2.out\n", - "!cat /tmp/hello-batch-3.out\n", - "!cat /tmp/hello-batch-4.out" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Flux also includes 2 more convenient options for submitting multiple copies of the same or similar jobs in parallel.\n", - "\n", - "First, there is `flux bulksubmit`. This command enqueues jobs based on a set of inputs which are substituted on the command line, similar to `xargs` and the GNU `parallel` utility. Unlike those programs, the jobs created by `flux bulksubmit` have access to the resources of an entire Flux instance instead of only the local system.\n", - "\n", - "Let's run a simple example of `flux bulksubmit` to see it in action." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux bulksubmit --watch --wait echo {} ::: foo bar baz" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The flags provided to `flux bulksubmit` tell it to print the output of each job to the terminal and wait for all the jobs to finish before returning.\n", - "\n", - "Second, there is the `-cc` flag to `flux submit`. This flag tells Flux to spawn multiple copies of a single command with different job IDs. Unlike `flux bulksubmit`, you cannot substitute arbitrary values into the command. Instead, when using the `-cc` flag, you can only substitute the job ID using `{cc}`.\n", - "\n", - "Let's run a simple example of `flux submit`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux submit --cc=1-10 --watch hostname" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Querying the status of jobs\n", - "\n", - "Similar to Slurm's `squeue`, users can check the status of all their jobs using `flux jobs`. To see what information `flux jobs` gives us, let's start a bunch of jobs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux submit hostname\n", - "!flux submit -N1 -n2 sleep inf\n", - "!flux run hostname\n", - "!flux run /bin/false\n", - "!flux run -n4 --label-io --time-limit=5s --env-remove=LD_LIBRARY_PATH hostname\n", - "!flux submit --cc=1-10 --watch hostname\n", - "!flux submit -N1 -n2 sleep inf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To see the status of all pending, running, or completed jobs, we will run `flux jobs`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Users can also filter or expand what jobs they see by providing flags to `flux jobs`. The full list of flags can be obtained using `flux jobs --help` (for usage statement style) or `flux help jobs` (for man page style).\n", - "\n", - "Let's run the two code cells below to see information on all completed jobs and failed jobs respectively." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs -a" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs -f failed" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Canceling running jobs\n", - "\n", - "Similar to Slurm's `scancel`, users can kill running jobs and cancel pending jobs using `flux cancel`. This command can be used to kill/cancel individual jobs or all jobs.\n", - "\n", - "Let's run the command below to cancel the last submitted job. Note that `flux job last` gives us the ID of the most recently submitted job." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux cancel $(flux job last)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's run the `flux cancel --all` to cancel all running and pending jobs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux cancel --all" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Hierarchical scheduling with Flux\n", - "\n", - "With traditional batch schedulers (e.g., Slurm), all job requests from all users are submitted to one centralized service. In this case, the maximum job throughput is one job per second.\n", - "\n", - "
\n", - "\n", - "
\n", - "Image created by Vanessa Sochat for Flux Framework Components documentation
\n", - "
\n", - "\n", - "The throughput of this approach is limited by the scheduler's ability to process a single job. To improve throughput, Flux introduces the ability to launch multiple Flux instances within an existing Flux instance. This creates a hierarchy of Flux instances across which job requests can be distributed. For example, let's say we create a Flux instance that has control of some number of nodes. We then create 3 child instances (each with its own scheduler and queue). By scheduling across this hierarchy of instances, we get a throughput of 1x3, or 3 jobs per second.\n", - "\n", - "
\n", - "\n", - "
\n", - "Image created by Vanessa Sochat for Flux Framework Components documentation
\n", - "
\n", - "\n", - "By leveraging a hierarchy of Flux instances to achieve a divide-and-conquer approach to scheduling, we can exponentially increase throughput. The figure below (from our [learning guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html#fully-hierarchical-resource-management-techniques)) shows this exponential increase in an actual experiment. We submit 500 jobs/second using only a three-level hierarchy, whereas a centralized scheduler (1-Level in the figure) achieves only one 1 job/second.\n", - "\n", - "
\n", - "\n", - "
\n", - "Image from Flux learning guide
\n", - "
\n", - "\n", - "There are different ways to create hierarchies of Flux instances. In this tutorial, we will focus on 2 of them:\n", - "1. Nested invocations of `flux batch`\n", - "2. The `flux tree` command" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Nested invocations of flux batch\n", - "\n", - "As mentioned in the [Traditional batch scheduling with Flux]() section, `flux batch` is the command used to submit non-interactive, batch script-based jobs to Flux.\n", - "\n", - "The `flux batch` command can be invoked in a nested fashion within a batch script run by another `flux batch` command. When a job submitted with `flux batch` starts running, Flux creates a new Flux instance over the resources reserved for that job. In other words, before starting the script that the user provides, `flux batch` creates a new child in the hierarchy of Flux instances. Since a Flux instance has the same capabilities no matter where it lies in the hierarchy, this newly created instance can schedule its resources in the same way that a system-wide Flux instance can. As a result, the newly created Flux instance can be used to perform additional `flux batch` commands over its subset of the resources.\n", - "\n", - "To show this in action, let's look at `sub_job1.sh` and `sub_job2.sh`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Code\n", - "Code(filename='sub_job1.sh', language='bash')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.display import Code\n", - "Code(filename='sub_job2.sh', language='bash')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When scheduled with `flux batch`, `sub_job1.sh` will run in a new Flux instance. It will then run `flux batch` again to run `sub_job2.sh`. Because the second `flux batch` command is within `sub_job1.sh`, the job request produced by the second `flux batch` command will go to the scheduler of the child Flux instance instead of the parent Flux instance.\n", - "\n", - "We can see this in action by running the cell below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux batch -N1 ./sub_job1.sh" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once we have submitted `sub_job1.sh`, we can look at the hierarchy for all the jobs we've run using `flux pstree`. Normally, this command can be used to show jobs in a Flux instance. However, since we are running in a Jupyter notebook, this command will have limited functionality. So, instead of just running the single command, we will run `flux pstree -a` to look at **all** jobs. In a more complex environment with more jobs, this command would show a deeper nesting. You can see examples of more complex outputs [here](https://flux-framework.readthedocs.io/en/latest/jobs/hierarchies.html?h=pstree#flux-pstree-command)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux pstree -a" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### The flux tree command\n", - "\n", - "`flux tree` is a prototype tool that allows you to easily create a hierarchy of Flux instances and submit work to different levels it. Alternatively, it can be thought of as a way to create a nested hierarchy of jobs that scale out.\n", - "\n", - "Let's run the command, look at the output, and talk about it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux tree -T2x2 -J 4 -N 1 -c 4 -o ./tree.out -Q easy:fcfs hostname \n", - "!cat ./tree.out" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the above cell, we run `flux tree` and look at the output file. The flags to `flux tree` do the following:\n", - "* `-T2x2`: spawn 2 Flux instances under the current instance and then spawn 2 more Flux instances under each of the other 2 (resulting in 4 leaf instances)\n", - "* `-N 1`: deploy this hierarchy across 1 node\n", - "* `-c 4`: deploy this hierarchy with 4 cores per node\n", - "* `-o ./tree.out`: write performance data for the hierarchy to `./tree.out`\n", - "* `-Q easy:fcfs`: use the EASY scheduling policy (backfilling with reservations) in the first level of the hierarchy and use the fcfs policy (first come, first served) in the second (i.e., leaf) level\n", - "\n", - "With these flags, `flux tree` creates the hierarchy shown in the image below, with each leaf-level instance scheduling the `hostname` program.\n", - "\n", - "
\n", - "\n", - "
\n", - "Image created by Ian Lumsden based on images by Vanessa Sochat
\n", - "
\n", - "\n", - "For this tutorial, we show `flux tree` with a relatively simple job (i.e., `hostname`). However, since this command accepts any valid jobspec that can be recognized by `flux submit`, it can be used to rapidly deploy much more complex scenarios, including scenarios where different programs are run on each leaf-level instance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# This concludes Module 2.\n", - "\n", - "In this module, we demonstrated how to:\n", - "1. Use Flux for traditional batch scheduling similar to what is provided by other schedulers like Slurm\n", - "2. Use Flux for hierarchical scheduling to achieve greater scheduling throughput\n", - "\n", - "To continue with the tutorial, open [Module 3](./03_flux_framework.ipynb)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb deleted file mode 100644 index d395656..0000000 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb +++ /dev/null @@ -1,321 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Module 6: Supplement\n", - "\n", - "This extra module covers various other aspects of Flux that we did not get to in this tutorial. Feel free to try thing out and play around!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Flux uptime\n", - "Flux provides an `uptime` utility to display properties of the Flux instance such as state of the current instance, how long it has been running, its size and if scheduling is disabled or stopped. The output shows how long the instance has been up, the instance owner, the instance depth (depth in the Flux hierarchy), and the size of the instance (number of brokers)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Flux Process and Job Utilities\n", - "### Flux top\n", - "Flux provides a feature-full version of `top` for nested Flux instances and jobs. In the JupyterLab terminal, invoke `flux top` to see the \"sleep\" jobs. If they have already completed you can resubmit them. \n", - "\n", - "We recommend not running `flux top` in the notebook as it is not designed to display output from a command that runs continuously.\n", - "\n", - "### Flux pstree\n", - "In analogy to `top`, Flux provides `flux pstree`. Try it out in the JupyterLab terminal or here in the notebook.\n", - "\n", - "### Flux proxy\n", - "\n", - "#### Interacting with a job hierarchy with `flux proxy`\n", - "\n", - "Flux proxy is used to route messages to and from a Flux instance. We can use `flux proxy` to connect to a running Flux instance and then submit more nested jobs inside it. You may want to edit `sleep_batch.sh` with the JupyterLab text editor (double click the file in the window on the left) to sleep for `60` or `120` seconds. Then from the JupyterLab terminal, run, you'll want to run the below. Yes, we really want you to open a terminal in the Jupyter launcher FILE-> NEW -> TERMINAL and run the commands below!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```bash\n", - "# The terminal will start at the root, ensure you are in the right spot!\n", - "# jovyan - that's you! \n", - "cd /home/jovyan/flux-radiuss-tutorial-2023/notebook/\n", - "\n", - "# Outputs the JOBID\n", - "flux batch --nslots=2 --cores-per-slot=1 --nodes=2 ./sleep_batch.sh\n", - "\n", - "# Put the JOBID into an environment variable\n", - "JOBID=$(flux job last)\n", - "\n", - "# See the flux process tree\n", - "flux pstree -a\n", - "\n", - "# Connect to the Flux instance corresponding to JOBID above\n", - "flux proxy ${JOBID}\n", - "\n", - "# Note the depth is now 1 and the size is 2: we're one level deeper in a Flux hierarchy and we have only 2 brokers now.\n", - "flux uptime\n", - "\n", - "# This instance has 2 \"nodes\" and 2 cores allocated to it\n", - "flux resource list\n", - "\n", - "# Have you used the top command in your terminal? We have one for flux!\n", - "flux top\n", - "```\n", - "\n", - "`flux top` was pretty cool, right? 😎️" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Submission API\n", - "Flux also provides first-class python bindings which can be used to submit jobs programmatically. The following script shows this with the `flux.job.submit()` call:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import json\n", - "import flux\n", - "from flux.job import JobspecV1\n", - "from flux.job.JobID import JobID" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f = flux.Flux() # connect to the running Flux instance\n", - "compute_jobreq = JobspecV1.from_command(\n", - " command=[\"./compute.py\", \"120\"], num_tasks=1, num_nodes=1, cores_per_task=1\n", - ") # construct a jobspec\n", - "compute_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\") # set the CWD\n", - "print(JobID(flux.job.submit(f,compute_jobreq)).f58) # submit and print out the jobid (in f58 format)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### `flux.job.get_job(handle, jobid)` to get job info" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This is a new command to get info about your job from the id!\n", - "fluxjob = flux.job.submit(f,compute_jobreq)\n", - "fluxjobid = JobID(fluxjob.f58)\n", - "print(f\"🎉️ Hooray, we just submitted {fluxjobid}!\")\n", - "\n", - "# Here is how to get your info. The first argument is the flux handle, then the jobid\n", - "jobinfo = flux.job.get_job(f, fluxjobid)\n", - "print(json.dumps(jobinfo, indent=4))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs -a | grep compute" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Under the hood, the `Jobspec` class is creating a YAML document that ultimately gets serialized as JSON and sent to Flux for ingestion, validation, queueing, scheduling, and eventually execution. We can dump the raw JSON jobspec that is submitted, where we can see the exact resources requested and the task set to be executed on those resources." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(compute_jobreq.dumps(indent=2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then replicate our previous example of submitting multiple heterogeneous jobs and testing that Flux co-schedules them." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "compute_jobreq = JobspecV1.from_command(\n", - " command=[\"./compute.py\", \"120\"], num_tasks=4, num_nodes=2, cores_per_task=2\n", - ")\n", - "compute_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\")\n", - "print(JobID(flux.job.submit(f, compute_jobreq)))\n", - "\n", - "io_jobreq = JobspecV1.from_command(\n", - " command=[\"./io-forwarding.py\", \"120\"], num_tasks=1, num_nodes=1, cores_per_task=1\n", - ")\n", - "io_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\")\n", - "print(JobID(flux.job.submit(f, io_jobreq)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux jobs -a | grep compute" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use the FluxExecutor class to submit large numbers of jobs to Flux. This method uses python's `concurrent.futures` interface. Example snippet from `~/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py`:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "``` python \n", - "with FluxExecutor() as executor:\n", - " compute_jobspec = JobspecV1.from_command(args.command)\n", - " futures = [executor.submit(compute_jobspec) for _ in range(args.njobs)]\n", - " # wait for the jobid for each job, as a proxy for the job being submitted\n", - " for fut in futures:\n", - " fut.jobid()\n", - " # all jobs submitted - print timings\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Submit a FluxExecutor based script.\n", - "%run ../flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py -n200 /bin/sleep 0" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Diving Deeper Into Flux's Internals\n", - "\n", - "Flux uses [hwloc](https://github.com/open-mpi/hwloc) to detect the resources on each node and then to populate its resource graph.\n", - "\n", - "You can access the topology information that Flux collects with the `flux resource` subcommand:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux resource list" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Flux can also bootstrap its resource graph based on static input files, like in the case of a multi-user system instance setup by site administrators. [More information on Flux's static resource configuration files](https://flux-framework.readthedocs.io/en/latest/adminguide.html#resource-configuration). Flux provides a more standard interface to listing available resources that works regardless of the resource input source: `flux resource`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# To view status of resources\n", - "!flux resource status" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Flux has a command for controlling the queue within the `job-manager`: `flux queue`. This includes disabling job submission, re-enabling it, waiting for the queue to become idle or empty, and checking the queue status:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux queue disable \"maintenance outage\"\n", - "!flux queue enable\n", - "!flux queue -h" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each Flux instance has a set of attributes that are set at startup that affect the operation of Flux, such as `rank`, `size`, and `local-uri` (the Unix socket usable for communicating with Flux). Many of these attributes can be modified at runtime, such as `log-stderr-level` (1 logs only critical messages to stderr while 7 logs everything, including debug messages)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux getattr rank\n", - "!flux getattr size\n", - "!flux getattr local-uri\n", - "!flux setattr log-stderr-level 3\n", - "!flux lsattr -v" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb deleted file mode 100644 index b0ccfe6..0000000 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb +++ /dev/null @@ -1,115 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "
\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Introduction to Flux: Next-Generation Resource Management for Exascale Workflows and Job Scheduling\n", - "\n", - "## What is Flux Framework? 🤔️\n", - " \n", - "Flux is a flexible framework for resource management, built for your site. The framework consists of a suite of projects, tools, services, and libraries which may be used to build site-custom resource managers for High Performance Computing centers. Flux is a next-generation resource manager and scheduler with many transformative capabilities like hierarchical scheduling and resource management (you can think of it as \"fractal scheduling\") and directed-graph based resource representations.\n", - "\n", - "To provide some brief, added background on Flux and a bit more motivation for our tutorial, start by watching our YouTube video:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%html\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## I'm ready! How do I do this tutorial? 😁️\n", - "\n", - "This tutorial is split into 5 modules, each of which has a notebook:\n", - "* [Module 1: Getting started with Flux](./01_flux_tutorial.ipynb) (the rest of this notebook)\n", - "* [Module 2: Using Flux for traditional and hierarchical scheduling](./02_flux_scheduling.ipynb)\n", - "* [Module 3: Using Flux to manage and deploy distributed services](./03_flux_framework.ipynb)\n", - "* [Module 4: Using DYAD to accelerate distributed Deep Learning (DL) training](./04_dyad_dlio.ipynb)\n", - "* [Module 5: Lessons learned, next steps, and discussion](./05_flux_tutorial_conclusions.ipynb)\n", - "\n", - "To go through this tutorial, you need to go through these modules in the order (1-5). To step through examples in each module's notebook, you need to execute cells. To run a cell, press `Shift+Enter` on your keyboard. If you prefer, you can also paste the shell commands in the JupyterLab terminal and execute them there.\n", - "\n", - "Let's get started!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Module 1: Getting started with Flux\n", - "\n", - "The main unit of interaction with Flux is the **Flux instance**. A Flux instance consists of a collection of **Flux brokers**. These brokers are connected together and deploy a fully functional set of services which manage compute resources under its domain with the capability to launch jobs on those resources. A Flux instance may be running as the default resource manager on a cluster, a job in a resource manager such as Slurm, LSF, or Flux itself, or as a test instance launched locally.\n", - "\n", - "When run as a job in another resource manager, Flux is started like an MPI program, e.g., under Slurm we might run `srun [OPTIONS] flux start [SCRIPT]`. Flux is unique in that a test instance which mimics a multi-node instance can be started locally with simply `flux start --test-size=N`. This offers users to a way to learn and test interfaces and commands without access to an HPC cluster.\n", - "\n", - "To start a Flux session with 4 brokers in your container, run:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux start --test-size=4 flux getattr size" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The output indicates the number of brokers started successfully.\n", - "\n", - "# This concludes Module 1.\n", - "\n", - "In this module, we introduced Flux and showed how to get started with Flux, particularly on systems using a different system-wide scheduler.\n", - "\n", - "Next, we use Flux for both traditional batch scheduling and hierarchical scheduling. To continue, open [Module 2](./02_flux_scheduling.ipynb)." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb deleted file mode 100644 index 1fbfa2b..0000000 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb +++ /dev/null @@ -1,671 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "dd3e912b-3428-4bc7-88bd-97686406b75a", - "metadata": { - "tags": [] - }, - "source": [ - "# DYAD\n", - "\n", - "DYAD is a synchronization and data movement tool for computational science workflows built on top of Flux. DYAD aims to provide the benefits of in situ and in transit tools (e.g., fine-grained synchronization between producer and consumer applications, fast data access due to spatial locality) while relying on a file-based data abstraction to maximize portability and minimize code change requirements for workflows. More specifically, DYAD aims to overcome the following challenges associated with traditional shared-storage and modern in situ and in transit data movement approaches:\n", - "\n", - "* Lack of per-file object synchronization in shared-storage approaches\n", - "* Poor temporal and spatial locality in shared-storage approaches\n", - "* Poor performance for file metadata operations in shared-storage approaches (and possibly some in situ and in transit approaches)\n", - "* Poor portability and the introduction of required code changes for in situ and in transit approaches\n", - "\n", - "In resolving these challenges, DYAD aims to provide the following to users:\n", - "\n", - "* Good performance (similar to in situ and in transit) due to on- or near-node temporary storage of data\n", - "* Transparent per-file object synchronization between producer and consumer applications\n", - "* Little to no code change to existing workflows to achieve the previous benefits\n", - "\n", - "To demonstrate DYAD's capabilities, we will use the simple demo applications found in the `dyad_demo` directory. This directory contains C and C++ implementations of a single producer application and a single consumer application. The producer application generates several files, each consisting of 10, 32-bit integers, and registers them with DYAD. The consumer application uses DYAD to wait until the desired file is produced. Then, if needed, it will use DYAD to retrieve the generated files from the Flux broker on which the producer application is running. Finally, the consumer application will read and validate the contents of each file.\n", - "\n", - "To start, specify which versions of the producer and consumer applications you would like to use by setting the `producer_program` and `consumer_program` variables. There are two versions for the producer (i.e., `c_prod` and `cpp_prod`) and two versions for the consumer (i.e., `c_cons` and `cpp_cons`)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0fa41e2c-80b1-498e-8ff9-7df6250c7a5d", - "metadata": {}, - "outputs": [], - "source": [ - "producer_program = \"/opt/dyad_demo/c_prod\" # Change to \"/opt/dyad_demo/cpp_prod\" for C++\n", - "consumer_program = \"/opt/dyad_demo/c_cons\" # Change to \"/opt/dyad_demo/cpp_cons\" for C++" - ] - }, - { - "cell_type": "markdown", - "id": "03cacf9d-f98a-45bb-9422-5648428c690f", - "metadata": {}, - "source": [ - "Next, specify the number of files you wish to generate and transfer by setting the `num_files_transfered` variable." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f9b72d51-f294-4e82-ab74-e77f922dc0af", - "metadata": {}, - "outputs": [], - "source": [ - "num_files_transfered = 10" - ] - }, - { - "cell_type": "markdown", - "id": "4dad884f-449e-4b00-bbc9-955fa9f31066", - "metadata": {}, - "source": [ - "The next step is to set the directories for DYAD to track. Each DYAD-enabled application tracks two directories: a **producer-managed directory** and a **consumer-managed directory**. At least one of these directories must be specified to use DYAD.\n", - "\n", - "When a producer-managed directory is provided, DYAD will store information about any file stored in that directory (or its subdirectories) into a namespace within the Flux key-value store (KVS). This information is later used by DYAD to transfer files from producer to consumer.\n", - "\n", - "When a consumer-managed directory is provided, DYAD will block the application whenever a file inside that directory (or subdirectory) is opened. This blocking will last until DYAD sees information about the file in the Flux KVS namespace. If the information retrieved from the KVS indicates that the file is actually located elsewhere, DYAD will use Flux's remote procedure call (RPC) system to ask the Flux broker at the file's location to transfer the file. If a transfer occurs, the file's contents will be stored at the file path passed to the original file opening function (e.g., `open`, `fopen`).\n", - "\n", - "In this demo, we will use 3 different directories: one unique to the consumer (`consumer_managed_directory`), one unique to the producer (`producer_managed_directory`), and one shared between producer and consumer (`shared_managed_directory`). Set the 3 variables in the cell below to specify these directories." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "60b0d5e0-fcf7-4fc9-a203-cdea84cd4950", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "consumer_managed_directory = \"/tmp/cons\"\n", - "producer_managed_directory = \"/tmp/prod\"\n", - "shared_managed_directory = \"/tmp/shared\"" - ] - }, - { - "cell_type": "markdown", - "id": "5bfa6706-0af5-4da8-bbc1-3edb9bccf953", - "metadata": {}, - "source": [ - "Finally, empty these directories or create new ones if they do not already exist." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0dac6c9-43dd-4b4f-89e3-bfb180122f71", - "metadata": {}, - "outputs": [], - "source": [ - "!rm -rf {consumer_managed_directory}\n", - "!mkdir -p {consumer_managed_directory}\n", - "!chmod 755 {consumer_managed_directory}\n", - "!rm -rf {producer_managed_directory}\n", - "!mkdir -p {producer_managed_directory}\n", - "!chmod 755 {producer_managed_directory}\n", - "!rm -rf {shared_managed_directory}\n", - "!mkdir -p {shared_managed_directory}\n", - "!chmod 755 {shared_managed_directory}" - ] - }, - { - "cell_type": "markdown", - "id": "39b1aeec-d2b1-4f7e-80b1-519e4da2bff0", - "metadata": {}, - "source": [ - "## Example 1\n", - "\n", - "In this first example, we will be using DYAD to transfer data between a producer and consumer in different locations (e.g., on different nodes of a supercomputer). However, since this demo assumes we are running on a single AWS node, we will simulate the difference in locations by specifying different directories for the producer's managed directory and the consumer's managed directory. Normally, these directories would be the same and would both point to local, on-node storage.\n", - "\n", - "In this example, data will be transfered from the proudcer's managed directory to the consumer's managed directory. Additionally, each file opening call (e.g,. `open`, `fopen`) in the consumer application will be blocked until the relevant file is available in the producer's managed directory. The figure below illustrates this transfer and synchronization process." - ] - }, - { - "cell_type": "markdown", - "id": "aa5a6347-e407-47fd-9984-1a8f76b25b38", - "metadata": {}, - "source": [ - "
\n", - "
\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "427c8a90-3d00-403d-825c-7e24d2117512", - "metadata": {}, - "source": [ - "Before running the DYAD-enabled applications, there are two things we must do:\n", - "1. Setup a namespace in the Flux KVS to be used by DYAD\n", - "2. Load DYAD's Flux module\n", - "\n", - "To begin, set the `kvs_namespace` variable to the namespace you wish to use for DYAD. This namespace can be any string value you want." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e2190770-fe49-4343-b2c8-eb625eb980d2", - "metadata": {}, - "outputs": [], - "source": [ - "kvs_namespace = \"dyad_test\"" - ] - }, - { - "cell_type": "markdown", - "id": "e116b785-5bdb-441b-9171-47e0b27a6e7d", - "metadata": {}, - "source": [ - "Next, create the namespace by running `flux kvs namespace create`. The cell below also runs `flux kvs namespace list` to allow you to verify that the namespace was created successfully." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a41c3f13-7b04-4ec5-9c5d-c5033d4977ca", - "metadata": {}, - "outputs": [], - "source": [ - "!flux kvs namespace create {kvs_namespace}\n", - "!flux kvs namespace list" - ] - }, - { - "cell_type": "markdown", - "id": "0840b124-f805-432e-9764-1b167df39f64", - "metadata": {}, - "source": [ - "The next step is to load DYAD's Flux module. This module is the component of DYAD that actually sends files from producer to consumer.\n", - "\n", - "To start this step, set `dyad_module` below to the path to the DYAD module (i.e., `dyad.so`). For this demo, DYAD has already been installed under the `/usr` prefix, so the path to the DYAD module should be `/usr/lib/dyad.so`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cdb1dfc4-1f8a-434d-8be8-626382d124c6", - "metadata": {}, - "outputs": [], - "source": [ - "dyad_module = \"/usr/lib/dyad.so\"" - ] - }, - { - "cell_type": "markdown", - "id": "6c2260bc", - "metadata": {}, - "source": [ - "Next, choose the communication backend for DYAD to use. This backend is used by DYAD's data transport layer (DTL) component to move data from producer to consumer. Currently, valid values are:\n", - "* `UCX`: use Unified Communication X for data movement\n", - "* `FLUX_RPC`: use Flux Remote Procedure Call (RPC) feature for data movement" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "46b38519", - "metadata": {}, - "outputs": [], - "source": [ - "dtl_mode = \"UCX\"" - ] - }, - { - "cell_type": "markdown", - "id": "aab31cd8-5034-4450-bb1b-7b299fc5be86", - "metadata": {}, - "source": [ - "Finally, load the DYAD module by running `flux module load` on each broker. We load the module onto each broker because, normally, we would not know exactly which brokers the producer and consumer would be running on.\n", - "\n", - "When being loaded, the DYAD module takes a single command-line argument: the producer-managed directory. The module uses this directory to determine the path to any files it needs to transfer to consumers." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "edc65fdb-f746-46f6-81df-17602fd94acc", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module load {dyad_module} {producer_managed_directory} {dtl_mode}" - ] - }, - { - "cell_type": "markdown", - "id": "a71e4d07-f17e-4416-8f8d-0e36137b461a", - "metadata": {}, - "source": [ - "After loading the module, we can double check it has been loaded by running the cell below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7061d3af-5d12-4edb-aa9e-6a678798ef14", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module list | grep dyad" - ] - }, - { - "cell_type": "markdown", - "id": "efcaef56-02e4-43fd-af28-2b6689db19e6", - "metadata": {}, - "source": [ - "Now, we will generate the shell commands that we will use to run the producer and consumer applications. These commands can be broken down into three pieces.\n", - "\n", - "First, the commands will set the `LD_PRELOAD` environment variable if running the C version of the producer or consumer. We set `LD_PRELOAD` because DYAD's C API uses the preload trick to intercept the `open`, `close`, `fopen`, and `fclose` functions.\n", - "\n", - "Second, the commands set a couple of environment variables to configure DYAD. The environment variables used in this example are:\n", - "* `DYAD_KVS_NAMESPACE`: specifies the Flux KVS namespace to use with DYAD\n", - "* `DYAD_DTL_MODE`: sets the communication backend to use for data movement\n", - "* `DYAD_PATH_PRODUCER`: sets the producer-managed path\n", - "* `DYAD_PATH_CONSUMER`: sets the consumer-managed path\n", - "\n", - "Finally, the rest of the commands are the invocation of the applications themselves.\n", - "\n", - "Run the following 2 cells to generate and see the commands for the producer and consumer." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c6def81f-f17a-4601-9572-b00d2959287f", - "metadata": {}, - "outputs": [], - "source": [ - "producer_launch_cmd = \"{preload} DYAD_KVS_NAMESPACE={kvs_namespace} DYAD_DTL_MODE={dtl_mode} \\\n", - "DYAD_PATH_PRODUCER={producer_managed_directory} flux exec -r 0 \\\n", - "{producer_program} {num_files_transfered} {producer_managed_directory}\".format(\n", - " preload=\"LD_PRELOAD=\\\"/usr/lib/dyad_wrapper.so\\\"\" if producer_program.split(\"/\")[-1].strip().startswith(\"c_\") else \"\",\n", - " kvs_namespace=kvs_namespace,\n", - " dtl_mode=dtl_mode,\n", - " producer_managed_directory=producer_managed_directory,\n", - " producer_program=producer_program,\n", - " num_files_transfered=num_files_transfered,\n", - ")\n", - "print(producer_launch_cmd)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "42d3007b-d6b4-40b9-a4a1-153aef536c90", - "metadata": {}, - "outputs": [], - "source": [ - "consumer_launch_cmd = \"{preload} DYAD_KVS_NAMESPACE={kvs_namespace} DYAD_DTL_MODE={dtl_mode} \\\n", - "DYAD_PATH_CONSUMER={consumer_managed_directory} flux exec -r 1 \\\n", - "{consumer_program} {num_files_transfered} {consumer_managed_directory}\".format(\n", - " preload=\"LD_PRELOAD=\\\"/usr/lib/dyad_wrapper.so\\\"\" if producer_program.split(\"/\")[-1].strip().startswith(\"c_\") else \"\",\n", - " kvs_namespace=kvs_namespace,\n", - " dtl_mode=dtl_mode,\n", - " consumer_managed_directory=consumer_managed_directory,\n", - " consumer_program=consumer_program,\n", - " num_files_transfered=num_files_transfered,\n", - ")\n", - "print(consumer_launch_cmd)" - ] - }, - { - "cell_type": "markdown", - "id": "7f51f9ea-c48c-4b75-a780-92059a1c7c61", - "metadata": {}, - "source": [ - "Finally, we will run the producer and consumer applications. Thanks to DYAD's fine-grained, per-file synchronization features, the order in which we launch the applications does not matter. In this example, we will run the consumer first to illustrate DYAD's synchronization features.\n", - "\n", - "Run the cell below to run the consumer. You will see that the consumer will immediately begin waiting for data to be made available." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9b2d62ba-d772-40a9-ab54-cef5695fc869", - "metadata": {}, - "outputs": [], - "source": [ - "!{consumer_launch_cmd}" - ] - }, - { - "cell_type": "markdown", - "id": "c413a90d-7429-4cad-bb98-fdaf8bbe5644", - "metadata": {}, - "source": [ - "Now that the consumer is running, we will run the producer. However, Jupyter will not let us launch the producer from within this notebook for as long as the consumer is running. To get around this, we will use the Jupyter Lab terminal.\n", - "\n", - "First, copy the producer command from above. Then, from the top of the file explorer on the left, click the plus (`+`) button. In the new Jupyter Lab tab that opens, click on \"Terminal\" (in the \"Other\" category) to launch the Jupyter Lab terminal. Finally, paste the producer command into the terminal, and run it.\n", - "\n", - "We know that the applications ran successfully if the consumer outputs \"OK\" for each file it checks." - ] - }, - { - "cell_type": "markdown", - "id": "c03e13e5-f8f8-4e33-bd5a-9432309dc2e8", - "metadata": {}, - "source": [ - "To see that the files were transfered, we can check the contents of the producer-managed and consumer-managed directories. If everything worked correctly, we will see the same files in both directories.\n", - "\n", - "Run the next two cells to check the contents of these directories." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3b4e23bc-2f13-4bc3-bf58-7a2dc838d47c", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r 0 ls -lah {producer_managed_directory}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "55ee61b5-1df8-4036-8709-23dec67de7d6", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r 1 ls -lah {consumer_managed_directory}" - ] - }, - { - "cell_type": "markdown", - "id": "1a5ca339-d930-48d5-89f6-519b01a92fc6", - "metadata": {}, - "source": [ - "Before moving onto the next example, we need to remove the KVS namespace and unload the DYAD module. We cannot just reuse the namspace and module from this example for two reasons.\n", - "\n", - "First, the keys in the KVS that DYAD uses are based on the paths to the files *relative to the producer- and consumer-managed directories.* Since we are using the same applications for the next example, these relative paths will be the same, which means the keys will already be present in the KVS. This can interfere with the synchronization of the consumer.\n", - "\n", - "Second, the DYAD module currently tracks only a single directory at a time. We will be using a different directory for the next example, so we will need to startup the DYAD module from scratch to track this new directory.\n", - "\n", - "Run the next two cells to unload the DYAD module and remove the KVS namespace." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0bcea21e-f2e2-488f-bd95-8534f78c70b6", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module unload dyad" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d3197840-01e4-4479-8d9b-440e03155ca9", - "metadata": {}, - "outputs": [], - "source": [ - "!flux kvs namespace remove {kvs_namespace}" - ] - }, - { - "cell_type": "markdown", - "id": "4f437928-c3b6-4ff7-8d78-7ed31b09cda0", - "metadata": {}, - "source": [ - "Run this cell to verify that the DYAD module and KVS namespace are no longer present." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d537769c-6566-48cf-a150-20d40150f059", - "metadata": {}, - "outputs": [], - "source": [ - "!echo \"Modules Post-Cleanup\"\n", - "!echo \"====================\"\n", - "!flux module list\n", - "!echo \"\"\n", - "!echo \"KVS Namespaces Post-Cleanup\"\n", - "!echo \"===========================\"\n", - "!flux kvs namespace list" - ] - }, - { - "cell_type": "markdown", - "id": "de9f7143-f22a-4eea-911e-582f6c90e529", - "metadata": {}, - "source": [ - "## Example 2\n", - "\n", - "In the second example, we will show how DYAD can help workflows even if data is in shared storage (e.g., parallel file system) by still providing built-in and transparent fine-grained synchronization.\n", - "\n", - "The figure below illustrates the data movement that will happen in this example." - ] - }, - { - "cell_type": "markdown", - "id": "90ed7911-f507-4a69-a6f9-59185887a097", - "metadata": {}, - "source": [ - "
\n", - "
\n", - "
" - ] - }, - { - "cell_type": "markdown", - "id": "1c13f27e-551c-4841-8f58-825844d88cd9", - "metadata": {}, - "source": [ - "To start, we must setup the Flux KVS namespace and DYAD module again. \n", - "\n", - "Run the cells below to setup the Flux KVS namespace and the DYAD module." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a9bcc65-0780-4652-87b3-a0b942dd48b2", - "metadata": {}, - "outputs": [], - "source": [ - "!flux kvs namespace create {kvs_namespace}\n", - "!flux kvs namespace list" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c43fcd2a-9291-407c-a313-f9be8a85cf4d", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module load {dyad_module} {shared_managed_directory} {dtl_mode}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8f3db83-8b97-440a-8b5f-f7cf76656928", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module list | grep dyad" - ] - }, - { - "cell_type": "markdown", - "id": "4b4a1949-2454-4e5b-aeba-ed420e42e620", - "metadata": {}, - "source": [ - "Next, we will generate the shell commands that we will use to run the producer and consumer applications. The only differences between these commands and the ones in Example 1 are as follows:\n", - "* The `DYAD_PATH_PRODUCER`, `DYAD_PATH_CONSUMER`, and second command-line argument to the applications all have the same value (i.e., the value of `shared_managed_directory` from the top of the notebook).\n", - "* The `DYAD_SHARED_STORAGE` environment variable is provided and set to 1. This tells DYAD to only perform fine-grained synchronization, rather than both synchronization and file transfer.\n", - "\n", - "Run the next two cells to generate the commands." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "37fa6ba6-375e-4aba-9253-f5e37cc701b9", - "metadata": {}, - "outputs": [], - "source": [ - "producer_launch_cmd = \"{preload} DYAD_KVS_NAMESPACE={kvs_namespace} DYAD_DTL_MODE={dtl_mode} \\\n", - "DYAD_PATH_PRODUCER={producer_managed_directory} DYAD_SHARED_STORAGE=1 \\\n", - "flux exec -r 0 \\\n", - "{producer_program} {num_files_transfered} {producer_managed_directory}\".format(\n", - " preload=\"LD_PRELOAD=\\\"/usr/lib/dyad_wrapper.so\\\"\" if producer_program.split(\"/\")[-1].strip().startswith(\"c_\") else \"\",\n", - " kvs_namespace=kvs_namespace,\n", - " dtl_mode=dtl_mode,\n", - " producer_managed_directory=shared_managed_directory,\n", - " producer_program=producer_program,\n", - " num_files_transfered=num_files_transfered,\n", - ")\n", - "print(producer_launch_cmd)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "02f13978-be62-4330-a1d7-3574c1d09573", - "metadata": {}, - "outputs": [], - "source": [ - "consumer_launch_cmd = \"{preload} DYAD_KVS_NAMESPACE={kvs_namespace} DYAD_DTL_MODE={dtl_mode} \\\n", - "DYAD_PATH_CONSUMER={consumer_managed_directory} DYAD_SHARED_STORAGE=1 \\\n", - "flux exec -r 1 \\\n", - "{consumer_program} {num_files_transfered} {consumer_managed_directory}\".format(\n", - " preload=\"LD_PRELOAD=\\\"/usr/lib/dyad_wrapper.so\\\"\" if producer_program.split(\"/\")[-1].strip().startswith(\"c_\") else \"\",\n", - " kvs_namespace=kvs_namespace,\n", - " dtl_mode=dtl_mode,\n", - " consumer_managed_directory=shared_managed_directory,\n", - " consumer_program=consumer_program,\n", - " num_files_transfered=num_files_transfered,\n", - ")\n", - "print(consumer_launch_cmd)" - ] - }, - { - "cell_type": "markdown", - "id": "c11fa139-026c-4b8d-8b64-3b73ba4c1ab8", - "metadata": {}, - "source": [ - "Finally, we will run the producer and consumer applications. To show how DYAD provides fine-grained synchronization even to shared storage workflows (e.g., workflows that use the parallel file system for data movement), we will run the consumer first.\n", - "\n", - "Run the cell below to run the consumer. The consumer will immediately begin waiting for data to be made available in shared storage." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce54e2b4-d5fb-4451-bdf0-143450892292", - "metadata": {}, - "outputs": [], - "source": [ - "!{consumer_launch_cmd}" - ] - }, - { - "cell_type": "markdown", - "id": "ae80ba91-149e-46b6-b1ac-3742626b0664", - "metadata": {}, - "source": [ - "Now that the consumer is running, we will run the producer. Just like Example 1, we will run the producer by copying the producer command from above and running it in the Jupyter Lab terminal.\n", - "\n", - "As with Example 1, we know that the applications ran successfully if the consumer outputs \"OK\" for each file it checks." - ] - }, - { - "cell_type": "markdown", - "id": "eb92651e-ca2d-4c88-bb3f-95aef77d3938", - "metadata": {}, - "source": [ - "Finally, we need to remove the KVS namespace and unload the DYAD module.\n", - "\n", - "Run the next two cells to do this.\n", - "\n", - "Run the final code cell to verify that the DYAD module and KVS namespace are no longer present." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a8be46d2-e138-4849-9b51-6a02542f0bdd", - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all flux module unload dyad" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "de0bec06-3c6b-4644-b6a3-db4183bc3d46", - "metadata": {}, - "outputs": [], - "source": [ - "!flux kvs namespace remove {kvs_namespace}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16dbbcc2-aea2-4ba7-9410-4c21c5d0858f", - "metadata": {}, - "outputs": [], - "source": [ - "!echo \"Modules Post-Cleanup\"\n", - "!echo \"====================\"\n", - "!flux module list\n", - "!echo \"\"\n", - "!echo \"KVS Namespaces Post-Cleanup\"\n", - "!echo \"===========================\"\n", - "!flux kvs namespace list" - ] - }, - { - "cell_type": "markdown", - "id": "81d7d87f-1e09-42c8-b165-8902551f6847", - "metadata": {}, - "source": [ - "# This concludes the notebook tutorial for DYAD.\n", - "\n", - "## If you are interested in learning more about DYAD, check out our [ReadTheDocs page](https://dyad.readthedocs.io/en/latest/), our [GitHub repository](https://github.com/flux-framework/dyad), and our [short paper](https://dyad.readthedocs.io/en/latest/_downloads/27090817b034a89b76e5538e148fea9e/ShortPaper_2022_eScience_LLNL.pdf) and [poster](https://dyad.readthedocs.io/en/latest/_downloads/1f11761622683662c33fe0086d1d7ad2/Poster_2022_eScience_LLNL.pdf) from eScience 2022.\n", - "\n", - "## If you are interested in working with us, please reach out to Jae-Seung Yeom (yeom2@llnl.gov) or Ian Lumsden (ilumsden@vols.utk.edu)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "71d04206-343f-4407-880c-d67e659656d6", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sleep_batch.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/sleep_batch.sh similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sleep_batch.sh rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/sleep_batch.sh diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/sub_job1.sh similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/sub_job1.sh diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/sub_job2.sh similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/sub_job2.sh