From a46de66d1cbb3c6f6ac325716562f52222facf69 Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 28 Mar 2024 22:44:27 -0600 Subject: [PATCH 1/8] add: flux radiuss tutorial 2024 This adds the new directory for the Flux Riken tutorial, with the following additions: 1. flux-tree was removed from flux-sched and is added here 2. tutorial files were kept in rse-ops, are now moved here 3. New tutorial content: flux tree and hierarchy section/examples 4. New tutorial content: flux archive (previously flux filemap) 5. images that show a dummy example of job throughout 6. update of names in login page / directory to be more general 7. automated builds updated for riken Signed-off-by: vsoch --- .github/workflows/docker-builds.yaml | 18 +- 2024-RADIUSS-AWS/JupyterNotebook/README.md | 1017 +++++++ .../aws/cluster-autoscaler-autodiscover.yaml | 180 ++ .../JupyterNotebook/aws/config-aws-ssl.yaml | 79 + .../JupyterNotebook/aws/config-aws.yaml | 62 + .../JupyterNotebook/aws/eksctl-config.yaml | 110 + .../aws/eksctl-radiuss-tutorial-2024.yaml | 80 + .../JupyterNotebook/aws/storageclass.yaml | 7 + .../JupyterNotebook/docker/Dockerfile.hub | 9 + .../JupyterNotebook/docker/Dockerfile.init | 13 + .../JupyterNotebook/docker/Dockerfile.spawn | 117 + .../JupyterNotebook/docker/entrypoint.sh | 3 + .../JupyterNotebook/docker/flux-icon.png | Bin 0 -> 117673 bytes .../JupyterNotebook/docker/init-entrypoint.sh | 11 + .../docker/jupyter-launcher.yaml | 69 + .../JupyterNotebook/docker/login.html | 168 ++ .../JupyterNotebook/docker/start.sh | 2 + .../JupyterNotebook/flux-tree/flux-tree | 847 ++++++ .../flux-tree/flux-tree-helper.py | 214 ++ .../JupyterNotebook/gcp/config.yaml | 62 + .../JupyterNotebook/requirements.txt | 339 +++ .../JupyterNotebook/requirements_venv.txt | 9 + .../JupyterNotebook/tutorial/.gitignore | 2 + .../configs/workload/dyad_unet3d_demo.yaml | 35 + .../dlio_extensions/dyad_torch_data_loader.py | 184 ++ .../.github/workflows/main.yml | 33 + .../flux-workflow-examples/.mergify.yml | 18 + .../tutorial/flux-workflow-examples/Makefile | 25 + .../tutorial/flux-workflow-examples/README.md | 73 + .../async-bulk-job-submit/README.md | 99 + .../async-bulk-job-submit/bulksubmit.py | 62 + .../bulksubmit_executor.py | 67 + .../comms-module/Makefile | 19 + .../comms-module/README.md | 36 + .../flux-workflow-examples/comms-module/app.c | 129 + .../comms-module/compute.lua | 62 + .../comms-module/io-forwarding.lua | 57 + .../tutorial/flux-workflow-examples/conf.py | 83 + .../data-conduit/Makefile | 13 + .../data-conduit/README.md | 84 + .../data-conduit/compute.py | 39 + .../data-conduit/conduit.c | 182 ++ .../data-conduit/datastore.py | 68 + .../hierarchical-launching/README.md | 35 + .../hierarchical-launching/ensemble.sh | 12 + .../hierarchical-launching/parent.sh | 14 + .../tutorial/flux-workflow-examples/index.rst | 95 + .../job-cancel/README.md | 43 + .../job-cancel/submitter.py | 51 + .../job-ensemble/README.md | 104 + .../job-ensemble/compute.lua | 18 + .../job-ensemble/ensemble.sh | 21 + .../job-ensemble/io-forwarding.lua | 18 + .../job-ensemble/kvs-watch-until.lua | 82 + .../job-status-control/README.md | 69 + .../job-status-control/bookkeeper.py | 54 + .../job-status-control/compute.py | 17 + .../job-status-control/io-forwarding.py | 17 + .../job-submit-api/README.md | 106 + .../job-submit-api/compute.py | 17 + .../job-submit-api/io-forwarding.py | 17 + .../job-submit-api/submitter.py | 23 + .../job-submit-api/submitter2.py | 23 + .../job-submit-cli/README.md | 81 + .../job-submit-cli/compute.lua | 17 + .../job-submit-cli/compute.py | 17 + .../job-submit-cli/io-forwarding.lua | 17 + .../job-submit-cli/io-forwarding.py | 17 + .../job-submit-wait/README.md | 150 + .../job-submit-wait/compute.py | 17 + .../submitter_sliding_window.py | 54 + .../job-submit-wait/submitter_wait_any.py | 45 + .../submitter_wait_in_order.py | 44 + .../job-watch/job-watch.sh | 11 + .../kvs-python-bindings/README.md | 63 + .../kvs-python-bindings/kvsput-usrdata.py | 24 + .../flux-workflow-examples/requirements.txt | 3 + .../synchronize-events/README.md | 51 + .../synchronize-events/compute.lua | 23 + .../synchronize-events/io-forwarding.lua | 23 + .../tutorial/notebook/01_flux_tutorial.ipynb | 2584 +++++++++++++++++ .../tutorial/notebook/02_flux_framework.ipynb | 306 ++ .../tutorial/notebook/03_dyad_dlio.ipynb | 518 ++++ .../04_flux_tutorial_conclusions.ipynb | 90 + .../tutorial/notebook/Flux-logo.svg | 1 + .../tutorial/notebook/dyad/dyad_example1.svg | 1 + .../tutorial/notebook/dyad/dyad_example2.svg | 1 + .../tutorial/notebook/hello-batch.sh | 8 + .../tutorial/notebook/img/dl-training-io.png | Bin 0 -> 36427 bytes .../notebook/img/dyad-software-stack.png | Bin 0 -> 89601 bytes .../notebook/img/dyad-unet3d-results.svg | 149 + .../tutorial/notebook/img/dyad_design.png | Bin 0 -> 184051 bytes .../notebook/img/flux-broker-design.png | Bin 0 -> 35296 bytes .../notebook/img/flux-instance-pre-tbon.png | Bin 0 -> 12218 bytes .../notebook/img/flux-instance-w-tbon.png | Bin 0 -> 34570 bytes .../tutorial/notebook/img/flux-tree.png | Bin 0 -> 47131 bytes .../tutorial/notebook/img/instance-submit.png | Bin 0 -> 68509 bytes .../tutorial/notebook/img/scaled-submit.png | Bin 0 -> 77025 bytes .../tutorial/notebook/img/single-submit.png | Bin 0 -> 26094 bytes .../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 | 15 + .../tutorial/notebook/sub_job1.sh | 5 + .../tutorial/notebook/sub_job2.sh | 4 + 106 files changed, 11659 insertions(+), 7 deletions(-) create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/README.md create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/cluster-autoscaler-autodiscover.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws-ssl.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-config.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-radiuss-tutorial-2024.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/aws/storageclass.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.hub create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.init create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/docker/entrypoint.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/flux-icon.png create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/docker/init-entrypoint.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/docker/login.html create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/docker/start.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree-helper.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/gcp/config.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/requirements.txt create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/requirements_venv.txt create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/.gitignore create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.github/workflows/main.yml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.mergify.yml create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/Makefile create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/Makefile create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/app.c create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/compute.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/io-forwarding.lua create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/conf.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/Makefile create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/compute.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/datastore.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/index.rst create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/submitter.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/compute.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/io-forwarding.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/kvs-watch-until.lua create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/bookkeeper.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/compute.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/io-forwarding.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/compute.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/io-forwarding.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter2.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/compute.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_sliding_window.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_any.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_in_order.py create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/kvsput-usrdata.py create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/requirements.txt create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/compute.lua create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/io-forwarding.lua create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/03_dyad_dlio.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/Flux-logo.svg create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dl-training-io.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-software-stack.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad_design.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-broker-design.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-pre-tbon.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-w-tbon.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-tree.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/instance-submit.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/scaled-submit.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/single-submit.png create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/02_flux_scheduling.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sleep_batch.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh diff --git a/.github/workflows/docker-builds.yaml b/.github/workflows/docker-builds.yaml index 77bc97f..c22eee4 100644 --- a/.github/workflows/docker-builds.yaml +++ b/.github/workflows/docker-builds.yaml @@ -10,13 +10,17 @@ jobs: strategy: fail-fast: false matrix: -# Tutorial is 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"]] - test: [["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"]] + +# Tutorials are over - these builds are disabled +# ["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"]] steps: - name: Clone the code diff --git a/2024-RADIUSS-AWS/JupyterNotebook/README.md b/2024-RADIUSS-AWS/JupyterNotebook/README.md new file mode 100644 index 0000000..8e3d00f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/README.md @@ -0,0 +1,1017 @@ +# Flux + Jupyter via KubeSpawner + +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) + +Pre-requisites: + + - kubectl, (eksctl|gcloud), and (optionally) docker installed locally + - A cloud with a Kubernetes cluster deployed (AWS and Google) + - Excitement to learn about Flux! + +For AWS Tutorial Day users: + +> To run the AWS tutorial, visit https://tutorial.flux-framework.org. You can use any login you want, but choose something relatvely uncommon (like your email address) or you may end up sharing a JupyterLab instance with another user. The tutorial password will be provided to you. + +## 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. + +```bash +docker build -t ghcr.io/flux-framework/flux-jupyter-hub:radiuss-2024 -f docker/Dockerfile.hub . +docker build -t ghcr.io/flux-framework/flux-jupyter-spawn:radiuss-2024 -f docker/Dockerfile.spawn . +docker build -t ghcr.io/flux-framework/flux-jupyter-init:radiuss-2024 -f docker/Dockerfile.init . +``` + +Note that these are available under the flux-framework organization GitHub packages, so you shouldn't need +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: + +```bash +docker build -t flux-tutorial -f docker/Dockerfile.spawn . +docker network create jupyterhub + +# Here is how to run an entirely contained tutorial (the notebook in the container) +docker run --rm -it --entrypoint /start.sh -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 flux-tutorial +``` + +If you want to develop the ipynb files, you can bind the tutorials directory: + +```bash +docker run --rm -it --entrypoint /start.sh -v $PWD/tutorial:/home/jovyan/flux-tutorial-2024 -v /var/run/docker.sock:/var/run/docker.sock --net jupyterhub --name jupyterhub -p 8888:8888 flux-tutorial +``` + +And then editing and saving will save to your host. You can also File -> Download if you forget to do +this bind. Either way, when the container is running you can open the localhost or 127.0.0.1 (home sweet home!) link in your browser on port 8888. You'll want to go to flux-tutorial-2024 -> notebook to see the notebook. +You'll need to select http only (and bypass the no certificate warning). + +## Deploy to Kubernetes + +### 1. Create Cluster + +#### Google Cloud + +Here is how to create the cluster on Google Cloud using [gcloud](https://cloud.google.com/sdk/docs/install) (and assuming you have logged in +with [gcloud auth login](https://cloud.google.com/sdk/gcloud/reference/auth/login): + +```bash +export GOOGLE_PROJECT=myproject +gcloud container clusters create flux-jupyter --project $GOOGLE_PROJECT \ + --zone us-central1-a --machine-type n1-standard-2 \ + --num-nodes=4 --enable-network-policy --enable-intra-node-visibility +``` + +#### AWS + +Here is how to create an equivalent cluster on AWS (EKS). We will be using [eksctl](https://eksctl.io/introduction/), which +you should install. + +```bash +# Create an EKS cluster with autoscaling with default storage +eksctl create cluster --config-file aws/eksctl-config.yaml + +# Create an EKS cluster with io1 node storage but no autoscaling, used for the RADIUSS 2023 tutorial +eksctl create cluster --config-file aws/eksctl-radiuss-tutorial-2023.yaml +``` + +You can find vanilla (manual) instructions [here](https://z2jh.jupyter.org/en/stable/kubernetes/amazon/step-zero-aws-eks.html) if you +are interested in how it works. We emulate the logic there using eksctl. Then generate a secret token - we will add this to [config-aws.yaml](aws/config-aws.yaml) (without SSL) or [config-aws-ssl.yaml](aws/config-aws-ssl.yaml) (with SSL). When your cluster is ready, this will deploy an EBS CSI driver: + +```bash +kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master" +``` + +And install the cluster-autoscaler: + +```bash +kubectl apply -f aws/cluster-autoscaler-autodiscover.yaml +``` + +If you want to use a different storage class than the default (`gp2`), you also need to create the new storage class (`gp3` here) and set it as the default storage class: + +```bash +kubectl apply -f aws/storageclass.yaml +kubectl patch storageclass gp3 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' +kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}' +``` + +Most of the information I needed to read about this was [here](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md) - the Jupyter documentation wasn't super helpful beyond saying to install it. Also note that I got this (seemingly working) without the `propagateASGTags` set to true, but that is something that I've seen have issue. +You can look at the autoscaler pod logs for information. + +While the spawned containers (e.g., where you run your notebook) don't use these volumes, the hub will. +You can read about [gp2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volume-types.html) class. +Note that we will be using [config-aws.yaml](aws/config-aws.yaml) if you don't need SSL, and [config-aws-ssl.yaml](aws/config-aws-ssl.yaml) if you do. For the latter, the jupyter spawner will generate let's encrypt certificates for us, given that we have correctly configured DNS. + +### 2. Deploy JupyterHub + +We will use [helm](https://helm.sh/docs/helm/helm_install/) to install charts and deploy. + +```bash +helm repo add jupyterhub https://hub.jupyter.org/helm-chart/ +helm repo update +``` + +You can see the versions available: + +```bash +helm search repo jupyterhub +``` +```console +NAME CHART VERSION APP VERSION DESCRIPTION +bitnami/jupyterhub 4.2.0 4.0.2 JupyterHub brings the power of notebooks to gro... +jupyterhub/jupyterhub 3.0.2 4.0.2 Multi-user Jupyter installation +jupyterhub/pebble 1.0.1 v2.3.1 This Helm chart bootstraps Pebble: an ACME serv... +``` + +Note that chart versions don't always coincide with software (or "app") versions. At the time of writing this, +we are using the jupyterhub/jupyterhub 3.0.2/4.0.2 versions, and our container bases point to 3.0.2 tags for the +corresponding images. Next, see the values we can set, which likely will come from a config*.yaml that we will choose. + +```bash +helm show values jupyterhub/jupyterhub +``` + +
+ +Example values for the jupyterhub helm chart + +```console +# fullnameOverride and nameOverride distinguishes blank strings, null values, +# and non-blank strings. For more details, see the configuration reference. +fullnameOverride: "" +nameOverride: + +# enabled is ignored by the jupyterhub chart itself, but a chart depending on +# the jupyterhub chart conditionally can make use this config option as the +# condition. +enabled: + +# custom can contain anything you want to pass to the hub pod, as all passed +# Helm template values will be made available there. +custom: {} + +# imagePullSecret is configuration to create a k8s Secret that Helm chart's pods +# can get credentials from to pull their images. +imagePullSecret: + create: false + automaticReferenceInjection: true + registry: + username: + password: + email: +# imagePullSecrets is configuration to reference the k8s Secret resources the +# Helm chart's pods can get credentials from to pull their images. +imagePullSecrets: [] + +# hub relates to the hub pod, responsible for running JupyterHub, its configured +# Authenticator class KubeSpawner, and its configured Proxy class +# ConfigurableHTTPProxy. KubeSpawner creates the user pods, and +# ConfigurableHTTPProxy speaks with the actual ConfigurableHTTPProxy server in +# the proxy pod. +hub: + revisionHistoryLimit: + config: + JupyterHub: + admin_access: true + authenticator_class: dummy + service: + type: ClusterIP + annotations: {} + ports: + nodePort: + extraPorts: [] + loadBalancerIP: + baseUrl: / + cookieSecret: + initContainers: [] + nodeSelector: {} + tolerations: [] + concurrentSpawnLimit: 64 + consecutiveFailureLimit: 5 + activeServerLimit: + deploymentStrategy: + ## type: Recreate + ## - sqlite-pvc backed hubs require the Recreate deployment strategy as a + ## typical PVC storage can only be bound to one pod at the time. + ## - JupyterHub isn't designed to support being run in parallel. More work + ## needs to be done in JupyterHub itself for a fully highly available (HA) + ## deployment of JupyterHub on k8s is to be possible. + type: Recreate + db: + type: sqlite-pvc + upgrade: + pvc: + annotations: {} + selector: {} + accessModes: + - ReadWriteOnce + storage: 1Gi + subPath: + storageClassName: + url: + password: + labels: {} + annotations: {} + command: [] + args: [] + extraConfig: {} + extraFiles: {} + extraEnv: {} + extraContainers: [] + extraVolumes: [] + extraVolumeMounts: [] + image: + name: jupyterhub/k8s-hub + tag: "3.0.2" + pullPolicy: + pullSecrets: [] + resources: {} + podSecurityContext: + fsGroup: 1000 + containerSecurityContext: + runAsUser: 1000 + runAsGroup: 1000 + allowPrivilegeEscalation: false + lifecycle: {} + loadRoles: {} + services: {} + pdb: + enabled: false + maxUnavailable: + minAvailable: 1 + networkPolicy: + enabled: true + ingress: [] + egress: [] + egressAllowRules: + cloudMetadataServer: true + dnsPortsCloudMetadataServer: true + dnsPortsKubeSystemNamespace: true + dnsPortsPrivateIPs: true + nonPrivateIPs: true + privateIPs: true + interNamespaceAccessLabels: ignore + allowedIngressPorts: [] + allowNamedServers: false + namedServerLimitPerUser: + authenticatePrometheus: + redirectToServer: + shutdownOnLogout: + templatePaths: [] + templateVars: {} + livenessProbe: + # The livenessProbe's aim to give JupyterHub sufficient time to startup but + # be able to restart if it becomes unresponsive for ~5 min. + enabled: true + initialDelaySeconds: 300 + periodSeconds: 10 + failureThreshold: 30 + timeoutSeconds: 3 + readinessProbe: + # The readinessProbe's aim is to provide a successful startup indication, + # but following that never become unready before its livenessProbe fail and + # restarts it if needed. To become unready following startup serves no + # purpose as there are no other pod to fallback to in our non-HA deployment. + enabled: true + initialDelaySeconds: 0 + periodSeconds: 2 + failureThreshold: 1000 + timeoutSeconds: 1 + existingSecret: + serviceAccount: + create: true + name: + annotations: {} + extraPodSpec: {} + +rbac: + create: true + +# proxy relates to the proxy pod, the proxy-public service, and the autohttps +# pod and proxy-http service. +proxy: + secretToken: + annotations: {} + deploymentStrategy: + ## type: Recreate + ## - JupyterHub's interaction with the CHP proxy becomes a lot more robust + ## with this configuration. To understand this, consider that JupyterHub + ## during startup will interact a lot with the k8s service to reach a + ## ready proxy pod. If the hub pod during a helm upgrade is restarting + ## directly while the proxy pod is making a rolling upgrade, the hub pod + ## could end up running a sequence of interactions with the old proxy pod + ## and finishing up the sequence of interactions with the new proxy pod. + ## As CHP proxy pods carry individual state this is very error prone. One + ## outcome when not using Recreate as a strategy has been that user pods + ## have been deleted by the hub pod because it considered them unreachable + ## as it only configured the old proxy pod but not the new before trying + ## to reach them. + type: Recreate + ## rollingUpdate: + ## - WARNING: + ## This is required to be set explicitly blank! Without it being + ## explicitly blank, k8s will let eventual old values under rollingUpdate + ## remain and then the Deployment becomes invalid and a helm upgrade would + ## fail with an error like this: + ## + ## UPGRADE FAILED + ## Error: Deployment.apps "proxy" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate' + ## Error: UPGRADE FAILED: Deployment.apps "proxy" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate' + rollingUpdate: + # service relates to the proxy-public service + service: + type: LoadBalancer + labels: {} + annotations: {} + nodePorts: + http: + https: + disableHttpPort: false + extraPorts: [] + loadBalancerIP: + loadBalancerSourceRanges: [] + # chp relates to the proxy pod, which is responsible for routing traffic based + # on dynamic configuration sent from JupyterHub to CHP's REST API. + chp: + revisionHistoryLimit: + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + image: + name: jupyterhub/configurable-http-proxy + # tag is automatically bumped to new patch versions by the + # watch-dependencies.yaml workflow. + # + tag: "4.5.6" # https://github.com/jupyterhub/configurable-http-proxy/tags + pullPolicy: + pullSecrets: [] + extraCommandLineFlags: [] + livenessProbe: + enabled: true + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 30 + timeoutSeconds: 3 + readinessProbe: + enabled: true + initialDelaySeconds: 0 + periodSeconds: 2 + failureThreshold: 1000 + timeoutSeconds: 1 + resources: {} + defaultTarget: + errorTarget: + extraEnv: {} + nodeSelector: {} + tolerations: [] + networkPolicy: + enabled: true + ingress: [] + egress: [] + egressAllowRules: + cloudMetadataServer: true + dnsPortsCloudMetadataServer: true + dnsPortsKubeSystemNamespace: true + dnsPortsPrivateIPs: true + nonPrivateIPs: true + privateIPs: true + interNamespaceAccessLabels: ignore + allowedIngressPorts: [http, https] + pdb: + enabled: false + maxUnavailable: + minAvailable: 1 + extraPodSpec: {} + # traefik relates to the autohttps pod, which is responsible for TLS + # termination when proxy.https.type=letsencrypt. + traefik: + revisionHistoryLimit: + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + image: + name: traefik + # tag is automatically bumped to new patch versions by the + # watch-dependencies.yaml workflow. + # + tag: "v2.10.4" # ref: https://hub.docker.com/_/traefik?tab=tags + pullPolicy: + pullSecrets: [] + hsts: + includeSubdomains: false + preload: false + maxAge: 15724800 # About 6 months + resources: {} + labels: {} + extraInitContainers: [] + extraEnv: {} + extraVolumes: [] + extraVolumeMounts: [] + extraStaticConfig: {} + extraDynamicConfig: {} + nodeSelector: {} + tolerations: [] + extraPorts: [] + networkPolicy: + enabled: true + ingress: [] + egress: [] + egressAllowRules: + cloudMetadataServer: true + dnsPortsCloudMetadataServer: true + dnsPortsKubeSystemNamespace: true + dnsPortsPrivateIPs: true + nonPrivateIPs: true + privateIPs: true + interNamespaceAccessLabels: ignore + allowedIngressPorts: [http, https] + pdb: + enabled: false + maxUnavailable: + minAvailable: 1 + serviceAccount: + create: true + name: + annotations: {} + extraPodSpec: {} + secretSync: + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + image: + name: jupyterhub/k8s-secret-sync + tag: "3.0.2" + pullPolicy: + pullSecrets: [] + resources: {} + labels: {} + https: + enabled: false + type: letsencrypt + #type: letsencrypt, manual, offload, secret + letsencrypt: + contactEmail: + # Specify custom server here (https://acme-staging-v02.api.letsencrypt.org/directory) to hit staging LE + acmeServer: https://acme-v02.api.letsencrypt.org/directory + manual: + key: + cert: + secret: + name: + key: tls.key + crt: tls.crt + hosts: [] + +# singleuser relates to the configuration of KubeSpawner which runs in the hub +# pod, and its spawning of user pods such as jupyter-myusername. +singleuser: + podNameTemplate: + extraTolerations: [] + nodeSelector: {} + extraNodeAffinity: + required: [] + preferred: [] + extraPodAffinity: + required: [] + preferred: [] + extraPodAntiAffinity: + required: [] + preferred: [] + networkTools: + image: + name: jupyterhub/k8s-network-tools + tag: "3.0.2" + pullPolicy: + pullSecrets: [] + resources: {} + cloudMetadata: + # block set to true will append a privileged initContainer using the + # iptables to block the sensitive metadata server at the provided ip. + blockWithIptables: true + ip: 169.254.169.254 + networkPolicy: + enabled: true + ingress: [] + egress: [] + egressAllowRules: + cloudMetadataServer: false + dnsPortsCloudMetadataServer: true + dnsPortsKubeSystemNamespace: true + dnsPortsPrivateIPs: true + nonPrivateIPs: true + privateIPs: false + interNamespaceAccessLabels: ignore + allowedIngressPorts: [] + events: true + extraAnnotations: {} + extraLabels: + hub.jupyter.org/network-access-hub: "true" + extraFiles: {} + extraEnv: {} + lifecycleHooks: {} + initContainers: [] + extraContainers: [] + allowPrivilegeEscalation: false + uid: 1000 + fsGid: 100 + serviceAccountName: + storage: + type: dynamic + extraLabels: {} + extraVolumes: [] + extraVolumeMounts: [] + static: + pvcName: + subPath: "{username}" + capacity: 10Gi + homeMountPath: /home/jovyan + dynamic: + storageClass: + pvcNameTemplate: claim-{username}{servername} + volumeNameTemplate: volume-{username}{servername} + storageAccessModes: [ReadWriteOnce] + image: + name: jupyterhub/k8s-singleuser-sample + tag: "3.0.2" + pullPolicy: + pullSecrets: [] + startTimeout: 300 + cpu: + limit: + guarantee: + memory: + limit: + guarantee: 1G + extraResource: + limits: {} + guarantees: {} + cmd: jupyterhub-singleuser + defaultUrl: + extraPodConfig: {} + profileList: [] + +# scheduling relates to the user-scheduler pods and user-placeholder pods. +scheduling: + userScheduler: + enabled: true + revisionHistoryLimit: + replicas: 2 + logLevel: 4 + # plugins are configured on the user-scheduler to make us score how we + # schedule user pods in a way to help us schedule on the most busy node. By + # doing this, we help scale down more effectively. It isn't obvious how to + # enable/disable scoring plugins, and configure them, to accomplish this. + # + # plugins ref: https://kubernetes.io/docs/reference/scheduling/config/#scheduling-plugins-1 + # migration ref: https://kubernetes.io/docs/reference/scheduling/config/#scheduler-configuration-migrations + # + plugins: + score: + # These scoring plugins are enabled by default according to + # https://kubernetes.io/docs/reference/scheduling/config/#scheduling-plugins + # 2022-02-22. + # + # Enabled with high priority: + # - NodeAffinity + # - InterPodAffinity + # - NodeResourcesFit + # - ImageLocality + # Remains enabled with low default priority: + # - TaintToleration + # - PodTopologySpread + # - VolumeBinding + # Disabled for scoring: + # - NodeResourcesBalancedAllocation + # + disabled: + # We disable these plugins (with regards to scoring) to not interfere + # or complicate our use of NodeResourcesFit. + - name: NodeResourcesBalancedAllocation + # Disable plugins to be allowed to enable them again with a different + # weight and avoid an error. + - name: NodeAffinity + - name: InterPodAffinity + - name: NodeResourcesFit + - name: ImageLocality + enabled: + - name: NodeAffinity + weight: 14631 + - name: InterPodAffinity + weight: 1331 + - name: NodeResourcesFit + weight: 121 + - name: ImageLocality + weight: 11 + pluginConfig: + # Here we declare that we should optimize pods to fit based on a + # MostAllocated strategy instead of the default LeastAllocated. + - name: NodeResourcesFit + args: + scoringStrategy: + resources: + - name: cpu + weight: 1 + - name: memory + weight: 1 + type: MostAllocated + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + image: + # IMPORTANT: Bumping the minor version of this binary should go hand in + # hand with an inspection of the user-scheduelrs RBAC resources + # that we have forked in + # templates/scheduling/user-scheduler/rbac.yaml. + # + # Debugging advice: + # + # - Is configuration of kube-scheduler broken in + # templates/scheduling/user-scheduler/configmap.yaml? + # + # - Is the kube-scheduler binary's compatibility to work + # against a k8s api-server that is too new or too old? + # + # - You can update the GitHub workflow that runs tests to + # include "deploy/user-scheduler" in the k8s namespace report + # and reduce the user-scheduler deployments replicas to 1 in + # dev-config.yaml to get relevant logs from the user-scheduler + # pods. Inspect the "Kubernetes namespace report" action! + # + # - Typical failures are that kube-scheduler fails to search for + # resources via its "informers", and won't start trying to + # schedule pods before they succeed which may require + # additional RBAC permissions or that the k8s api-server is + # aware of the resources. + # + # - If "successfully acquired lease" can be seen in the logs, it + # is a good sign kube-scheduler is ready to schedule pods. + # + name: registry.k8s.io/kube-scheduler + # tag is automatically bumped to new patch versions by the + # watch-dependencies.yaml workflow. The minor version is pinned in the + # workflow, and should be updated there if a minor version bump is done + # here. We aim to stay around 1 minor version behind the latest k8s + # version. + # + tag: "v1.26.7" # ref: https://github.com/kubernetes/kubernetes/tree/master/CHANGELOG + pullPolicy: + pullSecrets: [] + nodeSelector: {} + tolerations: [] + labels: {} + annotations: {} + pdb: + enabled: true + maxUnavailable: 1 + minAvailable: + resources: {} + serviceAccount: + create: true + name: + annotations: {} + extraPodSpec: {} + podPriority: + enabled: false + globalDefault: false + defaultPriority: 0 + imagePullerPriority: -5 + userPlaceholderPriority: -10 + userPlaceholder: + enabled: true + image: + name: registry.k8s.io/pause + # tag is automatically bumped to new patch versions by the + # watch-dependencies.yaml workflow. + # + # If you update this, also update prePuller.pause.image.tag + # + tag: "3.9" + pullPolicy: + pullSecrets: [] + revisionHistoryLimit: + replicas: 0 + labels: {} + annotations: {} + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + resources: {} + corePods: + tolerations: + - key: hub.jupyter.org/dedicated + operator: Equal + value: core + effect: NoSchedule + - key: hub.jupyter.org_dedicated + operator: Equal + value: core + effect: NoSchedule + nodeAffinity: + matchNodePurpose: prefer + userPods: + tolerations: + - key: hub.jupyter.org/dedicated + operator: Equal + value: user + effect: NoSchedule + - key: hub.jupyter.org_dedicated + operator: Equal + value: user + effect: NoSchedule + nodeAffinity: + matchNodePurpose: prefer + +# prePuller relates to the hook|continuous-image-puller DaemonsSets +prePuller: + revisionHistoryLimit: + labels: {} + annotations: {} + resources: {} + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + extraTolerations: [] + # hook relates to the hook-image-awaiter Job and hook-image-puller DaemonSet + hook: + enabled: true + pullOnlyOnChanges: true + # image and the configuration below relates to the hook-image-awaiter Job + image: + name: jupyterhub/k8s-image-awaiter + tag: "3.0.2" + pullPolicy: + pullSecrets: [] + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + podSchedulingWaitDuration: 10 + nodeSelector: {} + tolerations: [] + resources: {} + serviceAccount: + create: true + name: + annotations: {} + continuous: + enabled: true + pullProfileListImages: true + extraImages: {} + pause: + containerSecurityContext: + runAsUser: 65534 # nobody user + runAsGroup: 65534 # nobody group + allowPrivilegeEscalation: false + image: + name: registry.k8s.io/pause + # tag is automatically bumped to new patch versions by the + # watch-dependencies.yaml workflow. + # + # If you update this, also update scheduling.userPlaceholder.image.tag + # + tag: "3.9" + pullPolicy: + pullSecrets: [] + +ingress: + enabled: false + annotations: {} + ingressClassName: + hosts: [] + pathSuffix: + pathType: Prefix + tls: [] + +# cull relates to the jupyterhub-idle-culler service, responsible for evicting +# inactive singleuser pods. +# +# The configuration below, except for enabled, corresponds to command-line flags +# for jupyterhub-idle-culler as documented here: +# https://github.com/jupyterhub/jupyterhub-idle-culler#as-a-standalone-script +# +cull: + enabled: true + users: false # --cull-users + adminUsers: true # --cull-admin-users + removeNamedServers: false # --remove-named-servers + timeout: 3600 # --timeout + every: 600 # --cull-every + concurrency: 10 # --concurrency + maxAge: 0 # --max-age + +debug: + enabled: false + +global: + safeToShowValues: false +``` + +
+ +#### Changes You Might Need to Make: + +- Change the config*.yaml image-> name and tag that you deploy to use your images. +- You might want to change the number of user placeholder pods +- Also change the hub->concurrentSpawnLimit +- Change the password, ssl secret, and domain name if applicable +- Change the aws/eksctl-config.yaml autoscaling ranges depending on your needs. +- Remove pullPolicy Always if you don't expect to want to update/re-pull an image every time (ideal for production) + +And here is how to deploy, assuming the default namespace. Please choose your cloud appropriately! + +```bash +# This is for Google Cloud +helm install flux-jupyter jupyterhub/jupyterhub --values gcp/config.yaml + +# This is for Amazon EKS without SSL +helm install flux-jupyter jupyterhub/jupyterhub --values aws/config-aws.yaml + +# This is for Amazon EKS with SSL (assuming DNS is configured) +helm install flux-jupyter jupyterhub/jupyterhub --values aws/config-aws-ssl.yaml +``` + +If you mess something up, you can change the file and run `helm upgrade`: + +```bash +helm upgrade flux-jupyter jupyterhub/jupyterhub --values aws/config-aws-ssl.yaml +``` + +If you REALLY mess something up, you can tear the whole thing down and then install again: + +```bash +helm uninstall flux-jupyter +``` + +Note that in practice of bringing this up and down many times, we have seen the proxy-public +not create a handful of times. If this happens, just tear down everything, wait for all pods +to terminate, and then start freshly. When you run a command, also note that the terminal will hang! +You can see progress in another terminal: + +```bash +$ kubectl get pods +``` + +or try watching: + +```bash +$ kubectl get pods --watch +``` + +When it's done, you should see: + +```bash +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +continuous-image-puller-nvr4g 1/1 Running 0 5m31s +hub-7d59dfb748-mrfdv 1/1 Running 0 5m31s +proxy-d9dfbf77b-v488t 1/1 Running 0 5m31s +user-scheduler-587fcc5479-c4mmk 1/1 Running 0 5m31s +user-scheduler-587fcc5479-x6jmk 1/1 Running 0 5m31s +``` + +(The numbers of each above might vary based on the size of your cluster). And the terminal provides a lot of useful output: + +
+ +Output of Terminal on Completed Install + +```console +NAME: flux-jupyter +LAST DEPLOYED: Sun Aug 27 15:00:15 2023 +NAMESPACE: default +STATUS: deployed +REVISION: 1 +TEST SUITE: None +NOTES: +. __ __ __ __ __ + / / __ __ ____ __ __ / /_ ___ _____ / / / / __ __ / /_ + __ / / / / / / / __ \ / / / / / __/ / _ \ / ___/ / /_/ / / / / / / __ \ +/ /_/ / / /_/ / / /_/ / / /_/ / / /_ / __/ / / / __ / / /_/ / / /_/ / +\____/ \__,_/ / .___/ \__, / \__/ \___/ /_/ /_/ /_/ \__,_/ /_.___/ + /_/ /____/ + + You have successfully installed the official JupyterHub Helm chart! + +### Installation info + + - Kubernetes namespace: default + - Helm release name: flux-jupyter + - Helm chart version: 3.0.2 + - JupyterHub version: 4.0.2 + - Hub pod packages: See https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/3.0.2/images/hub/requirements.txt + +### Followup links + + - Documentation: https://z2jh.jupyter.org + - Help forum: https://discourse.jupyter.org + - Social chat: https://gitter.im/jupyterhub/jupyterhub + - Issue tracking: https://github.com/jupyterhub/zero-to-jupyterhub-k8s/issues + +### Post-installation checklist + + - Verify that created Pods enter a Running state: + + kubectl --namespace=default get pod + + If a pod is stuck with a Pending or ContainerCreating status, diagnose with: + + kubectl --namespace=default describe pod + + If a pod keeps restarting, diagnose with: + + kubectl --namespace=default logs --previous + + - Verify an external IP is provided for the k8s Service proxy-public. + + kubectl --namespace=default get service proxy-public + + If the external ip remains , diagnose with: + + kubectl --namespace=default describe service proxy-public + + - Verify web based access: + + You have not configured a k8s Ingress resource so you need to access the k8s + Service proxy-public directly. + + If your computer is outside the k8s cluster, you can port-forward traffic to + the k8s Service proxy-public with kubectl to access it from your + computer. + + kubectl --namespace=default port-forward service/proxy-public 8080:http + + Try insecure HTTP access: http://localhost:8080 +``` + +
+ +### 3. Get Public Proxy + +Then to find the public proxy: + +```bash +kubectl get service proxy-public +``` +```console +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +proxy-public LoadBalancer 10.96.179.168 80:32530/TCP 7m22s +``` +or: + +```bash +kubectl get service proxy-public --output jsonpath='{.status.loadBalancer.ingress[].ip}' +``` + +Note that for Google, it looks like an ip address. For aws you get a string monster! + +```console +a054af2758c1549f780a433e5515a9d4-1012389935.us-east-2.elb.amazonaws.com +``` + +This might take a minute to fully be there - if it doesn't work immediately give it that. +At this point, you should be able to login as any user, open the notebook (nested two levels) +and interact with Flux! Remember that if you don't see the service, try deleting everything and +starting fresh. If that doesn't work, there might be some new error we didn't anticipate, +and you can look at logs. + +### Clean up + +For both: + +```bash +helm uninstall flux-jupyter +``` + +For Google Cloud: + +```bash +gcloud container clusters delete flux-jupyter +``` + +For AWS: + +```bash +# If you don't do this first, it will tell the pods are un-evictable and loop forever +$ kubectl delete pod --all-namespaces --all --force +# Then delete the cluster +$ eksctl delete cluster --config-file aws/eksctl-config.yaml --wait +``` + +In practice, you'll need to start deleting with `eksctl` and then you will see the pod eviction warning +(because they were re-created) and you'll need to run the command again, and then it will clean up. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/cluster-autoscaler-autodiscover.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/cluster-autoscaler-autodiscover.yaml new file mode 100644 index 0000000..56869d0 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/cluster-autoscaler-autodiscover.yaml @@ -0,0 +1,180 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler + name: cluster-autoscaler + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cluster-autoscaler + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +rules: + - apiGroups: [""] + resources: ["events", "endpoints"] + verbs: ["create", "patch"] + - apiGroups: [""] + resources: ["pods/eviction"] + verbs: ["create"] + - apiGroups: [""] + resources: ["pods/status"] + verbs: ["update"] + - apiGroups: [""] + resources: ["endpoints"] + resourceNames: ["cluster-autoscaler"] + verbs: ["get", "update"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["watch", "list", "get", "update"] + - apiGroups: [""] + resources: + - "namespaces" + - "pods" + - "services" + - "replicationcontrollers" + - "persistentvolumeclaims" + - "persistentvolumes" + verbs: ["watch", "list", "get"] + - apiGroups: ["extensions"] + resources: ["replicasets", "daemonsets"] + verbs: ["watch", "list", "get"] + - apiGroups: ["policy"] + resources: ["poddisruptionbudgets"] + verbs: ["watch", "list"] + - apiGroups: ["apps"] + resources: ["statefulsets", "replicasets", "daemonsets"] + verbs: ["watch", "list", "get"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "csinodes", "csidrivers", "csistoragecapacities"] + verbs: ["watch", "list", "get"] + - apiGroups: ["batch", "extensions"] + resources: ["jobs"] + verbs: ["get", "list", "watch", "patch"] + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] + - apiGroups: ["coordination.k8s.io"] + resourceNames: ["cluster-autoscaler"] + resources: ["leases"] + verbs: ["get", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["create", "list", "watch"] + - apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["cluster-autoscaler-status", "cluster-autoscaler-priority-expander"] + verbs: ["delete", "get", "update", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cluster-autoscaler + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-autoscaler +subjects: + - kind: ServiceAccount + name: cluster-autoscaler + namespace: kube-system + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + k8s-addon: cluster-autoscaler.addons.k8s.io + k8s-app: cluster-autoscaler +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: cluster-autoscaler +subjects: + - kind: ServiceAccount + name: cluster-autoscaler + namespace: kube-system + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + app: cluster-autoscaler +spec: + replicas: 1 + selector: + matchLabels: + app: cluster-autoscaler + template: + metadata: + labels: + app: cluster-autoscaler + annotations: + prometheus.io/scrape: 'true' + prometheus.io/port: '8085' + spec: + priorityClassName: system-cluster-critical + securityContext: + runAsNonRoot: true + runAsUser: 65534 + fsGroup: 65534 + seccompProfile: + type: RuntimeDefault + serviceAccountName: cluster-autoscaler + containers: + - image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.26.2 + name: cluster-autoscaler + resources: + limits: + cpu: 100m + memory: 600Mi + requests: + cpu: 100m + memory: 600Mi + command: + - ./cluster-autoscaler + - --v=4 + - --stderrthreshold=info + - --cloud-provider=aws + - --skip-nodes-with-local-storage=false + - --expander=least-waste + - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/jupyterhub + volumeMounts: + - name: ssl-certs + mountPath: /etc/ssl/certs/ca-certificates.crt # /etc/ssl/certs/ca-bundle.crt for Amazon Linux Worker Nodes + readOnly: true + imagePullPolicy: "Always" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumes: + - name: ssl-certs + hostPath: + path: "/etc/ssl/certs/ca-bundle.crt" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws-ssl.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws-ssl.yaml new file mode 100644 index 0000000..6e49053 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws-ssl.yaml @@ -0,0 +1,79 @@ +# A few notes! +# The hub -> authentic class defaults to "dummy" +# We shouldn't need any image pull secrets assuming public +# There is a note about the database being a sqlite pvc +# (and a TODO for better solution for Kubernetes) + +# This is the concurrent spawn limit, likely should be increased (deafults to 64) +hub: + concurrentSpawnLimit: 128 + config: + DummyAuthenticator: + password: butter + JupyterHub: + admin_access: true + authenticator_class: dummy + db: + pvc: + # Defaults to 1Gi + storage: 32Gi + # Add the storageclass name, defaults to gp2 + storageClassName: gp3 + + # This is the image I built based off of jupyterhub/k8s-hub, 3.0.2 at time of writing this + image: + name: ghcr.io/flux-framework/flux-jupyter-hub + tag: "radiuss-2024" + pullPolicy: Always + +# # https://z2jh.jupyter.org/en/latest/administrator/optimization.html#scaling-up-in-time-user-placeholders +# scheduling: +# podPriority: +# enabled: true +# userPlaceholder: +# # Specify 3 dummy user pods will be used as placeholders +# replicas: 3 + +proxy: + https: + enabled: true + hosts: + - tutorial.flux-framework.org + letsencrypt: + contactEmail: you@email.com + +# This is the "spawn" image +singleuser: + image: + name: ghcr.io/flux-framework/flux-jupyter-spawn + tag: "radiuss-2024" + pullPolicy: Always + cpu: + limit: 2 + guarantee: 2 + memory: + limit: '4G' + guarantee: '4G' + cmd: /entrypoint.sh + + # This runs as the root user, who clones and changes ownership to uid 1000 + initContainers: + - name: init-myservice + image: ghcr.io/flux-framework/flux-jupyter-init:radiuss-2024 + command: ["/entrypoint.sh"] + volumeMounts: + - name: flux-tutorial + mountPath: /home/jovyan + + # This is how we get the tutorial files added + storage: + type: none + + # gitRepo volume is deprecated so we need another way + # https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo + extraVolumes: + - name: flux-tutorial + emptyDir: {} + extraVolumeMounts: + - name: flux-tutorial + mountPath: /home/jovyan diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws.yaml new file mode 100644 index 0000000..cd84861 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/config-aws.yaml @@ -0,0 +1,62 @@ +# A few notes! +# The hub -> authentic class defaults to "dummy" +# We shouldn't need any image pull secrets assuming public +# There is a note about the database being a sqlite pvc +# (and a TODO for better solution for Kubernetes) + +# This is the concurrent spawn limit, likely should be increased (deafults to 64) +hub: + concurrentSpawnLimit: 10 + config: + DummyAuthenticator: + password: butter + JupyterHub: + admin_access: true + authenticator_class: dummy + + # This is the image I built based off of jupyterhub/k8s-hub, 3.0.2 at time of writing this + image: + name: ghcr.io/flux-framework/flux-jupyter-hub + tag: "radiuss-2024" + pullPolicy: Always + +# https://z2jh.jupyter.org/en/latest/administrator/optimization.html#scaling-up-in-time-user-placeholders +scheduling: + podPriority: + enabled: true + userPlaceholder: + # Specify 3 dummy user pods will be used as placeholders + replicas: 3 + +# This is the "spawn" image +singleuser: + image: + name: ghcr.io/flux-framework/flux-jupyter-spawn + tag: "radiuss-2024" + pullPolicy: Always + cpu: + limit: 1 + memory: + limit: '4G' + cmd: /entrypoint.sh + + # This runs as the root user, who clones and changes ownership to uid 1000 + initContainers: + - name: init-myservice + image: ghcr.io/flux-framework/flux-jupyter-init:radiuss-2024 + command: ["/entrypoint.sh"] + volumeMounts: + - name: flux-tutorial + mountPath: /home/jovyan + + # This is how we get the tutorial files added + storage: + type: none + # gitRepo volume is deprecated so we need another way + # https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo + extraVolumes: + - name: flux-tutorial + emptyDir: {} + extraVolumeMounts: + - name: flux-tutorial + mountPath: /home/jovyan diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-config.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-config.yaml new file mode 100644 index 0000000..d88f0b1 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-config.yaml @@ -0,0 +1,110 @@ +# https://www.arhea.net/posts/2020-06-18-jupyterhub-amazon-eks +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: jupyterhub + region: us-east-2 + +iam: + withOIDC: true + serviceAccounts: + - metadata: + name: cluster-autoscaler + namespace: kube-system + labels: + aws-usage: "cluster-ops" + app.kubernetes.io/name: cluster-autoscaler + + # https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md + attachPolicy: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "autoscaling:DescribeAutoScalingGroups" + - "autoscaling:DescribeAutoScalingInstances" + - "autoscaling:DescribeLaunchConfigurations" + - "autoscaling:DescribeTags" + - "autoscaling:SetDesiredCapacity" + - "autoscaling:TerminateInstanceInAutoScalingGroup" + - "ec2:DescribeLaunchTemplateVersions" + Resource: '*' + + - metadata: + name: ebs-csi-controller-sa + namespace: kube-system + labels: + aws-usage: "cluster-ops" + app.kubernetes.io/name: aws-ebs-csi-driver + attachPolicy: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "ec2:AttachVolume" + - "ec2:CreateSnapshot" + - "ec2:CreateTags" + - "ec2:CreateVolume" + - "ec2:DeleteSnapshot" + - "ec2:DeleteTags" + - "ec2:DeleteVolume" + - "ec2:DescribeInstances" + - "ec2:DescribeSnapshots" + - "ec2:DescribeTags" + - "ec2:DescribeVolumes" + - "ec2:DetachVolume" + Resource: '*' + +availabilityZones: ["us-east-2a", "us-east-2b", "us-east-2c"] +managedNodeGroups: + - name: ng-us-east-2a + iam: + withAddonPolicies: + autoScaler: true + instanceType: m5.large + volumeSize: 30 + desiredCapacity: 1 + minSize: 1 + maxSize: 3 + privateNetworking: true + availabilityZones: + - us-east-2a + # I didn't set this, but I know it's been an issue + # propagateASGTags: true + tags: + k8s.io/cluster-autoscaler/enabled: "true" + k8s.io/cluster-autoscaler/jupyterhub: "owned" + + - name: ng-us-east-2b + iam: + withAddonPolicies: + autoScaler: true + instanceType: m5.large + volumeSize: 30 + desiredCapacity: 1 + minSize: 1 + maxSize: 3 + privateNetworking: true + availabilityZones: + - us-east-2b + # propagateASGTags: true + tags: + k8s.io/cluster-autoscaler/enabled: "true" + k8s.io/cluster-autoscaler/jupyterhub: "owned" + + - name: ng-us-east-2c + iam: + withAddonPolicies: + autoScaler: true + instanceType: m5.large + volumeSize: 30 + desiredCapacity: 1 + minSize: 1 + maxSize: 3 + privateNetworking: true + availabilityZones: + - us-east-2c + # propagateASGTags: true + tags: + k8s.io/cluster-autoscaler/enabled: "true" + k8s.io/cluster-autoscaler/jupyterhub: "owned" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-radiuss-tutorial-2024.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-radiuss-tutorial-2024.yaml new file mode 100644 index 0000000..93c04d9 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/eksctl-radiuss-tutorial-2024.yaml @@ -0,0 +1,80 @@ +# https://www.arhea.net/posts/2020-06-18-jupyterhub-amazon-eks +apiVersion: eksctl.io/v1alpha5 +kind: ClusterConfig +metadata: + name: jupyterhub + region: us-east-1 + +iam: + withOIDC: true + serviceAccounts: + - metadata: + name: ebs-csi-controller-sa + namespace: kube-system + labels: + aws-usage: "cluster-ops" + app.kubernetes.io/name: aws-ebs-csi-driver + attachPolicy: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "ec2:AttachVolume" + - "ec2:CreateSnapshot" + - "ec2:CreateTags" + - "ec2:CreateVolume" + - "ec2:DeleteSnapshot" + - "ec2:DeleteTags" + - "ec2:DeleteVolume" + - "ec2:DescribeInstances" + - "ec2:DescribeSnapshots" + - "ec2:DescribeTags" + - "ec2:DescribeVolumes" + - "ec2:DetachVolume" + Resource: '*' + +availabilityZones: + - us-east-1a + - us-east-1b + - us-east-1c + +managedNodeGroups: + - name: ng-us-east-1a + instanceType: m6a.8xlarge + volumeSize: 256 + volumeType: gp3 + volumeIOPS: 16000 + volumeThroughput: 512 + desiredCapacity: 1 + minSize: 1 + maxSize: 6 + privateNetworking: true + availabilityZones: + - us-east-1a + + - name: ng-us-east-1b + instanceType: m6a.8xlarge + volumeSize: 256 + volumeType: gp3 + volumeIOPS: 16000 + volumeThroughput: 512 + desiredCapacity: 1 + minSize: 1 + maxSize: 6 + privateNetworking: true + availabilityZones: + - us-east-1b + + - name: ng-us-east-1c + instanceType: m6a.8xlarge + volumeSize: 256 + volumeType: gp3 + volumeIOPS: 16000 + volumeThroughput: 512 + desiredCapacity: 1 + minSize: 1 + maxSize: 6 + privateNetworking: true + availabilityZones: + - us-east-1c + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/aws/storageclass.yaml b/2024-RADIUSS-AWS/JupyterNotebook/aws/storageclass.yaml new file mode 100644 index 0000000..b9bef8f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/aws/storageclass.yaml @@ -0,0 +1,7 @@ +kind: StorageClass +apiVersion: storage.k8s.io/v1 +metadata: + name: gp3 +provisioner: kubernetes.io/aws-ebs +volumeBindingMode: WaitForFirstConsumer +reclaimPolicy: Delete diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.hub b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.hub new file mode 100644 index 0000000..595a53e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.hub @@ -0,0 +1,9 @@ +ARG JUPYTERHUB_VERSION=3.0.2 +FROM jupyterhub/k8s-hub:$JUPYTERHUB_VERSION + +# Add template override directory and copy our example +# Replace the default +USER root +RUN mv /usr/local/share/jupyterhub/templates/login.html /usr/local/share/jupyterhub/templates/_login.html +COPY ./docker/login.html /usr/local/share/jupyterhub/templates/login.html +USER jovyan diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.init b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.init new file mode 100644 index 0000000..b4ba70c --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.init @@ -0,0 +1,13 @@ +FROM alpine/git + +ENV NB_USER=jovyan \ + NB_UID=1000 \ + HOME=/home/jovyan + +RUN adduser \ + -D \ + -g "Default user" \ + -u ${NB_UID} \ + -h ${HOME} \ + ${NB_USER} +COPY ./docker/init-entrypoint.sh /entrypoint.sh diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn new file mode 100644 index 0000000..bdc27d6 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn @@ -0,0 +1,117 @@ +FROM fluxrm/flux-sched:focal + +# Based off of https://github.com/jupyterhub/zero-to-jupyterhub-k8s/tree/main/images/singleuser-sample +# Local usage +# docker run -p 8888:8888 -v $(pwd):/home/jovyan/work test + +USER root + +ENV NB_USER=jovyan \ + NB_UID=1000 \ + HOME=/home/jovyan + +RUN adduser \ + --disabled-password \ + --gecos "Default user" \ + --uid ${NB_UID} \ + --home ${HOME} \ + --force-badname \ + ${NB_USER} + +RUN apt-get update \ + # && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends \ + gcc-10 \ + g++-10 \ + ca-certificates \ + dnsutils \ + iputils-ping \ + python3.9 \ + python3.9-dev \ + python3-pip \ + python3-venv \ + openmpi-bin \ + openmpi-common \ + libopenmpi-dev \ + liblz4-dev \ + tini \ + # requirement for nbgitpuller + git \ + && rm -rf /var/lib/apt/lists/* + +COPY ./requirements_venv.txt ./requirements_venv.txt +RUN python3 -m pip install -r requirements_venv.txt + +COPY ./requirements.txt ./requirements.txt +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 \ + && cd ucx \ + && git checkout v1.13.1 \ + && ./autogen.sh \ + && ./configure --disable-optimizations --enable-logging --enable-debug --disable-assertions --enable-mt --disable-params-check \ + --without-go --without-java --disable-cma --without-cuda --without-gdrcopy --without-verbs --without-knem --without-rmdacm \ + --without-rocm --without-xpmem --without-fuse3 --without-ugni --prefix=/usr CC=$(which gcc) CXX=$(which g++) \ + && make -j \ + && sudo make install \ + && cd .. \ + && rm -rf ucx + +RUN git clone https://github.com/flux-framework/dyad.git \ + && cd dyad \ + && git checkout tutorial-riken-2024 \ + && mkdir build \ + && cd build \ + && cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDYAD_ENABLE_UCX_DATA=ON .. \ + && sudo make install -j \ + && cd ../pydyad \ + && python3 -m build --wheel . \ + && pip install $(ls ./dist/*.whl | head -1) \ + && 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* + +# 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 && \ + 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 + +# Give jovyan user permissions to tutorial materials +RUN chmod -R 777 ~/flux-tutorial-2024 + +WORKDIR $HOME +COPY ./docker/flux-icon.png $HOME/flux-icon.png + +# note that previous examples are added via git volume in config.yaml +ENV SHELL=/usr/bin/bash +ENV FLUX_URI_RESOLVE_LOCAL=t + +EXPOSE 8888 +ENTRYPOINT ["tini", "--"] + +# This is for JupyterHub +COPY ./docker/entrypoint.sh /entrypoint.sh + +# This is for a local start +COPY ./docker/start.sh /start.sh + +RUN mkdir -p $HOME/.local/share && \ + chmod 777 $HOME/.local/share + +USER ${NB_USER} + +CMD ["flux", "start", "--test-size=4", "jupyter", "lab"] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/entrypoint.sh b/2024-RADIUSS-AWS/JupyterNotebook/docker/entrypoint.sh new file mode 100755 index 0000000..8b11568 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +/usr/bin/flux start --test-size=4 /usr/local/bin/jupyterhub-singleuser \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/flux-icon.png b/2024-RADIUSS-AWS/JupyterNotebook/docker/flux-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d50aa52d32a78c54ffcbd597b84d489fa3d80ae7 GIT binary patch literal 117673 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

1EHvr9mpbNpSP2MBiWfYkH?TkxkzQ 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 z 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 literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/init-entrypoint.sh b/2024-RADIUSS-AWS/JupyterNotebook/docker/init-entrypoint.sh new file mode 100755 index 0000000..293e799 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/init-entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +# Copy the notebook icon +# This would be for the customized launcher, not working yet +# wget https://flux-framework.org/assets/images/Flux-logo-mark-only-full-color.png +# mv Flux-logo-mark-only-full-color.png /home/jovyan/flux-icon.png + +# We need to clone to the user home, and then change permissions to uid 1000 +# That uid is shared by jovyan here and the spawn container +# git clone https://github.com/rse-ops/flux-radiuss-tutorial-2023 /home/jovyan/flux-tutorial +chown -R 1000 /home/jovyan diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml new file mode 100644 index 0000000..becbc80 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml @@ -0,0 +1,69 @@ +# 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: 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: Flux Framework Portal + description: Flux Framework portal for projects, releases, and publication. + source: https://flux-framework.org/ + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Flux Documentation + source: https://flux-framework.readthedocs.io/en/latest/ + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Flux Cheat Sheet + source: https://flux-framework.org/cheat-sheet/ + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Flux Glossary of Terms + source: https://flux-framework.readthedocs.io/en/latest/glossary.html + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Flux Comics + source: https://flux-framework.readthedocs.io/en/latest/comics/fluxonomicon.html + description: come and meet FluxBird - the pink bird who knows things! + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Flux Learning Guide + source: https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html + description: learn about what Flux does, how it works, and real research applications + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Getting Started with Flux and Go + source: https://converged-computing.github.io/flux-go + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] +- title: Getting Started with Flux in C + source: https://converged-computing.github.io/flux-c-examples/ + description: ...looking for contributors! + type: url + catalog: Flux Resources + args: + sandbox: [ 'allow-same-origin', 'allow-scripts', 'allow-downloads', 'allow-modals', 'allow-popups'] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/login.html b/2024-RADIUSS-AWS/JupyterNotebook/docker/login.html new file mode 100644 index 0000000..d997a0f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/login.html @@ -0,0 +1,168 @@ +{% extends "page.html" %} +{% if announcement_login is string %} + {% set announcement = announcement_login %} +{% endif %} + +{% block login_widget %} +{% endblock %} + +{% block stylesheet %} +{{ super() }} + +{% endblock %} + +{% block main %} + +{% block login %} +

+{% block login_container %} + +
+ + + +
+

Flux Tutorial running on AWS

+ +
+
+
+

Sign in

+
+
+ + + + {% if login_error %} + + {% endif %} + + + + + + + + + {% block login_terms %} + {% if login_term_url %} + + {% endif %} + {% endblock login_terms %} + +
+
+
+{% endblock login_container %} +
+{% endblock login %} + +{% endblock %} + +{% block script %} +{{ super() }} + +{% endblock %} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/start.sh b/2024-RADIUSS-AWS/JupyterNotebook/docker/start.sh new file mode 100755 index 0000000..bad19ab --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/start.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/bin/flux start --test-size=4 /usr/local/bin/jupyter-lab --ip=0.0.0.0 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree new file mode 100644 index 0000000..f12a9b8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree @@ -0,0 +1,847 @@ +#! /bin/bash + +############################################################## +# Copyright 2020 Lawrence Livermore National Security, LLC +# (c.f. AUTHORS, NOTICE.LLNS, LICENSE) +# +# This file is part of the Flux resource manager framework. +# For details, see https://github.com/flux-framework. +# +# SPDX-License-Identifier: LGPL-3.0 +############################################################## + +set -o errexit +set -o pipefail +#set -o xtrace + +FT_TOPO='1' # store arg to --topology (e.g., 2x2) +FT_QUEUE='default' # store arg to --queue-policy (e.g., fcfs:easy) +FT_PARAMS='default' # store arg to --queue-params +FT_MATCH='default' # store arg to --match-policy (e.g., low:high) +FT_PERF_OUT='%^+_no' # store perf-out filename given by --perf-out +FT_PERF_FORMAT='{treeid:<15s} {elapse:>20f} {begin:>20f} {end:>20f} {match:>15f} '\ +'{njobs:>15d} {my_nodes:>5d} {my_cores:>4d} {my_gpus:>4d}' + # store perf-out with format given by --perf-format +FT_FXLOGS='%^+_no' # dir in which flux logs are produced +FT_FXRDIR='%^+_no' # flux rundir attribute to pass +FT_LEAF='no' # is this a leaf instance (given by --leaf)? +FT_G_PREFIX='tree' # store hierarchical path given by --prefix +FT_DRY_RUN='no' # --dry-run for testing? +FT_INTER='no' # is an option for internal levels given? +FT_MAX_EXIT_CODE=0 # maximum exit code detected +FT_MAX_FLUX_JOBID=0 # maximum flux jobid that reports max exit code +FT_MAX_TREE_ID="" # FLUX_TREE_ID that reports max exit code +FT_MAX_JOBSCRIPT_IX="" # FLUX_TREE_JOBSCRIPT_INDEX reporting max code + # --prefix is an internal-use-only option +FT_JOB_NAME='%^+_no' # job name to use when submitting children +ORIG_FLUXION_QMANAGER_OPTIONS='' # +ORIG_FLUXION_RESOURCE_OPTIONS='' # to apply and unapply FLUXION_RESOURCE options +ORIG_FLUXION_QMANAGER_RC_NOOP='' # module load options. +ORIG_FLUXION_RESOURCE_RC_NOOP='' # + +declare -i FT_NJOBS=1 # store num of jobs to run, given by --njobs +declare -i FT_NNODES=1 # store num of nodes assigned, given by --nnodes +declare -i FT_NCORES=1 # store num of cores per node (--ncores-per-node) +declare -i FT_NGPUS=0 # store num of gpus per node (--ngpus-per-node) +declare -r top_prefix='tree' # prefix name to identify the top Flux instance +declare -r t_delim='x' # topology delimiter +declare -r p_delim=':' # match policy delimiter +declare -r perf_format='%-15s %20s %20s %20s %15s %15s %5s %4s %4s' +declare -a FT_CL=() # save the jobscript command into an array +declare -A mp_policies=( # make sure to update this when match + [low]=1 # policies are updated. + [high]=1 + [locality]=1 + [variation]=1 + [default]=1 +) +declare -A qp_policies=( # make sure to update this when + [fcfs]=1 # queuing policies are updated. + [easy]=1 + [hybrid]=1 + [conservative]=1 + [default]=1 +) +declare -A q_params=( # make sure to update this when + [queue-depth]=1 # queuing parameters are updated. + [reservation-depth]=1 + [default]=1 +) +declare -a jobids # array to store a set of submitted job IDs + +declare -r long_opts='help,leaf,flux-logs:,flux-rundir:,nnodes:'\ +',ncores-per-node:,ngpus-per-node:,topology:,queue-policy:,queue-params:'\ +',match-policy:,njobs:,perf-out:,perf-format:,prefix:,job-name:,dry-run' +declare -r short_opts='hlf:r:N:c:g:T:Q:P:M:J:o:X:d' +declare -r prog=${0##*/} +declare -r usage=" +Usage: ${prog} [OPTIONS] -- Jobscript\n\ +\n\ +Create a Flux instance hierarchy according to the specified\n\ +policies and schedule/run the specified number\n\ +of Jobscripts at the last level of this hierarchy.\n\ +\n\ +If --topology=2x4 and --njobs=32 are given, for instance,\n\ +2 Flux instances will be spawned from within the current instance,\n\ +each of which will in turn spawn 4 child Flux instances, totaling\n\ +8 instances at the last level of this hierarchy.\n\ +Once this is done, 4 jobs (of Jobscripts) will be scheduled\n\ +and executed at each of these 8 last-level Flux instances.\n\ +\n\ +The resources specified by --nnodes (total number of nodes) and\n\ +--ncores-per-node (total number of cores per node)\n\ +are recursively divided such that each sibling Flux instance\n\ +will be assigned to an equal split of the resources of their\n\ +parent instance. In addition, --ngpus-per-node can be given,\n\ +in which case the given GPU count will also be split.\n\ +If not given, it is assumed that there is no GPU on nodes.\n\ +\n\ +Jobscript is expected to submit one or more programs through\n\ +the flux-job submit command or its variants.\n\ +Jobscript is passed with five environment variables:\n\ +FLUX_TREE_ID, FLUX_TREE_JOBSCRIPT_INDEX, FLUX_TREE_NNODES,\n\ +FLUX_TREE_NCORES_PER_NODE and FLUX_TREE_NGPUS_PER_NODE.\n\ +FLUX_TREE_ID is an ID string uniquely identifying the hierarchical\n\ +path of the Flux instance on which Jobscript is being executed.\n\ +FLUX_TREE_JOBSCRIPT_INDEX is the integer ID of each jobscript\n\ +invocation local to the Flux instance. It starts from 1 and\n\ +sequentially increases.\n\ +FLUX_TREE_NNODES is the number nodes assigned to the instance.\n\ +FLUX_TREE_NCORES_PER_NODE is the number of cores per node\n\ +assigned to the instance.\n\ +FLUX_TREE_NGPUS_PER_NODE is the number of GPUs per node\n\ +assigned to the instance.\n\ +\n\ +If --queue-policy (additionally --queue-params) and/or\n\ +--match-policy are given, each level of this hierarchy will\n\ +be set to the specified queuing and matching policies and\n\ +parameters. Otherwise, all levels will be configured\n\ +to be used either the default policies or policies specified\n\ +through the FLUXION_RESOURCE_OPTIONS and/or FLUXION_QMANAGER_OPTIONS\n\ +environment variables.\n\ +\n\ +If any one of Jobscripts returns a non-zero exit code, flux-tree\n\ +detects the script invocation exited with the highest code and print\n\ +both that exit code and the outputs printed from executing the script.\n\ +In this case, FLUX_TREE_ID and FLUX_TREE_JOBSCRIPT_INDEX are also\n\ +reported in the from of \${FLUX_TREE_ID}@index[\${FLUX_TREE_JOBSCRIPT_INDEX}]\n\ +\n\ +Options:\n\ + -h, --help Display this message\n\ + -l, --leaf Leaf instance. Directly submit jobs\n\ + to enclosing Flux instance. Mutually-exclusive\n\ + with internal tree-node options like -T.\n\ + (default=${FT_LEAF})\n\ + -f, --flux-logs=DIR Dump Flux logs for all instances into DIR\n\ + -r, --flux-rundir=DIR Set the rundir attribute of each Flux tree instance\n\ + into a subdirectory within DIR. The content\n\ + stores will be redirected to them as well\n\ + -N, --nnodes=NNODES Total num of nodes to use\n\ + (default=${FT_NNODES})\n\ + -c, --ncores-per-node=NCORES Total num of cores per node to use\n\ + (default=${FT_NCORES})\n\ + -g, --ngpus-per-node=NGPUS Total num of gpus per node to use\n\ + (default=${FT_NGPUS})\n\ + -T, --topology=HPOLICY Topology of Flux instance hierarchy:\n\ + e.g., 2x2 (default=${FT_TOPO})\n\ + -Q, --queue-policy=QPOLICY Queuing policy for each level of\n\ + the hierarchy: e.g., easy:fcfs\n\ + -P, --queue-params=QPARAMS Queuing parameters for each level of\n\ + the hierarchy: e.g.,\n\ + queue-depth=5:reservation-depth=5\n\ + -M, --match-policy=MPOLICY Match policy for each level of\n\ + the hierarchy: e.g., low:high\n\ + -J, --njobs=NJOBS Total num of Jobscripts to run\n\ + (default=${FT_NJOBS})\n\ + -o, --perf-out=FILENAME Dump the performance data into\n\ + the given file (default: don't print)\n\ + --perf-format=FORMAT Dump the performance data with the given\n\ + format. Uses the python format\n\ + specification mini-language.\n\ + Example: \"{treeid:<15s},{elapse:>20f}\"\n\ + --job-name=NAME Name to use when submitting child jobs\n\ + -- Stop parsing options after this\n\ +" + +die() { echo -e "${prog}:" "$@"; exit 1; } +warn() { echo -e "${prog}: warning:" "$@"; } +dr_print() { echo -e "${prog}: dry-run:" "$@"; } + +# +# Roll up the performance records for each Flux instance to the KVS +# guest namespace of the parent Flux instance or print them out if top level. +# +rollup() { + local prefix="${1}" + local blurb="${2}" + local out="${3}" + local num_children="${4}" + local format="${5}" + + if [[ "${prefix}" == "${top_prefix}" && "${out}" != "%^+_no" ]]; then + flux tree-helper --perf-out="${out}" --perf-format="${format}" \ + ${num_children} "tree-perf" "${FT_JOB_NAME}" <<< "${blurb}" + else + flux tree-helper ${num_children} "tree-perf" "${FT_JOB_NAME}" \ + <<< "${blurb}" + fi +} + + +# +# Return a JSON string out of the performance data passed. +# +jsonify() { + local prefix="${1}" + local njobs="${2}" + local nnodes="${3}" + local ncores="${4}" + local ngpus="${5}" + local begin="${6}" + local end="${7}" + local avg=0 + local avail="no" + local el_match=0 + + # Print resource match time only for internal study + # flux-resource isn't a public command + if [[ "x${FT_DRY_RUN}" = "xno" ]] + then + flux ion-resource -h > /dev/null 2>&1 && avail="yes" + fi + + if [[ "${avail}" = "yes" ]] + then + avg=$(flux ion-resource stat | grep "Avg" | awk '{print $4}') + el_match=$(awk "BEGIN {print ${avg}*${njobs}*1000000.0}") + fi + + local elapse=0 + elapse=$(awk "BEGIN {print ${end} - ${begin}}") + echo "{\"treeid\":\"${prefix}\",\"njobs\":${njobs},\"my_nodes\":${nnodes},\ +\"my_cores\":${ncores},\"my_gpus\":${ngpus},\"perf\":{\"begin\":${begin},\ +\"end\":${end},\"elapse\":${elapse},\"match\":${el_match}}}" +} + + +# +# Fetch the next topology parameter that will be passed to +# the next-level Flux instances. E.g., If the current level topology +# is 2x3x4, the topology handled at the next level will be 3x4. +# +next_topo() { + local topo="${1}" + local nx='' + local nfields=0 + nfields=$(echo "${topo}" | awk -F"${t_delim}" '{print NF}') + # Remove the first topo parameter + [[ ${nfields} -gt 1 ]] && nx="${topo#*${t_delim}}" + echo "${nx}" +} + + +# +# Fetch the next policy parameter that will be passed to +# the next-level Flux instances. E.g., If the current policy parameter +# is high:low:locality, the policies handled at the next level +# will be low:locality. +# +next_policy_or_param() { + local policy_or_param="${1}" + local nx="" + local nfields=0 + nfields=$(echo "${policy_or_param}" | awk -F"${p_delim}" '{print NF}') + [[ ${nfields} -gt 1 ]] && nx="${policy_or_param#*${p_delim}}" + echo "${nx}" +} + + +# +# Check if the given queuing policy is valid +# +qpolicy_check() { + local policy=${1%%${p_delim}*} + [[ "x${policy}" = "x" ]] && return 1 + [[ "${qp_policies["${policy}"]:-missing}" = "missing" ]] && return 1 + return 0 +} + + +# +# Check if the given match policy is valid +# +mpolicy_check() { + local policy=${1%%${p_delim}*} + [[ "x${policy}" = "x" ]] && return 1 + [[ "${mp_policies["${policy}"]:-missing}" = "missing" ]] && return 1 + return 0 +} + + +# +# Check if the given queue param is valid +# +qparams_check() { + local param='' + param=$(echo "${1}" | awk -F"${p_delim}" '{print $1}') + param=${1%%${p_delim}*} + local final_param='' + final_param=${param##*,} + + for i in $(seq 1 10) + do + local token1=${param%%,*} + local token2=${token1%=*} + [[ "x${token2}" = "x" ]] && return 1 + [[ "${q_params["${token2}"]:-missing}" = "missing" ]] && return 1 + [[ "x${token1}" = "x${final_param}" ]] && break + param=${param#*,} + done + return 0 +} + + +# +# Calculate the number of jobs to execute based on the number of Flux instances +# being used at a level and the rank of the instance amongst its siblings. +# +get_my_njobs(){ + local njobs="${1}" + local size="${2}" # rank starts from 1 + local rank="${3}" + echo $(( njobs / size + (size + njobs % size)/(size + rank) )) +} + + +# +# Calculate the total number of cores that will be assigned to a child +# Flux instance based on the total number of nodes and cores per node +# assigned to the current Flux instance as well as the size and rank parameter. +# +get_my_cores(){ + local nnodes="${1}" + local ncores="${2}" + local size="${3}" + local rank="${4}" + local t_cores=$(( nnodes * ncores )) + echo $(( t_cores / size + (size + t_cores % size) / (size + rank) )) +} + + +# +# Calculate the total number of GPUs that will be assigned to a child +# Flux instance based on the total number of nodes and GPUs per node +# assigned to the current Flux instance as well as the size and rank parameter. +# +get_my_gpus(){ + local nnodes="${1}" + local ngpus="${2}" + local size="${3}" + local rank="${4}" + local t_gpus=$(( nnodes * ngpus )) + echo $(( t_gpus / size + (size + t_gpus % size) / (size + rank) )) +} + + +# +# Adjust the number of Flux instances to spawn at the next level +# if the amount of resources managed by the parent instance is small. +# +get_effective_size(){ + local ncores="${1}" + local ngpus="${2}" + local size="${3}" + [[ ${ngpus} -ne 0 && ${ngpus} -lt ${size} ]] && size=${ngpus} + [[ ${ncores} -lt ${size} ]] && size=${ncores} + echo "${size}" +} + + +# +# Calculate the total number of nodes that will be assigned to a child +# Flux instance based on the total number of cores per node as well as +# the total number of cores assigned to this child instance. Returns +# minimum num of nodes required. +# +get_my_nodes(){ + local ncores="${1}" + local m_cores="${2}" + echo $(( m_cores / ncores + (ncores + m_cores % ncores) / (ncores + 1 ))) +} + + +# +# Apply all of the policies for the target Flux instance +# by setting environment variables. +# +apply_policies() { + local queue_policy="${1%%${p_delim}*}" + local queue_param="${2%%${p_delim}*}" + local match_policy="${3%%${p_delim}*}" + + ORIG_FLUXION_QMANAGER_OPTIONS=${FLUXION_QMANAGER_OPTIONS:-none} + ORIG_FLUXION_RESOURCE_OPTIONS=${FLUXION_RESOURCE_OPTIONS:-none} + ORIG_FLUXION_QMANAGER_RC_NOOP=${FLUXION_QMANAGER_RC_NOOP:-none} + ORIG_FLUXION_RESOURCE_RC_NOOP=${FLUXION_RESOURCE_RC_NOOP:-none} + unset FLUXION_QMANAGER_RC_NOOP + unset FLUXION_RESOURCE_RC_NOOP + + if [[ "${queue_policy}" != "default" ]] + then + export FLUXION_QMANAGER_OPTIONS="queue-policy=${queue_policy}" + fi + if [[ "${queue_param}" != "default" ]] + then + local qo="${FLUXION_QMANAGER_OPTIONS}" + export FLUXION_QMANAGER_OPTIONS="${qo:+${qo},}queue-params=${queue_param}" + fi + if [[ "${match_policy}" != "default" ]] + then + export FLUXION_RESOURCE_OPTIONS="hwloc-allowlist=node,core,gpu \ +policy=${match_policy}" + fi + if [[ "x${FT_DRY_RUN}" = "xyes" ]] + then + dr_print "FLUXION_QMANAGER_OPTIONS:${FLUXION_QMANAGER_OPTIONS}" + dr_print "FLUXION_RESOURCE_OPTIONS:${FLUXION_RESOURCE_OPTIONS}" + fi +} + + +# +# Undo all of the policies set for the target Flux instance +# by unsetting environment variables. +# +unapply_policies() { + unset FLUXION_QMANAGER_OPTIONS + unset FLUXION_RESOURCE_OPTIONS + + if [ "${ORIG_FLUXION_QMANAGER_OPTIONS}" != "none" ] + then + export FLUXION_QMANAGER_OPTIONS="${ORIG_FLUXION_QMANAGER_OPTIONS}" + fi + if [ "${ORIG_FLUXION_RESOURCE_OPTIONS}" != "none" ] + then + export FLUXION_RESOURCE_OPTIONS="${ORIG_FLUXION_RESOURCE_OPTIONS}" + fi + if [ "${ORIG_FLUXION_QMANAGER_RC_NOOP}" != "none" ] + then + export FLUXION_QMANAGER_RC_NOOP="${ORIG_FLUXION_QMANAGER_RC_NOOP}" + fi + if [ "${ORIG_FLUXION_RESOURCE_RC_NOOP}" != "none" ] + then + export FLUXION_RESOURCE_RC_NOOP="${ORIG_FLUXION_RESOURCE_RC_NOOP}" + fi + if [[ "x${FT_DRY_RUN}" = "xyes" ]] + then + dr_print "FLUXION_QMANAGER_OPTIONS:${FLUXION_QMANAGER_OPTIONS}" + dr_print "FLUXION_RESOURCE_OPTIONS:${FLUXION_RESOURCE_OPTIONS}" + dr_print "FLUXION_QMANAGER_RC_NOOP:${FLUXION_QMANAGER_RC_NOOP}" + dr_print "FLUXION_RESOURCE_RC_NOOP:${FLUXION_RESOURCE_RC_NOOP}" + fi +} + + + +################################################################################ +# # +# Handle Leaf or Internal Flux Instances # +# # +################################################################################ + +# +# Execute the script. Export a predefined set of +# environment variables and execute the given jobscript. +# +execute() { + local prefix="${1}" + local nnodes="${2}" + local ncores="${3}" + local ngpus="${4}" + local njobs="${5}" + local rc=0 + + for job in $(seq 1 "${njobs}"); + do + export FLUX_TREE_ID="${prefix}" + export FLUX_TREE_JOBSCRIPT_INDEX="${job}" + export FLUX_TREE_NNODES="${nnodes}" + export FLUX_TREE_NCORES_PER_NODE="${ncores}" + export FLUX_TREE_NGPUS_PER_NODE="${ngpus}" + + if [[ "x${FT_DRY_RUN}" = "xyes" ]] + then + dr_print "FLUX_TREE_ID=${FLUX_TREE_ID}" + dr_print "FLUX_TREE_JOBSCRIPT_INDEX=${FLUX_TREE_JOBSCRIPT_INDEX}" + dr_print "FLUX_TREE_NCORES_PER_NODE=${FLUX_TREE_NCORES_PER_NODE}" + dr_print "FLUX_TREE_NGPUS_PER_NODE=${FLUX_TREE_NGPUS_PER_NODE}" + dr_print "FLUX_TREE_NNODES=${FLUX_TREE_NNODES}" + dr_print "eval ${FT_CL[@]}" + continue + else + rc=0 + "${FT_CL[@]}" || rc=$? + if [[ ${rc} -gt ${FT_MAX_EXIT_CODE} ]] + then + FT_MAX_EXIT_CODE=${rc} + FT_MAX_TREE_ID="${FLUX_TREE_ID}" + FT_MAX_JOBSCRIPT_IX="${FLUX_TREE_JOBSCRIPT_INDEX}" + fi + fi + done + + [[ "x${FT_DRY_RUN}" = "xno" ]] && flux queue drain + + if [[ "x${FT_MAX_TREE_ID}" != "x" ]] + then + warn "${FT_CL[@]}: exited with exit code (${FT_MAX_EXIT_CODE})" + warn "invocation id: ${FT_MAX_TREE_ID}@index[${FT_MAX_JOBSCRIPT_IX}]" + warn "output displayed above, if any" + fi + + unset FLUX_TREE_ID + unset FLUX_TREE_NNODES + unset FLUX_TREE_NCORES_PER_NODE +} + + +# +# Entry point to execute the job script. When this is invoke, +# the parent Flux instance has already been started. +# Measure the elapse time of the job script execution, and +# dump the performance data. +# +leaf() { + local prefix="${1}" + local nnodes="${2}" + local ncores="${3}" + local ngpus="${4}" + local njobs="${5}" + local perfout="${6}" + local format="${7}" + + # Begin Time Stamp + local B='' + B=$(date +%s.%N) + + execute "$@" + + # End Time Stamp + local E='' + E=$(date +%s.%N) + + local o='' + + o=$(jsonify "${prefix}" "${njobs}" "${nnodes}" "${ncores}" \ +"${ngpus}" "${B}" "${E}") + rollup "${prefix}" "${o}" "${perfout}" "0" "${format}" +} + + +# +# Roll up exit code from child instances +# +rollup_exit_code() { + local rc=0 + for job in "${jobids[@]}" + do + rc=0 + flux job status --exception-exit-code=255 ${job} || rc=$? + if [[ ${rc} -gt ${FT_MAX_EXIT_CODE} ]] + then + FT_MAX_EXIT_CODE=${rc} + FT_MAX_FLUX_JOBID=${job} + fi + done + + if [[ "${FT_MAX_FLUX_JOBID}" != "0" ]] + then + flux job attach ${FT_MAX_FLUX_JOBID} || true + fi +} + +# +# Submit the specified number of Flux instances at the next level of the calling +# instance. Use flux-tree recursively. Instances that have 0 jobs assigned are +# not launched. +# +submit() { + local prefix="${1}" + local nx_topo=$(next_topo "${2}") + local nx_queue=$(next_policy_or_param "${3}") + local nx_q_params=$(next_policy_or_param "${4}") + local nx_match=$(next_policy_or_param "${5}") + local nnodes="${6}" + local ncores="${7}" + local ngpus="${8}" + local size="${9}" + local njobs="${10}" + local log="${11}" + local rdir="${12}" + + # Flux instance rank-agnostic command-line options for the next level + local T="${nx_topo:+--topology=${nx_topo}}" + T="${T:---leaf}" + local Q="${nx_queue:+--queue-policy=${nx_queue}}" + local P="${nx_q_params:+--queue-params=${nx_q_params}}" + local M="${nx_match:+--match-policy=${nx_match}}" + local F='' + [[ "x${log}" != "x%^+_no" ]] && F="--flux-logs=${log}" + local R='' + [[ "x${rdir}" != "x%^+_no" ]] && R="--flux-rundir=${rdir}" + local rank=0 + + # Main Loop to Submit the Next-Level Flux Instances + size=$(get_effective_size "${ncores}" "${ngpus}" "${size}") + apply_policies "${3}" "${4}" "${5}" + for rank in $(seq 1 "${size}"); do + local my_cores=0 + my_cores=$(get_my_cores "${nnodes}" "${ncores}" "${size}" "${rank}") + local my_gpus=0 + my_gpus=$(get_my_gpus "${nnodes}" "${ngpus}" "${size}" "${rank}") + local my_njobs=0 + my_njobs=$(get_my_njobs "${njobs}" "${size}" "${rank}") + + [[ "${my_njobs}" -eq 0 ]] && break + + # Flux instance rank-aware command-line options + local J="--njobs=${my_njobs}" + local o='' + if [[ x"${log}" != "x%^+_no" ]] + then + if [[ "x${FT_DRY_RUN}" != "xyes" ]] + then + mkdir -p "${log}" + fi + o="-o,-Slog-filename=${log}/${prefix}.${rank}.log" + fi + if [[ x"${rdir}" != "x%^+_no" ]] + then + if [[ "x${FT_DRY_RUN}" != "xyes" ]] + then + rm -rf "${rdir}/${prefix}.${rank}.pfs" + mkdir -p "${rdir}/${prefix}.${rank}.pfs" + fi + o="${o:+${o} }-o,-Srundir=${rdir}/${prefix}.${rank}.pfs" + fi + local N=0 + N=$(get_my_nodes "${ncores}" "${my_cores}") + local c=0 + c=$((my_cores/N + (my_cores + my_cores % N)/(my_cores + 1))) + local g=0 + g=$((my_gpus/N + (my_gpus + my_gpus % N)/(my_gpus + 1))) + local G='' + [[ ${g} -gt 0 ]] && G="-g ${g}" + local X="--prefix=${prefix}.${rank}" + + if [[ "x${FT_DRY_RUN}" = "xyes" ]] + then + dr_print "Rank=${rank}: N=${N} c=${c} ${G:+g=${G}} ${o:+o=${o}}" + dr_print "Rank=${rank}: ${T:+T=${T}}" + dr_print "Rank=${rank}: ${Q:+Q=${Q}} ${P:+P=${P}} ${M:+M=${M}}" + dr_print "Rank=${rank}: ${X:+X=${X}} ${J:+J=${J}} ${FT_CL:+S=${FT_CL[@]}}" + dr_print "" + continue + fi + jobid=$(\ +flux submit --job-name=${FT_JOB_NAME} -N${N} -n${N} -c${c} ${G} \ + flux start ${o} \ + flux tree -N${N} -c${c} ${G} ${T} ${Q} ${P} ${M} ${F} ${R} ${X} ${J} \ + -- "${FT_CL[@]}") + jobids["${rank}"]="${jobid}" + done + + [[ "x${FT_DRY_RUN}" = "xno" ]] && flux queue drain && rollup_exit_code + unapply_policies +} + + +# +# Collect the performance record for sibling Flux instances at one level. +# For each child instance, get the performance record from the guest KVS +# namespace, which had all of the records gathered for the subtree rooted +# at this instance, and add that to the current record with its child key. +# +coll_perf() { + local prefix="${1}" + local nnodes="${2}" + local ncores="${3}" + local ngpus="${4}" + local njobs="${5}" + local begin="${6}" + local end="${7}" + local perfout="${8}" + local nchildren="${9}" + local format="${10}" + + # + # Make a JSON string from the performance data + # + local blurb='' + blurb=$(jsonify "${prefix}" "${njobs}" "${nnodes}" "${ncores}" "${ngpus}" "${begin}" "${end}") + rollup "${prefix}" "${blurb}" "${perfout}" "${nchildren}" "${format}" +} + + +# +# Entry point to submit child Flux instances at the next level from the +# calling Flux instance. Measure the elapse time of running all of these +# Flux instances. Collect the performance record for that level at the end. +# +internal() { + local prefix="${1}" + local nnodes="${6}" + local ncores="${7}" + local ngpus="${8}" + local njobs="${10}" + local perfout="${13}" + local format="${14}" + + # Begin Time Stamp + local B='' + B=$(date +%s.%N) + + submit "$@" + + # End Time Stamp + local E='' + E=$(date +%s.%N) + + if [[ "x${FT_DRY_RUN}" = "xyes" ]]; then + nchildren=0 + else + nchildren=${#jobids[@]} + fi + coll_perf "${prefix}" "${nnodes}" "${ncores}" "${ngpus}" \ +"${njobs}" "${B}" "${E}" "${perfout}" "${nchildren}" "${format}" +} + + +################################################################################ +# # +# Main # +# # +################################################################################ + +main() { + local leaf="${1}" # is this a leaf Flux instance? + local prefix="${2}" # id showing hierarchical path of the instance + local topo="${3}" # topology shape at the invoked level + local queue="${4}" # queuing policies at the invoked level and below + local param="${5}" # queue parameters at the invoked level and below + local match="${6}" # match policy shape at the invoked level + local nnodes="${7}" # num of nodes allocated to this instance + local ncores="${8}" # num of cores per node + local ngpus="${9}" # num of gpus per node + local njobs="${10}" # num of jobs assigned to this Flux instance + local flogs="${11}" # flux log output option + local frdir="${12}" # flux rundir attribute + local out="${13}" # perf output filename + local format="${14}" # perf output format + local size=0 + + if [[ ${leaf} = "yes" ]] + then + # + # flux-tree is invoked for a leaf: all of the internal Flux instances + # leading to this leaf have been instantiated and ${script} should + # be executed on the last-level Flux instance. + # + leaf "${prefix}" "${nnodes}" "${ncores}" "${ngpus}" "${njobs}" \ + "${out}" "${format}" + else + # + # flux-tree is invoked to instantiate ${size} internal Flux instances + # at the next level of the calling instance. + # + size=${topo%%${t_delim}*} + internal "${prefix}" "${topo}" "${queue}" "${param}" "${match}" \ + "${nnodes}" "${ncores}" "${ngpus}" "${size}" "${njobs}" \ + "${flogs}" "${frdir}" "${out}" "${format}" + fi + + exit ${FT_MAX_EXIT_CODE} +} + + +################################################################################ +# # +# Commandline Parsing and Validate Options # +# # +################################################################################ + +GETOPTS=$(/usr/bin/getopt -o ${short_opts} -l ${long_opts} -n "${prog}" -- "${@}") +eval set -- "${GETOPTS}" +rcopt=$? + +while true; do + case "${1}" in + -h|--help) echo -ne "${usage}"; exit 0 ;; + -l|--leaf) FT_LEAF="yes"; shift 1 ;; + -d|--dry-run) FT_DRY_RUN="yes"; shift 1 ;; + -f|--flux-logs) FT_FXLOGS="${2}"; shift 2 ;; + -r|--flux-rundir) FT_FXRDIR="${2}"; shift 2 ;; + -N|--nnodes) FT_NNODES=${2}; shift 2 ;; + -c|--ncores-per-node) FT_NCORES=${2}; shift 2 ;; + -g|--ngpus-per-node) FT_NGPUS=${2}; shift 2 ;; + -T|--topology) FT_TOPO="${2}"; FT_INTER="yes"; shift 2 ;; + -Q|--queue-policy) FT_QUEUE="${2}"; FT_INTER="yes"; shift 2 ;; + -P|--queue-params) FT_PARAMS="${2}"; FT_INTER="yes"; shift 2 ;; + -M|--match-policy) FT_MATCH="${2}"; FT_INTER="yes"; shift 2 ;; + -J|--njobs) FT_NJOBS=${2}; shift 2 ;; + -o|--perf-out) FT_PERF_OUT="${2}"; shift 2 ;; + --perf-format) FT_PERF_FORMAT="${2}"; shift 2 ;; + -X|--prefix) FT_G_PREFIX="${2}"; shift 2 ;; + --job-name) FT_JOB_NAME="${2}"; shift 2 ;; + --) shift; break; ;; + *) die "Invalid option '${1}'\n${usage}" ;; + esac +done + +FT_SCRIPT="${1}" +FT_CL=( "${@}" ) + +[[ "$#" -lt 1 || "${rcopt}" -ne 0 ]] && die "${usage}" + +[[ ! -x $(which ${FT_SCRIPT}) ]] && die "cannot execute ${FT_SCRIPT}!" + +[[ "${FT_NNODES}" -le 0 ]] && die "nnodes must be greater than 0!" + +[[ "${FT_NCORES}" -le 0 ]] && die "ncores must be greater than 0!" + +[[ "${FT_NGPUS}" -lt 0 ]] && die "incorrect ngpus!" + +qpolicy_check "${FT_QUEUE}" || die "invalid queue policy!" + +mpolicy_check "${FT_MATCH}" || die "invalid match policy!" + +qparams_check "${FT_PARAMS}" || die "invalid queue params!" + +if [[ "${FT_INTER}" = "yes" && "${FT_LEAF}" = "yes" ]] +then + die "--leaf must not be used together with internal tree-node options!" +fi + +# if the user did not set a name, then use a partially random string to prevent +# conflicts with other flux-tree instances during performance data collection +# via flux-tree-helper +if [[ "$FT_JOB_NAME" == '%^+_no' ]]; then + # code copied from: + # https://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string + FT_JOB_NAME="flux-tree-$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)" +fi + + +################################################################################ +# # +# Invoke the Main Entry Level # +# # +################################################################################ + +main "${FT_LEAF}" "${FT_G_PREFIX}" "${FT_TOPO}" "${FT_QUEUE}" "${FT_PARAMS}" \ + "${FT_MATCH}" "${FT_NNODES}" "${FT_NCORES}" "${FT_NGPUS}" "${FT_NJOBS}" \ + "${FT_FXLOGS}" "${FT_FXRDIR}" "${FT_PERF_OUT}" "${FT_PERF_FORMAT}" + +# +# vi:tabstop=4 shiftwidth=4 expandtab +# diff --git a/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree-helper.py b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree-helper.py new file mode 100644 index 0000000..eba17d5 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/flux-tree/flux-tree-helper.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 + +############################################################## +# Copyright 2020 Lawrence Livermore National Security, LLC +# (c.f. AUTHORS, NOTICE.LLNS, LICENSE) +# +# This file is part of the Flux resource manager framework. +# For details, see https://github.com/flux-framework. +# +# SPDX-License-Identifier: LGPL-3.0 +############################################################## + +import os +import sys +import time +import json +import argparse +import logging + +import flux +import flux.util +import flux.kvs +import flux.job + +LOGGER = logging.getLogger("flux-tree-helper") + + +def get_child_jobids(flux_handle, num_children, child_name): + """ + Get the jobids of num_children instances. Will repeatedly query the + job-info module until num_children jobids are collected, with sleeps + inbetween queries. + """ + jobids = set() + since = 0.0 + LOGGER.debug("Getting IDs of inactive children with name == %s", child_name) + while True: + for job in flux.job.job_list_inactive( + flux_handle, + max_entries=num_children, + since=since, + attrs=["t_inactive"], + name=child_name, + ).get_jobs(): + jobid = job["id"] + since = max(since, job["t_inactive"]) + jobids.add(jobid) + if len(jobids) >= num_children: + break + LOGGER.debug( + "Only %d out of %d children are inactive, sleeping before trying again", + len(jobids), + num_children, + ) + time.sleep(1) + return jobids + + +def get_this_instance_data(): + data = json.load(sys.stdin) + return data + + +def get_child_data(flux_handle, num_children, child_name, kvs_key): + child_data = [] + jobids = get_child_jobids(flux_handle, num_children, child_name) + for jobid in jobids: + kvs_dir = flux.job.job_kvs_guest(flux_handle, jobid) + child_data.append(kvs_dir[kvs_key]) + return child_data + + +def combine_data(this_instance_data, child_data): + this_instance_data["child"] = child_data + return this_instance_data + + +class PerfOutputFormat(flux.util.OutputFormat): + """ + Store a parsed version of the program's output format, + allowing the fields to iterated without modifiers, building + a new format suitable for headers display, etc... + """ + + # List of legal format fields and their header names + headings = dict( + treeid="TreeID", + elapse="Elapsed(sec)", + begin="Begin(Epoch)", + end="End(Epoch)", + match="Match(usec)", + njobs="NJobs", + my_nodes="NNodes", + my_cores="CPN", + my_gpus="GPN", + ) + + def __init__(self, fmt): + """ + Parse the input format fmt with string.Formatter. + Save off the fields and list of format tokens for later use, + (converting None to "" in the process) + + Throws an exception if any format fields do not match the allowed + list of headings above. + """ + # Support both new and old style OutputFormat constructor: + try: + super().__init__(fmt, headings=self.headings, prepend="") + except TypeError: + super().__init__(PerfOutputFormat.headings, fmt) + + +def write_data_to_file(output_filename, output_format, data): + def json_traverser(data): + fieldnames = PerfOutputFormat.headings.keys() + output = {k: v for k, v in data.items() if k in fieldnames} + output.update(data["perf"]) + yield output + for child in data["child"]: + yield from json_traverser(child) + + formatter = PerfOutputFormat(output_format) + with open(output_filename, "w") as outfile: + header = formatter.header() + "\n" + outfile.write(header) + fmt = formatter.get_format() + "\n" + for data_row in json_traverser(data): + # newline = formatter.format(data_row) + newline = fmt.format(**data_row) + outfile.write(newline) + + +def write_data_to_parent(flux_handle, kvs_key, data): + try: + parent_uri = flux_handle.flux_attr_get("parent-uri") + except FileNotFoundError: + return + parent_handle = flux.Flux(parent_uri) + + try: + parent_kvs_namespace = flux_handle.flux_attr_get("parent-kvs-namespace").decode( + "utf-8" + ) + except FileNotFoundError: + return + env_name = "FLUX_KVS_NAMESPACE" + os.environ[env_name] = parent_kvs_namespace + + flux.kvs.put(parent_handle, kvs_key, data) + flux.kvs.commit(parent_handle) + + +def parse_args(): + parser = argparse.ArgumentParser( + prog="flux-tree-helper", formatter_class=flux.util.help_formatter() + ) + parser.add_argument( + "num_children", + type=int, + help="number of children to collect data from. Should be 0 at leaves.", + ) + parser.add_argument( + "kvs_key", type=str, help="key to use when propagating data up through the tree" + ) + parser.add_argument( + "job_name", + type=str, + help="name of the child jobs to use when filtering the inactive jobs", + ) + parser.add_argument( + "--perf-out", + type=str, + help="Dump the performance data into the given file. " + "Assumed to be given at the root instance.", + ) + parser.add_argument( + "--perf-format", + type=str, + help="Dump the performance data with the given format string.", + ) + return parser.parse_args() + + +@flux.util.CLIMain(LOGGER) +def main(): + args = parse_args() + flux_handle = None + try: + flux_handle = flux.Flux() + except FileNotFoundError: + flux_handle = None + + LOGGER.debug("Getting this instance's data") + this_data = get_this_instance_data() + if flux_handle is not None and args.num_children > 0: + LOGGER.debug("Getting children's data") + child_data = get_child_data( + flux_handle, args.num_children, args.job_name, args.kvs_key + ) + else: + child_data = [] + LOGGER.debug("Combining data") + combined_data = combine_data(this_data, child_data) + if flux_handle is not None: + LOGGER.debug("Writing data to parent's KVS") + write_data_to_parent(flux_handle, args.kvs_key, combined_data) + if args.perf_out: + LOGGER.debug("Writing data to file") + write_data_to_file(args.perf_out, args.perf_format, combined_data) + + +if __name__ == "__main__": + main() diff --git a/2024-RADIUSS-AWS/JupyterNotebook/gcp/config.yaml b/2024-RADIUSS-AWS/JupyterNotebook/gcp/config.yaml new file mode 100644 index 0000000..a91987a --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/gcp/config.yaml @@ -0,0 +1,62 @@ +# A few notes! +# The hub -> authentic class defaults to "dummy" +# We shouldn't need any image pull secrets assuming public +# There is a note about the database being a sqlite pvc +# (and a TODO for better solution for Kubernetes) + +# This is the concurrent spawn limit, likely should be increased (deafults to 64) +hub: + concurrentSpawnLimit: 10 + config: + DummyAuthenticator: + password: butter + JupyterHub: + admin_access: true + authenticator_class: dummy + + # This is the image I built based off of jupyterhub/k8s-hub, 3.0.2 at time of writing this + image: + name: ghcr.io/flux-framework/flux-jupyter-hub + tag: "radiuss-2024" + pullPolicy: Always + +# https://z2jh.jupyter.org/en/latest/administrator/optimization.html#scaling-up-in-time-user-placeholders +scheduling: + podPriority: + enabled: true + userPlaceholder: + # Specify 3 dummy user pods will be used as placeholders + replicas: 3 + +# This is the "spawn" image +singleuser: + image: + name: ghcr.io/flux-framework/flux-jupyter-spawn + tag: "radiuss-2024" + pullPolicy: Always + cpu: + limit: 1 + memory: + limit: '4G' + cmd: /entrypoint.sh + +# initContainers: +# - name: init-myservice +# image: alpine/git +# command: ["git", "clone", "https://github.com/rse-ops/flux-radiuss-tutorial-2023", "/home/jovyan/flux-tutorial"] +# volumeMounts: +# - name: flux-tutorial +# mountPath: /home/jovyan + + # This is how we get the tutorial files added + storage: + type: none + + # gitRepo volume is deprecated so we need another way + # https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo + extraVolumes: + - name: flux-tutorial + emptyDir: {} + extraVolumeMounts: + - name: flux-tutorial + mountPath: /home/jovyan/ diff --git a/2024-RADIUSS-AWS/JupyterNotebook/requirements.txt b/2024-RADIUSS-AWS/JupyterNotebook/requirements.txt new file mode 100644 index 0000000..0d9d99e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/requirements.txt @@ -0,0 +1,339 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# Use the "Run workflow" button at https://github.com/jupyterhub/zero-to-jupyterhub-k8s/actions/workflows/watch-dependencies.yaml +# +alembic==1.11.3 + # via jupyterhub +anyio==3.7.1 + # via jupyter-server +argon2-cffi==23.1.0 + # via + # jupyter-server + # nbclassic +argon2-cffi-bindings==21.2.0 + # via argon2-cffi +arrow==1.2.3 + # via isoduration +asttokens==2.2.1 + # via stack-data +async-generator==1.10 + # via jupyterhub +async-lru==2.0.4 + # via jupyterlab +attrs==23.1.0 + # via + # jsonschema + # referencing +babel==2.12.1 + # via jupyterlab-server +backcall==0.2.0 + # via ipython +beautifulsoup4==4.12.2 + # via nbconvert +bleach==6.0.0 + # via nbconvert +certifi==2023.7.22 + # via requests +certipy==0.1.3 + # via jupyterhub +cffi==1.15.1 + # via + # argon2-cffi-bindings + # cryptography +charset-normalizer==3.2.0 + # via requests +comm==0.1.4 + # via ipykernel +cryptography==41.0.3 + # via pyopenssl +debugpy==1.6.7.post1 + # via ipykernel +decorator==5.1.1 + # via ipython +defusedxml==0.7.1 + # via nbconvert +executing==1.2.0 + # via stack-data +fastjsonschema==2.18.0 + # via nbformat +fqdn==1.5.1 + # via jsonschema +greenlet==2.0.2 + # via sqlalchemy +idna==3.4 + # via + # anyio + # jsonschema + # requests +ipykernel==6.25.1 + # via + # jupyterlab + # nbclassic +ipython==8.13.0 + # via ipykernel +ipython-genutils==0.2.0 + # via nbclassic +isoduration==20.11.0 + # via jsonschema +jedi==0.19.0 + # via ipython +jinja2==3.1.2 + # via + # jupyter-server + # jupyterhub + # jupyterlab + # jupyterlab-server + # nbclassic + # nbconvert +json5==0.9.14 + # via jupyterlab-server +jsonpointer==2.4 + # via jsonschema +jsonschema[format-nongpl]==4.19.0 + # via + # jupyter-events + # jupyter-telemetry + # jupyterlab-server + # nbformat +jsonschema-specifications==2023.7.1 + # via jsonschema +jupyter-client==8.3.0 + # via + # ipykernel + # jupyter-server + # nbclassic + # nbclient +jupyter-core==5.3.1 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # nbclassic + # nbclient + # nbconvert + # nbformat +jupyter-events==0.7.0 + # via jupyter-server +jupyter-lsp==2.2.0 + # via jupyterlab +jupyter-server==2.7.2 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # nbclassic + # nbgitpuller + # notebook-shim +jupyter-server-terminals==0.4.4 + # via jupyter-server +jupyter-telemetry==0.1.0 + # via jupyterhub +jupyterhub==4.0.2 + # via -r requirements.in +jupyterlab==4.0.5 + # via -r requirements.in +jupyterlab-pygments==0.2.2 + # via nbconvert +jupyterlab-server==2.24.0 + # via jupyterlab +mako==1.2.4 + # via alembic +markupsafe==2.1.3 + # via + # jinja2 + # mako + # nbconvert +matplotlib-inline==0.1.6 + # via + # ipykernel + # ipython +mistune==3.0.1 + # via nbconvert +nbclassic==1.0.0 + # via -r requirements.in +nbclient==0.8.0 + # via nbconvert +nbconvert==7.7.4 + # via + # jupyter-server + # nbclassic +nbformat==5.9.2 + # via + # jupyter-server + # nbclassic + # nbclient + # nbconvert +nbgitpuller==1.2.0 + # via -r requirements.in +nest-asyncio==1.5.7 + # via + # ipykernel + # nbclassic +notebook-shim==0.2.3 + # via + # jupyterlab + # nbclassic +oauthlib==3.2.2 + # via jupyterhub +overrides==7.4.0 + # via jupyter-server +packaging==23.1 + # via + # ipykernel + # jupyter-server + # jupyterhub + # jupyterlab + # jupyterlab-server + # nbconvert +pamela==1.1.0 + # via jupyterhub +pandocfilters==1.5.0 + # via nbconvert +parso==0.8.3 + # via jedi +pexpect==4.8.0 + # via ipython +pickleshare==0.7.5 + # via ipython +platformdirs==3.10.0 + # via jupyter-core +prometheus-client==0.17.1 + # via + # jupyter-server + # jupyterhub + # nbclassic +prompt-toolkit==3.0.39 + # via ipython +psutil==5.9.5 + # via ipykernel +ptyprocess==0.7.0 + # via + # pexpect + # terminado +pure-eval==0.2.2 + # via stack-data +pycparser==2.21 + # via cffi +pygments==2.16.1 + # via + # ipython + # nbconvert +pyopenssl==23.2.0 + # via certipy +python-dateutil==2.8.2 + # via + # arrow + # jupyter-client + # jupyterhub +python-json-logger==2.0.7 + # via + # jupyter-events + # jupyter-telemetry +pyyaml==6.0.1 + # via jupyter-events +pyzmq==25.1.1 + # via + # ipykernel + # jupyter-client + # jupyter-server + # nbclassic +referencing==0.30.2 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +requests==2.31.0 + # via + # jupyterhub + # jupyterlab-server +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rpds-py==0.9.2 + # via + # jsonschema + # referencing +ruamel-yaml==0.17.32 + # via jupyter-telemetry +ruamel-yaml-clib==0.2.7 + # via ruamel-yaml +send2trash==1.8.2 + # via + # jupyter-server + # nbclassic +six==1.16.0 + # via + # asttokens + # bleach + # python-dateutil + # rfc3339-validator +sniffio==1.3.0 + # via anyio +soupsieve==2.4.1 + # via beautifulsoup4 +sqlalchemy==2.0.20 + # via + # alembic + # jupyterhub +stack-data==0.6.2 + # via ipython +terminado==0.17.1 + # via + # jupyter-server + # jupyter-server-terminals + # nbclassic +tinycss2==1.2.1 + # via nbconvert +tornado==6.3.3 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterhub + # jupyterlab + # nbclassic + # nbgitpuller + # terminado +traitlets==5.9.0 + # via + # comm + # ipykernel + # ipython + # jupyter-client + # jupyter-core + # jupyter-events + # jupyter-server + # jupyter-telemetry + # jupyterhub + # jupyterlab + # matplotlib-inline + # nbclassic + # nbclient + # nbconvert + # nbformat +typing-extensions==4.7.1 + # via + # alembic + # sqlalchemy +uri-template==1.3.0 + # via jsonschema +urllib3==2.0.4 + # via requests +wcwidth==0.2.6 + # via prompt-toolkit +webcolors==1.13 + # via jsonschema +webencodings==0.5.1 + # via + # bleach + # tinycss2 +websocket-client==1.6.1 + # via jupyter-server diff --git a/2024-RADIUSS-AWS/JupyterNotebook/requirements_venv.txt b/2024-RADIUSS-AWS/JupyterNotebook/requirements_venv.txt new file mode 100644 index 0000000..01cea55 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/requirements_venv.txt @@ -0,0 +1,9 @@ +# Used for the DYAD notebook +Pygments +build +ipykernel +jsonschema +cffi +ply +pyyaml +dlio_benchmark @ git+https://github.com/argonne-lcf/dlio_benchmark.git \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/.gitignore b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/.gitignore new file mode 100644 index 0000000..3ebb2aa --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/.gitignore @@ -0,0 +1,2 @@ +flux*.out +.ipynb_checkpoints diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml new file mode 100644 index 0000000..27641a3 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml @@ -0,0 +1,35 @@ +model: unet3d + +framework: pytorch + +workflow: + generate_data: False + train: True + checkpoint: False + +dataset: + data_folder: data/unet3d/ + format: npz + num_files_train: 16 + num_samples_per_file: 1 + record_length: 4096 + +reader: + data_loader: pytorch + batch_size: 1 + read_threads: 1 + file_shuffle: seed + sample_shuffle: seed + multiprocessing_context: spawn + data_loader_classname: dyad_torch_data_loader.DyadTorchDataLoader + data_loader_sampler: index + +train: + epochs: 1 + computation_time: 1 + +checkpoint: + checkpoint_folder: checkpoints/unet3d + checkpoint_after_epoch: 5 + epochs_between_checkpoints: 2 + model_size: 499153191 \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py new file mode 100644 index 0000000..21f652a --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py @@ -0,0 +1,184 @@ +""" + Copyright (c) 2022, UChicago Argonne, LLC + All Rights Reserved + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +""" +from time import time +import math +import pickle +import torch +from torch.utils.data import Dataset, DataLoader, RandomSampler, SequentialSampler + +from dlio_benchmark.common.constants import MODULE_DATA_LOADER +from dlio_benchmark.common.enumerations import Shuffle, DatasetType, DataLoaderType +from dlio_benchmark.data_loader.base_data_loader import BaseDataLoader +from dlio_benchmark.reader.reader_factory import ReaderFactory +from dlio_benchmark.utils.utility import utcnow, DLIOMPI +from dlio_benchmark.utils.config import ConfigArguments + +from pydyad import Dyad, dyad_open +from pydyad.bindings import DTLMode, DTLCommMode +import numpy as np +import flux +import os + + + +class DYADTorchDataset(Dataset): + """ + Currently, we only support loading one sample per file + TODO: support multiple samples per file + """ + def __init__(self, format_type, dataset_type, epoch, num_samples, num_workers, batch_size): + self.format_type = format_type + self.dataset_type = dataset_type + self.epoch_number = epoch + self.num_samples = num_samples + self.reader = None + self.num_images_read = 0 + self.batch_size = batch_size + args = ConfigArguments.get_instance() + self.serial_args = pickle.dumps(args) + if num_workers == 0: + self.worker_init(-1) + self.broker_per_node = 1 + + def worker_init(self, worker_id): + # Configure PyTorch components + pickle.loads(self.serial_args) + self._args = ConfigArguments.get_instance() + self._args.configure_dlio_logging(is_child=True) + self.reader = ReaderFactory.get_reader(type=self.format_type, + dataset_type=self.dataset_type, + thread_index=worker_id, + epoch_number=self.epoch_number) + # Start initializing DYAD + # Create Dyad object to interact with DYAD's C internals + self.dyad_io = Dyad() + # Create a handle to Flux and get the rank of the broker that this process is running on + self.f = flux.Flux() + self.broker_rank = self.f.get_rank() + # Obtain DYAD's managed directory from the DYAD_PATH environment variable + self.dyad_managed_directory = os.getenv("DYAD_PATH", "") + # Get the DTL mode (UCX or FLUX_RPC) from the DYAD_DTL_MODE environment variable + dtl_str = os.getenv("DYAD_DTL_MODE", "FLUX_RPC") + mode = DTLMode.DYAD_DTL_FLUX_RPC + if dtl_str == "UCX": + mode = DTLMode.DYAD_DTL_UCX + # Initialize DYAD + self.dyad_io.init(debug=self._args.debug, check=False, shared_storage=False, reinit=False, + async_publish=True, fsync_write=False, key_depth=3, + service_mux=self.broker_per_node, + key_bins=1024, kvs_namespace=os.getenv("DYAD_KVS_NAMESPACE"), + prod_managed_path=self.dyad_managed_directory, cons_managed_path=self.dyad_managed_directory, + dtl_mode=mode, dtl_comm_mode=DTLCommMode.DYAD_COMM_RECV) + + def __len__(self): + return self.num_samples + + def __getitem__(self, image_idx): + # For the requested sample (indicated by image_idx), determine the file + # containing the sample and the index of the sample within that file + self.num_images_read += 1 + step = int(math.ceil(self.num_images_read / self.batch_size)) + filename, sample_index = self._args.global_index_map[image_idx] + is_present = False + file_obj = None + base_fname = filename + # Use DYAD's `get_metadata` function to check if the file has already been cached + # into DYAD + if self.dyad_managed_directory != "": + base_fname = os.path.join(self.dyad_managed_directory, os.path.basename(filename)) + file_obj = self.dyad_io.get_metadata(fname=base_fname, should_wait=False, raw=True) + is_present = True + # If the file has already been cached in DYAD, use `dyad_open` to open the file. + # Then, pass the Python File object returned from `dyad_open` to NumPy to read data. + if file_obj: + access_mode = "remote" + file_node_index = int(file_obj.contents.owner_rank*1.0 / self.broker_per_node) + with dyad_open(base_fname, "rb", dyad_ctx=self.dyad_io, metadata_wrapper=file_obj) as f: + try: + data = np.load(f, allow_pickle=True)["x"] + except: + data = self._args.resized_image + self.dyad_io.free_metadata(file_obj) + # If the file has not been cached in DYAD, first read the file using a DLIO reader. + # Then, write the data into the DYAD managed directory using `dyad_open`. + else: + data = self.reader.read_index(image_idx, step) + if is_present: + with dyad_open(base_fname, "wb", dyad_ctx=self.dyad_io) as f: + np.savez(f, x=data) + + return data + +class DyadTorchDataLoader(BaseDataLoader): + def __init__(self, format_type, dataset_type, epoch_number): + super().__init__(format_type, dataset_type, epoch_number, DataLoaderType.PYTORCH) + + def read(self): + do_shuffle = True if self._args.sample_shuffle != Shuffle.OFF else False + dataset = DYADTorchDataset(self.format_type, self.dataset_type, self.epoch_number, self.num_samples, self._args.read_threads, self.batch_size) + if do_shuffle: + sampler = RandomSampler(dataset) + else: + sampler = SequentialSampler(dataset) + if self._args.read_threads >= 1: + prefetch_factor = math.ceil(self._args.prefetch_size / self._args.read_threads) + else: + prefetch_factor = self._args.prefetch_size + if prefetch_factor > 0: + if self._args.my_rank == 0: + else: + prefetch_factor = 2 + if self._args.my_rank == 0: + logging.debug(f"{utcnow()} Setup dataloader with {self._args.read_threads} workers {torch.__version__}") + if self._args.read_threads==0: + kwargs={} + else: + kwargs={'multiprocessing_context':self._args.multiprocessing_context, + 'prefetch_factor': prefetch_factor} + if torch.__version__ != '1.3.1': + kwargs['persistent_workers'] = True + if torch.__version__ == '1.3.1': + if 'prefetch_factor' in kwargs: + del kwargs['prefetch_factor'] + self._dataset = DataLoader(dataset, + batch_size=self.batch_size, + sampler=sampler, + num_workers=self._args.read_threads, + pin_memory=True, + drop_last=True, + worker_init_fn=dataset.worker_init, + **kwargs) + else: + self._dataset = DataLoader(dataset, + batch_size=self.batch_size, + sampler=sampler, + num_workers=self._args.read_threads, + pin_memory=True, + drop_last=True, + worker_init_fn=dataset.worker_init, + **kwargs) # 2 is the default value + + # self._dataset.sampler.set_epoch(epoch_number) + + def next(self): + super().next() + total = self._args.training_steps if self.dataset_type is DatasetType.TRAIN else self._args.eval_steps + for batch in self._dataset: + yield batch + + def finalize(self): + pass diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.github/workflows/main.yml b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.github/workflows/main.yml new file mode 100644 index 0000000..5d301e8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.github/workflows/main.yml @@ -0,0 +1,33 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +on: [pull_request] +jobs: + check-pr: + name: check formatting + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - run: git fetch origin master + - uses: flux-framework/pr-validator@master + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Lint with flake8 + run: | + pip install flake8 + pip install black + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + black . diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.mergify.yml b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.mergify.yml new file mode 100644 index 0000000..65c6341 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/.mergify.yml @@ -0,0 +1,18 @@ +pull_request_rules: + - name: rebase and merge when passing all checks + conditions: + - base=master + - status-success="check formatting (3.6)" + - status-success="check formatting (3.7)" + - status-success="check formatting (3.8)" + - label="merge-when-passing" + - label!="work-in-progress" + - "approved-reviews-by=@flux-framework/core" + - "#approved-reviews-by>0" + - "#changes-requested-reviews-by=0" + - -title~=^\[*[Ww][Ii][Pp] + actions: + merge: + method: merge + strict: smart + strict_method: rebase diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/Makefile b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/Makefile new file mode 100644 index 0000000..f219905 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/Makefile @@ -0,0 +1,25 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile check spelling $(SCHEMA_DIRS) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +check: spelling + +spelling: + @$(SPHINXBUILD) -W -b spelling "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md new file mode 100644 index 0000000..9208b12 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md @@ -0,0 +1,73 @@ +**WARNING** + +This repository has been archived. It is no longer maintained and it is +likely the examples do not work or are no longer good or suggested +examples. + +Please look elswhere for examples. + +**Flux Workflow Examples** + +The examples contained here demonstrate and explain some simple use-cases with Flux, +and make use of Flux's command-line interface (CLI), Flux's C library, +and the Python and Lua bindings to the C library. + +**Requirements** + +The examples assume that you have installed: + +1. A recent version of Flux + +2. Python 3.6+ + +3. Lua 5.1+ + +**_1. [CLI: Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-cli)_** + +Launch a flux instance and schedule/launch compute and io-forwarding jobs on +separate nodes using the CLI + +**_2. [Python: Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-api)_** + +Schedule/launch compute and io-forwarding jobs on separate nodes using the Python bindings + +**_3. [Python: Job Submit/Wait](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-wait)_** + +Submit jobs and wait for them to complete using the Flux Python bindings + +**_4. [Python: Asynchronous Bulk Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/async-bulk-job-submit)_** + +Asynchronously submit jobspec files from a directory and wait for them to complete in any order + +**_5. [Python: Tracking Job Status and Events](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-status-control)_** + +Submit job bundles, get event updates, and wait until all jobs complete + +**_6. [Python: Job Cancellation](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-cancel)_** + +Cancel a running job + +**_7. [Lua: Use Events](https://github.com/flux-framework/flux-workflow-examples/tree/master/synchronize-events)_** + +Use events to synchronize compute and io-forwarding jobs running on separate +nodes + +**_8. [Python: Simple KVS Example](https://github.com/flux-framework/flux-workflow-examples/tree/master/kvs-python-bindings)_** + +Use KVS Python interfaces to store user data into KVS + +**_9. [CLI/Lua: Job Ensemble Submitted with a New Flux Instance](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-ensemble)_** + +Submit job bundles, print live job events, and exit when all jobs are complete + +**_10. [CLI: Hierarchical Launching](https://github.com/flux-framework/flux-workflow-examples/tree/master/hierarchical-launching)_** + +Launch a large number of sleep 0 jobs + +**_11. [C/Lua: Use a Flux Comms Module](https://github.com/flux-framework/flux-workflow-examples/tree/master/comms-module)_** + +Use a Flux Comms Module to communicate with job elements + +**_12. [C/Python: A Data Conduit Strategy](https://github.com/flux-framework/flux-workflow-examples/tree/master/data-conduit)_** + +Attach to a job that receives OS time data from compute jobs diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md new file mode 100644 index 0000000..719af07 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md @@ -0,0 +1,99 @@ +## Python Asynchronous Bulk Job Submission + +Parts (a) and (b) demonstrate different implementations of the same basic use-case---submitting +large numbers of jobs to Flux. For simplicity, in these examples all of the jobs are identical. + +In part (a), we use the `flux.job.submit_async` and `flux.job.wait` functions to submit jobs and wait for them. +In part (b), we use the `FluxExecutor` class, which offers a higher-level interface. It is important to note that +these two different implementations deal with very different kinds of futures. +The executor's futures fulfill in the background and callbacks added to the futures may +be invoked by different threads; the `submit_async` futures do not fulfill in the background, callbacks are always +invoked by the same thread that added them, and sharing the futures among threads is not supported. + +### Setup - Downloading the Files + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/async-bulk-job-submit +``` + +### Part (a) - Using `submit_async` + +#### Description: Asynchronously submit jobspec files from a directory and wait for them to complete in any order + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Make a **jobs** directory: + +`mkdir jobs` + +3. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +4. Store the jobspec of a `sleep 0` job in the **jobs** directory: + +`flux mini run --dry-run -n1 sleep 0 > jobs/0.json` + +5. Copy the jobspec of **job0** 1024 times to create a directory of 1025 `sleep 0` jobs: + +``for i in `seq 1 1024`; do cp jobs/0.json jobs/${i}.json; done`` + +6. Run the **bulksubmit.py** script and pass all jobspec in the **jobs** directory as an argument with a shell glob `jobs/*.json`: + +`./bulksubmit.py jobs/*.json` + +``` +bulksubmit: Starting... +bulksubmit: submitted 1025 jobs in 3.04s. 337.09job/s +bulksubmit: First job finished in about 3.089s +|██████████████████████████████████████████████████████████| 100.0% (29.4 job/s) +bulksubmit: Ran 1025 jobs in 34.9s. 29.4 job/s +``` + +### Notes to Part (a) + +- `h = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. + +- `job_submit_async(h, jobspec.read(), waitable=True).then(submit_cb)` submits a jobspec, returning a future which will be fulfilled when the submission of this job is complete. + +`.then(submit_cb)`, called on the returned future, will cause our callback `submit_cb()` to be invoked when the submission of this job is complete and a jobid is available. To process job submission RPC responses and invoke callbacks, the flux reactor for handle `h` must be run: + +```python +if h.reactor_run() < 0: + h.fatal_error("reactor start failed") +``` + +The reactor will return automatically when there are no more outstanding RPC responses, i.e., all jobs have been submitted. + +- `job.wait(h)` waits for any job submitted with the `FLUX_JOB_WAITABLE` flag to transition to the **INACTIVE** state. + + +### Part (b) - Using FluxExecutor + +#### Description: Asynchronously submit a single command repeatedly + +If continuing from part (a), skip to step 3. + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Run the **bulksubmit_executor.py** script and pass the command (`/bin/sleep 0` in this example) and the number of times to run it (default is 100): + +`./bulksubmit_executor.py -n200 /bin/sleep 0` + +``` +bulksubmit_executor: submitted 200 jobs in 0.45s. 441.15job/s +bulksubmit_executor: First job finished in about 1.035s +|██████████████████████████████████████████████████████████| 100.0% (24.9 job/s) +bulksubmit_executor: Ran 200 jobs in 8.2s. 24.4 job/s +``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit.py new file mode 100755 index 0000000..c1a2e9a --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import time +import sys +import flux + +from flux import job +from flux import constants + +t0 = time.time() +jobs = [] +label = "bulksubmit" + +# open connection to broker +h = flux.Flux() + + +def log(s): + print(label + ": " + s) + + +def progress(fraction, length=72, suffix=""): + fill = int(round(length * fraction)) + bar = "\u2588" * fill + "-" * (length - fill) + s = "\r|{0}| {1:.1f}% {2}".format(bar, 100 * fraction, suffix) + sys.stdout.write(s) + if fraction == 1.0: + sys.stdout.write("\n") + + +def submit_cb(f): + jobs.append(job.submit_get_id(f)) + + +# asynchronously submit jobspec files from a directory +log("Starting...") +for file in sys.argv[1:]: + with open(file) as jobspec: + job.submit_async(h, jobspec.read(), waitable=True).then(submit_cb) + +if h.reactor_run() < 0: + h.fatal_error("reactor start failed") + +total = len(jobs) +dt = time.time() - t0 +jps = len(jobs) / dt +log("submitted {0} jobs in {1:.2f}s. {2:.2f}job/s".format(total, dt, jps)) + +count = 0 +while count < total: + # wait for jobs to complete in any order + job.wait(h) + count = count + 1 + if count == 1: + log("First job finished in about {0:.3f}s".format(time.time() - t0)) + suffix = "({0:.1f} job/s)".format(count / (time.time() - t0)) + progress(count / total, length=58, suffix=suffix) + +dt = time.time() - t0 +log("Ran {0} jobs in {1:.1f}s. {2:.1f} job/s".format(total, dt, total / dt)) + +# vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py new file mode 100755 index 0000000..5280863 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import time +import sys +import argparse +import concurrent.futures as cf + +from flux.job import FluxExecutor, JobspecV1 + + +def log(label, s): + print(label + ": " + s) + + +def progress(fraction, length=72, suffix=""): + fill = int(round(length * fraction)) + bar = "\u2588" * fill + "-" * (length - fill) + s = f"\r|{bar}| {100 * fraction:.1f}% {suffix}" + sys.stdout.write(s) + if fraction == 1.0: + sys.stdout.write("\n") + + +def main(): + parser = argparse.ArgumentParser( + description="Submit a command repeatedly using FluxExecutor" + ) + parser.add_argument( + "-n", + "--njobs", + type=int, + metavar="N", + help="Set the total number of jobs to run", + default=100, + ) + parser.add_argument("command", nargs=argparse.REMAINDER) + args = parser.parse_args() + if not args.command: + args.command = ["true"] + t0 = time.perf_counter() + label = "bulksubmit_executor" + with FluxExecutor() as executor: + compute_jobspec = JobspecV1.from_command(args.command) + futures = [executor.submit(compute_jobspec) for _ in range(args.njobs)] + # wait for the jobid for each job, as a proxy for the job being submitted + for fut in futures: + fut.jobid() + # all jobs submitted - print timings + dt = time.perf_counter() - t0 + jps = args.njobs / dt + log(label, f"submitted {args.njobs} jobs in {dt:.2f}s. {jps:.2f}job/s") + # wait for jobs to complete + for i, _ in enumerate(cf.as_completed(futures)): + if i == 0: + log( + label, + f"First job finished in about {time.perf_counter() - t0:.3f}s", + ) + jps = (i + 1) / (time.perf_counter() - t0) + progress((i + 1) / args.njobs, length=58, suffix=f"({jps:.1f} job/s)") + # print time summary + dt = time.perf_counter() - t0 + log(label, f"Ran {args.njobs} jobs in {dt:.1f}s. {args.njobs / dt:.1f} job/s") + + +if __name__ == "__main__": + main() diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/Makefile b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/Makefile new file mode 100644 index 0000000..ccc018d --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/Makefile @@ -0,0 +1,19 @@ +all: capp.so ioapp.so + +FLUX_CORE_LIBS = $(shell pkg-config --libs flux-core) +FLUX_CORE_INCLUDES = $(shell pkg-config --cflags flux-core) + +ioapp.so: ioapp.o + gcc -Wl,--no-undefined --disable-static -shared -export-dynamic $^ -o $@ $(FLUX_CORE_LIBS) + +ioapp.o: app.c + gcc $(FLUX_CORE_INCLUDES) $^ -DIO_SERVICE=1 -fPIC -c -o $@ + +capp.so: capp.o + gcc -Wl,--no-undefined --disable-static -shared -export-dynamic $^ -o $@ $(FLUX_CORE_LIBS) + +capp.o: app.c + gcc $(FLUX_CORE_INCLUDES) $^ -DCOMP_SERVICE=1 -fPIC -c -o $@ + +clean: + rm *.o *.so diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md new file mode 100644 index 0000000..3acdc5c --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md @@ -0,0 +1,36 @@ +### Using a Flux Comms Module + +#### Description: Use a Flux comms module to communicate with job elements + +##### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/comms-module +``` + +##### Execution + +1. `salloc -N3 -ppdebug` + +2. Point to `flux-core`'s `pkgconfig` directory: + +| Shell | Command | +| ----- | ---------- | +| tcsh | `setenv PKG_CONFIG_PATH /lib/pkgconfig` | +| bash/zsh | `export PKG_CONFIG_PATH='/lib/pkgconfig'` | + +3. `make` + +4. Add the directory of the modules to `FLUX_MODULE_PATH`; if the module was +built in the current dir: + +`export FLUX_MODULE_PATH=${FLUX_MODULE_PATH}:$(pwd)` + +5. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +6. `flux submit -N 2 -n 2 ./compute.lua 120` + +7. `flux submit -N 1 -n 1 ./io-forwarding.lua 120` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/app.c b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/app.c new file mode 100644 index 0000000..336ecfb --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/app.c @@ -0,0 +1,129 @@ +#include +#include +#include + +#if !defined (IO_SERVICE) && !defined (COMP_SERVICE) +# error "Either IO_SERVICE or COMP_SERVICE macro is needed" +#endif + +struct app_ctx { + flux_t *h; + int count; + flux_msg_handler_t **handlers; +}; + +static void freectx (void *arg) +{ + struct app_ctx *ctx = (struct app_ctx *)arg; + flux_msg_handler_delvec (ctx->handlers); + free (ctx); +} + +static struct app_ctx *getctx (flux_t *h) +{ +#if IO_SERVICE + struct app_ctx *ctx = flux_aux_get (h, "ioapp"); +#elif COMP_SERVICE + struct app_ctx *ctx = flux_aux_get (h, "capp"); +#endif + if (!ctx) { + ctx = malloc (sizeof (*ctx)); + ctx->count = 0; + ctx->handlers = NULL; +#if IO_SERVICE + flux_aux_set (h, "ioapp", ctx, freectx); +#elif COMP_SERVICE + flux_aux_set (h, "capp", ctx, freectx); +#endif + } + return ctx; +} + +#if IO_SERVICE +static void io_request_cb (flux_t *h, flux_msg_handler_t *w, + const flux_msg_t *msg, void *arg) +{ + const char *topic = NULL; + struct app_ctx *ctx = getctx (h); + int data = 0; + + if (flux_request_unpack (msg, &topic, "{s:i}", "data", &data)) + goto error; + ctx->count++; + if (flux_respond_pack (h, msg, "{s:i}", "count", ctx->count) < 0) + flux_log_error (h, "%s", __FUNCTION__); + flux_log (h, LOG_DEBUG, "count: %d", ctx->count); + return; + +error: + flux_log_error (h, "%s", __FUNCTION__); + if (flux_respond (h, msg, NULL) < 0) + flux_log_error (h, "%s: flux_respond", __FUNCTION__); +} +#endif + +#if COMP_SERVICE +static void comp_request_cb (flux_t *h, flux_msg_handler_t *w, + const flux_msg_t *msg, void *arg) +{ + const char *topic = NULL; + struct app_ctx *ctx = getctx (h); + int data = 0; + + flux_log (h, LOG_INFO, "comp_request_cb:"); + if (flux_request_unpack (msg, &topic, "{s:i}", "data", &data)) + goto error; + + ctx->count++; + if (flux_respond_pack (h, msg, "{s:i}", "count", ctx->count) < 0) + flux_log_error (h, "%s", __FUNCTION__); + return; + +error: + flux_log_error (h, "%s", __FUNCTION__); + if (flux_respond (h, msg, NULL) < 0) + flux_log_error (h, "%s: flux_respond", __FUNCTION__); +} +#endif + +static struct flux_msg_handler_spec htab[] = { +#if IO_SERVICE + { FLUX_MSGTYPE_REQUEST, "ioapp.io", io_request_cb, 0 }, +#endif + +#if COMP_SERVICE + { FLUX_MSGTYPE_REQUEST, "capp.comp", comp_request_cb, 0 }, +#endif + + FLUX_MSGHANDLER_TABLE_END +}; + + +int mod_main (flux_t *h, int argc, char **argv) +{ + + struct app_ctx *ctx = getctx (h); + if (flux_msg_handler_addvec (h, htab, (void *)h, + &ctx->handlers) < 0) { + flux_log (ctx->h, LOG_ERR, "flux_msg_handler_addvec: %s", strerror (errno)); + goto done; + } + + if (flux_reactor_run (flux_get_reactor (h), 0) < 0) { + flux_log (h, LOG_ERR, "flux_reactor_run: %s", strerror (errno)); + goto done; + } + +done: + return 0; +} + +#if IO_SERVICE +MOD_NAME ("ioapp"); +#elif COMP_SERVICE +MOD_NAME ("capp"); +#endif + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/compute.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/compute.lua new file mode 100755 index 0000000..f505f54 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/compute.lua @@ -0,0 +1,62 @@ +#!/usr/bin/env lua + +local f, err = require 'flux' .new () +local amount = tonumber (arg[1]) or 120 +local rank = tonumber (os.getenv('FLUX_TASK_RANK')) or 0 +local frank = tonumber (os.getenv('FLUX_LOCAL_RANKS')) or 0 +io.stdout:setvbuf ("no") + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: compute.lua seconds") + print (" Compute for seconds") + os.exit (1) +end + +-- subscribe app.io.go event +local rc, err = f:subscribe ("app.io.go") +if not rc then + print ("Failed to subscribe an event, %s", err) + os.exit (1) +end + +-- the leader rank of compute job installs app module +if rank == 0 then + os.execute ("flux module load -r " .. 0 .. " capp") + os.execute ("flux module list") +end + +-- wait for an event sent from the leader of io-forwarding job to sync +-- between io job's installing the app module and sending a request later +print ("Block until we hear go message from the an io forwarder") +local rc, err = f:recv_event () +if not rc then + print ("Failed to receive an event, %s", err) + os.exit (1) +end + +if rank == 0 then + local rc, err = f:sendevent ({ data = "please proceed" }, "app.comp.go") + if not rc then error (err) end + print ("Sent a go event") +end + +local resp, err = f:rpc ("ioapp.io", { data = rank }) +if not resp then + if err == "Function not implemented" then + print ("ioapp.io request handler isn't loaded") + else + print (err) + end +else + print ("Count so far: " .. resp.count) +end + +print ("Will compute for " .. amount .. " seconds") +sleep (amount) +f:unsubscribe ("app.io.go") + +-- vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/io-forwarding.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/io-forwarding.lua new file mode 100755 index 0000000..0f9f78f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/io-forwarding.lua @@ -0,0 +1,57 @@ +#!/usr/bin/env lua + +local flux = require 'flux' +local f = flux.new () +local amount = tonumber (arg[1]) or 120 +local rank = tonumber (os.getenv('FLUX_TASK_RANK')) or 0 +local frank = tonumber (os.getenv('FLUX_LOCAL_RANKS')) or 0 +io.stdout:setvbuf ("no") + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: io-forward.lua seconds") + print (" Forward I/O requests for seconds") + os.exit (1) +end + +-- subscribe app.comp.go event +local rc, err = f:subscribe ("app.comp.go") +if not rc then + print ("Failed to subscribe an event, %s", err) + os.exit (1) +end + +if rank == 0 then + os.execute ("flux module load -r " .. 0 .. " ioapp") + os.execute ("flux module list") + local rc, err = f:sendevent ({ data = "please proceed" }, "app.io.go") + if not rc then error (err) end + print ("Sent a go event") +end + +-- Wait for an event sent from the leader of compute job to sync +-- between compute job's installing the app module and sending a request later +print ("Block until we hear go message from the a leader compute process") +local rc, err = f:recv_event () +if not rc then + print ("Failed to receive an, %s", err) + os.exit (1) +end + +local resp, err = f:rpc ("capp.comp", { data = rank }) +if not resp then + if err == "Function not implemented" then + print ("capp.comp request handler isn't loaded") + else + print (err) + end +end + +print ("Will forward IO requests for " .. amount .. " seconds") +sleep (amount) +f:unsubscribe ("app.comp.go") + +-- vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/conf.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/conf.py new file mode 100644 index 0000000..75f3e5c --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/conf.py @@ -0,0 +1,83 @@ +############################################################### +# Copyright 2020 Lawrence Livermore National Security, LLC +# (c.f. AUTHORS, NOTICE.LLNS, COPYING) +# +# This file is part of the Flux resource manager framework. +# For details, see https://github.com/flux-framework. +# +# SPDX-License-Identifier: LGPL-3.0 +############################################################### + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'Flux' +copyright = '''Copyright 2020 Lawrence Livermore National Security, LLC and Flux developers. + +SPDX-License-Identifier: LGPL-3.0''' +author = 'This page is maintained by the Flux community.' + +# The full version, including alpha/beta/rc tags +release = '0.1.0' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.intersphinx', + 'sphinxcontrib.spelling', + 'recommonmark', +] + +# sphinxcontrib.spelling settings +spelling_word_list_filename = [ + 'spell.en.pws' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'README.md'] + +master_doc = 'index' +source_suffix = ['.rst', '.md'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [ +] + +# -- Options for man output ------------------------------------------------- + +man_pages = [ +] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/Makefile b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/Makefile new file mode 100644 index 0000000..56abc33 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/Makefile @@ -0,0 +1,13 @@ +all: conduit.so + +FLUX_CORE_LIBS = $(shell pkg-config --libs flux-core) +FLUX_CORE_INCLUDES = $(shell pkg-config --cflags flux-core) + +conduit.so: conduit.o + gcc -Wl,--no-undefined --disable-static -shared -export-dynamic $^ -o $@ $(FLUX_CORE_LIBS) + +conduit.o: conduit.c + gcc $(FLUX_CORE_INCLUDES) $^ -fPIC -c -o $@ + +clean: + rm *.o *.so diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md new file mode 100644 index 0000000..a68aedb --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md @@ -0,0 +1,84 @@ +## A Data Conduit Strategy + +### Description: Use a data stream to send packets through + +#### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/data-conduit +``` + +#### Execution + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Point to `flux-core`'s `pkgconfig` directory: + +| Shell | Command | +| ----- | ---------- | +| tcsh | `setenv PKG_CONFIG_PATH /lib/pkgconfig` | +| bash/zsh | `export PKG_CONFIG_PATH='/lib/pkgconfig'` | + +3. `make` + +4. Add the directory of the modules to `FLUX_MODULE_PATH`, if the module was built in the current directory: + +`export FLUX_MODULE_PATH=${FLUX_MODULE_PATH}:$(pwd)` + +5. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +6. Submit the **datastore** script: + +`flux submit -N 1 -n 1 ./datastore.py` + +7. Submit and resubmit five **compute** scripts to send time data to **datastore**: + +`flux submit -N 1 -n 1 ./compute.py 1` + +`flux submit -N 1 -n 1 ./compute.py 1` + +`flux submit -N 1 -n 1 ./compute.py 1` + +`flux submit -N 1 -n 1 ./compute.py 1` + +`flux submit -N 1 -n 1 ./compute.py 1` + +8. Attach to the **datastore** job to see the data sent by the **compute.py** scripts: + +`flux job attach 1900070043648` + +``` +Starting.... +Module was loaded successfully... +finished initialize... +starting run() +Waiting for a packet +{u'test': 101} +Waiting for a packet +{u'test': 101, u'1578431137': u'os.time'} +Waiting for a packet +{u'test': 101, u'1578431137': u'os.time', u'1578431139': u'os.time'} +Waiting for a packet +{u'test': 101, u'1578431140': u'os.time', u'1578431137': u'os.time', u'1578431139': u'os.time'} +Waiting for a packet +{u'test': 101, u'1578431140': u'os.time', u'1578431137': u'os.time', u'1578431139': u'os.time', u'1578431141': u'os.time'} +Bye bye! +run finished... +``` + +--- + +### Notes + +- `f = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. + +- `kvs.put()` places the value of _udata_ under the key **"conduit"**. Once the key-value pair is put, the change must be committed with `kvs.commit()`. The value can then be retrieved with `kvs.get()`. + +- `f.rpc()` creates a new RPC object consisting of a specified topic and payload (along with additional flags) that are exchanged with a Flux service. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/compute.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/compute.py new file mode 100644 index 0000000..d03f871 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/compute.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import argparse +import time +import os +import re +import flux +import json +from flux import kvs +from flux.message import Message + +parser = argparse.ArgumentParser(description="compute for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +f = flux.Flux() +udata = "conduit" +kvs.put(f, "conduit", udata) +kvs.commit(f) + +cr = kvs.get(f, "conduit") +print(cr) + +os_time = int(time.time()) +payload = {str(os_time): "os.time"} +new_payload = {"data": json.dumps(payload)} +print("Sending ", json.dumps(new_payload)) + +# this data is ultimately flowed into the data store +f.rpc("conduit.put", new_payload, 0) + + +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c new file mode 100644 index 0000000..790f84f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct conduit_ctx { + flux_t *h; + struct sockaddr_un server_sockaddr; + struct sockaddr_un client_sockaddr; + int client_sock; + bool connected; + char *sockname; + char *csockname; + flux_msg_handler_t **handlers; +}; + +static void freectx (void *arg) +{ + struct conduit_ctx *ctx = (struct conduit_ctx *)arg; + flux_msg_handler_delvec (ctx->handlers); + free (ctx->sockname); + free (ctx->csockname); + if (ctx->connected) + close (ctx->client_sock); + free (ctx); +} + +static struct conduit_ctx *getctx (flux_t *h) +{ + struct conduit_ctx *ctx = flux_aux_get (h, "conduit"); + if (!ctx) { + char *user = getenv ("USER"); + ctx = malloc (sizeof (*ctx)); + ctx->connected = false; + ctx->handlers = NULL; + asprintf (&(ctx->sockname), "/tmp/%s/mysock", user? user : ""); + asprintf (&(ctx->csockname),"/tmp/%s/mycsock", user? user : ""); + flux_aux_set (h, "conduit", ctx, freectx); + } + return ctx; +} + +/* Foward the received JSON string to the datastore.py */ +static int conduit_send (flux_t *h, const char *json_str) +{ + int rc = -1; + int n = 0; + struct conduit_ctx *ctx = getctx (h); + + n = (int) strlen (json_str); + if ((rc = send (ctx->client_sock, (void *)&n, sizeof (n), 0)) == -1) { + flux_log_error (h, "send error %s", __FUNCTION__); + return rc; + } + if ((rc = send (ctx->client_sock, (void *)json_str, n, 0)) == -1) { + flux_log_error (h, "send error %s", __FUNCTION__); + return rc; + } + flux_log (h, LOG_INFO, "conduit_send succeed"); + return 0; +} + +/* request callback called when conduit.put request is invoked */ +static void conduit_put_request_cb (flux_t *h, flux_msg_handler_t *w, + const flux_msg_t *msg, void *arg) +{ + int rc = -1; + const char *topic = NULL; + struct conduit_ctx *ctx = getctx (h); + const char *data = NULL; + + flux_log (h, LOG_INFO, "conduit_put_request_cb:"); + if (ctx->connected == false) { + flux_log (h, LOG_INFO, "conduit not connected"); + errno = ENOTCONN; + goto done; + } + if (flux_request_unpack (msg, &topic, "{s:s}", "data", &data)) { + flux_log_error (h, "%s", __FUNCTION__); + goto done; + } + if (conduit_send (h, data) < 0) + errno = EPROTO; +done: + if (flux_respond (h, msg, errno, NULL) < 0) + flux_log_error (h, "%s: flux_respond", __FUNCTION__); +} + +/* open the Unix domain socket to talk to datastore.py */ +static int conduit_open (flux_t *h) +{ + struct conduit_ctx *ctx = getctx (h); + int rc = -1; + int len = 0; + char buf[256]; + memset(&(ctx->server_sockaddr), 0, sizeof(struct sockaddr_un)); + memset(&(ctx->client_sockaddr), 0, sizeof(struct sockaddr_un)); + + if ((ctx->client_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + flux_log (h, LOG_ERR, "SOCKET ERROR = %d\n", errno); + goto done; + } + + ctx->client_sockaddr.sun_family = AF_UNIX; + strcpy(ctx->client_sockaddr.sun_path, ctx->csockname); + len = sizeof(ctx->client_sockaddr); + unlink (ctx->csockname); + if ((rc = bind(ctx->client_sock, + (struct sockaddr *)&ctx->client_sockaddr, len)) == -1) { + flux_log (h, LOG_ERR, "BIND ERROR: %d\n", errno); + close(ctx->client_sock); + goto done; + } + flux_log (h, LOG_INFO, "Conduit client socket bound\n"); + + ctx->server_sockaddr.sun_family = AF_UNIX; + strcpy(ctx->server_sockaddr.sun_path, ctx->sockname); + if ((rc = connect(ctx->client_sock, + (struct sockaddr *)&ctx->server_sockaddr, len)) == -1) { + flux_log (h, LOG_ERR, "CONNECT ERROR = %d\n", errno); + close(ctx->client_sock); + goto done; + } + + ctx->connected = true; + flux_log (h, LOG_INFO, "Conduit socket connected\n"); + conduit_send (h, "{\"test\":101}"); + rc = 0; +done: + return rc; +} + + +static struct flux_msg_handler_spec htab[] = { + { FLUX_MSGTYPE_REQUEST, "conduit.put", conduit_put_request_cb, 0 }, + FLUX_MSGHANDLER_TABLE_END +}; + +int mod_main (flux_t *h, int argc, char **argv) +{ + uint32_t rank = 0; + struct conduit_ctx *ctx = getctx (h); + + if (conduit_open (h) < 0) { + flux_log (ctx->h, LOG_ERR, "conduit_open failed"); + goto done; + } + if (flux_get_rank (h, &rank) < 0) { + flux_log (ctx->h, LOG_ERR, "flux_get_rank failed"); + goto done; + } + + /* Put the rank where this module is loaded into conduit key + */ + flux_kvs_txn_t *txn = flux_kvs_txn_create (); + flux_kvs_txn_pack (txn, 0, "conduit", "i", rank); + flux_kvs_commit (h, 0, txn); + flux_kvs_txn_destroy (txn); + if (flux_msg_handler_addvec (h, htab, (void *)h, + &ctx->handlers) < 0) { + flux_log (ctx->h, LOG_ERR, "flux_msg_handler_addvec: %s", strerror (errno)); + goto done; + } + if (flux_reactor_run (flux_get_reactor (h), 0) < 0) { + flux_log (h, LOG_ERR, "flux_reactor_run: %s", strerror (errno)); + goto done; + } + +done: + return 0; +} + +MOD_NAME ("conduit"); + +/* + * vi:tabstop=4 shiftwidth=4 expandtab + */ diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/datastore.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/datastore.py new file mode 100755 index 0000000..d5fcc48 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/datastore.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 + +import socket +import struct +import json +import sys +import os + +sockdir = os.path.join("/tmp", os.environ["USER"]) +sockname = os.path.join(sockdir, "mysock") + +store = {} +sock = "" + + +def initialize(): + global sock + if not os.path.exists(sockdir): + os.mkdir(sockdir) + if os.path.exists(sockname): + os.remove(sockname) + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.bind(sockname) + sock.listen(1) + cmd = "flux module load ./conduit.so" + os.system(cmd) + + +def run(): + global sock + global store + connection, client_address = sock.accept() + for x in range(5): + print("Waiting for a packet") + mybytes = bytearray(4) + nbytes, address = connection.recvfrom_into(mybytes, 4) + if nbytes == 0: + break + size = ( + mybytes[0] * 1 + + mybytes[1] * 256 + + mybytes[2] * 65536 + + mybytes[3] * 16777216 + ) + data = bytearray(size) + nbytes, address = connection.recvfrom_into(data, size) + dict_blob = json.loads(data.decode("ascii")) + + if dict_blob is not None: + store.update(dict_blob) + print(store) + else: + print("Mallformed data, discarding") + + connection.close() + cmd = "flux module remove conduit" + os.system(cmd) + print("Bye bye!") + + +def main(): + print("Starting....") + initialize() + run() + + +if __name__ == "__main__": + main() diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md new file mode 100644 index 0000000..7f9c7ca --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md @@ -0,0 +1,35 @@ +## Hierarchical Launching + +### Description: Launch an ensemble of sleep 0 tasks + +#### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/hierarchical-launching +``` + +#### Execution + +1. `salloc -N3 -ppdebug` + +2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. `./parent.sh` + +``` +Mon Nov 18 15:31:08 PST 2019 +13363018989568 +13365166473216 +13367095853056 +First Level Done +Mon Nov 18 15:34:13 PST 2019 +``` + + +### Notes + +- You can increase the number of jobs by increasing `NCORES` in `parent.sh` and +`NJOBS` in `ensemble.sh`. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh new file mode 100755 index 0000000..0edca81 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh + +NJOBS=750 +MAXTIME=$(expr ${NJOBS} + 2) + +for i in `seq 1 ${NJOBS}`; do + flux mini submit --nodes=1 --ntasks=1 --cores-per-task=1 sleep 0 +done + +flux jobs +flux queue drain +echo "Final Level Done" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh new file mode 100755 index 0000000..84ef464 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +NCORES=3 + +date + +for i in `seq 1 ${NCORES}`; do + flux mini submit -N 1 -n 1 flux start ./ensemble.sh +done + +flux queue drain +echo "First Level Done" + +date diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/index.rst b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/index.rst new file mode 100644 index 0000000..aa335e9 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/index.rst @@ -0,0 +1,95 @@ +Flux Workflow Examples +---------------------- + +The examples contained here demonstrate and explain some simple use-cases with Flux, +and make use of Flux's command-line interface (CLI), Flux's C library, and the Python and Lua bindings to the C library. +The entire set of examples can be downloaded by cloning the `Github repo `_. + +The examples assume that you have installed: + +#. A recent version of Flux + +#. Python 3.6+ + +#. Lua 5.1+ + +:doc:`CLI: Job Submission ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Launch a flux instance and schedule/launch compute and io-forwarding +jobs on separate nodes using the CLI + +:doc:`Python: Job Submission ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Schedule/launch compute and io-forwarding jobs on separate nodes using +the Python bindings + +:doc:`Python: Job Submit/Wait ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Submit jobs and wait for them to complete using the Flux Python bindings + +:doc:`Python: Asynchronous Bulk Job Submission ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Asynchronously submit jobspec files from a directory and wait for them +to complete in any order + +:doc:`Python: Tracking Job Status and Events ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Submit job bundles and wait until all jobs complete + +:doc:`Python: Job Cancellation ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Cancel a running job + +:doc:`Lua: Use Events ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use events to synchronize compute and io-forwarding jobs running on +separate nodes + +:doc:`Python: Simple KVS Example ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use KVS Python interfaces to store user data into KVS + +:doc:`CLI/Lua: Job Ensemble Submitted with a New Flux Instance ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Submit job bundles, print live job events, and exit when all jobs are +complete + +:doc:`CLI: Hierarchical Launching ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Launch a large number of sleep 0 jobs + +:doc:`C/Lua: Use a Flux Comms Module ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use a Flux Comms Module to communicate with job elements + +:doc:`C/Python: A Data Conduit Strategy ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Attach to a job that receives OS time data from compute jobs + +.. toctree:: + :hidden: + + job-submit-cli/README + job-submit-api/README + job-submit-wait/README + async-bulk-job-submit/README + job-status-control/README + job-cancel/README + synchronize-events/README + kvs-python-bindings/README + job-ensemble/README + hierarchical-launching/README + comms-module/README + data-conduit/README diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md new file mode 100644 index 0000000..af1d3b8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md @@ -0,0 +1,43 @@ +## Job Cancellation + +### Description: Cancel a running job + +#### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-cancel +``` + +#### Execution + +1. Launch the submitter script: + +`./submitter.py $(flux resource list -no {ncores} --state=up)` + +_note: for older versions of Flux, you might need to instead run: `./submitter.py $(flux hwloc info | awk '{print $3}')`_ + +``` +Submitted 1st job: 2241905819648 +Submitted 2nd job: 2258951471104 + +First submitted job status (2241905819648) - RUNNING +Second submitted job status (2258951471104) - PENDING + +Canceled first job: 2241905819648 + +First submitted job status (2241905819648) - CANCELED +Second submitted job status (2258951471104) - RUNNING +``` + +### Notes + +- `f = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. + +- `flux.job.submit(f, sleep_jobspec, waitable=True)` submits a jobspec, returning a job ID that can be used to interact with the submitted job. + +- `flux.job.cancel(f, jobid)` cancels the job. + +- `flux.job.wait_async(f, jobid)` will wait for the job to complete (or in this case, be canceled). It returns a Flux future, which can be used to process the result later. Only jobs submitted with `waitable=True` can be waited for. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/submitter.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/submitter.py new file mode 100644 index 0000000..b95584f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/submitter.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import time +import argparse + +import flux +from flux.job import JobspecV1 + +f = flux.Flux() + +parser = argparse.ArgumentParser( + description=""" + Description: Submit two 'sleep 60' jobs that take up + all resources on a node. + """ +) +parser.add_argument(dest="cores", help="number of cores on the node") +args = parser.parse_args() + +# submit a sleep job that takes up all resources +sleep_jobspec = JobspecV1.from_command( + ["sleep", "60"], num_tasks=1, cores_per_task=int(args.cores) +) +first_jobid = flux.job.submit(f, sleep_jobspec, waitable=True) +print("Submitted 1st job: %d" % (int(first_jobid))) +time.sleep(1) + +# submit a second sleep job - will be scheduled, but not run +sleep_jobspec = JobspecV1.from_command( + ["sleep", "60"], num_tasks=1, cores_per_task=int(args.cores) +) +second_jobid = flux.job.submit(f, sleep_jobspec, waitable=True) +print("Submitted 2nd job: %d\n" % (int(second_jobid))) +time.sleep(1) + +# get list of JobInfo objects - fetch their ID's and current status +jobs = flux.job.JobList(f, max_entries=2).jobs() +print("First submitted job status (%d) - %s" % (int(jobs[1].id.dec), jobs[1].status)) +print("Second submitted job status (%d) - %s\n" % (int(jobs[0].id.dec), jobs[0].status)) + +# cancel the first job +flux.job.cancel(f, first_jobid) +future = flux.job.wait_async(f, first_jobid).wait_for(5.0) +return_id, success, errmsg = future.get_status() +print("Canceled first job: %d\n" % (int(return_id))) +time.sleep(1) + +# the second job should now run since the first was canceled +jobs = flux.job.JobList(f, max_entries=2).jobs() +print("First submitted job status (%d) - %s" % (int(jobs[1].id.dec), jobs[1].status)) +print("Second submitted job status (%d) - %s" % (int(jobs[0].id.dec), jobs[0].status)) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md new file mode 100644 index 0000000..3f303ea --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md @@ -0,0 +1,104 @@ +### Job Ensemble Submitted with a New Flux Instance + +#### Description: Launch a flux instance and submit one instance of an io-forwarding job and 50 compute jobs, each spanning the entire set of nodes. + +#### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-ensemble +``` + +#### Execution + +1. `salloc -N3 -ppdebug` + +2. `cat ensemble.sh` + +``` +#!/usr/bin/env sh + +NJOBS=10 +MAXTIME=$(expr ${NJOBS} + 2) +JOBIDS="" + +JOBIDS=$(flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua ${MAXTIME}) +for i in `seq 1 ${NJOBS}`; do + JOBIDS="${JOBIDS} $(flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 1)" +done + +flux jobs +flux queue drain + +# print mock-up prevenance data +for i in ${JOBIDS}; do + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo "Jobid: ${i}" + KVSJOBID=$(flux job id --from=dec --to=kvs ${i}) + flux kvs get ${KVSJOBID}.R | jq +done +``` + +3. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out ./ensemble.sh` + +``` +JOBID USER NAME STATE NTASKS NNODES RUNTIME RANKS +1721426247680 fluxuser compute.lu RUN 4 2 0.122s [1-2] +1718322462720 fluxuser compute.lu RUN 4 2 0.293s [0,2] +1715201900544 fluxuser compute.lu RUN 4 2 0.481s [0-1] +1712299442176 fluxuser compute.lu RUN 4 2 0.626s [1-2] +1709296320512 fluxuser compute.lu RUN 4 2 0.885s [0,2] +1706293198848 fluxuser compute.lu RUN 4 2 1.064s [0-1] +1691378253824 fluxuser io-forward RUN 1 1 1.951s 0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Jobid: 1691378253824 +{ + "version": 1, + "execution": { + "R_lite": [ + { + "rank": "0", + "children": { + "core": "0-1" + } + } + ] + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Jobid: 1694414929920 +{ + "version": 1, + "execution": { + "R_lite": [ + { + "rank": "1-2", + "children": { + "core": "0-3" + } + } + ] + } +} +. +. +. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Jobid: 1721426247680 +{ + "version": 1, + "execution": { + "R_lite": [ + { + "rank": "1-2", + "children": { + "core": "8-11" + } + } + ] + } +} + +``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/compute.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/compute.lua new file mode 100755 index 0000000..e5159fd --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/compute.lua @@ -0,0 +1,18 @@ +#!/usr/bin/env lua + +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: compute.lua seconds") + print (" Compute for seconds") + os.exit (1) +end + +print ("Will compute for " .. amount .. " seconds") +sleep (amount) + +-- vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh new file mode 100755 index 0000000..8c76593 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env sh + +NJOBS=10 +MAXTIME=$(expr ${NJOBS} + 2) +JOBIDS="" + +JOBIDS=$(flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua ${MAXTIME}) +for i in `seq 1 ${NJOBS}`; do + JOBIDS="${JOBIDS} $(flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 1)" +done + +flux jobs +flux queue drain + +# print mock-up prevenance data +for i in ${JOBIDS}; do + echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + echo "Jobid: ${i}" + KVSJOBID=$(flux job id --from=dec --to=kvs ${i}) + flux kvs get ${KVSJOBID}.R | jq +done diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/io-forwarding.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/io-forwarding.lua new file mode 100755 index 0000000..3427b1e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/io-forwarding.lua @@ -0,0 +1,18 @@ +#!/usr/bin/env lua + +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: io-forward.lua seconds") + print (" Forward I/O requests for seconds") + os.exit (1) +end + +print ("Will forward IO requests for " .. amount .. " seconds") +sleep (amount) + +-- vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/kvs-watch-until.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/kvs-watch-until.lua new file mode 100755 index 0000000..16e63ae --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/kvs-watch-until.lua @@ -0,0 +1,82 @@ +#!/usr/bin/env lua +-- +-- Exit only if/when all ranks have exited 'unknown' state +-- +local usage = [[ +Usage: kvs-wait-until [OPTIONS] KEY CODE +Watch kvs KEY until Lua code CODE returns true. +(CODE is supplied key value in variable 'v') +If -t, --timeout is provided, and the timeout expires, then +exit with non-zero exit status. + -h, --help Display this message + -v, --verbose Print value on each watch callback + -t, --timeout=T Wait at most T seconds (before exiting +]] + +local getopt = require 'flux.alt_getopt' .get_opts +local timer = require 'flux.timer'.new() +local f = require 'flux' .new() + +local function printf (...) + io.stdout:write (string.format (...)) +end +local function log_err (...) + io.stdout:write (string.format (...)) +end + +local opts, optind = getopt (arg, "hvt:", + { verbose = 'v', + timeout = 't', + help = 'h' + } + ) +if opts.h then print (usage); os.exit (0) end + +local key = arg [optind] +local callback = arg [optind+1] + +if not key or not callback then + log_err ("KVS key and callback code required\n") + print (usage) + os.exit (1) +end + +callback = "return function (v) return "..callback.." end" +local fn, err = loadstring (callback, "callback") +if not fn then + log_err ("code compile error: %s", err) + os.exit (1) +end +local cb = fn () + +local kw, err = f:kvswatcher { + key = key, + handler = function (kw, result) + if opts.v then + printf ("%4.03fs: %s = %s\n", + timer:get0(), + key, tostring (result)) + end + -- Do not pass nil result to callback: + if result == nil then return end + local ok, rv = pcall (cb, result) + if not ok then error (rv) end + if ok and rv then + os.exit (0) + end + end +} + +if opts.t then + local tw, err = f:timer { + timeout = opts.t * 1000, + handler = function (f, to) + log_err ("%4.03fs: Timeout expired!\n", timer:get0()) + os.exit (1) + end + } +end + +timer:set () +f:reactor () +-- vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md new file mode 100644 index 0000000..bbd3704 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md @@ -0,0 +1,69 @@ +## Using Flux Job Status and Control API + +### Description: Submit job bundles, get event updates, and wait until all jobs complete + +#### Setup + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-status-control +``` + +#### Execution + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -p pdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Run the bookkeeper executable along with the number of jobs to be submitted (if no size is specified, 6 jobs are submitted: 3 instances of **compute.py**, and 3 instances of **io-forwarding,py**): + +`./bookkeeper.py 2` + +``` +bookkeeper: all jobs submitted +bookkeeper: waiting until all jobs complete +job 39040581632 triggered event 'submit' +job 39040581633 triggered event 'submit' +job 39040581632 triggered event 'depend' +job 39040581632 triggered event 'priority' +job 39040581632 triggered event 'alloc' +job 39040581633 triggered event 'depend' +job 39040581633 triggered event 'priority' +job 39040581633 triggered event 'alloc' +job 39040581632 triggered event 'start' +job 39040581633 triggered event 'start' +job 39040581632 triggered event 'finish' +job 39040581633 triggered event 'finish' +job 39040581633 triggered event 'release' +job 39040581633 triggered event 'free' +job 39040581633 triggered event 'clean' +job 39040581632 triggered event 'release' +job 39040581632 triggered event 'free' +job 39040581632 triggered event 'clean' +bookkeeper: all jobs completed +``` + +--- + +### Notes + +- The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: +```python +compute_jobreq = JobspecV1.from_command( + command=["./compute.py", "10"], num_tasks=4, num_nodes=2, cores_per_task=2 +) +compute_jobreq.cwd = os.getcwd() +compute_jobreq.environment = dict(os.environ) +``` + +- `with FluxExecutor() as executor:` creates a new `FluxExecutor` which can be used to submit jobs, wait for them to complete, and get event updates. Using the executor as a context manager (`with ... as ...:`) ensures it is shut down properly. + +- `executor.submit(compute_jobreq)` returns a `concurrent.futures.Future` subclass which completes when the underlying job is done. The jobid of the underlying job can be fetched with the `.jobid([timeout])` method (which waits until the jobid is ready). + +- Throughout the course of a job, various events will occur to it. `future.add_event_callback(event, event_callback)` adds a callback which will be invoked when the given event occurs. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/bookkeeper.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/bookkeeper.py new file mode 100755 index 0000000..a7cef19 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/bookkeeper.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import os +import argparse + +from flux.job import JobspecV1, FluxExecutor + + +def event_callback(future, event): + print(f"job {future.jobid()} triggered event {event.name!r}") + + +# main +def main(): + # set up command-line parser + parser = argparse.ArgumentParser( + description="submit and wait for the completion of " + "N bundles, each consisting of compute " + "and io-forwarding jobs" + ) + parser.add_argument( + "njobs", metavar="N", type=int, help="the number of bundles to submit and wait", + ) + args = parser.parse_args() + # set up jobspecs + compute_jobreq = JobspecV1.from_command( + command=["./compute.py", "10"], num_tasks=6, num_nodes=3, cores_per_task=2 + ) + compute_jobreq.cwd = os.getcwd() + compute_jobreq.environment = dict(os.environ) + io_jobreq = JobspecV1.from_command( + command=["./io-forwarding.py", "10"], num_tasks=3, num_nodes=3, cores_per_task=1 + ) + io_jobreq.cwd = os.getcwd() + io_jobreq.environment = dict(os.environ) + # submit jobs and register event callbacks for all events + with FluxExecutor() as executor: + futures = [executor.submit(compute_jobreq) for _ in range(args.njobs // 2)] + futures.extend( + executor.submit(io_jobreq) for _ in range(args.njobs // 2, args.njobs) + ) + print("bookkeeper: all jobs submitted") + for fut in futures: + # each event can have a different callback + for event in executor.EVENTS: + fut.add_event_callback(event, event_callback) + print("bookkeeper: waiting until all jobs complete") + # exiting the context manager waits for the executor to complete all futures + print("bookkeeper: all jobs completed") + + +main() + +# vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/compute.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/compute.py new file mode 100755 index 0000000..1f860f2 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/compute.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="compute for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will compute for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/io-forwarding.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/io-forwarding.py new file mode 100755 index 0000000..217ed0e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/io-forwarding.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="forward I/O requests for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will forward I/O requests for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md new file mode 100644 index 0000000..12cf931 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md @@ -0,0 +1,106 @@ +## Job Submit API + +To run the following examples, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-submit-api +``` + +### Part(a) - Using a direct job.submit RPC + +#### Description: Schedule and launch compute and io-forwarding jobs on separate nodes + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -p pdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Run the submitter executable: + +`./submitter.py` + +4. List currently running jobs: + +`flux jobs` + +``` +JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS +ƒ5W8gVwm moussa1 io-forward R 1 1 19.15s 2 +ƒ5Vd2kJs moussa1 compute.py R 4 2 19.18s [0-1] +``` + +5. Information about jobs, such as the submitted job specification, an eventlog, and the resource description format **R** are stored in the KVS. The data can be queried via the `job-info` module via the `flux job info` command. For example, to fetch **R** for a job which has been allocated resources: + +`flux job info ƒ5W8gVwm R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"2","children":{"core":"0"}}]}} +``` + +`flux job info ƒ5Vd2kJs R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-1","children":{"core":"0-3"}}]}} +``` + +### Part(b) - Using a direct job.submit RPC + +#### Description: Schedule and launch both compute and io-forwarding jobs across all nodes + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -p pdebug` + +2. Launch another Flux instance on the current allocation: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Run the second submitter executable: + +`./submitter2.py` + +4. List currently running jobs: + +`flux jobs` + +``` +JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS +ƒctYadhh moussa1 io-forward R 3 3 3.058s [0-2] +ƒct1StnT moussa1 compute.py R 6 3 3.086s [0-2] +``` + +5. Fetch **R** for the jobs that have been allocated resources: + +`flux job info ƒctYadhh R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} +``` + +`flux job info ƒct1StnT R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} +``` + +--- + +### Notes + +- `f = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. + + +- The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: +```python +compute_jobreq = JobspecV1.from_command( + command=["./compute.py", "120"], num_tasks=4, num_nodes=2, cores_per_task=2 +) +compute_jobreq.cwd = os.getcwd() +compute_jobreq.environment = dict(os.environ) +``` + +- `flux.job.submit(f, compute_jobreq)` submits the job to be run, and returns a job ID once it begins running. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/compute.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/compute.py new file mode 100755 index 0000000..1f860f2 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/compute.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="compute for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will compute for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/io-forwarding.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/io-forwarding.py new file mode 100755 index 0000000..217ed0e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/io-forwarding.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="forward I/O requests for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will forward I/O requests for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter.py new file mode 100755 index 0000000..51f2408 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import flux +from flux.job import JobspecV1 + +f = flux.Flux() + +compute_jobreq = JobspecV1.from_command( + command=["./compute.py", "120"], num_tasks=4, num_nodes=2, cores_per_task=2 +) +compute_jobreq.cwd = os.getcwd() +compute_jobreq.environment = dict(os.environ) +print(flux.job.submit(f, compute_jobreq)) + +io_jobreq = JobspecV1.from_command( + command=["./io-forwarding.py", "120"], num_tasks=1, num_nodes=1, cores_per_task=1 +) +io_jobreq.cwd = os.getcwd() +io_jobreq.environment = dict(os.environ) +print(flux.job.submit(f, io_jobreq)) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter2.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter2.py new file mode 100755 index 0000000..670acff --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/submitter2.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +import json +import os +import re +import flux +from flux.job import JobspecV1 + +f = flux.Flux() + +compute_jobreq = JobspecV1.from_command( + command=["./compute.py", "120"], num_tasks=6, num_nodes=3, cores_per_task=2 +) +compute_jobreq.cwd = os.getcwd() +compute_jobreq.environment = dict(os.environ) +print(flux.job.submit(f, compute_jobreq)) + +io_jobreq = JobspecV1.from_command( + command=["./io-forwarding.py", "120"], num_tasks=3, num_nodes=3, cores_per_task=1 +) +io_jobreq.cwd = os.getcwd() +io_jobreq.environment = dict(os.environ) +print(flux.job.submit(f, io_jobreq)) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md new file mode 100644 index 0000000..b50e71a --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md @@ -0,0 +1,81 @@ +## Job Submit CLI + +To run the following examples, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-submit-cli +``` + +### Part(a) - Partitioning Schedule + +#### Description: Launch a flux instance and schedule/launch compute and io-forwarding jobs on separate nodes + +1. `salloc -N3 -ppdebug` + +2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. `flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 120` + +4. `flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120` + +5. List running jobs: + +`flux jobs` + +``` +JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS +ƒ3ETxsR9H moussa1 io-forward R 1 1 2.858s 2 +ƒ38rBqEWT moussa1 compute.lu R 4 2 15.6s [0-1] +``` + +6. Get information about job: + +`flux job info ƒ3ETxsR9H R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"2","children":{"core":"0-1"}}]}} +``` + +`flux job info ƒ38rBqEWT R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-1","children":{"core":"0-3"}}]}} +``` + +### Part(b) - Overlapping Schedule + +#### Description: Launch a flux instance and schedule/launch both compute and io-forwarding jobs across all nodes + +1. `salloc -N3 -ppdebug` + +2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. `flux mini submit --nodes=3 --ntasks=6 --cores-per-task=2 ./compute.lua 120` + +4. `flux mini submit --nodes=3 --ntasks=3 --cores-per-task=1 ./io-forwarding.lua 120` + +5. List jobs in KVS: + +`flux jobs` + +``` +JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS +ƒ3ghmgCpw moussa1 io-forward R 3 3 16.91s [0-2] +ƒ3dSybfQ3 moussa1 compute.lu R 6 3 24.3s [0-2] + +``` + +6. Get information about job: + +`flux job info ƒ3ghmgCpw R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"4"}}]}} +``` + +`flux job info ƒ3dSybfQ3 R` + +``` +{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} +``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.lua new file mode 100755 index 0000000..4fbccc8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.lua @@ -0,0 +1,17 @@ +#!/usr/bin/env lua + +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: compute.lua seconds") + print (" Compute for seconds") + os.exit (1) +end + +print ("Will compute for " .. amount .. " seconds") +sleep (amount) + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.py new file mode 100755 index 0000000..1f860f2 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/compute.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="compute for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will compute for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.lua new file mode 100755 index 0000000..46ccda0 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.lua @@ -0,0 +1,17 @@ +#!/usr/bin/env lua + +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: io-forward.lua seconds") + print (" Forward I/O requests for seconds") + os.exit (1) +end + +print ("Will forward IO requests for " .. amount .. " seconds") +sleep (amount) + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.py new file mode 100755 index 0000000..217ed0e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/io-forwarding.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="forward I/O requests for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will forward I/O requests for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md new file mode 100644 index 0000000..1f8745e --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md @@ -0,0 +1,150 @@ +## Python Job Submit/Wait + +To run the following examples, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/job-submit-wait +``` + +### Part(a) - Python Job Submit/Wait + +#### Description: Submit jobs asynchronously and wait for them to complete in any order + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Submit the **submitter_wait_any.py** script, along with the number of jobs you want to run (if no argument is passed, 10 jobs are submitted): + +`./submitter_wait_any.py 10` + +``` +submit: 46912591450240 compute_jobspec +submit: 46912591450912 compute_jobspec +submit: 46912591451080 compute_jobspec +submit: 46912591363152 compute_jobspec +submit: 46912591362984 compute_jobspec +submit: 46912591451360 bad_jobspec +submit: 46912591451528 bad_jobspec +submit: 46912591451696 bad_jobspec +submit: 46912591451864 bad_jobspec +submit: 46912591452032 bad_jobspec +wait: 46912591451528 Error: job returned exit code 1 +wait: 46912591451864 Error: job returned exit code 1 +wait: 46912591451360 Error: job returned exit code 1 +wait: 46912591451696 Error: job returned exit code 1 +wait: 46912591452032 Error: job returned exit code 1 +wait: 46912591450240 Success +wait: 46912591363152 Success +wait: 46912591450912 Success +wait: 46912591451080 Success +wait: 46912591362984 Success +``` + +--- + +### Part(b) - Python Job Submit/Wait (Sliding Window) + +#### Description: Asynchronously submit jobs and keep at most a number of those jobs active + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Submit the **submitter_sliding_window.py** script, along with the number of jobs you want to run and the size of the window (if no argument is passed, 10 jobs are submitted and the window size is 2 jobs): + +`./submitter_sliding_window.py 10 3` + +``` +submit: 5624175788032 +submit: 5624611995648 +submit: 5625014648832 +wait: 5624175788032 Success +submit: 5804329533440 +wait: 5624611995648 Success +submit: 5804648300544 +wait: 5625014648832 Success +submit: 5805084508160 +wait: 5804329533440 Success +submit: 5986144223232 +wait: 5804648300544 Success +submit: 5986462990336 +wait: 5805084508160 Success +submit: 5986882420736 +wait: 5986144223232 Success +submit: 6164435697664 +wait: 5986462990336 Success +wait: 5986882420736 Success +wait: 6164435697664 Success +``` + +--- + +### Part(c) - Python Job Submit/Wait (Specific Job ID) + +#### Description: Asynchronously submit jobs, block/wait for specific jobs to complete + +1. Allocate three nodes from a resource manager: + +`salloc -N3 -ppdebug` + +2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: + +`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. Submit the **submitter_wait_in_order.py** script, along with the number of jobs you want to run (if no argument is passed, 10 jobs are submitted): + +`./submitter_wait_in_order.py 10` + +``` +submit: 46912593818008 compute_jobspec +submit: 46912593818176 compute_jobspec +submit: 46912593818344 compute_jobspec +submit: 46912593818512 compute_jobspec +submit: 46912593738048 compute_jobspec +submit: 46912519873816 bad_jobspec +submit: 46912593818792 bad_jobspec +submit: 46912593818960 bad_jobspec +submit: 46912593819128 bad_jobspec +submit: 46912593819296 bad_jobspec +wait: 46912593818008 Success +wait: 46912593818176 Success +wait: 46912593818344 Success +wait: 46912593818512 Success +wait: 46912593738048 Success +wait: 46912519873816 Error: job returned exit code 1 +wait: 46912593818792 Error: job returned exit code 1 +wait: 46912593818960 Error: job returned exit code 1 +wait: 46912593819128 Error: job returned exit code 1 +wait: 46912593819296 Error: job returned exit code 1 +``` + +--- + +### Notes + +- The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: + +```python +# create jobspec for compute.py +compute_jobspec = JobspecV1.from_command(command=["./compute.py", "15"], num_tasks=4, num_nodes=2, cores_per_task=2) +compute_jobspec.cwd = os.getcwd() +compute_jobspec.environment = dict(os.environ) +``` + +- Using the executor as a context manager (`with FluxExecutor() as executor`) ensures it shuts down properly. + +- `executor.submit(jobspec)` returns a future which completes when the job is done. + +- `future.exception()` blocks until the future is complete and returns (not raises) an exception if the job was canceled or was otherwise prevented from execution. Otherwise the method returns ``None``. + +- `future.result()` blocks until the future is complete and returns the return code of the job. If the job succeeded, the return code will be 0. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/compute.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/compute.py new file mode 100755 index 0000000..1f860f2 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/compute.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import argparse +import time + +parser = argparse.ArgumentParser(description="compute for seconds") +parser.add_argument( + "integer", + metavar="S", + type=int, + help="an integer for the number of seconds to compute", +) + +args = parser.parse_args() + +print("Will compute for " + str(args.integer) + " seconds.") +time.sleep(args.integer) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_sliding_window.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_sliding_window.py new file mode 100755 index 0000000..cfec311 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_sliding_window.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import os +import argparse +import collections +import concurrent.futures as cf + +from flux.job import JobspecV1, FluxExecutor + + +def main(): + # parse command line + parser = argparse.ArgumentParser() + parser.add_argument("njobs", nargs="?", type=int, default=10) + parser.add_argument("window_size", nargs="?", type=int, default=2) + args = parser.parse_args() + print(args) + # create jobspec for compute.py + compute_jobspec = JobspecV1.from_command( + command=["./compute.py", "5"], num_tasks=4, num_nodes=2, cores_per_task=2 + ) + compute_jobspec.cwd = os.getcwd() + compute_jobspec.environment = dict(os.environ) + # create a queue of the jobspecs to submit + jobspec_queue = collections.deque(compute_jobspec for _ in range(args.njobs)) + futures = [] # holds incomplete futures + with FluxExecutor() as executor: + while jobspec_queue or futures: + if len(futures) < args.window_size and jobspec_queue: + fut = executor.submit(jobspec_queue.popleft()) + print(f"submit: {id(fut)}") + futures.append(fut) + else: + done, not_done = cf.wait(futures, return_when=cf.FIRST_COMPLETED) + futures = list(not_done) + for fut in done: + if fut.exception() is not None: + print( + f"wait: {id(fut)} Error: job raised error " + f"{fut.exception()}" + ) + elif fut.result() == 0: + print(f"wait: {id(fut)} Success") + else: + print( + f"wait: {id(fut)} Error: job returned " + f"exit code {fut.result()}" + ) + + +if __name__ == "__main__": + main() + +# vim: tabstop=4 shiftwidth=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_any.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_any.py new file mode 100755 index 0000000..890a1f0 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_any.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import os +import argparse +import concurrent.futures + +from flux.job import JobspecV1, FluxExecutor + + +def main(): + # parse command line + parser = argparse.ArgumentParser() + parser.add_argument("njobs", nargs="?", type=int, default=10) + args = parser.parse_args() + # create jobspec for compute.py + compute_jobspec = JobspecV1.from_command( + command=["./compute.py", "10"], num_tasks=4, num_nodes=2, cores_per_task=2 + ) + compute_jobspec.cwd = os.getcwd() + compute_jobspec.environment = dict(os.environ) + # create bad jobspec that will fail + bad_jobspec = JobspecV1.from_command(["/bin/false"]) + # create an executor to submit jobs + with FluxExecutor() as executor: + futures = [] + # submit half successful jobs and half failures + for _ in range(args.njobs // 2): + futures.append(executor.submit(compute_jobspec)) + print(f"submit: {id(futures[-1])} compute_jobspec") + for _ in range(args.njobs // 2, args.njobs): + futures.append(executor.submit(bad_jobspec)) + print(f"submit: {id(futures[-1])} bad_jobspec") + for fut in concurrent.futures.as_completed(futures): + if fut.exception() is not None: + print(f"wait: {id(fut)} Error: job raised error {fut.exception()}") + elif fut.result() == 0: + print(f"wait: {id(fut)} Success") + else: + print(f"wait: {id(fut)} Error: job returned exit code {fut.result()}") + + +if __name__ == "__main__": + main() + +# vim: tabstop=4 shiftwidth=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_in_order.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_in_order.py new file mode 100755 index 0000000..cad6491 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/submitter_wait_in_order.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import argparse +import os + +from flux.job import JobspecV1, FluxExecutor + + +def main(): + # parse command line + parser = argparse.ArgumentParser() + parser.add_argument("njobs", nargs="?", type=int, default=10) + args = parser.parse_args() + # create jobspec for compute.py + compute_jobspec = JobspecV1.from_command( + command=["./compute.py", "10"], num_tasks=4, num_nodes=2, cores_per_task=2 + ) + compute_jobspec.cwd = os.getcwd() + compute_jobspec.environment = dict(os.environ) + bad_jobspec = JobspecV1.from_command(["/bin/false"]) + # create an executor to submit jobs + with FluxExecutor() as executor: + futures = [] + # submit half successful jobs and half failures + for _ in range(args.njobs // 2): + futures.append(executor.submit(compute_jobspec)) + print(f"submit: {id(futures[-1])} compute_jobspec") + for _ in range(args.njobs // 2, args.njobs): + futures.append(executor.submit(bad_jobspec)) + print(f"submit: {id(futures[-1])} bad_jobspec") + # wait for each future in turn + for fut in futures: + if fut.exception() is not None: + print(f"wait: {id(fut)} Error: job raised error {fut.exception()}") + elif fut.result() == 0: + print(f"wait: {id(fut)} Success") + else: + print(f"wait: {id(fut)} Error: job returned exit code {fut.result()}") + + +if __name__ == "__main__": + main() + +# vim: tabstop=4 shiftwidth=4 expandtab 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 new file mode 100755 index 0000000..7784c52 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-watch/job-watch.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo "25 blueberry pancakes on the table... 25 blueberry pancakes! 🥞️" +sleep 3 +echo "Eat a stack, for a snack, 15 blueberry pancakes on the table! 🥄️" +sleep 3 +echo "15 blueberry pancakes on the table... 15 blueberry pancakes! 🥞️" +sleep 2 +echo "Throw a stack... it makes a smack! 15 blueberry pancakes on the wall! 🥞️" +sleep 2 +echo "You got some cleaning to do 🧽️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md new file mode 100644 index 0000000..0a67026 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md @@ -0,0 +1,63 @@ +## KVS Python Binding Example + +### Description: Use the KVS Python interface to store user data into KVS + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/kvs-python-bindings +``` + +1. Launch a Flux instance by running `flux start`, redirecting log messages to the file `out` in the current directory: + +`flux start -s 1 -o,-S,log-filename=out` + +2. Submit the Python script: + +`flux mini submit -N 1 -n 1 ./kvsput-usrdata.py` + +``` +6705031151616 +``` + +3. Attach to the job and view output: + +`flux job attach 6705031151616` + +``` +hello world +hello world again +``` + +4. Each job is run within a KVS namespace. `FLUX_KVS_NAMESPACE` is set, which is automatically read and used by the KVS operations in the handle. To take a look at the job's KVS, convert its job ID to KVS: + +`flux job id --from=dec --to=kvs 6705031151616` + +``` +job.0000.0619.2300.0000 +``` + +5. The keys for this job will be put at the root of the namespace, which is mounted under "guest". To get the value stored under the first key "usrdata": + +`flux kvs get job.0000.0619.2300.0000.guest.usrdata` + +``` +"hello world" +``` + +6. Get the value stored under the second key "usrdata2": + +`flux kvs get job.0000.0619.2300.0000.guest.usrdata2` + +``` +"hello world again" +``` + +### Notes + +- `f = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. + +- `kvs.put()` places the value of _udata_ under the key **"usrdata"**. Once the key-value pair is put, the change must be committed with `kvs.commit()`. The value can then be retrieved with `kvs.get()` + +- `kvs.get()` on a directory will return a KVSDir object which supports the `with` compound statement. `with` guarantees a commit is called on the directory. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/kvsput-usrdata.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/kvsput-usrdata.py new file mode 100755 index 0000000..0a5cb77 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/kvsput-usrdata.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import sys +import flux +import os +from flux import kvs + +f = flux.Flux() +udata = "hello world" +# using function interface +kvs.put(f, "usrdata", udata) +# commit is required to effect the above put op to the server +kvs.commit(f) +print(kvs.get(f, "usrdata")) + +# get() on a directory will return a KVSDir object which supports +# the "with" compound statement. "with" guarantees a commit is called +# on the directory. +with kvs.get(f, ".") as kd: + kd["usrdata2"] = "hello world again" + +print(kvs.get(f, "usrdata2")) + +# vi: ts=4 sw=4 expandtab diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/requirements.txt b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/requirements.txt new file mode 100644 index 0000000..1463f8f --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/requirements.txt @@ -0,0 +1,3 @@ +sphinx-rtd-theme +sphinxcontrib-spelling +recommonmark diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md new file mode 100644 index 0000000..3fa4b53 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md @@ -0,0 +1,51 @@ +### Using Events with Separate Nodes + +#### Description: Using events to synchronize compute and io-forwarding jobs running on separate nodes + +If you haven't already, download the files and change your working directory: + +``` +$ git clone https://github.com/flux-framework/flux-workflow-examples.git +$ cd flux-workflow-examples/synchronize-events +``` + +1. `salloc -N3 -ppdebug` + +2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` + +3. `flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 120` + +**Output -** `225284456448` + +4. `flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120` + +**Output -** `344889229312` + +5. List running jobs: + +`flux jobs` + +``` +JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS +ƒA4TgT7d moussa1 io-forward R 1 1 4.376s 2 +ƒ6vEcj7M moussa1 compute.lu R 4 2 11.51s [0-1] +``` + +6. Attach to running or completed job output: + +`flux job attach ƒ6vEcj7M` + +``` +Block until we hear go message from the an io forwarder +Block until we hear go message from the an io forwarder +Recv an event: please proceed +Recv an event: please proceed +Will compute for 120 seconds +Will compute for 120 seconds +Block until we hear go message from the an io forwarder +Block until we hear go message from the an io forwarder +Recv an event: please proceed +Recv an event: please proceed +Will compute for 120 seconds +Will compute for 120 seconds +``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/compute.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/compute.lua new file mode 100755 index 0000000..925be4c --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/compute.lua @@ -0,0 +1,23 @@ +#!/usr/bin/env lua + +local f, err = require 'flux' .new () + +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: compute.lua seconds") + print (" Compute for seconds") + os.exit (1) +end + +print ("Block until we hear go message from the an io forwarder") +f:subscribe ("app.iof.go") +local t, tag = f:recv_event () +print ("Recv an event: " .. t.data ) +print ("Will compute for " .. amount .. " seconds") +sleep (amount) + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/io-forwarding.lua b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/io-forwarding.lua new file mode 100755 index 0000000..bad77f8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/io-forwarding.lua @@ -0,0 +1,23 @@ +#!/usr/bin/env lua + +local flux = require 'flux' +local f = flux.new () +local amount = tonumber (arg[1]) or 120 + +local function sleep (n) + os.execute ("sleep " .. n) +end + +if #arg ~= 1 then + print ("Usage: io-forward.lua seconds") + print (" Forward I/O requests for seconds") + os.exit (1) +end + +local rc, err = f:sendevent ({ data = "please proceed" }, "app.iof.go") +if not rc then error (err) end +print ("Sent a go event") + +print ("Will forward IO requests for " .. amount .. " seconds") +sleep (amount) + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb new file mode 100644 index 0000000..98def89 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/01_flux_tutorial.ipynb @@ -0,0 +1,2584 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2507d149-dcab-458a-a554-37388e0ee13a", + "metadata": { + "tags": [] + }, + "source": [ + "
\n", + "
\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "40e867ba-f689-4301-bb60-9a448556bb84", + "metadata": { + "tags": [] + }, + "source": [ + "# Welcome to the Flux Tutorial\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, 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", + "> 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", + "\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", + "\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, + "id": "d71ecd22-8552-4b4d-9bc4-61d86f8d33fe", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%html\n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "15e82c38-8465-49ac-ae2b-b0bb56a79ec9", + "metadata": { + "tags": [] + }, + "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", + "\n", + "## Resources\n", + "\n", + "> Looking for other resources? We got you covered! 🤓️\n", + "\n", + " - [https://flux-framework.org/](https://flux-framework.org/) Flux Framework portal for projects, releases, and publication.\n", + " - [Flux Documentation](https://flux-framework.readthedocs.io/en/latest/).\n", + " - [Flux Framework Cheat Sheet](https://flux-framework.org/cheat-sheet/)\n", + " - [Flux Glossary of Terms](https://flux-framework.readthedocs.io/en/latest/glossary.html)\n", + " - [Flux Comics](https://flux-framework.readthedocs.io/en/latest/comics/fluxonomicon.html) come and meet FluxBird - the pink bird who knows things!\n", + " - [Flux Learning Guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html) learn about what Flux does, how it works, and real research applications \n", + " - [Getting Started with Flux and Go](https://converged-computing.github.io/flux-go/)\n", + " - [Getting Started with Flux in C](https://converged-computing.github.io/flux-c-examples/) *looking for contributors*\n", + "\n", + "To read the Flux manpages and get help, run `flux help`. To get documentation on a subcommand, run, e.g. `flux help config`. Here is an example of running `flux help` right from the notebook. Yes, did you know we are running in a Flux Instance right now?" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c7d616de-70cd-4090-bd43-ffacb5ade1f6", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: flux [OPTIONS] COMMAND ARGS\n", + " -h, --help Display this message.\n", + " -v, --verbose Be verbose about environment and command search\n", + " -V, --version Display command and component versions\n", + " -p, --parent Set environment of parent instead of current instance\n", + "\n", + "For general Flux documentation, please visit\n", + " https://flux-framework.readthedocs.io\n", + "\n", + "run and submit jobs, allocate resources\n", + " submit submit a job to a Flux instance\n", + " run run a Flux job interactively\n", + " bulksubmit submit jobs in bulk to a Flux instance\n", + " alloc allocate a new Flux instance for interactive use\n", + " batch submit a batch script to Flux\n", + "\n", + "list and interact with jobs\n", + " jobs list jobs submitted to Flux\n", + " top display running Flux jobs\n", + " pstree display job hierarchies\n", + " cancel cancel one or more jobs\n", + " pgrep/pkill search or cancel matching jobs\n", + " job get job status, info, etc (see: flux help job)\n", + " proxy proxy connections to Flux jobs and instances\n", + " watch monitor one or more Flux jobs\n", + " update update active Flux jobs\n", + "\n", + "get resource, queue and other instance information\n", + " resource list/manipulate Flux resource status\n", + " queue list and manipulate flux queues\n", + " overlay Show flux overlay network status\n", + " uptime Tell how long Flux has been up and running\n", + "\n", + "other useful commands\n", + " start bootstrap a local Flux instance\n", + " version Display flux version information\n", + " config Manage/query Flux configuration\n", + " env Print the flux environment or execute a command inside it\n", + " hostlist fetch, combine, and manipulate Flux hostlists\n", + "\n", + "See 'flux help COMMAND' for more information about a specific command.\n" + ] + } + ], + "source": [ + "!flux help" + ] + }, + { + "cell_type": "markdown", + "id": "ae33fef6-278c-4996-8534-fd15e548b338", + "metadata": { + "tags": [] + }, + "source": [ + "Did you know you can also get help for a specific command? For example, let's run, e.g. `flux help jobs` to get information on a sub-command:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "2e54f640-283a-4523-8dde-9617fd6ef0c5", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# We have commented this out because the output is huge! Feel free to uncomment (remove the #) and run the command\n", + "#!flux help jobs" + ] + }, + { + "cell_type": "markdown", + "id": "17e435d6-0927-4966-a4d7-47a128c94158", + "metadata": { + "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." + ] + }, + { + "cell_type": "markdown", + "id": "70e3df1d-32c9-4996-b6f7-2fa85f4c02ad", + "metadata": { + "tags": [] + }, + "source": [ + "# Creating Flux Instances\n", + "\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", + "\n", + "To start a Flux session with 4 brokers in your container, run:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d568de50-f9e0-452f-8364-e52853013d83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "!flux start --test-size=4 flux getattr size" + ] + }, + { + "cell_type": "markdown", + "id": "e693f2d9-651f-4f58-bf53-62528caa83d9", + "metadata": {}, + "source": [ + "The output indicates the number of brokers started successfully." + ] + }, + { + "cell_type": "markdown", + "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", + "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": "code", + "execution_count": 6, + "id": "6057ce25-d1b3-4cc6-b26a-4b05a1639616", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 05:01:16 run 54m, owner jovyan, depth 0, size 4\n" + ] + } + ], + "source": [ + "!flux uptime" + ] + }, + { + "cell_type": "markdown", + "id": "dee2d6af-43fa-490e-88e9-10f13e660125", + "metadata": { + "tags": [] + }, + "source": [ + "# 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", + "\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
One-off submit of a single job (not interactive)srunflux submit
One-off submit of a single job (interactive)srun --ptyflux run
Submitting batch jobssbatchflux batch
Submiting interactive jobssallocflux alloc
Querying the status of jobssqueue/scontrol show job job_idflux jobs/flux job info job_id
Cancelling running jobsscancelflux cancel
\n", + "\n", + "## Submission CLI\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. " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRovbySzK\n" + ] + } + ], + "source": [ + "!flux submit hostname" + ] + }, + { + "cell_type": "markdown", + "id": "a7e4c25e-3ca8-4277-bb70-a0e94bcd223b", + "metadata": {}, + "source": [ + "`submit` supports common options like `--nnodes`, `--ntasks`, and `--cores-per-task`. There are short option equivalents (`-N`, `-n`, and `-c`, respectively) of these options as well. `--cores-per-task=1` is the default." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "571d8c3d-b24a-415e-b9ac-f58b99a7e92c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRp4rqT1h\n" + ] + } + ], + "source": [ + "!flux submit -N1 -n2 sleep inf" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "usage: flux submit [OPTIONS...] COMMAND [ARGS...]\n", + "\n", + "enqueue a job\n", + "\n", + "positional arguments:\n", + " command Job command and arguments\n", + "\n", + "optional arguments:\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", + " otherwise in Flux standard duration, e.g. 30s,\n", + " 2d, 1.5h\n", + " --urgency=N Set job urgency (0-31), hold=0, default=16,\n", + " expedite=31\n" + ] + } + ], + "source": [ + "# Let's peek at the help for flux submit!\n", + "!flux submit --help | head -n 15" + ] + }, + { + "cell_type": "markdown", + "id": "ac798095", + "metadata": {}, + "source": [ + "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "993a4f746854\n" + ] + } + ], + "source": [ + "!flux run hostname" + ] + }, + { + "cell_type": "markdown", + "id": "53357a9d-11d8-4c2d-87d8-c30ae38d01ba", + "metadata": {}, + "source": [ + "The output from the previous command is the hostname (a container ID string in this case). If the job exits with a non-zero exit code this will be reported by `flux job attach` (occurs implicitly with `flux run`). For example, execute the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "flux-job: task(s) exited with exit code 1\n" + ] + } + ], + "source": [ + "!flux run /bin/false" + ] + }, + { + "cell_type": "markdown", + "id": "6b2b5c3f-e24a-45a8-a10c-e10bfdbb7b87", + "metadata": {}, + "source": [ + "A job submitted with `run` can be canceled with two rapid `Cltr-C`s in succession, or a user can detach from the job with `Ctrl-C Ctrl-Z`. The user can then re-attach to the job by using `flux job attach JOBID`." + ] + }, + { + "cell_type": "markdown", + "id": "81e5213d", + "metadata": {}, + "source": [ + "`flux submit` and `flux run` also support many other useful flags:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "02032748", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3: 993a4f746854\n", + "2: 993a4f746854\n", + "1: 993a4f746854\n", + "0: 993a4f746854\n" + ] + } + ], + "source": [ + "!flux run -n4 --label-io --time-limit=5s --env-remove=LD_LIBRARY_PATH hostname" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f52bb357-a7ce-458d-9c3f-4d664eca4fbd", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment and run this help command if you want to see all the flags for flux run\n", + "# !flux run --help" + ] + }, + { + "cell_type": "markdown", + "id": "91e9ed6c", + "metadata": {}, + "source": [ + "The `flux bulksubmit` command enqueues jobs based on a set of inputs which are substituted on the command line, similar to `xargs` and the GNU `parallel` utility, except the jobs have access to the resources of an entire Flux instance instead of only the local system." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f0e82702", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRptxz9Nf\n", + "ƒRptxz9Ng\n", + "ƒRptxz9Nh\n", + "bar\n", + "foo\n", + "baz\n" + ] + } + ], + "source": [ + "!flux bulksubmit --watch --wait echo {} ::: foo bar baz" + ] + }, + { + "cell_type": "markdown", + "id": "392a8056-1661-4b76-9ca3-5e536c687e82", + "metadata": {}, + "source": [ + "The `--cc` option to `submit` makes repeated submission even easier via, `flux submit --cc=IDSET`:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0ea1962b-1831-4bd2-8dab-c61fd710df9c", + "metadata": {}, + "outputs": [ + { + "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" + ] + } + ], + "source": [ + "!flux submit --cc=1-10 --watch hostname" + ] + }, + { + "cell_type": "markdown", + "id": "27ca3706-8bb4-4fd6-a37c-e6135fb05604", + "metadata": {}, + "source": [ + "Try it in the JupyterLab terminal with a progress bar and jobs/s rate report: `flux submit --cc=1-100 --watch --progress --jps hostname`\n", + "\n", + "Note that `--wait` is implied by `--watch`." + ] + }, + { + "cell_type": "markdown", + "id": "4c5a18ff-8d6a-47e9-a164-931ed1275ef4", + "metadata": {}, + "source": [ + "Of course, Flux can launch more than just single-node, single-core jobs. We can submit multiple heterogeneous jobs and Flux will co-schedule the jobs while also ensuring no oversubscription of resources (e.g., cores).\n", + "\n", + "Note: in this tutorial, we cannot assume that the host you are running on has multiple cores, thus the examples below only vary the number of nodes per job. Varying the `cores-per-task` is also possible on Flux when the underlying hardware supports it (e.g., a multi-core node)." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "brazilian-former", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRqJMWpEB\n", + "ƒRqTXkNPy\n" + ] + } + ], + "source": [ + "!flux submit --nodes=2 --ntasks=2 --cores-per-task=1 --job-name simulation sleep inf\n", + "!flux submit --nodes=1 --ntasks=1 --cores-per-task=1 --job-name analysis sleep inf" + ] + }, + { + "cell_type": "markdown", + "id": "641f446c-b2e8-40d8-b6bd-eb6b9dba3c71", + "metadata": {}, + "source": [ + "### `flux watch` to watch jobs\n", + "\n", + "Wouldn't it be cool to submit a job and then watch it? Well, yeah! We can do this now with flux watch. Let's run a fun example, and then watch the output. We have sleeps in here interspersed with echos only to show you the live action! 🥞️\n", + "Also note a nice trick - you can always use `flux job last` to get the last JOBID.\n", + "Here is an example (not runnable, as notebooks don't support environment variables) for getting and saving a job id:\n", + "\n", + "```bash\n", + "flux submit hostname\n", + "JOBID=$(flux job last)\n", + "```\n", + "\n", + "And then you could use the variable `$JOBID` in your subsequent script or interactions with Flux! So what makes `flux watch` different from `flux job attach`? Aside from the fact that `flux watch` is read-only, `flux watch` can watch many (or even all (`flux watch --all`) jobs at once!" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5ad231c2-4cdb-4d18-afc2-7cb3a74759c2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRqcDpADD\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", + "Throw a stack... it makes a smack! 15 blueberry pancakes on the wall! 🥞️\n", + "You got some cleaning to do 🧽️\n" + ] + } + ], + "source": [ + "!flux submit ../flux-workflow-examples/job-watch/job-watch.sh\n", + "!flux watch $(flux job last)" + ] + }, + { + "cell_type": "markdown", + "id": "3f8c2af2", + "metadata": {}, + "source": [ + "### Listing job properties with `flux jobs`\n", + "\n", + "We can now list the jobs in the queue with `flux jobs` and we should see both jobs that we just submitted. Jobs that are instances are colored blue in output, red jobs are failed jobs, and green jobs are those that completed successfully. Note that the JupyterLab notebook may not display these colors. You will be able to see them in the terminal." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "institutional-vocabulary", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "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" + ] + } + ], + "source": [ + "!flux jobs" + ] + }, + { + "cell_type": "markdown", + "id": "77ca4277", + "metadata": {}, + "source": [ + "Since those jobs won't ever exit (and we didn't specify a timelimit), let's cancel them all now and free up the resources." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "46dd8ec8-6c64-4d8d-9a00-949f5f58c07b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "flux-cancel: Canceled 3 jobs (0 errors)\n", + " JOBID USER NAME ST NTASKS NNODES TIME INFO\n" + ] + } + ], + "source": [ + "# This was previously flux cancelall -f\n", + "!flux cancel --all\n", + "!flux jobs" + ] + }, + { + "cell_type": "markdown", + "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", + "\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", + "Note: Flux also provides a `flux alloc` which is an interactive version of `flux batch`, but demonstrating that in a Jupyter notebook is difficult due to the lack of pseudo-terminal." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "blank-carpet", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRvcKDdSf\n", + "ƒRvkLjjvw\n" + ] + } + ], + "source": [ + "!flux batch --nslots=2 --cores-per-slot=1 --nodes=2 ./sleep_batch.sh\n", + "!flux batch --nslots=2 --cores-per-slot=1 --nodes=2 ./sleep_batch.sh" + ] + }, + { + "cell_type": "markdown", + "id": "da98bfa1", + "metadata": {}, + "source": [ + "The contents of `sleep_batch.sh`:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "381a3f6c-0da1-4923-801f-486ca5226d3c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
#!/bin/bash\n",
+       "#FLUX: --nodes=2\n",
+       "#FLUX: --nslots=2\n",
+       "#FLUX: --cores-per-slot=1\n",
+       "\n",
+       "echo "Starting my batch job"\n",
+       "echo "Print the resources allocated to this batch job"\n",
+       "flux resource list\n",
+       "\n",
+       "echo "Use sleep to emulate a parallel program"\n",
+       "echo "Run the program at a total of 2 processes each requiring"\n",
+       "echo "1 core. These processes are equally spread across 2 nodes."\n",
+       "flux run -N 2 -n 2 sleep 30\n",
+       "flux run -N 2 -n 2 sleep 30\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", + "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}nodes=2}\n", + "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}nslots=2}\n", + "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}cores\\PYZhy{}per\\PYZhy{}slot=1}\n", + "\n", + "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Starting my batch job\\PYZdq{}}\n", + "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Print the resources allocated to this batch job\\PYZdq{}}\n", + "flux\\PY{+w}{ }resource\\PY{+w}{ }list\n", + "\n", + "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Use sleep to emulate a parallel program\\PYZdq{}}\n", + "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Run the program at a total of 2 processes each requiring\\PYZdq{}}\n", + "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}1 core. These processes are equally spread across 2 nodes.\\PYZdq{}}\n", + "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }\\PYZhy{}n\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", + "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }\\PYZhy{}n\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "#!/bin/bash\n", + "#FLUX: --nodes=2\n", + "#FLUX: --nslots=2\n", + "#FLUX: --cores-per-slot=1\n", + "\n", + "echo \"Starting my batch job\"\n", + "echo \"Print the resources allocated to this batch job\"\n", + "flux resource list\n", + "\n", + "echo \"Use sleep to emulate a parallel program\"\n", + "echo \"Run the program at a total of 2 processes each requiring\"\n", + "echo \"1 core. These processes are equally spread across 2 nodes.\"\n", + "flux run -N 2 -n 2 sleep 30\n", + "flux run -N 2 -n 2 sleep 30\n" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Code\n", + "Code(filename='sleep_batch.sh', language='bash')" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "edff8993-3c39-4f46-939d-4c8be5739fbc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒRvuBhTSF\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[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[0;0m\n", + "ƒRvkLjjvw:\n", + " ƒKZL1bM jovyan sleep R 2 2 0.060s 993a4f[746854,746854]\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", + "0: stdout redirected to /tmp/cheese.txt\n", + "0: stderr redirected to /tmp/cheese.txt\n" + ] + }, + { + "data": { + "text/html": [ + "
Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Here we are submitting a job that generates output, and asking to write it to /tmp/cheese.txt\n", + "!flux submit --out /tmp/cheese.txt echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\"\n", + "\n", + "# This will show us JOBIDs\n", + "!flux jobs\n", + "\n", + "# We can even see jobs in sub-instances with \"-R\" (for recursive)\n", + "!flux jobs -R\n", + "\n", + "# You could copy a JOBID from above and paste it in the line below to examine the job's resources and output\n", + "# or get the last jobid with \"flux job last\" (this is what we will do here)\n", + "# JOBID=\"ƒFoRYVpt7\"\n", + "\n", + "# Note here we are using flux job last to see the last one\n", + "# The \"R\" here asks for the resource spec\n", + "!flux job info $(flux job last) R\n", + "\n", + "# When we attach it will direct us to our output file\n", + "!flux job attach $(flux job last)\n", + "\n", + "# And we can look at the output file to see our expected output!\n", + "from IPython.display import Code\n", + "Code(filename='/tmp/cheese.txt', language='text')" + ] + }, + { + "cell_type": "markdown", + "id": "f4e525e2-6c89-4c14-9fae-d87a0d4fc574", + "metadata": {}, + "source": [ + "To list all completed jobs, run `flux jobs -a`:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "df8a8b7c-f475-4a51-8bc6-9983dc9d78ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "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[0;0m" + ] + } + ], + "source": [ + "!flux jobs -a" + ] + }, + { + "cell_type": "markdown", + "id": "3e415ecc-f451-4909-a2bf-351a639cd7fa", + "metadata": {}, + "source": [ + "To restrict the output to failed (i.e., jobs that exit with nonzero exit code, time out, or are canceled or killed) jobs, run:" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "032597d2-4b02-47ea-a5e5-915313cdd7f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "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[0;0m" + ] + } + ], + "source": [ + "!flux jobs -f failed" + ] + }, + { + "cell_type": "markdown", + "id": "04b405b1-219f-489c-abfc-e2983e82124a", + "metadata": {}, + "source": [ + "# 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", + "\n", + "![img/single-submit.png](img/single-submit.png)\n", + "\n", + "The throughput is limited by the workload manager's ability to process a single job. We can improve upon this by simply adding another level, perhaps with three instances. For example, let's say we create a flux allocation or batch that has control of some number of child nodes. We might launch three new instances (each with its own scheduler and queue) at that level two, and all of a sudden, we get a throughput of 1x3, or three jobs per second. \n", + "\n", + "![img/instance-submit.png](img/instance-submit.png)\n", + "\n", + "\n", + "All of a sudden, the throughout can increase exponentially because we are essentially submitting to different schedulers. The example above is not impressive, but our [learning guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html#fully-hierarchical-resource-management-techniques) (Figure 10) has a beautiful example of how it can scale, done via an actual experiment. We were able to submit 500 jobs/second using only three levels, vs. close to 1 job/second with one level. \n", + "\n", + "![img/scaled-submit.png](img/scaled-submit.png)\n", + "\n", + "And for an interesting detail, you can vary the scheduler algorithm or topology within each sub-instance, meaning that you can do some fairly interesting things with scheduling work, and all without stressing the top level system instance. Next, let's look at a prototype tool called `flux-tree` that you can use to see how this works.\n", + "\n", + "## Flux tree\n", + "\n", + "Flux tree is a prototype tool that allows you to easily submit work to different levels of your flux instance, or more specifically, creating a nested hierarchy of jobs that scale out. Let's run the command, look at the output, and talk about it." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "2735b1ca-e761-46be-b509-a86b771628fc", + "metadata": {}, + "outputs": [ + { + "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" + ] + } + ], + "source": [ + "!flux tree -T2x2 -J 4 -N 1 -c 4 -o ./tree.out -Q easy:fcfs hostname \n", + "! cat ./tree.out" + ] + }, + { + "cell_type": "markdown", + "id": "9d5fe7a0-af54-4c90-be6f-75f50c918dea", + "metadata": {}, + "source": [ + "In the above, we are running `flux-tree` and looking at the output file. What is happening is that the `flux tree` command is creating a hierarchy of instances. Based on their names you can tell that:\n", + "\n", + " - `2x2` in the command is the topology\n", + " - It says to create two flux instances, and make them each spawn two more.\n", + " - `tree` is the root\n", + " - `tree.1` is the first instance\n", + " - `tree.2` is the second instance\n", + " - `tree.1.1` and `tree.1.2` refer to the nested instances under `tree.1`\n", + " - `tree.2.1` and `tree.2.2` refer to the nested instances under `tree.2`\n", + " \n", + "And we provided the command `hostname` to this script, but a more complex example would generate more interested hierarchies,\n", + "and with differet functionality for each. Note that although this is just a dummy prototype, you could use `flux-tree` for actual work,\n", + "or more likely, you would want to use `flux batch` to submit multiple commands within a single flux instance to take advantage of the same\n", + "hierarchy. \n", + "\n", + "## Flux batch\n", + "\n", + "Next, let's look at an example that doesn't use `flux tree` but instead uses `flux batch`, which is how you will likely interact with your nested instances. Let's start with a batch script `hello-batch.sh`.\n", + "\n", + "##### hello-batch.sh\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "e82863e5-b2a1-456b-9ff1-f669b3525fa1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
#!/bin/bash\n",
+       "\n",
+       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-1.out echo "Hello job 1 from $(hostname) 💛️"\n",
+       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-2.out echo "Hello job 2 from $(hostname) 💚️"\n",
+       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-3.out echo "Hello job 3 from $(hostname) 💙️"\n",
+       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-4.out echo "Hello job 4 from $(hostname) 💜️"\n",
+       "# Wait for the jobs to finish\n",
+       "flux job wait --all\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", + "\n", + "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}1.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 1 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💛️}\\PY{l+s+s2}{\\PYZdq{}}\n", + "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}2.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 2 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💚️}\\PY{l+s+s2}{\\PYZdq{}}\n", + "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}3.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 3 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💙️}\\PY{l+s+s2}{\\PYZdq{}}\n", + "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}4.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 4 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💜️}\\PY{l+s+s2}{\\PYZdq{}}\n", + "\\PY{c+c1}{\\PYZsh{} Wait for the jobs to finish}\n", + "flux\\PY{+w}{ }job\\PY{+w}{ }\\PY{n+nb}{wait}\\PY{+w}{ }\\PYZhy{}\\PYZhy{}all\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "#!/bin/bash\n", + "\n", + "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-1.out echo \"Hello job 1 from $(hostname) 💛️\"\n", + "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-2.out echo \"Hello job 2 from $(hostname) 💚️\"\n", + "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-3.out echo \"Hello job 3 from $(hostname) 💙️\"\n", + "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-4.out echo \"Hello job 4 from $(hostname) 💜️\"\n", + "# Wait for the jobs to finish\n", + "flux job wait --all" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Code\n", + "Code(filename='hello-batch.sh', language='bash')" + ] + }, + { + "cell_type": "markdown", + "id": "6bc17bac-2fc4-4418-8939-e930f9929976", + "metadata": {}, + "source": [ + "We would provide this script to run with `flux batch` that is going to:\n", + "\n", + "1. Create a flux instance with the top level resources you specify\n", + "2. Submit jobs to the scheduler controlled by the broker of that sub-instance\n", + "3. Run the four jobs, with `--flags=waitable` and `flux job wait --all` to wait for the output file\n", + "4. Within the batch script, you can add `--wait` or `--flags=waitable` to individual jobs, and use `flux queue drain` to wait for the queue to drain, _or_ `flux job wait --all` to wait for the jobs you flagged to finish. \n", + "\n", + "Note that when you submit a batch job, you'll get a job id back for the _batch job_, and usually when you look at the output of that with `flux job attach $jobid` you will see the output file(s) where the internal contents are written. Since we want to print the output file easily to the terminal, we are waiting for the batch job by adding the `--flags=waitable` and then waiting for it. Let's try to run our batch job now." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "72358a03-6f1f-4c5e-91eb-cab71883a232", + "metadata": {}, + "outputs": [ + { + "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" + ] + } + ], + "source": [ + "! flux batch --flags=waitable --out /tmp/flux-batch.out -N2 ./hello-batch.sh\n", + "! flux job wait\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", + "id": "75c0ae3f-2813-4ae8-83be-00be3df92a4b", + "metadata": {}, + "source": [ + "Excellent! Now let's look at another batch example. Here we have two job scripts:\n", + "\n", + "- sub_job1.sh: Is going to be run with `flux batch` and submit sub_job2.sh\n", + "- sub_job2.sh: Is going to be submit by sub_job1.sh.\n", + "\n", + "You can see that below." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "2e6976f8-dbb6-405e-a06b-47c571aa1cdf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
#!/bin/bash\n",
+       "\n",
+       "flux batch -N1 ./sub_job2.sh\n",
+       "flux queue drain\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", + "\n", + "flux\\PY{+w}{ }batch\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }./sub\\PYZus{}job2.sh\n", + "flux\\PY{+w}{ }queue\\PY{+w}{ }drain\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "#!/bin/bash\n", + "\n", + "flux batch -N1 ./sub_job2.sh\n", + "flux queue drain\n" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Code(filename='sub_job1.sh', language='bash')" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "a0719cc9-6bf2-4285-b5d7-6cc534fc364c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
#!/bin/bash\n",
+       "\n",
+       "flux run -N1 sleep 30\n",
+       "
\n" + ], + "text/latex": [ + "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", + "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", + "\n", + "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", + "\\end{Verbatim}\n" + ], + "text/plain": [ + "#!/bin/bash\n", + "\n", + "flux run -N1 sleep 30\n" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Code(filename='sub_job2.sh', language='bash')" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "8640a611-38e4-42b1-a913-89e0c76c8014", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒSRXzaub5\n" + ] + } + ], + "source": [ + "# Submit it!\n", + "!flux batch -N1 ./sub_job1.sh" + ] + }, + { + "cell_type": "markdown", + "id": "b29c3a4a-2b77-4ab9-8e0c-9f5228e61016", + "metadata": {}, + "source": [ + "And now that we've submit, let's look at the hierarchy for all the jobs we just ran. Here is how to try flux pstree, which normally can show jobs in an instance, but it has limited functionality given we are in a notebook! So instead of just running the single command, let's add \"-a\" to indicate \"show me ALL jobs.\"\n", + "More complex jobs and in a different environment would have deeper nesting. You can [see examples here](https://flux-framework.readthedocs.io/en/latest/jobs/hierarchies.html?h=pstree#flux-pstree-command)." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "2d2b1f0b-e6c2-4583-8068-7c76fa341884", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".\n", + "├── ./sub_job1.sh\n", + "├── ./hello-batch.sh:CD\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", + "├── 13*[hostname:CD]\n", + "└── false:F\n" + ] + } + ], + "source": [ + "!flux pstree -a" + ] + }, + { + "cell_type": "markdown", + "id": "7724130f-b0db-4ccf-a01e-98907b9a27ca", + "metadata": {}, + "source": [ + "You can also try a more detailed view with `flux pstree -a -X`!" + ] + }, + { + "cell_type": "markdown", + "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", + "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", + "id": "a609b2f8-e24d-40c7-b022-ce02e91a49f8", + "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", + "id": "997faffc", + "metadata": {}, + "source": [ + "## 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, + "id": "third-comment", + "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": 33, + "id": "selective-uganda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒSRjxR7UT\n" + ] + } + ], + "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", + "id": "0c4b260f-f08a-46ae-ad66-805911a857a7", + "metadata": {}, + "source": [ + "### `flux.job.get_job(handle, jobid)` to get job info" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ed65cb46-8d8a-41f0-bec1-92b9a89e6db2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "🎉️ Hooray, we just submitted ƒSRkkNjD9!\n", + "{\n", + " \"t_depend\": 1712898157.9034483,\n", + " \"t_run\": 0.0,\n", + " \"t_cleanup\": 0.0,\n", + " \"t_inactive\": 0.0,\n", + " \"duration\": 0.0,\n", + " \"expiration\": 0.0,\n", + " \"name\": \"compute.py\",\n", + " \"cwd\": \"/home/jovyan/flux-tutorial/flux-workflow-examples/job-submit-api/\",\n", + " \"queue\": \"\",\n", + " \"project\": \"\",\n", + " \"bank\": \"\",\n", + " \"ntasks\": 1,\n", + " \"ncores\": 1,\n", + " \"nnodes\": 1,\n", + " \"priority\": 16,\n", + " \"ranks\": \"\",\n", + " \"nodelist\": \"\",\n", + " \"success\": \"\",\n", + " \"result\": \"\",\n", + " \"waitstatus\": \"\",\n", + " \"id\": 56141966999552,\n", + " \"t_submit\": 1712898157.8924408,\n", + " \"t_remaining\": 0.0,\n", + " \"state\": \"SCHED\",\n", + " \"username\": \"jovyan\",\n", + " \"userid\": 1000,\n", + " \"urgency\": 16,\n", + " \"runtime\": 0.0,\n", + " \"status\": \"SCHED\",\n", + " \"returncode\": \"\",\n", + " \"dependencies\": [],\n", + " \"annotations\": {},\n", + " \"exception\": {\n", + " \"occurred\": \"\",\n", + " \"severity\": \"\",\n", + " \"type\": \"\",\n", + " \"note\": \"\"\n", + " }\n", + "}\n" + ] + } + ], + "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": 35, + "id": "5d679897-7054-4f96-b340-7f39245aca89", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " ƒSRkkNjD9 jovyan compute.py F 1 1 0.014s 993a4f746854\n", + " ƒSRjxR7UT jovyan compute.py F 1 1 0.019s 993a4f746854\n" + ] + } + ], + "source": [ + "!flux jobs -a | grep compute" + ] + }, + { + "cell_type": "markdown", + "id": "d332f9c9", + "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": 36, + "id": "efa06478", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"resources\": [\n", + " {\n", + " \"type\": \"node\",\n", + " \"count\": 1,\n", + " \"with\": [\n", + " {\n", + " \"type\": \"slot\",\n", + " \"count\": 1,\n", + " \"with\": [\n", + " {\n", + " \"type\": \"core\",\n", + " \"count\": 1\n", + " }\n", + " ],\n", + " \"label\": \"task\"\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"tasks\": [\n", + " {\n", + " \"command\": [\n", + " \"./compute.py\",\n", + " \"120\"\n", + " ],\n", + " \"slot\": \"task\",\n", + " \"count\": {\n", + " \"per_slot\": 1\n", + " }\n", + " }\n", + " ],\n", + " \"attributes\": {\n", + " \"system\": {\n", + " \"duration\": 0,\n", + " \"cwd\": \"/home/jovyan/flux-tutorial/flux-workflow-examples/job-submit-api/\"\n", + " }\n", + " },\n", + " \"version\": 1\n", + "}\n" + ] + } + ], + "source": [ + "print(compute_jobreq.dumps(indent=2))" + ] + }, + { + "cell_type": "markdown", + "id": "73bbc90e", + "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": 37, + "id": "industrial-privacy", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒSRuYNU9m\n", + "ƒSRv8UBdh\n" + ] + } + ], + "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": 38, + "id": "pregnant-creativity", + "metadata": {}, + "outputs": [ + { + "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" + ] + } + ], + "source": [ + "!flux jobs -a | grep compute" + ] + }, + { + "cell_type": "markdown", + "id": "a8051640", + "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", + "id": "binary-trace", + "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": 39, + "id": "cleared-lawsuit", + "metadata": {}, + "outputs": [ + { + "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" + ] + } + ], + "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" + ] + }, + { + "cell_type": "markdown", + "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", + "\n", + "You can access the topology information that Flux collects with the `flux resource` subcommand:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "id": "scenic-chassis", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " STATE NNODES NCORES NGPUS NODELIST\n", + " free 3 30 0 993a4f[746854,746854,746854]\n", + " allocated 1 10 0 993a4f746854\n", + " down 0 0 0 \n" + ] + } + ], + "source": [ + "!flux resource list" + ] + }, + { + "cell_type": "markdown", + "id": "0086e47e", + "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": 45, + "id": "prime-equilibrium", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " STATE UP NNODES NODELIST\n", + " avail \u001b[01;32m ✔\u001b[0;0m 4 993a4f[746854,746854,746854,746854]\n" + ] + } + ], + "source": [ + "# To view status of resources\n", + "!flux resource status" + ] + }, + { + "cell_type": "markdown", + "id": "5ee1c49d", + "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": 46, + "id": "800de4eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job submission is disabled: maintenance outage\n", + "Job submission is enabled\n", + "usage: flux-queue [-h] {status,list,enable,disable,start,stop,drain,idle} ...\n", + "\n", + "optional arguments:\n", + " -h, --help show this help message and exit\n", + "\n", + "subcommands:\n", + "\n", + " {status,list,enable,disable,start,stop,drain,idle}\n" + ] + } + ], + "source": [ + "!flux queue disable \"maintenance outage\"\n", + "!flux queue enable\n", + "!flux queue -h" + ] + }, + { + "cell_type": "markdown", + "id": "67aa7559", + "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": 47, + "id": "biblical-generic", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "4\n", + "local:///tmp/flux-rWMT6G/local-0\n", + "broker.boot-method simple\n", + "broker.critical-ranks 0-1\n", + "broker.mapping [[0,1,4,1]]\n", + "broker.pid 8\n", + "broker.quorum 4\n", + "broker.quorum-timeout 1m\n", + "broker.rc1_path /etc/flux/rc1\n", + "broker.rc3_path /etc/flux/rc3\n", + "broker.starttime 1712894811.07\n", + "conf.shell_initrc /etc/flux/shell/initrc.lua\n", + "conf.shell_pluginpath /usr/lib/flux/shell/plugins\n", + "config.path -\n", + "content.backing-module content-sqlite\n", + "content.hash sha1\n", + "hostlist 993a4f[746854,746854,746854,746854]\n", + "instance-level 0\n", + "jobid -\n", + "local-uri local:///tmp/flux-rWMT6G/local-0\n", + "log-critical-level 2\n", + "log-filename -\n", + "log-forward-level 7\n", + "log-level 7\n", + "log-ring-size 1024\n", + "log-stderr-level 3\n", + "log-stderr-mode leader\n", + "parent-kvs-namespace -\n", + "parent-uri -\n", + "rank 0\n", + "rundir /tmp/flux-rWMT6G\n", + "security.owner 1000\n", + "size 4\n", + "statedir -\n", + "tbon.connect_timeout 30s\n", + "tbon.descendants 3\n", + "tbon.endpoint ipc:///tmp/flux-rWMT6G/tbon-0\n", + "tbon.level 0\n", + "tbon.maxlevel 2\n", + "tbon.parent-endpoint -\n", + "tbon.prefertcp 0\n", + "tbon.tcp_user_timeout 20s\n", + "tbon.topo kary:2\n", + "tbon.torpid_max 30s\n", + "tbon.torpid_min 5s\n", + "tbon.zmqdebug 0\n", + "version 0.61.1\n" + ] + } + ], + "source": [ + "!flux getattr rank\n", + "!flux getattr size\n", + "!flux getattr local-uri\n", + "!flux setattr log-stderr-level 3\n", + "!flux lsattr -v" + ] + }, + { + "cell_type": "markdown", + "id": "d74fdfcf", + "metadata": {}, + "source": [ + "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`), and we will interact with the `kvs` module in a few cells. For the most part, services are implemented by modules of the same name (e.g., `kvs` implements the `kvs` service and thus the `kvs.lookup` RPC). In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "id": "spatial-maintenance", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-info 4 R \n", + "sched-fluxion-resource 4 R \n", + "heartbeat 1 R \n", + "job-manager 1 R \n", + "connector-local 0 R \n", + "content-sqlite 2 R content-backing\n", + "kvs 2 R \n", + "resource 2 R \n", + "kvs-watch 4 R \n", + "job-exec 4 R \n", + "barrier idle R \n", + "job-list 4 R \n", + "content 2 R \n", + "sched-fluxion-qmanager 4 R sched\n", + "cron idle R \n", + "job-ingest 4 R \n" + ] + } + ], + "source": [ + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "id": "ad7090eb", + "metadata": {}, + "source": [ + "We can actually unload the Fluxion modules (the scheduler modules from flux-sched) and replace them with `sched-simple` (the scheduler that comes built-into flux-core) as a demonstration of this functionality:" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "id": "df4bc2d5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-info 4 R \n", + "heartbeat 0 R \n", + "job-manager 0 R \n", + "connector-local 0 R \n", + "content-sqlite 3 R content-backing\n", + "kvs 0 R \n", + "resource 0 R \n", + "kvs-watch 4 R \n", + "job-exec 4 R \n", + "sched-simple 0 R sched\n", + "barrier idle R \n", + "job-list 5 R \n", + "content 3 R \n", + "cron idle R \n", + "job-ingest 0 R \n" + ] + } + ], + "source": [ + "!flux module unload sched-fluxion-qmanager\n", + "!flux module unload sched-fluxion-resource\n", + "!flux module load sched-simple\n", + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "id": "722c4ecf", + "metadata": {}, + "source": [ + "We can now reload the Fluxion scheduler, but this time, let's pass some extra arguments to specialize our Flux instance. In particular, let's populate our resource graph with nodes, sockets, and cores and limit the scheduling depth to 4." + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "c34899ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-info 6 R \n", + "heartbeat 1 R \n", + "job-manager 0 R \n", + "connector-local 0 R \n", + "content-sqlite 0 R content-backing\n", + "kvs 0 R \n", + "resource 0 R \n", + "kvs-watch 6 R \n", + "job-exec 0 R \n", + "barrier idle R \n", + "job-list 6 R \n", + "sched-fluxion-resource 0 R \n", + "sched-fluxion-qmanager 0 R sched\n", + "content 0 R \n", + "cron idle R \n", + "job-ingest 1 R \n", + "2024-04-12T05:02:45.207563Z sched-fluxion-qmanager.debug[0]: effective queue params (queue=default): queue-depth=4\n" + ] + } + ], + "source": [ + "!flux dmesg -C\n", + "!flux module unload sched-simple\n", + "!flux module load sched-fluxion-resource load-allowlist=node,socket,core\n", + "!flux module load sched-fluxion-qmanager queue-params=queue-depth=4\n", + "!flux module list\n", + "!flux dmesg | grep queue-depth" + ] + }, + { + "cell_type": "markdown", + "id": "ed4b0e04", + "metadata": {}, + "source": [ + "The key-value store (KVS) is a core component of a Flux instance. The `flux kvs` command provides a utility to list and manipulate values of the KVS. Modules of Flux use the KVS to persistently store information and retrieve it later on (potentially after a restart of Flux). One example of KVS use by Flux is the `resource` module, which stores the resource set `R` of the current Flux instance:" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "nervous-broadcast", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "admin archive job resource\n", + "R eventlog\n", + "\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"version\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"execution\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"R_lite\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"rank\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"0-3\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"children\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"core\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"0-9\"\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"starttime\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m0\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"expiration\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m0\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"nodelist\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[0;32m\"993a4f[746854,746854,746854,746854]\"\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + "\u001b[1;39m}\u001b[0m\n" + ] + } + ], + "source": [ + "!flux kvs ls \n", + "!flux kvs ls resource\n", + "!flux kvs get resource.R | jq" + ] + }, + { + "cell_type": "markdown", + "id": "c3920f9e", + "metadata": {}, + "source": [ + "Flux provides a built-in mechanism for executing commands on nodes without requiring a job or resource allocation: `flux exec`. `flux exec` is typically used by sys admins to execute administrative commands and load/unload modules across multiple ranks simultaneously." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "e9507c7b-de5c-4129-9a99-c943614a9ba2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "!flux exec -r 2 flux getattr rank # only execute on rank 2" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "6a9de119-abc4-4917-a339-2010ccc7b9b7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n", + "3\n", + "2\n" + ] + } + ], + "source": [ + "!flux exec flux getattr rank # execute on all ranks" + ] + }, + { + "cell_type": "markdown", + "id": "c9c3e767-0459-4218-a8cf-0f98bd32d6bf", + "metadata": {}, + "source": [ + "# This concludes Module 1.\n", + "\n", + "In this module, we covered:\n", + "1. Submitting jobs with Flux\n", + "2. The Flux Hierarchy\n", + "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)" + ] + } + ], + "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/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb new file mode 100644 index 0000000..bc4ff66 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/02_flux_framework.ipynb @@ -0,0 +1,306 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "
\n", + "
\n", + "\n", + "# Module 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", + "2. Management of Flux services\n", + "3. Examples of services in Flux (`flux kvs` and `flux archive`)\n", + "\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", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Ian Lumsden for this tutorial
\n", + "
\n", + "\n", + "Each broker is a program built on top of the ∅MQ networking library. The broker contains two main components. First, the broker implements Flux-specific networking abstractions over ∅MQ, such as remote-proceedure call (RPC) and publication-subscription (pub-sub). Second, the broker contains several core services, such as PMI (for MPI support), run control support (for enabling automatic startup of other services), and, most importantly, broker module management. The remainder of a Flux broker's functionality comes from broker modules: specially designed services that the broker can deploy in independent OS threads. Some examples of broker modules provided by Flux include:\n", + "* Job scheduling (both [traditional and hierarchical](./02_flux_scheduling.ipynb))\n", + "* Fluxion (Flux's advanced graph-based scheduler)\n", + "* Banks and accounting (for system-wide deployments of Flux)\n", + "* PMIx (for OpenMPI)\n", + "* An in-memory content store (useful for preloading data into pods on cloud)\n", + "\n", + "When Flux starts, it launches one or more brokers across the resources it manages. By default, Flux will launch one broker per node, but this can be configured (e.g., with the `--test-size` flag to `flux start` shown in [Module 1](./01_flux_tutorial.ipynb)). After launching the brokers, Flux will designate one broker as the \"leader\" and the rest as \"followers\". The leader serves as entrypoint into the Flux instance, and it serves as the starting point for most Flux commands. The distribution of brokers and the \"leader-follower\" designations are shown in the following figure:\n", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Vanessa Sochat for Flux Framework Components documentation
\n", + "
\n", + "\n", + "After launching the brokers and designating a leader, Flux uses the brokers' network abstractions to connect the brokers together into what we call the \"tree-based overlay network\", or TBON for short. This network is shown in the figure below. This overlay network connects brokers together in a pre-defined tree-based topology (e.g., *k*-ary and binomial). Whenever brokers or instances of distributed services running on top of the brokers need to communicate, they can send messages up and down this tree-structured network. This tree-structured network is used over alternative designs (e.g., all-to-all networks used by MPI) because it provides better scalability (by minimizing communication), security, and fault tolerance for a service-focused framework. More information about these benefits and Flux's overall design can be found in our [publications](https://flux-framework.org/publications/) (particularly our [2014 paper on Flux](https://ieeexplore.ieee.org/document/7103433) presented at ICPP).\n", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Vanessa Sochat for Flux Framework Components documentation
\n", + "
\n", + "\n", + "### How Flux instances support services\n", + "\n", + "Services in Flux are implemented as broker modules that can be deployed across one or more brokers. Once deployed, these services can leverage the other components of the broker, including message routing over the TBON and services provided by other broker modules. As a result, broker modules allow for the creation of composable, easily deployable services for Flux instances." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Management of Flux services\n", + "\n", + "To manage and query services, Flux provides the `flux module` command. The sub-commands provided by `flux module` can be seen by running the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux module --help" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "While going through [Module 2](./02_flux_scheduling.ipynb), we've already encountered some built-in services provided by Flux, such as:\n", + "* `job-ingest` (used by Flux submission commands like `flux batch` and `flux run`)\n", + "* `job-list` (used by `flux jobs`)\n", + "* `sched-fluxion-qmanager` (used by `flux tree`)\n", + "* `sched-fluxion-resource` (also used by `flux tree`)\n", + "\n", + "We can see that these services are loaded and available by running the cell below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Users and system administrators can easily load and unload services using the `flux module load` and `flux module remove` commands. To show this, let's unload Fluxion (Flux's graph-based scheduler) and replace it with the built-in simple scheduler." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux module remove sched-fluxion-qmanager\n", + "!flux module remove sched-fluxion-resource\n", + "!flux module load sched-simple\n", + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this code block, we unload the 2 services that comprise Fluxion: `sched-fluxion-qmanager` and `sched-fluxion-resource`. Next, we load the simple scheduler (`sched-simple`), and, finally, we look at the running servicees. We now see that Fluxion is not available, and the simple scheduler is.\n", + "\n", + "Next, let's reload Fluxion, but, this time, let's pass some extra arguments to specialize our Flux instance. In particular, we will limit the scheduling depth to 4 and populate Fluxion's resource graph with:\n", + "* Nodes\n", + "* Sockets\n", + "* Cores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run flux dmesg to make sure sched-simple has no more work before unloading\n", + "!flux dmesg -C\n", + "!flux module remove sched-simple\n", + "!flux module load sched-fluxion-resource load-allowlist=node,socket,core\n", + "!flux module load sched-fluxion-qmanager queue-params=queue-depth=4\n", + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples of services in Flux\n", + "\n", + "In this section, we will cover two services that expand Flux's usefulness to diverse applications:\n", + "1. `flux kvs`\n", + "2. `flux archive`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### flux kvs\n", + "\n", + "One of the core services built into Flux is the key-value store (KVS). It is used in many other services, including most of Flux's resource management services, the `flux archive` service below, and DYAD (which we will explore in [Module 4](./04_dyad_dlio.ipynb)). These services use the KVS to persistantly store information and retrieve it later (potentially after a restart of Flux).\n", + "\n", + "The `flux kvs` command provides a utility to list and manipulate values of the KVS. As a example of using `flux kvs`, let's use the command to examine information saved by the `resource` service." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux kvs ls\n", + "!flux kvs ls resource\n", + "!flux kvs get resource.R | jq" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The KVS is such an essential component of Flux that we provide C and Python APIs to interact with it. To learn more about interacting with the KVS from these languages, take a look at these documentation pages:\n", + "* C's `flux_kvs_commit` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_commit.html)\n", + "* C's `flux_kvs_copy` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_copy.html)\n", + "* C's `flux_kvs_getroot` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_getroot.html)\n", + "* C's `flux_kvs_lookup` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_lookup.html)\n", + "* C's `flux_kvs_namespace_create` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_namespace_create.html)\n", + "* C's `flux_kvs_txn_create` [family of functions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/man3/flux_kvs_txn_create.html)\n", + "* Python's `flux.kvs` [module](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/python/autogenerated/flux.kvs.html#module-flux.kvs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 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", + "When using `flux archive`, we first have to create an named archive. In the code below, we will create a text file and then save it into an archive using `flux archive`. Note that, for larger files, you can speed up the creation and extraction of archives by using the `--mmap` flag." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\" > shared-file.txt\n", + "!flux archive create --name myarchive --directory $(pwd) shared-file.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we run this code, we are creating an archive in the leader broker. Now that the archive is created, we will want to extract its contents onto the other nodes of our cluster. To do this, we first need to ensure that the directory that we will extract into exists on those nodes. This can be done using `flux exec`. The `flux exec` command will execute a command on the nodes associated with specified brokers. Let's use `flux exec` to run `mkdir` on all the nodes of our cluster except the leader broker's node." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux exec -r all -x 0 mkdir -p $(pwd)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The flags provided to `flux exec` do the following:\n", + "* `-r all`: run across all brokers in the Flux instance\n", + "* `-x 0`: don't runn on broker 0 (i.e., the leader broker)\n", + "\n", + "Now that the directory has been created on all our nodes, we can extract the archive onto those nodes by combining `flux exec` and `flux archive extract`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux exec -r all -x 0 flux archive extract --name myarchive --directory $(pwd) shared-file.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, when we're done with the archive, we can remove it with `flux archive remove`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!flux archive remove --name myarchive" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, note that `flux archive` was named `flux filemap` in earlier versions of Flux.\n", + "\n", + "`flux kvs` and `flux archive` are two useful, but simple exammples of Flux services. Flux also supports more complex services, including services for runtime data movement, such as DYAD (covered in [Module 4](./04_dyad_dlio.ipynb))." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# This concludes Module 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)." + ] + } + ], + "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/03_dyad_dlio.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/03_dyad_dlio.ipynb new file mode 100644 index 0000000..703ad68 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/03_dyad_dlio.ipynb @@ -0,0 +1,518 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dd3e912b-3428-4bc7-88bd-97686406b75a", + "metadata": { + "tags": [] + }, + "source": [ + "# Module 3: Using DYAD to accelerate distributed Deep Learning (DL) training\n", + "\n", + "Now that we have seen how Flux enables the management and deployment of services, let's look at an example of using DYAD, an advanced Flux service for runtime data movement, in a real world application. Specifically, we will show how DYAD speeds up distributed Deep Learning (DL) training. In this module, we cover these topics:\n", + "1. Design of DYAD\n", + "2. Distributed DL training\n", + "3. Deep Learning I/O (DLIO) benchmark\n", + "4. Accelerating distributed DL training\n", + "\n", + "## Design of DYAD\n", + "\n", + "DYAD provides transparent, locality-aware, write-once, read-many file caching that runs on top of local NVMe and other burst buffer-style technologies (e.g., El Capitan Rabbit nodes). Figure X shows the components of DYAD, including the DYAD service (implemented as a Flux broker module), the DYAD client, and DYAD's data transport layer. DYAD uses the Flux KVS to store metadata about tracked files, and it uses Flux's remote proceedure call capabilities to communicate between client and service. DYAD also uses UCX to perform RDMA-based data transfer to move files.\n", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Ian Lumsden for a poster at SC'23
\n", + "
\n", + "\n", + "DYAD is designed to accelerate large, distributed workloads, such as distributed Deep Learning (DL) training and scientific computing workflows, on HPC systems. It is also designed be transparent, which allows users to leverage DYAD with little to no code refactoring. Unlike similar tools (e.g., DataSpaces and UnifyFS), which tend to optimize for write performance, DYAD aims to provide good write **and read** performance. To optimize read performance, DYAD uses a locality-aware \"Hierarchical Data Locator,\" which prioritizes node-local metadata and data retrieval to minimize the amount of network communications. When moving data from another node, DYAD also uses a streaming RPC over RDMA protocol, which uses preallocated buffers and connection caching to maximize network bandwidth. This process is shown in the figure below:\n", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Hari Devarajan for a paper submitted to SC'24
\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "d32e7976", + "metadata": {}, + "source": [ + "## Distributed DL Training\n", + "\n", + "Distributed DL training is an approach to speed up the training of large Deep Learning models by performing multiple epochs of training in parallel across multiple GPUs and, oftentimes, multiple nodes. This approach is supported by most major DL libraries, such as PyTorch and Tensorflow. In this module, we focus on PyTorch. When running training across multiple nodes and GPUs, PyTorch starts by spawning one process per GPU, called the worker. Each worker performs three major tasks:\n", + "1. Determining which samples from the dataset will comprise the batch for the next epoch of training (i.e., epoch *N+1*)\n", + "2. Reading these samples from the filesystem\n", + "3. Building a batch from these samples and moving the batch to the GPU\n", + "\n", + "To assist with reading the samples from the filesystem, each worker also spawns additional I/O processes. Each of these processes reads data and, optionally, transforms the data based on the configuration of the training pipeline. Figure X shows this process for a single GPU, a single worker, and a single spawned I/O process. In this figure, \"I/O\" indicates data being read from the filesystem, and \"Map\" indicates the optional transformation of data. \"Batch\" indicates the building of a batch from the read samples.\n", + "\n", + "
\n", + "\n", + "
\n", + "Image created by Ian Lumsden based on an image from this article
\n", + "
\n", + "\n", + "One key difference between distributed DL training and many conventional HPC applications (e.g., MPI-based simulations) is the asynchronous loading of data by workers during training. In many conventional HPC applications, data loading and computation are performed one after the one. On the other hand, as shown in Figure X, the loading of data in distributed DL training is asynchronous. In other words, while the GPU is training the DL model for epoch *N*, the worker reading and creating the batch for epoch *N+1*. This asynchronous loading of data can lead to imbalance between data loading and training. For example, Figure X shows a scenario where the data loading takes longer than training, resulting in idle time on the GPU, wasted resources, and, overall, an I/O bound application.\n", + "\n", + "At the end of each epoch of training, all workers and GPUs are synchronized so that the DL models from each GPU can be merged together. This synchronization and merging usually consists of an allreduce-style operation. This synchronization makes the effects of any imbalance between data loading and training more pronounced because, if even one worker and GPU become imbalanced, the performance of the entire distributed training will suffer." + ] + }, + { + "cell_type": "markdown", + "id": "bf6493c2", + "metadata": {}, + "source": [ + "## Deep Learning I/O Benchmark\n", + "\n", + "Due to limited resources and due to this module being about a data movement service (DYAD), we do not need to actually train a DL model in this tutorial. Instead, we accurately show DYAD's benefit to DL training without performing the training itself by using the Argonne National Laboratory's [Deep Learning I/O benchmark](https://github.com/argonne-lcf/dlio_benchmark), or DLIO for short.\n", + "\n", + "DLIO is a benchmark that aims to emulate the I/O behavior of Deep Learning applications. It has an extensible and modular design that allows it to use or mimic aspects (e.g., data formats, worker configuration, data loading-training balanced) of real-world applications. DLIO also has several useful support features, such as the ability to generate data with certain characteristics for users.\n", + "\n", + "To learn more about DLIO, check out the following links:\n", + "* [DLIO Paper](https://ieeexplore.ieee.org/document/9499416)\n", + "* [DLIO Repo](https://github.com/argonne-lcf/dlio_benchmark)" + ] + }, + { + "cell_type": "markdown", + "id": "be8da082", + "metadata": {}, + "source": [ + "## Accelerating DL training\n", + "\n", + "As mentioned in the [Design of DYAD](#design-of-dyad) section, DYAD provides write-once, read-many file caching. This feature is extremely useful in read-heavy workloads, like distributed DL training.\n", + "\n", + "In this section, we show DYAD's benefits to DL training using DLIO. More specifically, we first show an integration of DYAD into PyTorch through custom `Dataset` and `DataLoader` classes. Then, we run DYAD through a configuration of DLIO that mimics the training of a 3D U-Net model. Due to resource restrictions, we only run a small version of the 3D U-Net training pipeline. Finally, we show the I/O performance of DYAD compared against Lustre and [UnifyFS](https://ieeexplore.ieee.org/document/10177390) in training a full version of the 3D U-Net model at various scales on LLNL's [Corona](https://hpc.llnl.gov/hardware/compute-platforms/corona) supercomputer.\n", + "\n", + "### Integrating DYAD into PyTorch\n", + "\n", + "When using custom datasets or custom techniques/tools to read a dataset from storage, PyTorch requires the creation of `Dataset` and `DataLoader` classes. To use DYAD in PyTorch-based distributed DL training, we have implemented several of the `DYADTorchDataset` and `DyadTorchDataLoader` classes, which can both be found [here](../dlio_extensions/dyad_torch_data_loader.py). The `DYADTorchDataset` class is used to read samples from \"remote\" storage (if not previously read) or DYAD (if previously read), and it contains all the DYAD-specific code. The `DyadTorchDataLoader` class is a basic `DataLoader` which configures the \"I/O\" and \"Map\" steps of the data loading pipeline.\n", + "\n", + "In the following code cells, we show the DYAD-specific code in `DYADTorchDataset`. As you will see, this code is very similar to standard Python file I/O. As a result, this code serves as an example of DYAD's transparency.\n", + "\n", + "
\n", + "Note: due to several aspects of PyTorch's design (described below), DYAD cannot be used as transparently as normal. Normally, in Python, users would just have to replace the built-in `open` function for DYAD's `dyad_open`. As a result, this use case should be considered the *worst case* for DYAD's transparency.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c92da400", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import sys\n", + "import inspect\n", + "from pygments import highlight\n", + "from pygments.lexers import PythonLexer\n", + "from pygments.formatters import HtmlFormatter\n", + "from IPython.display import display, HTML\n", + "\n", + "sys.path.insert(0, os.path.abspath(\"../dlio_extensions/\"))\n", + "\n", + "from dyad_torch_data_loader import DYADTorchDataset" + ] + }, + { + "cell_type": "markdown", + "id": "8007ad75", + "metadata": {}, + "source": [ + "This first block of code shows the `DYADTorchDataset.worker_init` function. This function is called to initialize the I/O processes used to read samples. As a result, this function contains two parts: (1) the initialization of PyTorch internals and utilities and (2) the initialization of DYAD.\n", + "\n", + "Normally, DYAD is configured using environment variables, and, as a result, DYAD's initialization can be hidden from users. However, due to PyTorch's complexity and challenges in correctly propagating environment variables through PyTorch's dynamic process spawning, DYAD's transparent, environment variable-based initialization cannot be used in `DYADTorchDataset`. Instead, we manually initialize and configure DYAD using `Dyad.init()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27e463c0", + "metadata": {}, + "outputs": [], + "source": [ + "display(HTML(highlight(inspect.getsource(DYADTorchDataset.worker_init), PythonLexer(), HtmlFormatter(full=True))))" + ] + }, + { + "cell_type": "markdown", + "id": "32a146e6", + "metadata": {}, + "source": [ + "This second block of code shows the `DYADTorchDataset.__getitem__` function. This function is called by `DyadTorchDataLoader` to read individual samples for a batch from disk. With other `Dataset` classes, this function would simply identify the file containing the requested sample and read that sample from remote storage (e.g., Lustre) using Python's built-in `open` function. On the other hand, `DYADTorchDataset` does four things. First, it identifies the file containing the requested sample. Second, it uses DYAD's `get_metadata` function to check if that file has already been cached into DYAD. Third, if the file has already been cached, it will retrieve the sample using DYAD's `dyad_open` function. This function retrieves the sample from a different node, if needed, and then makes that sample available through an interface equivalent to Python's built-in `open` function. Finally, if the file has **not** been cached, it will read the sample from remote storage (e.g., Lustre) and cache the sample into DYAD for more efficient future reading." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ab755b0a", + "metadata": {}, + "outputs": [], + "source": [ + "display(HTML(highlight(inspect.getsource(DYADTorchDataset.__getitem__), PythonLexer(), HtmlFormatter(full=True))))" + ] + }, + { + "cell_type": "markdown", + "id": "fefd9ae3", + "metadata": {}, + "source": [ + "### Running DLIO with DYAD for a 3D U-Net model\n", + "\n", + "Now that we have seen how DYAD is integrated into PyTorch, we configure and run DYAD through a configuration of DLIO that mimics the training of a 3D U-Net model." + ] + }, + { + "cell_type": "markdown", + "id": "731d52a3", + "metadata": {}, + "source": [ + "#### Configuring DLIO and DYAD\n", + "\n", + "First, we configure DYAD. DYAD requires three settings for configuration:\n", + "1. A namespace in the Flux key-value store, which DYAD will use for metadata management\n", + "2. A \"managed directory,\" which DYAD will use to determine the files that should be tracked\n", + "3. A data transport layer (DTL) mode, which DYAD will use to select the underlying networking library for data transfer " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21abe5ee", + "metadata": {}, + "outputs": [], + "source": [ + "kvs_namespace = \"dyad\"\n", + "managed_directory = \"./dyad_data\"\n", + "dtl_mode = \"UCX\" # We currently only support UCX, so do not change this" + ] + }, + { + "cell_type": "markdown", + "id": "6e32bc27", + "metadata": {}, + "source": [ + "Next, we configure DLIO. DLIO requires several configuration settings. However, for this tutorial, the only one that should be set is the initial data directory, or the directory where the dataset initially resides at the start of training. When running DLIO, the `DYADTorchDataset` class dynamically copies files from this directory into DYAD's managed directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5e3438f", + "metadata": {}, + "outputs": [], + "source": [ + "initial_data_directory = \"./dlio_data\"" + ] + }, + { + "cell_type": "markdown", + "id": "d979369c", + "metadata": {}, + "source": [ + "Finally, we set the remaining configurations for DLIO. These should not be edited because they depend on the directory structure and configuration of this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92881a8f", + "metadata": {}, + "outputs": [], + "source": [ + "workers_per_node = 1\n", + "dyad_install_prefix = \"/usr/local\"\n", + "num_nodes = 2\n", + "dlio_extensions_dir = \"/home/jovyan/flux-tutorial-2024/dlio_extensions\"\n", + "workload = \"dyad_unet3d_demo\"" + ] + }, + { + "cell_type": "markdown", + "id": "801719eb", + "metadata": {}, + "source": [ + "To properly set the environment variables needed for running DLIO with DYAD, we create an environment file that is compatible with the `--env-file` flag of `flux submit`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ce527f2", + "metadata": {}, + "outputs": [], + "source": [ + "env_file = f\"\"\"\n", + "DYAD_KVS_NAMESPACE={kvs_namespace}\n", + "DYAD_DTL_MODE={dtl_mode}\n", + "DYAD_PATH={managed_directory}\n", + "LD_LIBRARY_PATH={dyad_install_prefix}/lib\n", + "PYTHONPATH={dlio_extensions_dir}:$PYTHONPATH\n", + "DLIO_PROFILER_ENABLE=0\n", + "\"\"\"\n", + "\n", + "with open(\"dlio_env.txt\", \"w\") as f:\n", + " f.write(env_file)" + ] + }, + { + "cell_type": "markdown", + "id": "398e110f", + "metadata": {}, + "source": [ + "#### Creating a Flux KVS namespace and starting the DYAD service\n", + "\n", + "Next, we start the DYAD service. This involves two steps. First, we need to create a namespace withing the Flux key-value store. This namespace is used by DYAD to store metadata about cached files. This metadata is then used by DYAD's Hierarchical Data Locator to locate files." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf132600", + "metadata": {}, + "outputs": [], + "source": [ + "!flux kvs namespace create {kvs_namespace}" + ] + }, + { + "cell_type": "markdown", + "id": "723cbeaf", + "metadata": {}, + "source": [ + "After creating the key-value store namespace, we start the DYAD service itself using the `flux module load` command. We run that command through `flux exec -r all` to deploy the service across all Flux brokers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3220ef03", + "metadata": {}, + "outputs": [], + "source": [ + "!flux exec -r all flux module load {dyad_install_prefix}/lib/dyad.so --mode={dtl_mode} {managed_directory}" + ] + }, + { + "cell_type": "markdown", + "id": "f95e0145", + "metadata": {}, + "source": [ + "Finally, we check that the service and key-value store namespace were successfully created with the cells below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4750013c", + "metadata": {}, + "outputs": [], + "source": [ + "!flux module list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3322e350", + "metadata": {}, + "outputs": [], + "source": [ + "!flux kvs namespace list" + ] + }, + { + "cell_type": "markdown", + "id": "c0dfe655", + "metadata": {}, + "source": [ + "#### Generating data for the 3D U-Net\n", + "\n", + "Before running DLIO, we need to obtain data for emulated training of the 3D U-Net. Instead of downloading the full dataset, we use DLIO to generate a smaller, synthetic version of the dataset for this tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2dd03ec1", + "metadata": {}, + "outputs": [], + "source": [ + "!flux run -N {num_nodes} --tasks-per-node=1 mkdir -p {managed_directory} \n", + "!flux run -N {num_nodes} --tasks-per-node=1 rm -r {managed_directory}/* \n", + "!flux run -N {num_nodes} --tasks-per-node=1 mkdir -p {initial_data_directory} \n", + "!flux run -N {num_nodes} --tasks-per-node=1 rm -r {initial_data_directory}/* " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4e5d30e", + "metadata": {}, + "outputs": [], + "source": [ + "!flux run -N {num_nodes} -o cpu-affinity=off --tasks-per-node={workers_per_node} --env-file=dlio_env.txt \\\n", + " dlio_benchmark --config-dir={dlio_extensions_dir}/configs workload={workload} \\\n", + " ++workload.dataset.data_folder={initial_data_directory} ++workload.workflow.generate_data=True \\\n", + " ++workload.workflow.train=False\n", + "!echo \"FINISHED GENERATING DATA\"" + ] + }, + { + "cell_type": "markdown", + "id": "3f14ffdd", + "metadata": {}, + "source": [ + "#### Emulating training of the 3D U-Net with DLIO\n", + "\n", + "Now, we run DLIO using the command below. As DLIO runs, it prints out logging statements showing how long sample reading takes. At the end of the run, DLIO prints out a performance summary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3437a068", + "metadata": {}, + "outputs": [], + "source": [ + "!flux run -N {num_nodes} -o cpu-affinity=on --tasks-per-node={workers_per_node} --env-file=dlio_env.txt \\\n", + " dlio_benchmark --config-dir={dlio_extensions_dir}/configs workload={workload} \\\n", + " ++workload.dataset.data_folder={initial_data_directory} ++workload.workflow.generate_data=False \\\n", + " ++workload.workflow.train=True\n", + "!echo \"FINISHED TRAINING\"" + ] + }, + { + "cell_type": "markdown", + "id": "573ce232", + "metadata": {}, + "source": [ + "#### Shutting down the DYAD service\n", + "\n", + "Now that we are done running DLIO, we need to shutdown the DYAD service and remove the key-value store namespace used by DYAD. This is done with the two Flux commands below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "755251df", + "metadata": {}, + "outputs": [], + "source": [ + "!flux kvs namespace remove {kvs_namespace}\n", + "!flux exec -r all flux module remove dyad" + ] + }, + { + "cell_type": "markdown", + "id": "cbd52626", + "metadata": {}, + "source": [ + "The following cells show that the DYAD service has been removed and that the namespace has been removed from the key-value store." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2bf50c8e", + "metadata": {}, + "outputs": [], + "source": [ + "!flux module list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e50c926e", + "metadata": {}, + "outputs": [], + "source": [ + "!flux kvs namespace list" + ] + }, + { + "cell_type": "markdown", + "id": "607cb1d2", + "metadata": {}, + "source": [ + "Finally, we need to remove all the files we generated while running DLIO. We use `flux run` to ensure that any node-local files are deleted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b99e3b4", + "metadata": {}, + "outputs": [], + "source": [ + "!flux run -N {num_nodes} --tasks-per-node=1 mkdir -p {managed_directory} \n", + "!flux run -N {num_nodes} --tasks-per-node=1 rm -r {managed_directory}/* \n", + "!flux run -N {num_nodes} --tasks-per-node=1 mkdir -p {initial_data_directory} \n", + "!flux run -N {num_nodes} --tasks-per-node=1 rm -r {initial_data_directory}/* " + ] + }, + { + "cell_type": "markdown", + "id": "68ea2fe7", + "metadata": {}, + "source": [ + "### Evaluating DYAD's performance for the 3D U-Net at scale on Corona\n", + "\n", + "
\n", + "\n", + "
\n", + "
\n", + "
\n", + "\n", + "Figure X shows the performance of Lustre, [UnifyFS](https://ieeexplore.ieee.org/document/10177390), and DYAD in terms of runtime and I/O bandwidth for the full version of the 3D U-Net training. As explained on the [webpage for the KiTS19 Challenge](https://kits19.grand-challenge.org/), the dataset for the full version of this application consists of 10,240, NPZ-formatted image files, resulting in a total dataset size of 1.36 TB. Within each epoch of PyTorch-based training, the model processes batches of 4 images using 6 I/O processes per GPU. The model trains for 20 epochs without checkpointing. The model scales from 8 to 64 nodes of LLNL's [Corona](https://hpc.llnl.gov/hardware/compute-platforms/corona) supercomputer, with 8 GPUs per node.\n", + "\n", + "In the leftmost plot of Figure X, we show the runtime of the training for Lustre, UnifyFS, and DYAD at 8, 16, 32, and 64 nodes. This plot shows that DYAD provides significant runtime improvement compared to Lustre and UnifyFS for the 3D U-Net, mainly due to locality optimizations. DYAD runs up to 7.5 times faster than Lustre and 1.88 times faster than UnifyFS, with less performance variability due to DYAD's use of node-local storage.\n", + "\n", + "In the middle plot of Figure X, we show the bandwidth per epoch of training across 512 GPUs (64 nodes). Because DYAD's capabilities allow for on-the-fly caching of data, its performance starts similar to that of Lustre. As more data is cached into DYAD, its bandwidth increases to 140 GB/s due to DYAD's streaming RPC over RDMA protocol. Finally, as even more data is cached, DYAD's bandwidth reaches 1409 GB/s because DYAD's locality-aware caching allows almost all sample reads to be performed directly on node-local NVMe. In comparison, both Lustre and Unify maintain consistent bandwidths well under those of DYAD. By the 20th epoch, DYAD speeds up training by 10.62 times compared to UnifyFS.\n", + "\n", + "Finally, in the rightmost plot of Figure X, we show how often DYAD retrieved data from node-local storage versus retrieving data from storage on a remote node in terms of percentage of data access requests. Initially, DYAD mostly performs remote requests. As training continues, more and more data is replicated with DYAD's locality-aware caching, resulting in a larger percentage of local requests. By epoch 13, almost all data is accessed through local requests. This transition from mostly remote requests to mostly local requests corresponds with the increase in bandwidth shown in the middle plot of Figure X." + ] + }, + { + "cell_type": "markdown", + "id": "81d7d87f-1e09-42c8-b165-8902551f6847", + "metadata": {}, + "source": [ + "# This concludes Module 3.\n", + "\n", + "In this module, we covered:\n", + "1. Design of DYAD\n", + "2. Distributed DL training\n", + "3. Deep Learning I/O (DLIO) benchmark\n", + "4. Accelerating distributed DL training\n", + "\n", + "To continue with the tutorial, open [Module 4](./04_flux_tutorial_conclusions.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": 5 +} diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb new file mode 100644 index 0000000..0bb171c --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/04_flux_tutorial_conclusions.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "
\n", + "
\n", + "\n", + "# Module 4: Lessons learned, next steps, and discussion\n", + "# This concludes the Flux tutorial! 😄️\n", + "\n", + "In this tutorial, we:\n", + "* Introduced Flux\n", + "* Showed how to get started with Flux\n", + "* Showed how to perform traditional batch scheduling with Flux\n", + "* Showed how to perform hierarchical scheduling with Flux\n", + "* 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", + "\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", + "\n", + "> Where can I learn to set this up on my own?\n", + "\n", + "If you're interested in installing Flux on your cluster, take a look at the [system instance instructions](https://flux-framework.readthedocs.io/en/latest/adminguide.html). If you are interested in running Flux on Kubernetes, check out the [Flux Operator](https://github.com/flux-framework/flux-operator). \n", + "\n", + "> How can I run this tutorial on my own?\n", + "\n", + "All materials for this tutorial (including other versions of the tutorial) can be found in our [Tutorials repo](https://github.com/flux-framework/Tutorials). To run this tutorial on your own, you can clone this repo, enter the directory for the version of the tutorial you want to run, and follow the instructions in that directory's README. All versions of this tutorial are designed to either be deployed to cloud (e.g., AWS) or be run locally using Docker.\n", + "\n", + "\n", + "## How can I learn more about Flux?\n", + "\n", + "We've got lots of resources for learning about Flux!\n", + "- [https://flux-framework.org/](https://flux-framework.org/) Flux Framework portal for projects, releases, and publication.\n", + " - [Flux Documentation](https://flux-framework.readthedocs.io/en/latest/).\n", + " - [Flux Framework Cheat Sheet](https://flux-framework.org/cheat-sheet/)\n", + " - [Flux Glossary of Terms](https://flux-framework.readthedocs.io/en/latest/glossary.html)\n", + " - [Flux Comics](https://flux-framework.readthedocs.io/en/latest/comics/fluxonomicon.html) come and meet FluxBird - the pink bird who knows things!\n", + " - [Flux Learning Guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html) learn about what Flux does, how it works, and real research applications \n", + " - [Getting Started with Flux and Go](https://converged-computing.github.io/flux-go/)\n", + " - [Getting Started with Flux in C](https://converged-computing.github.io/flux-c-examples/) *looking for contributors*\n", + "\n", + "We've also got resources for learning about DYAD!\n", + "* [DYAD's ReadTheDocs page](https://dyad.readthedocs.io/en/latest/)\n", + "* [DYAD's GitHub repository](https://github.com/flux-framework/dyad)\n", + "* [eScience 2022 Short Paper](https://dyad.readthedocs.io/en/latest/_downloads/27090817b034a89b76e5538e148fea9e/ShortPaper_2022_eScience_LLNL.pdf)\n", + "* [SC 2023 ACM Student Research Competition Extended Abstract](https://github.com/flux-framework/dyad/blob/main/docs/_static/ExtendedAbstract_2023_SC_ACM_SRC_DYAD.pdf)\n", + "* [IPDPS 2024 HiCOMB Workshop Paper](https://github.com/flux-framework/dyad/blob/main/docs/_static/Paper_2024_IPDPS_HiCOMB_DYAD.pdf)\n", + "\n", + "And, of course, you can always 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!\n", + "\n", + "![https://flux-framework.org/flux-operator/_static/images/flux-operator.png](https://flux-framework.org/flux-operator/_static/images/flux-operator.png)\n", + "\n", + ">> See you next year! 👋️😎️" + ] + } + ], + "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/Flux-logo.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/Flux-logo.svg new file mode 100644 index 0000000..f2d126b --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/Flux-logo.svg @@ -0,0 +1 @@ +Flux-logo-3 \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg new file mode 100644 index 0000000..e24636a --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example1.svg @@ -0,0 +1 @@ +ProducerConsumerNode1Node2Local StorageLocal StorageWI/O intercepting wrapper lib relying on KVS + RPCR \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg new file mode 100644 index 0000000..2539695 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/dyad/dyad_example2.svg @@ -0,0 +1 @@ +RWConsumerProducerP1P2Shared FSsync \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh new file mode 100755 index 0000000..3c39dc0 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/hello-batch.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +flux submit --flags=waitable -N1 --output=/tmp/hello-batch-1.out echo "Hello job 1 from $(hostname) 💛️" +flux submit --flags=waitable -N1 --output=/tmp/hello-batch-2.out echo "Hello job 2 from $(hostname) 💚️" +flux submit --flags=waitable -N1 --output=/tmp/hello-batch-3.out echo "Hello job 3 from $(hostname) 💙️" +flux submit --flags=waitable -N1 --output=/tmp/hello-batch-4.out echo "Hello job 4 from $(hostname) 💜️" +# Wait for the jobs to finish +flux job wait --all \ No newline at end of file diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dl-training-io.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dl-training-io.png new file mode 100644 index 0000000000000000000000000000000000000000..6129d0ef09a6a0f695406d9bdc4277b6b952b990 GIT binary patch literal 36427 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 literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-software-stack.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-software-stack.png new file mode 100644 index 0000000000000000000000000000000000000000..eae3071c24cb0c9213c416221d8004facdcde1cc GIT binary patch literal 89601 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 b@)}h$=NtN1dwG6L6lpBB# literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg new file mode 100644 index 0000000..d02c33d --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad-unet3d-results.svg @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad_design.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/dyad_design.png new file mode 100644 index 0000000000000000000000000000000000000000..88bc6492add2ec407a75de196177a20553b40b4a GIT binary patch literal 184051 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

MEjSpDZ4q5{BIgXMWA$^>MW=g~9 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 z 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 literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-broker-design.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-broker-design.png new file mode 100644 index 0000000000000000000000000000000000000000..267f1a66fd2004f81e26478b2966673d6a03e46c GIT binary patch literal 35296 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 MkhEYqzpmf^0hw59ZvX%Q literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-pre-tbon.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-pre-tbon.png new file mode 100644 index 0000000000000000000000000000000000000000..bc40a7e464f98eebf5a420af2432e39e407bc8d0 GIT binary patch literal 12218 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 E4~16Vj{pDw literal 0 HcmV?d00001 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-w-tbon.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/img/flux-instance-w-tbon.png new file mode 100644 index 0000000000000000000000000000000000000000..93a276e8c47a077536618abe0cc7194cb5247450 GIT binary patch literal 34570 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 z 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ʉbSl^<4$a!v7(M7J;6-CQxsUn6| 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 z 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 Mtm^xUx28e=1\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 new file mode 100644 index 0000000..d395656 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/06_supplement.ipynb @@ -0,0 +1,321 @@ +{ + "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 new file mode 100644 index 0000000..b0ccfe6 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/X01_flux_tutorial.ipynb @@ -0,0 +1,115 @@ +{ + "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 new file mode 100644 index 0000000..1fbfa2b --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/old/dyad.ipynb @@ -0,0 +1,671 @@ +{ + "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/notebook/sleep_batch.sh new file mode 100644 index 0000000..58496da --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sleep_batch.sh @@ -0,0 +1,15 @@ +#!/bin/bash +#FLUX: --nodes=2 +#FLUX: --nslots=2 +#FLUX: --cores-per-slot=1 + +echo "Starting my batch job" +echo "Print the resources allocated to this batch job" +flux resource list + +echo "Use sleep to emulate a parallel program" +echo "Run the program at a total of 2 processes each requiring" +echo "1 core. These processes are equally spread across 2 nodes." +flux run -N 2 -n 2 sleep 30 +flux run -N 2 -n 2 sleep 30 + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh new file mode 100755 index 0000000..6ec9cf8 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job1.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +flux batch -N1 ./sub_job2.sh +flux queue drain + diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh new file mode 100755 index 0000000..d947f19 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/notebook/sub_job2.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +flux run -N1 sleep 30 + From 770e1a4cd24f7cd81ef6e6a7b5fcf53626419958 Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 4 Jul 2024 22:49:35 -0600 Subject: [PATCH 2/8] 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 --- 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 32 files changed, 269 insertions(+), 2029 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/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 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 z zZ@-AkskBvr@~Kjd`Ry|l8#1TKd^7vl*yj;ya z6sE4fC=5sC09)uRNVylQyc-K>tRVG~x8Bb)U8Tklo||C54KIjRrN6Ltn#`V%dUZ4f z8XvWIY#eXq^@=qYBs%aywz1*rw&wsN6jH0~XT_O(ag57+aGYK^@mdwPnP>k_{LKU} zOKw*t;YY{$48nTyn<;+Jw>WX|?}|jU!-T{L^tJDF)bHP=*2iUbC1Zcu1Fu?@7H zPi_+Fdeer~RVz*Ih0Dc{v$}Lt4qdUfz|J_-w%6_pX$1P{>cjoYmki4+SKh&2U)Xi*%ADC z%T55N!|neSw}4Z^S&C3@n*21)R%_SG%@BdMEl4n+-GqdVcKq$ygM4A1USmeejIf{H z?Pa9wwiLGLsUjEw2{Wd(YeIMl@-4#1Gd0ZZ7!>)hA}_imPyZ{!Je{E!mMiF9JN^G? zy6S+any-C<1s17AQes&`L8J}3yAe>3E(Ph9W|vk{k&s3SK|w&eLkUSiLh0^K$#3xe zeSfmtJ9SSy=Q(F)zVUxZkww4_^ONz6aLOF)SN!oeLx037PqJ-Dy4+OCdFfU>!>t65 zNvgWc8cf_-n4fXN0&qx6^5D!&|j|zm6PBO*9zaClrhMt&B($=2`#2S{StxD@acU3*Hps`{WoiW7JFR z-#1XuvY*3_qxMm^<$eD;UiuJhjNV&Fs7F8AVSt2C3gwi=4RdVVR6fA>ad0aNWwYQg z^R3gB645f~xt$~bM%k-$I>SIPUxTh(Fixyvh5A$Hb26HB&-(ac1PuFeL9U$9bfA4 z`MHj82n#_WiZ!VNx$`hR{KNB<`tupudlPkPDz@?Y*j9>bDnTc^o0&oFA0FwX(CU}K zyYay^HFIoiEW+M9UzwQ8xv8zg#i+4`9+nNk8kFHnb#l6u6`P3n@*Csw_}cXj>q;C8 zrYYZEFn^IVRjn;Z)OOi(!0M24ijXWXQ<~?{J;C!R_%hPNa?)C~{N2>zgA^8V{+^4} zV9QV*t{u;Nb%@Tz5Vm?fa@m04H5?mrZKrfQKG8V)+2*P4q)}UEjrPc5&Yi9lvS62)7A@RD-FmC*Y@U3@zXGuM$h%Vu1k7gE3?y$E3twl=-Au|SX1Z6OP*Kt`o1dppe7vUQ1_ zZ*tN1xI7A%mbh3se&fC6K?X+@3>L# z`lope%nLC-v9&TNhOZ;QheqVs{Q2@$7%{N-w`seGy-i}F>@I&2UiH9N&8bj^s%*pK zy1CK$y1RNm1M@_{q$J-KtTe~27UNYI`>qE}?ln3%O&3ux=aIHfYsZ#79$^zbFV(cW zXSjJ&&90k$xRQl)f4#%Ys@R-tR16FYC0)x|CzuWkP^x;Q9?@K#t+m;AIf?c6hnIHv zo8QfMMb##Ry7EK_GwZeP6La9!tP~WQcuYG`sGH#RnXzpr&?`7&$t#MPrbjf1Q(@|v z$Y9u#v^43dRIaS7POmWDk_%(KUL&z^Y^NXgBZe1=V;&Mo<*ld zWLzgP+WC2*`18!ldZ&V*;#a5O5dC^$Eqb(X%A zn?_P$7JsV_08SlC^=Hqk1 z!GvY9?5=y6dQ#I-xuBxF8N2_E?T(v1;b-2&#!(1YX~wl+3Xfhk)>OWoCcSdEX>H86 zr%X%P1eugQ-0@lK{{2T0+8?t%@#tpw_G!GqTl}UOHEgJL`^dmY(<#~z&*A7sY}gnu zqi?M0^*CWAoN4^sj3O7)Rt@}l`-KK0b${_2zIqLwh+OPl9>xWe_(h zU80-!2bFSE@2-aDl%IUmS{2t;KrJQfx?w_4#!Ic28;+NaQ>PJ))r;FDbJfUHfrgn> zQm(T@o-+;w(G&zXVZc6vKtAdCzD(T?o%yzmj|srvB0H#u@K$&9ALHi{Fcm%cY+2=H zZ$^Oo<(vr@wVSMi9=*o0*>Da*ziqwFJfeY9mDW0`*#r6K9DIb;?jX6%YkCmJgWxJK zhF^VWlNp!4STZhGGxUY8`%tyV5u}Io2FKz7ixbf$iOX*|_&t!97nchqXBzB%3kXBeMyFowM2w14kiD*3q>->nW}MaK?w0JwkFoGK``Mfg*5`l{yIMaRARM`_s2e^D={H3&`tk z8*=y`cnuQ!kpdLwUAok^v09+Qqs5A|$yfKaD&9d}SW#K8uIw>S%^1|#sGtXzZ)ugA zejYKW^y~JUzUR|`zW88(c=ROHKnn5FHCc!(&qy!m*O=DkbwKhgQI#&X2 z5$^;0G3I~1F%prQt(CrWK(nai;j?zsC_-PYvX4UyBW#ug%llexT5i>VUgLe$*|aU~ zs1|iqa7bq3HM@ItwDr975lnt>ChBFPEB=~e(Ijc<#5~|;;>L~_Et;mL%&}Ru*Pa}f z%hXzR5IKT*fi*QUF1=MaWN%{Df0Mg1x7>TLaV+m`^0vD(J7GiH`@Vw;V@?Q1j+rJ? z>wcQk@-Bv;xsUDeKC#^-{QxkTRQOcEX-SWjSH91@?U6Gdly;PK5O?@vfkh)o6^ON4 z|JR94dQxtEO?4)rhbgZFK@=yG82PoIT$A>GIQ~LK@?u%TGQ+Uk`w4AUJvd(5l0U}i zng>x@4B_A0+@FP;z;y6^dO;#DRGbg{n?-6G3?BM+lZUQ-`VMxHy}Y}mDvB^1 zGN;<4c$mA_Xr)TaQV~~vO#NJL`~|gEXor&P>TObwZ*9RY#HpANQiCj9UOm7?PN-I9?rLZx*k+@9>CbiUjhqZTPHY|P^*|KJ}PRQDPz zcEX~I5k%-;3C%JtN>iU6^DzLcpVG*-9lxJru&TzUG$YG8L83ZW%2NXrf#}}pexj~> znh`7JXLqptgEthbojYFKsN4 z_ravDO z^=Tnlc7l{RVPd)kxfD1)xjJg?zBYM3TibvkYlSNjDI}X%lnT-(;C}ehIg|SKShaLn z=BG8|Ts0s_YvLLm2jwhhHhUa8jXm%OAK(?ms&wHf=llO1T=ZEe+t*2*8Q?aQW?q)C zu@Ze)kc-p6i7jGHMJ%Yn&86Y)D)Dq@v|lM>pzTOPIvJl4AgpVsh$c2)!C`N^@hjCF z;mPPZnuNZ;`EDVj8==^wzzLfv+!DF06Jp$~)re?f7<*uq#?Y-O1Z@MC;<@%lZAoP6 z{kJ^1>8r5X!>*Qf@F??;06KG_>ZA5`zo;o!=MlEF2&ax{^6}XE711Q7oL^5e&HzbD zDT0>AivF_os_VR_;xpbC_A&JwLEiXV&RRI6;FIbp4R!^3hK6Kb=2}b$rRw}pS6_^M z_OthhVBMh6;i9^JtD*Q{YHcogc(vYF1?*63x@$;#$awz=$?Zpj+UfTqCI=nJl5h_& z&@|(9smm1=Z_>8IO!o|t#N)EtvSFBrxtF@G(OA&4ShzR$FzG%+qP<71yJ~*2x_2o& zbvRCssQy_J4-tf9CJRpNmDn1;v^@1sELdqn3OpJu_H!0pv0jzxvJ6p=@^YbvAT=N% zW-%IjwtK~w^#TF?i+xAdZF}Rho&!@0BX0EwRgR|*UP0)vs(bWkAlAPhaAf_g$9gIg zP8!if=M5MrV=Az}QsOl{)M7YpqQDqHgnB{m?zmI&TETQ8%i|OFF)}R4NT5WocY+wZ ztd$|ZCoqTdZT%$f<0UM}MVgr=!8%1{sZdAlVBt_i6x=Yz6U|n!xThke>lNg(AP+4KfiHZpc+fMTZ!|)ITwb(&qbzi^gbisDU2onV zpZ@u@JDpzKqwZ&|qn)$pyVZkJ03~B1<@U-ev`p~CllG42d;gwdLG<#>G^iN;l%)QT zy0*rs2OiC#E~=46cwzH5JK z25r;%!Eh=C(?7q)#XZZ)_#&RRN>vW0V5yUbP~5;5qc-H80S*UQTn^b)^|c4#LCXw) zT+M;nD*zFFy~g-6NL)R*^L%Dyn8(O>=d!;<2bAw(dp&o?#)3g)z;M{b=!n zpnVX~2m?nM#BcE6aQN=mH)lP9PA2Lqw(jR=qKh39&)or`>6%Xu(YHz?#DIEXJ)!Uz zbNQxwx(|Oa!gEShZsFYLpbLJEu`k5t<5PN;kE}x*pP#pQy?a9E~9I875!E}Ga z#gy=%rDBBavo~*uy9%8NyncQ4xHnk3{h@&vLJ(60cBFCU;?Yg%aNdDAycJIT5vxh;Ds~gBG0W!3 zf4lMp4n+$^u))w~K6)^0bP5Q^c%MJWe=GC(I}D8iL4C{Dk$8o-IzMno#)+XOe(?Vp zXJ(9rD;%ndxeD;IwLHxBCmvt*Yf8xp!}YoKKCfWF^7kbm{s#6mfMr3Lq^e$O!jCo3p|um*x4y@&dr zNI7zlcb(rZ8-nYEcdGcorNNNt6TkvHauDe#tGUjD8?ulX6!;Y#uSzQ_Y(eh@uAFX9 z0IfifJqRO;wN}k!6vA#)g65<{{yn&m^m+P&Fam;9;8%S@bgCoX>?9b8QF%+LJ|r`$nP6_|Da)-vBkvh652H z#h}1%=)nQ~dXwAk_025&%|G#=CT?sg@H5mCs)mfp)_PXU3$D5%`!`8zkBsIIB{3|- z5Zdp*ZW6M~YksauhQ5TLX}tg5Bff>xyaKwIQ3PEuVo+CY88HYp@sfc%hBars@W9`i z;4f?`J>czw)FdiR9BkqP5{JlgbZq79)*4fQQH$O#06SkXDDW(_!5ySv#-l)!rfhqV z18oKPZltvGqzig&3M|DK`_9gHNo=>M@R(F>&#EjT#V!V_)9JenbWa(Thssj6Dp8o( z=EbpDPyYB7W9;yOWbHiloo*b^ESZY!0a?zkAiS8S;VUh%I zw`oG2v~`K?_16NTVwZKo&Mth1trq$$vZ-$*+#fMFMnM0*33@cAyoTi3q|Gw#vKey_ z!&$(#4v+=0i}||}M?G8IXQMOJ>ULhc{i0GC0gK6jt@+<~g0Uner~i99yz{WTod}8f z7l@7|H3B)C%Q)Ydx}5zv$Ft#o_?J3hxv7$L(3?Y?FsfMd_I;&zrfG?PON}xI@&0e6 zFJF;dtns_@#wPy9Sw5G`CiT1Z+Kt=mdpu`@OD4Y0?bq9J7>2MeJ0a-5I|6KjL*lMe z@w4M@Ll_iT3B>J{4UeYj4~-I60`6D+K}XG9Qe^*pDI-zz=qzH)LITNalXaUlTbag* zC}$G9Az2qnEU`;qY;Xwts^-f2*U9?hA4mQL2kKWZkTDSGxnQ(bN&IZz<<^-r3Lvgy zdpx|l@D`HgC>&BtC*oDjMgcO2XQrsZ{^hI`3v10AC)KG@2O^3va5@$Qg0ds|drN!sOy=9-TP+qlKD=Ey(kt;r z@q@Lo<|We6nh4^KFIYj7h-YunqIFkz&&XC}s!;JkM0c?H-%J1NO%?Q}oMJ#MzFG*> zD0XD%2auhTgCp<6Ew<_6TIzOkZb?WCb#;UT#QbJ>wFZ9zD<~$(jJYcoirr|Gyl`8X z?gt|Fy#!ad^`AbYxN|H2JS=2U9&*!+7NA6sAmi-6p8`i}7A;WB&vB*CXhonu zNG<2l=?_ufN#UvxG6x?u9sDGpHro%YACK}%l_f>xi6jP=3vg71++?Mj24!U0emwv3VmVvH@aCfVMHqYTv3^FI%y`06WaaIdcro`uvQuExAiOkS~F^! zqk`z4^aGNI0Z-g|`!$}3s;oDbIxi7ydy1~l=roF2J@stn`_4REgUww2pBX~nR}jAV zsG}zEW2dGsLWC&Of+tn*Z%|F*YEI++wn@Obz@cILYnjg<|BV6rifo!)Y{g^DIck*! zDT>L*h6LZy`0FozXL(<4 z@71C3{@EtUQB__1RWoLMBe6h!gymyDLbx4n# z_`;^t$0$;?zX9^eDgokR0Xkyt*5Ze)$S6<}n3FE*b_}e{MYb05)ww6ikcbj|b_g4E zrJak@oULVSak|V$DM*y_wa+_$D{9Gq;mqCfcwBo*{49Vwnoa%C6__c^$=#{*rGTRY ztIMb7@3zi5T>BcUCNZ@kscE$p*_&Rcy^g6zp|1Xd`EO&Ke9374TQo^6hU26eg$htM zq~@sPpdu}4l;-&TGk5sF0vL zY^q^zIfQQ5NSQr6wqNE-yWpx)^*B}$a2x`xdcM5V2W=D=<*vQIt%$2$e2E=uznpia zyP5$HM-KObDKo`?-_En`nx|}AAmeWmQ6>2DJ?y3j4X*p1zpF(;y46z+bsBd~#P@=h z>St1w@S;!@b~mn^3O3Np6Uzg#!5H=}Gv@2qtEjk*1PA@tB`ePCh5)ZOHp4hGqReGA z>$7UBy(uq1CgEwIlV{Aef98Vo1FmTDxtHGut@}eVO33cDQzqR(<@_r>K!v%xC)Ve0 zooo1=7O%Y^zai>wZG8TF>f(G00cVE&H((U7+j_SThOv=qxm=w&Hp1)%fjk{s+)Qjo z+PI@BGK4&aOLOP)FD_20FK39iEB&jMK3;7O>iWRW6 zf@Fi9ajt!9DQ_&QpD*~WfyzCRr6I)^1vlMoX~6=w=`koUYXllF;RcSC6x)h5e>gyZ zF0*@I2d5?o!E2>;@Ji8lZTsiffa3;@vp>{Zjpsq4OWTzHy%+laWZRtro=~s?!;wyP zy`1&blBIke8EPQ`mHb^i+dC0rLj+g;2Rr(5%Do@P8w19(ZXNwn>pnwlQ*2?}W`B@W z=rV!zxaM>WDOY;HPL9CJkN)pF*=tnS(1icJ83W#I)LJbHL`sdVN><>CSV`sLXtepG zUZKW9yf)gE8jr}}Eh_(306leW4JT3O2Uw3Bn}W)2vF^3@8>ctn*|f!a`=pVHBrpYY z3HUbozwuA!mpx|WaI)EIXfuIhr+u{o;jPUmp^MyQlP4u#2LZ2n50G%dQm{nmumSyZ zQw&?Sz}ex4n5oP8tiNe>k5e-%an~-M-YsE-r&!i*TkIgQmXG95;4KROStcZa8?WED zG=bV|eZSeSCb#yTT0g@uc7z@{V54MjPsXsjW3o&-iyQ0j3m3F%?kguW#aOMMAYBK`8M45*#$cfZr5-^@5P_a@47`o z7d|frSP58@od^x{xy(Y&rd=FJtci1v!ZkqnS${?h%@T7X~MKr1) z;GUKe8dSb=03u@EgznwBU$gm)V=_|9rkeaM3&8fRYjCJr8H85p{<_8z!5Ah7&sL$F zt@@Qf0g{W>FH;XA&h;Q9|DQfe-m6vN1Qtu4cjVYrl2+&FPEN(~_E~nhcLI+|Y}3OO zn~S^oZ}So^`JwP$OC+i1#i;W^O2!GjA2x$iPYTbC%Qe!5bE6VQ{Jxd@-3KL%!HPXx z(QMUqJU9=49g>V@9bZcv%hbq9m!rPNl}#5gnI}w0YnAhQFY>H<|L6*?Qlc|E9?x`V zuw9Orh-EdL9awoe>m4TaP;;N1Xoqdak`du<+#;Rwn&WBr=adHM)!Y79Z@ryK z`fJ2or*feev;@e$z1>N4;@sGkVQ#JihjO*pXni{0TV?m^hBE96+zoT^R zjgF}$PN)j7adZlDQ+3*{?hJ;^hya0Tj{EUSe)`3oK0z1$F*s42^1BN@C=$5qiQIyUCkP3zHlHj7dPQ*uch!*p;W+U#6y^okd?INkqPkmZlw2nB94#J#qI$5=`s* z6(6%t0&?asEKfQ0Xj*}a!xn{_QcaUb>3|%nNQSIcPDYTMhs&b-%X6#wGJEtoU2b2R zNYC9CzDVqo>l#rXV8RvilMfAh2pz8&hfuE?#5FKSb>?H4VE8qxR8VQGJ)u(e)=@sAKz}WOsf7e!PY8H*$V|0xE44D z+ledg1f!>^Ow6lBlf{gHnZjlmHLITIvs$h)Oy6Fi^K>eIe4~bATR`GK=&D#h-3EW1 zwn}w_cJ#^(1M@}YNttDeI0OqTQ^E)fj*>)3KGW*qRb_eFBy`HknmU`0HGYMkQdSIf zYd&QB{gIk{)}rh6fMI7c<;6#PYR5mV-)YLsPm42#*$>Wc=Mm!4%veAnIM*hFR1Y*%LvVyL^XXaZ-1^u_kx~&X3v}4N6?_j0|FpgDv)% zVe3k^2b7i6{@#m92P^OLJVWW7LEigUn!r~7R>pbVi}&PqOdex@ftS`;3|JGZ{N_)O zU<4oEDmL!T!y?N=%g54U49hEA$qFB7*%N|ao?l0i{Fw6MdZC^%t*%JP#8xedHSf3f z(nCT?7uSD2ZgBNmr(F6qg{$NjZOw^GXjmp2aI{hp3@_dam>mC_@|(A+{0=(d!Ina~ zz|aiu;?a)s1&8pzqHG3JbXhZ*XK7s9UleeeO#78(WItYW>-!%AYzfGsyVlS<1ks-l zm8X}#4g~_{Y64tVNWWG{t;1oM4g}QD8yM`TBG4j!PjRT%bTFzZ78xvAbmdppmiWNp zzL{Ye`bnoG(CPuF$cJ)(p8T^id|V zGq*qH6Q)D9z}rnxkTsy~06H_&yKh1v7Y_%vu-r!PI261{dL3UU)&FU-dW@Al=Nw*>772? zHEqh}++5ymyn;+K?fm3u`Lfl1V(A4W3D;+ky6{J1LOi zF|(fdExp8@~O75LY?%!ou6PFbx9)v&J$vbXf zP}7KfBn{!`#?i9i06R*U3zg|9)FK5_I0-}gFaK;G0vkiT0vY6J!u86CBnB%9_rJfI zV>5Z&*OAe#!OdNM-`e`Da$%YG%kPSa(}auXd%1h=jOy+$&#D6#XVBU@7g*mxAj=u& zFKq#^jA3lMG%0CQU~XX>QyDdiKKRIu=J1~isj zx=#JQiqrOpY9gMuKxjQ+s&IknAQQTMBm_%MAa|weM&|*>aA_r2n4W%C3|<71a)X1x zGetm#9_Xh)khu&BPD3Gi6NQCB@aCgHc}c|1=is3I8-dy&yyVDiNSCU$I#MrxeqJNC z?gl~M!X*(M@9zE0*uMw+hozjHYUi4Mz zOANqhP$n>D3tm+>lgN#rVXp!0N4mr$ET~HFN0vS>^`YJ{zdV}C|_y6rs8LI}=PLnNYd!zs>Pc-Ses)QtDyo;jG z{z2Pi{;Igq@;z^}1O#!xVPy9;3a|*UuwdVJ(u2NwBW}ly{?shUy^A#Dm8Gl2276{w z;WNP%16)WA03+RJYQj}&ai+Z3ApYR#o}fN;-u`{+Q2p4b^m*&!iVGa5SQJ|d)){2q zy#Em!O(Iy6}!$~6c>lHo-6A>)BxbjU`pE&pkf<6aIBBY#sU*&8n+y@E4xT~+V z{mO9p=T^puh+8889*$N)u6A^NL;uYcx{j@z5X68&$RaGw7qUN`W4&|0hlE^;ZDOGo z*Bl$~{-60H%dz{!S3#nG^bH95#DWMOna|!f!)I*$tb@a!cKzpTh;r{-vU}>C8N$mMW{6k=`V>voOMX%WUZ}+tWZRPT z2WQ1DV`ETtv(^a3`>52LSX1b6JlCQQ8K19F{hEQTx1+V4L7c4U-C2X!MQ92ZfXtuI z3}!S&2L3;eY_4G>fV#MVk^lup^%Jd9b%J%i*VbY7#P{JVwvd!+dpNZdVd=x%uTND|b4kpY+qa zxJ;)&r+krUos>N5xQyn_paqI!}1x9tGdfs_>Z)V)sk;FbowElhH zAJG(}q-HF&Zfm%p6PFZu75lyh0xnWY|4#56PNmV&-cbRS?jh7K{izYe^f*-$XX>~&#_pAI-0upa~sdJCoq z#Q-)pIc!|N+H@BD7|xESC_ZUvwm_+KU!rZpBw)F;>lDIdDz1@d<3O=w7}RH=+YfVo z`9;(Fod{*IX#Evvd3h7N92Fl-UV^7h0t*6LUA;PH`$*M+H5tfd;%szVlQ@kqsb5eC z;6I#$Aj!wDK=!vRV_#_x*M4vZN|v#Z`zvIh#Mn264}I1llYjJbPVzu_NJ9wll!+cp zD119gJP0KLUXlg$ct;b0I*^jKdd?l_oyK2=oD)*LhaX-!afXt+bTwy+51+#r>j98D z7q0JWKaD;dmGNTJj}95KeG_zA0$dZjq)f9lgqMfX|hCMpm8 z%Bf)O==5=%+tW$$5xQqpvml~za2)40J@7GD*qYz) z&BpY(+NF)=#V=}Jy!ov}sQ%R;+BSp^H;?zZ03K1taW$E8G)@l-OyqT_`F>t^%_rInD z>XPqxF6_Rwnf7l%0NK$ImC7nM1FlYkN5<-GMRsrBU$>7X) zyT(2ylfmR~&DuA)?ut~peR$vJxQi?>>5|#CQhgNa^VFxLawrIptNCwhhFW5B^)csRi*bm8Jq2MCIC|nx1?jMDT^b>fFXA{U^+gqeh=A8==M>P? zwTrrDfY!!X-TY-)%k$ah8o?HP`rY;|`Z7Hw#2K_r2r!XGz7~mNN$%}PZ}FyY5l*~?Lu{PEC4f#7)!Xg^ zrT?bKp+yabe!F54)wiy63;i4xc$HCHBX2w9MBc;;g~?+t67*H#Xs96g7MlF9_~Twq zuplpug(qA!3vlzUk(BK7-GJK*+vX=DBkiug(0SnaC-W0dH5_OS$ea93`2O-GF%c0# zBnx@IPq{|HYUCxEbwLRj#57MQ;nkjBio~e?U5HO|dn0^%@$M{xax4<@y$knHl>}N$vw@ zf(zE;If)ML=9N=VCu{8N+zeh&`ZbXp_lmg!!eQ9vT{np}?SE$)4k=*!fJ1<`e>qU@UbEE5Of8iaXfPahqxQ;Dq<-o1)}%c8$umovx9 zSaiCuc>#($X?1Bczn`efc1tiWNB(bpa{M(WV@EWT)^l<5>J8bv)Q_}fI%xqQT8yDO z-t(5Gp$P-kMV~-%9!HXKVuw68Ki7*7_Dw&Y-88j>MQOt#pvTfE8op|q!=xORrV2#N z6Pw5>!ljciEWX#Pdk;yuY4rlW06W|nm?ye*wb2zjz*rZKGv~DV=grBeh(j`1G>>?F zI6Bqw9(F_96v5&6&yU8Uek_u(St%6F?FdZtMgI*&js=i)Y;+3O!BH1(2seneZ|c=; zfJ$C(l#oY<1-X`{jsED24dMC*^7P|V&6m0#weBA*yeH?c0TyE@+CH(^;G4|#ec~Tf zV_4D+?5YB|VcMyESQjja$jF6eCS$vYE$=vw64!%CuoQmVz>ZtvoSb5!Hoj4b=cUm; z0E-ZFho@e#ey?pqUALf3nH!t*!G!M(6mBh*Wy2)HM>kY$4}J#XmyJJLV~qlB zU3qs8&@2KdoKNr0A>?UVwb}53L1YZL|clELv5)#;yca#AIs)(IlCb9GBmOsF_Kth#8s z+vxxk76YwmDJGk%Cs!HzwXo_&g9?&;23$6JZY*6#k+dsnnsj+Qb)7qD+LvOP2xDr> z`B+Quc};Y$kSKdFS2I*IYdvK85O~_o-{!kk_xKKf8yDmX?|Zgj}DYc zT^I_dE*%A*f`ykOCGR{xm?tE-485^!HsRLAAjn?1@YyZ;gG5jQs2}F342_CD{2ujl zz{BDX6$A^EDz1`C;$kFmy$5)IgybfDA^L&7kcrb;7$_<>9DbaYI`!Jd+IW-91Y+vk@$^>C6is=NSupcZQqkR4Ue&gQ1kqEyyje zNiX%(ot%1c<6Qyr2J`j2|Ltch07*ac7OboMYh+C6!HXwfOOZoM*R)}N4sZ5Cr3i@e zy~MuQsB^#x;A9loQlw9k?u|B4nJGLW3LC4wLYsyX&!AaU^K=qRyoh}6P9CT}VB754 z^C`;A(~NDv?EJr4d)7XPJ&Ju;QJpaiKg(s^o@;T;WIaQ&@;<&fn*%}r)dJYrN)1z4 zoRKsE9FqQ8V6mNZ+_nNP(vel@Y)6PUeXkuacK%PChL`4T;LY#a8AQbB;P>8)cWLAy zO^2L1Kkw_LSlWkq%-?m_%(Wz^(vpH}fR)LfzJ`9LO6H;3%;tZ`OrN)o$uFIjEhh>o zV)HJ*A+K=-R^qN~_KBs`s9+{CU{P!c*&f(zSV>0h`}*S?VF;+*{xrqM8MoSWm7yIN zT4LUXoC37B72?TVm}J~(EA>uo)Kw%qyh+tr5c|P7Tv;`%pE7U*Md(*w-oHbMse7Qw zPT+cCkh>Y;`_x-1@Y}!GhsoE21UIPKfrVR;!tj1SC)m-Dn!&z&(Zr<1kmPgU?(yqG zi1j$g{WX4w&rQ_)5ve*8!Yy^5Ftx;KXrLO`u$x}kx#yQAQZHI(>%KtcGg~vq#pLAb zKBuSN?j%Omog3%n(-Gl!HksKU4F>HSN5rX8RfvbH4-Z(pR;?V)z#`{oh&MwzrJwne zba5q=Po=IGn@8cT!C?%y*gEhz$7N1%gFU(aIlrMnt6jQx3` zu_dCi+~^p@%SC0CXk1dFtGtvu#~Y>i*GbO>v$_U(nmD!=<;a9(4CsXT#1?z^MpNeVwP7lmuP0(xgYvB)$adb+qHXtd_sRgR9z;Avw^;n0%z4V?7kbuTA8_wUz+!Ek ziL4FC+EZO#SX`dalgAhv_mN{BW%el@Q?L8n)_jYur0r9l0*G_|m1I9t!YzUfXE{a> zfOfMNibL|7^_bZVT3_EYQMd^FY~!5iA~MK23n-f*a?-_sO5-hqu$c4jj_a3e8SJxG z&1zuh$eW$Ui|Mhc_>r`@2rY7~N03_ADJu>x)S5kQ&RXowHG^^i^9m142@v2_8s*1V z=O@5sVf)hj@Z`KXHdEb~VVTGC1#nphyCHH$o+mvTe(GX(1AgttU|K-|$^9o=My5=F z4V#ANpD_}%qgd%>Z@~f+lrr82QBjH75tl2bAKi#x>JEfqd_84ljbE@}9qD6E>m>L* zOzC#)K=s`IZb&YVxc}lESDRfzlluo7GZ=yNS`Uwztcv)SZas^9PzhMuc213Ei#KkvAEDs6x(Rc``3K`q-Nc-}&DYGW{Qa;xmvNH% z(vi4v9aM4&Z-!7SYBUKwKkNH*Z9BD&Eefwc$O*V(I)?RNUeK&LAJMv3Em^6vsWn{6 z35K0f6JE^K5qsTnGLJqsgtxsymeABZax(NBfR#KqM(v#kgdz!Bj*N_($sIgPFN8QM@)F`-}j zgwJ&pwBdctdGZ`~@W8Czh~{UCF}=p0A^}!W#ywAibb&1es5>^|hD8Ft)HA+22Cuvr zTLCD?Wg&`;(6JRWTamI#Rwj7S`EuyvLg7+Fl0!{K;dv^_kHUEW3~^?^S=wjGcckWD z_*kTT8M-X04j48aY&L_r&0Q(Gce8!g-ji1I2IFG2t|E$PmiVm{^Qn-Nha>wM^Un+Q))} zSm^1~I~jLDDrFN{wqG~i|2z}y1=N0W5+(k>6kxr-5cXaLqhHsTDYv0vQqRMWhbr)n zB@(n@$M7rx>8V#7oou*dF<>xuiUpDlVB}nzdWOeqv?seGbD4JrmgJJ z?|g9+DhYC7+6Ql*Hoe=t@zDn#n;00q-32{C12ZBNXZoQ=TKt~!Bn!CY*_y`8l%7*J zi1+cND<%X@c>OGjOtV(O*jC16ul%-0UMd`)>+sFS^4F}y{@wq{s9~;1Sn>bo`HcKj z_i@(W80u(o`8gZ-%&!t&GvJ&Wdb0BOlfL)mgG;dvDwge-ur> z`QVksz~TQL=(W@Rf@G%Ze?=)(`=0x>&&%SJeeOo7Qta%z4!^UlMIuDE3=D}7Q3TDCBi&RjN=-0rDZ5)3) z40GC1%8Nw90-Hn8EO*Wr!(PH#GTcd)8Ov^pj$ebyluIJH}(=o24xL_aFo*l zYmo8@P94Fi9Mx`IdI(3ryQ0zjE61mJet2REs3ew_rtK$xm{q?>#&m=o1+xvI zjZ-z7c`&fxkTVBh)^(cOpL6HGJ=Lv}=<;uS$Phk6!TYpo`_-g+$CAH=-e5t-xi@>? zP8?6;=G59$iPd68z(ixfZvV{4d2Jp^51p^9$BK=%{n&RuaE^>?Ld&B4?iO=NKLv$) z+kR7tXoT2WZAqAEKVZ9rN~%KM@)koy8ZU$Ws0?u3EFKrl{$!;Kd5w1Jo)Zt2|MYcG z--suDi}&DsOz#1OVZQSA|IU+pG(8dQIQgQheD#eQ-EPB(>ve4X!pxI*Q>S|w!hd(~ zTrQ5P@R0f+Ek`&GWFdpx*GECzU2bcd0#dvbV=|7g5c1D+vkOX%QXb`A!IHfAr3};P z5Joi`Jt#ugvf^tl|^(SG>hER28oILnorm;-uc*<9)n{cw1$mV^a{sB;rM*;{05)s+S`4onl%;h%M6L*j@Zdr3(d0?ucxny zgXq^=OF&dPCW^REfX^njPB46+l(2D>1kY!8n+CV*Uj$i5O^ZL#+rfhGckHZqrzOYz0BHd-BWK+}k`r39 zslh#kM2BEM$oy-C+uvlB!TQqhW>{m|BEupJ%b*>T`ghn?Q+MNe*v;|5^HaCt7iaTb``b8Z z65px6OFazU+eYU_40nWLel0P`?B?}Re-@TI$jqa!EB+yC4&+`0)ch-4yU585?9K$} zl(8d*0u-#iQT|;mr)DY`P2@Ajw@SB zUkt_q)K}J{mQwv5_htO{7TK^#*PkiDhF%@vP^UL;O$X2*4_EqCj```Oo-)B3%6{y@`LxNK|@HFqhJg&oqs5H*_&w#N`z9S|_KQy@F7^ z?0l>Zq4TbGLI+xx|DvEE!4*8VY_#Kg#F6lV<)*7?j<0u?xy(R;HRIv@~7iM9Q}DBW_0TH;sZyMPqvt%w@&->E&r3skqNP znNDiB{Cfk*o+Ye2h} z3nWwt1B&&`%{~R(36!MR3=|1iTKiXD4fcmfa&nIsJIst(Y)y8MUvS(o=eL;|XVb8| z+k6iMf}K8i6TfD@w~2luTnX42%6uZDOkBo5UE?h_c5Ldi{jS zS60Ui<%i;Onr;n8AwocIKFTVse6eflbTVH>@B~7So-IF3VUxJbnyB3Q_CkR>^R%MJ zQ>p;(H)a8)pno5nRe~qTxCVH(I3Sdcyp>ejAK*B|UlDk_sru&m5|%i9BA>5n*|)s| zKafJM1K#AT>y9Js<@de!{)T(!+;jHXXYak%UMH*{eLXX;2RFjb6cj&hs+dLxI>PS>9p($* z)}a=>x;(DHqJI@myNaY{;Z4LRgtc7-Opbq{V_TFx?Be?&MIo4`H4vynXZMXm7h^03 zeF~0=4w%)M{-IW(xs{l&jwF}lVVtHs&h&IpDaLGkxer%-(vqzHq#=A0B3Z;MY|!zU z0S|nS+<*K1tLU_x-e1zbRg;%_;7vX!9vFbX)2;f#mA&7Jz=8+Q@F!Xgu|i1t4sCmh z)&t>hs1M&x$PCa>COp#5|NjIL;gkL_+Y)MTcaSPd{`t~C3A}xBh`_-8)=%W(VaOfR zus=29>ga~Cn~=A_*0W7G7Ut)QLGUtDNGH1D%P*p~QNfvEV=Dn5mnbf;B--1d10)U= zmpe=JRfD{Pkq01Ykwmp6B^fhz+GeAg%ICdb(6d%*j&t%#`3 z9ztDaouPGUzOm)uyzsbNEG(Q0^&k|c9_(Ww+2g3Z9_V=B5)CHUI>b;tacm1J0T_Pl zrCHDseQfJ%WKO0xFGJ%~E>HR%@$&gX2X&0XmoZmq96T3l7z8(NYMb9P?|lzkW&3~G z=lm=+Xni?E{r)TzlQ!egRSPnK6cUz#h1G^h16|WQq`KYP3 z_U>_Ve;2O(b*Y5de+)a2%&a2l_Ez4oYU^KKbCvZ?B+NODQ9+}3dGJPZ)oUgSfPT^5EP8s469*3Nym{#kHg;pJC)77nB?K+L8K9ds_#+>!F{vcRM>MNC*!rq-NDCfBS3L!-pU%Bwn#|POx zi}c?;NO35%i8_Yia-5O1fMw9Y{kK%!h?gC7C->|Yfs30Q7JGCoT%1VIJk%UIN-PLx z9v(y4E|lvPFTPo}JNK@rig!Q7?kK)TY#wv@#5Qe@ebnFoUX$9QN_Oh;*!CNJBk3Pc z=-Jk8y0(L5TQ}Y zD=-px=(Rb@RY=T2F7y@nG(PKWPY2RIr(NZnA8NZ(JR&ENEyDc93xh!{-!DFY93Y8{ z!%1QTYbrO{L}hNxTR`PPNGa$J4ahpWCR$j5WK*H*dlLMd}ar?+ftfR;x;LFJ3e$Ow+^0WP`Kcw@w{5H{<+^?)=M-%Ma&3 zxFf8%zh?H2g!{sKK(wg{o&4lV*~_2qX&B14{Qh|Ny8TN%eDgy$=G?<&T1uou zS;gmnn7V88bihH=uVqY^jUipGbI-YZs5t-gm#?$3xCShMLcm|bVyx~(HSU*v*UUzm zCSH{xnMZ+(=$+_Sm_+;}nVF?|_q5GIVgf-mvMW*e6vQEhubXb@%J*U<=W_{|>K!va zA~qx}3`B}OXn2srB2vXq@x;BEydnmph{obPabxx7^%#mDQV-_#x#w6D#AKcmSh0_S zC}7DdVf9rHM!yCm_c)$@=f*Wf%wC`gs?JkedKQHwOSl@`46sOa$9_@*vnx=pt>y`o zpA0&HagFVX)*ksrbiVt0Tws~5m67fd&O#;T1yZ2vN^tn9IAda~vFp&goq__>!4_%5 zBDazr+0I5wNl@f(FZ#UoQ6M(x$b973PQyAMR^G>|c)orl$05qyy4@T>D6Kv81C4s! zYKfCxzy1!2lU$I$aL;{tyGNRI?X0BtSyhnxa{R@(^0|CQMI9Z;G_sxKs&_SZVl0ZY z{4F*mvoOtnCx6#0Gd%wNTt~~^HOOH^GxCJOg8IEA4nC^&9UJ*`a3@cFyXR+3cix5K z;Y_nD-N|<2q@O*f6bJTQJSC46js0}Q{^`CT7?*}QQ@DfliPF!E^~B>XSLU7;DqW4^ z^)ES<288XAQr{#my}mASwf2dvBDs^UgDGls9gHRIhZUu~yBa1j(~HTRTx@OlHhA8QtT2XF^{^gy=X(ax$Q(lr2arkZ?mTKbkv9Uk8l%eMjO09#GC$nv6P?plk&m3-MUEG8x+v$Hq zCi72ssEpILpc7prva#zHL|fk+U%fL8f$~T@;y&S+KwZO2?f#jX!e2b4MLan0q(B|2|Aw|vFKvnZk)}#K5mLY7z7Q`9M9>%s|0PmCw<&QxO{C@1-LnALH@YP zoe#-FKty5hD%FHBN|H~@-{aen*PnT?Hhy^2;Q=aD5RhHWFDrlcT7XE7t8U-(?B5(* zo5vT2(Wot1t93%a-~$c%q;ztYbASTOkN2kQ^zQFch$!FQ{`PGJUO{*pq4Ay=e~U+a z_i!XuZ$?9jsTd2Sbq$Q!M%Z+sll=SVNr>Y~4FeIdvWuqf#m`CiFm`NXRF29O^C3-B z(IR)02|4oMckQqVIO~U_ZgaUTIBqR2{o~-wv)@f)5D{*v3o?pv_>?f_PJe?WT;pmy zv3iLnr15k^T9ONNH2I_Koc~MJIz<^8J`ZjP0n`&(rdBF^B4gb$~k{Bv<9TXXy63T{_+iP z4gHabdqrl+8#`i5c*NAlC;wYPJwiw7f?Bb82_k5R9MFlZei#~}iD^I2JOqnu-Bc0` z?ukV>N+&Hj9&}`7Ph{R@v|gjdtcZeABm-X-EBH+STk7v<{1td-@2o<&6Lis2%>+N@ zY4Ic}C~**Lglo-Ptza)qD9p@8iw@4|$rLWya^u<-ZJ|8hIBb(9uAR=AXt_5=CORdvNaN?Z^^on{`q|n3cz8xr5)rZ#8W7S?2D7c zhq^ma)>+DrhXe^8Ykfy3Ye)+2Uuv0$AUu_@B8sUmACf|9D z-ZdGO#PzmZzIKv%FHGD^h@|hKHo6Q1Bdhoop0!)2_aq#zpYxEM9NqFKA=P-|H@XulU6sc*Nq&UwkFct| z23g5!FNZ=ibQhZ5iQio6xk4LIz9AjIWMN2ifsNWhs&#cu?gNV$M@G?k~GRfsB@a+rqsN z$QSR99aM?!(a4Ro>m~KC*@NIxm0E$#vsUYc9Nw1iSgsz&4`w+Pi>a1|z24O7u+5q~ z%3tOUD_-;EDkbbl;D!vDFIwD6f1xRvjGAajK4MoX$^=2bTF^kZLCF zH+nvLW9K}Oqsws|2*@Y!%18X2;NgMtE84t!88~>!>(2mjM7@{ZT1(2u1eOcLHI03iaCIZA zB)DOrgTcduKz3oaXLsBAu<-4vp299+W2nA1oksVK111TJg2Z-J^8750q!6J0F!xzk z$8KLgZ;83kPWOJ2w0=gYOBWJo*f_sEi169P$Lmrd*Qq0NmDIi%lJt#97}i_PV}Hz^Cll3j@J=X;~SjfY-$HhVm?%t5N9t6KaAxr8Y2ITfsa1d_()FBo6`^;S?KvHK>ka-Y!&Z_&_ zXnnfiozzmbw_Ld6gvT%G5q`P&G0SI1c@Za{^B(M`4_mc!${*|GK4*8T?Q*}uL`QyX zwe(xrK6QIX`=-M4;{E?}0opT-y<9&fS*ohOscje3g-FMAhU5M|uQ!vv!qphvNeU9v ze?dOwRI4!ak*&@HQ9@^|d!aq?IYk$hne+YYV-1{e5;kuu3iMcp9xfs2l~=%Yu~x*c z@(QWz15BRF4*I7)|88r}DJ2n6ym}b*yQ~xz2Vq#eoC(&U`$r`5Yilop6FsG>pCk(? zhU5wN_rx(L;95Q!ylHw)kt94cjD{u(*!8>+!8`-a+xlOS1&0?98Z#^B6pC3}V7QB_i zB?}1#@CETO%jFh)8-)JW={wgiR}fO;#CYV&n%B;%mxdc{pfZ)7i7h!0U+^mFPMOl? zoM8*n5c@&|IzzE?aB}GyiXKDvA2La~4DH?IJ#N)?jUql$C zWeauOkeEVZ1t$YR0c_E}0eW25^GxS^U(^Ypd%Q1RrSDp;3iD<*oJ_nf@1KjxL(>bb ze?Pa2jp=2t}(Z~QAFk}U0hci`7F!Clg|^G zG2m$3Zi8TrAS}0PW3FOi!HdW%*uf*FG}5x4&a);bH9n$=hXEr-MO4acI<}8kR8&3@ z9DNk+JTXBSxW-4EkQ-=RF%^3>J@e$N~=hipk*v(5Eqv&EL9 zyreAsW!`w;U}4etCipKCTNWlzBkO{|9J6#;Bp%D8L#oKPH;RQ%2I%Hi019!^oln(U zC7F4lMc;oVk)>UYEh(W(>M?jfjbd4_bl>Koz)s`~3F9keZPPKbmbhHXMF;*6{x(63 z_wMhX{@OSHz^#VI8}YJQ|9}G`>&zA!uu)B8pbpn3@YH;;p-BMEbKI3NQLe(~x?)_~ z`Z&O)=&}gU#WOfQcF)pbd)kNt2uGPR7}aT@GVaZB9 z^Zo?*(*jK^Y@(Qsj>``9?QNXOLue#XJ`{+_0R7mo@``U-pMwJQ<1-V#R7R!ZO-xX5 z$LPnKu%j}AtOZE0lq^thk`3iNE_Nb~((|XW`*@lQ|5cG&nc)Ls))Ix19an|;t6BX1 z>x|1RacAE=m?yTQ71Sc=TtduJv#f*GLM>kZ9SOmKnroweE6P<@e0)>1CttB@M~TzT zM&v~3cw7Sqnpm|Tu{Ux@(M_Vr8&uFo7=51CVn(KBmS@sYQsj-kx{JaKeSxkq+L&Ll zpRT%6ohS&8%L~iW#i$cjR1F$3GtDeXkD_{*KNme*aHf`_sD*`&ROKL-S@6`ts?wt9 zAge?@Vqc0rA+`8Y6aboJUJc_Z$zWF~(GUM<6r^(+YPy#frZ3kW>|NQ62YE0UjJ07^ zRAD+J1~{{HKR-G_kW%ib)5=l6G9O`ouJIgh{fWeRrjMbtA(Y=g0qTb3rKeklJh7r? zg~wUvWhukyCg>CJ7al;2hc7FCJ|^#+OZGlQ1B5{pBOkAW#rb!Ws6-2E1mMirx>1uq zVWd)0PAMEqsz<=Km;E9kh$Ua!RA^%7gcSBWi(b9;&=-|#lJVqd!qkIk(Z*lyu~a@< z-?ffcQAJi28E|!#Xi7lP!RDs z+XHGLuE)61^w^%ymlrsh3g{kCKFq27>l z*AC|y0L`v=Ou*g1If;7gNDKoY(K}!lzS;p^mdRPJzJYmP-!Wb!WvW^H;)mSF2D6v} z8taPjNj$P2$5hqWC5JsL*yNDOADZ;TzgN8jHnYJ))a?ZOjPPnSM>s043m}TUbxO`c zoMX?7Q~i_tSZL8wMl?qRQ=o7ck5?WDt*-jSpJ+G6rJ3`^lY&3dc%;N*8U;krOVON^ zg4@nXYZXF~;K=G8pvL{m(MYi1KtonfSX8ix$Uxn_C`aBC`yCE6Zy=1@q2!|nM|3*Z2J*-ox zsFjMk1UGRgsb`^(uW@RfRo6s~Ev{!g4gH@kCm`nho>n(_0)UQ{#j? zaqzZPpDKSYJNGwJ9#K+z=>Uy{EHwXudmC^r!e9N4i+q!d^(RNc>q69Lv+9xN=Ci9w zQnLe_IEu_J9I2JwMWP%(Lqtb z0Gg9LaK?p-K$PSMD>>$jxN{BrVC47OUQWHDkWQ<;>b3>QEA-BMa9i32N@8Q}C3brz zzoKaPNY7zjD@O1-;NS%Tzvaug#NOwp-?PE%AprmR!etp_VJ)mO#T<8_FI9`%niQi| zu2YsjcUf~qFnwtJ69$uhiSjia$s7;-Q*3c(Y}yJ_fBOJSSBgJBUvQXP!lp{0cF+W~?=> z>*G^*RSc(*Db+BFU+b+)byodZjNQr>8*z&LGAvQ**Bu&4+A7^#4^w6sK$=`!F4Pjtp zQPTxUX)=m=_FIp6MP7o=%7^jL)2}7{JE`{Jw@WS8 z9#EOTK!ZM~(phs~)7P@+fI6BB4T6BlUk>uK;(G3XlUd+m5daxVgP8+g_o}3Mw$hS6 z2n91RhyczB+h@1aICF*bFEy>W%wLr$yX>5oQ-R^jsCTQ;>VxLp>u-w41fNT1Ct#IC5p-3#=&{OzP>wXlG zmytS71}mHr{EpUJ=lT{!ul|a0U$3lKCJ>1{pUx|8Y<7rzf<;h7e?#x-2e1lMR735&T98pcDV^noX)|YQ^{Iagj_~t5aY!d#SLAuX4O}I^4#zV?HRVVl*Cl2H;}s zk-yQhZ^_6>*Q`rK2=oBqVf*4I&R~GDW2~2$Vh6xbQ;mP2k3Wy)H9U`BnPRaf%_gjx zjrTU+yG1^MYo&A3{FqpjoUXmARk)NslkMuPb*M8^=FYkgDyjV+)7dHfPOlGCKRSJk zhP2GH;Tw`xJsAT{1wo1AwjgTK@#55H%T-+w|MI#!aCCt4H)ic`(g3*+L4m|W{@i3F ze|f%(+I;f(N#P{lR`JvxvHj)g*ymxzO13UbCvQ9}OuS4vn9GP}3TPUQjhOyXR(~vM zwK>@b#3(sqYNcuFQG$DqUTS#0FM0ccVT?)4D&gA4T%N72HCiaTTKfI`+1l}*)T@MX z@%_$xiy;-ibamk^!2c;Y&ggC@Rt*^!gnvt+_Q0TeYh^Bghlm4#>U&7M9&AFagKlgG?apRQPU~(d_!I(E|j^92q zm;E4wYc}T-s1D_kxN$}x*LgddwpR?#m5 z?r+;58Pq0UV;nn+;39U&UmNsG+GiuTkMJ+xFvBU@Or7M)muPuSe?(VL&2DtnzT;)$)j_~h)>2WdHNPH>2sH4Mve!E6V zTw^5$T$JtLrRIg0D^D(i3+RPts>S;)<+~I3eZlFpA#r0RqFBF>Yfsb8xPpL}Vf=f1 zYZom*CLAG*a9Gg6w{+=dEM#nr~yXFc=D#-)#kai!VcYK{wC#UeYidQd^oscd4#Z| z5od08l$-5(4kheOnyZ;RRcxk{kUCef0GSoX!uX4U8?;r)BC46=H8yCE_Kyi{`g-L2enF~Ocws7RL1U!W!{<1&jdW%QMsmL1#>+&x|B{ErC$RnAA|4Lh`wbWkw1M0ir}{6gVo#oD$Lshh@t%B}K;^%@t`6 zJiT`Vb#J41*4fN|Xr!~S7xZyHoT7LG0i+*mKk_OQ{g(cmrB2x42&HTv9p7Sf4IpE^ zhpp$XE9@vd^2cPkeeVpH_n?w_Iye@4cTy6skB=l%qcV!nB_#E7+oAr*AMZ0qhCX1` zt@eQKn>GAJu;fRr)I8a@W84JauUs{^T)rpwW`M#N&%d`C$Hs0j6OnO}tmeAK5n})g zpxHnL7&g~d*VnofYZdftZ4f|1-B&n_h&%7w{70En8BX8CmWqAbJ@f1;izIPV$&rSB z(?!LMU2^AAjr5q;eH#JOn@feFS?K=sYFoi&D(})blwPp;GmPSF^^rwc7RQDZ!J(qM zC2<=$p4|xq#?BRa)Eb=)bM!SSVan{`cN>-B*LJ0|MhJ~3*Z`uj3!RD$AWaZL>%dZ&wE9j_{qYYcNp4aFbIJ8E#rfgX(MPp(>Y|LG)7hi@aDUBJWY-{&HHs6WAh zAq)z(z5@UvUn}DKC!T_7md$TESdshG5qE6hP%~w#(}*(PO7EQhrwGRnCW3~_;uY3Y zc;Fu$K7S_~HQcpdP5PZ{8jn8n8~G-d!t5!9WfO`A5LZO|%-n@nGY06;F&XB2`M4Yj1!kDb}~lJisK*)9c7 z?{|W4cS-XoaFC2{CHzJ3pwUz)=_IvQ29xwD8n6lCP{P@u%x+6XmnjUrx8Diu79WVH zC=yr9)L~n|dzXWT;0Dj9ZXRiYC*99CX<+~mkVgqCvu=fJTY+*CYR&Do*IX6jBmMm! zx(_AvvebnEgcj)jzIO(`K@{pOJtzIWScCp5xt67XtQdkEjVm6V9+L2?|ACTaVPW~c z;wQC_1dx4v=#q4)ms{_cyJJlGDj6sJXdVH5ea1JPt?l32Ua&k-4E_a1xj?}MKBh({ zC2XwVr@ek}C{E`ppM*4^##-L$aw99WyqU`c&%GB7hH{mo5Mod)+38gYsaHT>N0I8y zLi^?4jD=hHHQ62w^@6>O+b6y|i&h;ruBsfMgYB<*`@lWjRvG5mf6U`K+VhCEMNW?Nadgqcl6nzq@_VkBIe?@PSRUCyF6bSEF7MM?S3yEY7iqcKDslo<1ZEXpp|_RzM^nK^G}} zsNRE}3D5R4Kex62bHNtLFDxYVUhtOXzNlgUWnpKM{WT6#l%ej@`%tR3)54>EKrR6y0VU%P>y6O9ce2GwO)Sol@Rd?RTw0HaJ{ZoCr@ zJby4qP2LwMWhx3%?zhGW2&w~)-n|P3w?u_yZLNTr@phd8lI&F>IR;}8(7G9;F(nJeUoB&50C|Zs?#rqaE+GZ04<%F z;m#JV9Px|%XnFBvZW7YJQJL#KB&hHHao3`}*up{)3a>Cf0)5q&i8|$u{-dL->&U2W zB(NE%kt8|7#~AK3l1mHY?Vs|)BRk9(*a#C(1&&yY1(?g)Ty7>(el4tDWS1uNcPWNz z-l742-l|om;Y2LcunWbG_Er8pbNg`|V_MtKA=DkCb@%=gfM{$?)G;uO@#^1p|;uJk`)@iye=?j4>_VX>~oHw$g=BVSc<&=JeK< znk}41I4Hc6TzVGyJI@8D90KTri+oBYLQSg<4nw;C>NW+*O{@2iC1N?gWk9q$oU7T5 z5N6{Wh7FC?)AFQ6?_8}Bmj(Ayqe%&*yxnQ%+hg5@RsZbL`v4v_nl>a+h|6{B>R1;Yh?{Ja&xzF zeafr82>-e4eOdd^C&%rs0fZw!!^_10*YNlLLTjgFi8_PEHQd{Zt?c4&lSlWJ1* zrzxIgTsEQ7ZH!_lT2SP@CtgM=_Yjd!zXG_q(L1@V=KM72H_MfKziT53FHO_c!3{FH zozi_MFPDrm(r_;#7L`Yb7r${?94sGOpxS%8pu_v0cl@;01#k;uh6hQu;ymyfr(j)v zasPrhdN%0c5j6zh;HBchsvTHDZn7!DHvr)@K4~of&etBQ^_^}`Hq2GNV7*)EnI*5@ z2E!{IlrT1w)NA5drJdjxro3NE7mSR?byS1RsxMAu)>#MuIbyK*)7$^y@M7WK@5b~y zX2K>>Y*FH$n|XaHL)sY=PlJ}8WIWO(4UoBvaMU@hJ$k;Ao%Fi9d(_7Jr!tZM$d9&v z>=+oEc8*Rii^*pPB0wJ`nAF%?pdc{b!*|fLs{%x&&T34HM=%-5d#GNMDpk=2&=foi zP89BZj}FHvvJpQW+qR{7>O4UNv#C`?^Ka=Fwlc%E5$5~52D2v~vC5hHn`LQ=F6o)c zl<)HX#~~%Yb^uU837(#lfmr9sqqL9Q?i5Nj)@iVSehaEMYOQHh5jc`vvlbKASX7Cq z%s_(L(su)%%C26miJD@PlS?s{nxm_3@i@=wsIWtSvuci`?m0Vq;aovL>q z%BePy$YJPD-9R1?hY&ke?asdr&UBH#JcaRMf=8S^J3jf+1)i+!otUaSZiiDf_=Xe^ zOTSM-FBUTYo5-ezqLV04ll!6EGfOi)f`b!Y_(VGYkArZF8qOHXTVCiF!LOuMPAkw3 zOH0uH1X%cWu{c4unm-I6dG*fo9+@bF${@gp(Z|k($G3-6BumGUY6(I%!r#<_I{Q;( z*Sd!sC$g;f+BIn&1L9Q+%J+lVn^jn!zCc|pEYgz$jEF(Cr)i%QdS(tt5?ss$^>ndZ ze>fU|JGy7BP&tUA-^!4~$vXt0*fPx4N~0Kxc43tOTJpFCZNv8=W`Hr_6i1Z(=U7&) zM$EA_uO4}+3k}JRG@|fXPu*5foP`;nF$5OFhwVsN6q`d4nnb_ijUu^xfcc6Nz(~K< zGwe~oJLZSeCmf2`!X$vBBo5@K#lL2U@@V{0?HHPJiz=g^f8M_?J4TLlR+&nZ>YM3n z2RMPpoTa|;6@SOt8ytVG`v9-^`*}+A+q`DK7j)TZ0A|7aVVMp-Mz)XvXFWH(8+WYS z5ebJEI)rs=(!rVUx!+zWY1!}U_zo?CF(XFaz|&%AC?iCMleKk@GLjtlWtZ2bSfac2 zIj{_L@U=fKVhHH>;*-0$DA zcX~T6r7McyXC21z7Yh_hDTc6jBKyewrzVyRQyb74;FHvv#y0=1Ij+kQ4)>7pBdqi9 ze%PMixyP%2zQ-X!o4($y5&6rX(O_6(*n?IaBY>0hMawml;1QdxWhrKSs?k9N_QCzKwv$Nx*Dm7O?;y3)%yAH4@JzUR7wP49f`7a%9 zV%(4hkPj_tr(U0FtJUDz6ynnuObrHi<3fW!+Eg4X$to+^ZgFb|uz_`vI`(CuocN^bD>ZjW`HxJs>olKl9|9x&u2mHn5DK44tIn`VKF;0 zcl({ov@15W;zF4!Ub-EO1(9L*9gb1FG!4~uAk0WI=e`6ZWI+D8q}DxBR4`W_s|H7I z?D$oQ|Ubf)5}MP>X5?b5Dt3_#L5JUCQD*Fe+L5qLsl zkOj`AdOC+U7QrlLf{X9i`DtV#$^;DGbW_8qWWl8w`?!%hHh7xmn$vubWEn)*df_E^enJ)`STF&nW9I6)#L8Zl?8R(OzUUnp{bfQlc}#(dA(+ zxZ0jiGc=OUVibv%ZSaCjw;aO3(Iz>vT1l9vE}}8M;mW-al$LGn4J}!+OJa#v4khZk zpTDc>1u~yDs{Bf2{uI|IcEpKw9qZNHX#w7Fp(Y|=50Tt*T1Ow0j~P#fdM6Zh7bUBe z^@$NqQvae@g&?xl5{+F(Z7Ar4z#I+I!)7llDrs z{p!@dYGGs&Ol(!S@QBg;@pFgINo6Nr%Y_csDdCZ9#eS6;s_8W^)u7vKU35WgRwOPS zgHbH23hg|&@jYpR1EzMgywEp8MZbzjyC6LOF9&>Ozy%<2XFm%R3(VJV=nS7dAaKA$ zp#Ek}+y+CyNI(mA#W7+5z9k~Q>IL{X>X;ca0NyuVtJ=E1**{FSN74RTZZ;>xeBP9KKUC=O7aS$tjp7EysU}1#SpWkAp>{r&XmScdtYzFw`h` z8FTd>B)OFH3Np0*Y87k)?I1-aA$}9hiW`;LGQ%n4>A}ewnlrhwPXgA_z$(hy+IdZU zqu=7EO_B%#l8;(2dmHE5@2B-c;7-^`^Dq02N4#|P09iJpBT`4;W+x|SHR(@*1WS)5 zTVGkOKcN_WqP%8u^x7l87Sq0zBMmr9OnrYc#z9LbRjN^!_e~W%5%r2VB%69i+V1w9 zYE+Fa7GY|58e2YXr-?q7+B&(J&HoqD=tb`k0DZKtp!D>Mq!ATpfBVJ$9@Ej{=sTXs z<_L4Y7o^rguo`HwQ|bvTjl@MUPU%a_aB65Lh&NXLUa{JCb6SM;lJ?PWxjC1P6D8@E zwe_3I>h|^$)ZFqs#spRsoRDEqRGZcZNz^KwTx|9ip5K<_m8mV>vAlsOu>pcLUcolf zXdYA994yz>&`ZnIB>1$mK1Ajgf>nVy>~CcW`|YfQ44Y42t3gU+?W09P{9t(a8T66V z@`*nIe^p0r1mlMf2mC@J=^?0)B`%*)s<%NDjV^hm$8sN4;Lqzro}rUVI}R znKQ+Epd*`CkBALJb;vLLdi=-tBbTvHVE3o8`;yR&`R^70re94}kqMDai5TZipN<7cTOa_H z9cbQtr~D$Lgu%5)h`nR{(Bg7@L$}-!HacFJu+>!+00mm6>~}r|u&>^L*xL_$&(c*m zafnF8jYK@;Pgd8=$YZD>7p=HOfw++8ofJ zKUG!Z5==Z7eD?W69~jmuk@4fREJ8Xm$+ifmM-#ZtTR_BYn0)X2C)*xYtaNa7Csu&j zz>;l-+3zif+%ZiN66eeG{??bZnL8=f%#w&$c1^g()+ebi9 zz;!QYxMTSnKUEKh?Q5bXov(3r(#4|MkUi4^u?+@z9D8*E&ATp-1XlcBNmlnS+m*Xu zuu2Kir-VKfgZw!YcwZ6+s!Fx7oUzo~{X<^YW*{vyZa7yWI!|R`8D5@P6TN-GTe;3 zP9Vf%z-Om*cAbeCH;3RYSU*Atg7MnC#9w-fIfnWt>AQ$8rsau?4~5Epha_{`9g)D2 zy(Z;$@C7&1q_Q^9gs!top1HWju>jt4;&Dh$XR2c?sJfb|Ks~-bOFcC zGS}+POam5)CHL16T#n}l;#0a`Bzc7%J;ezSEo;Pq2JdhY$T$$lOLdBcvvgO3)Tf6~ zrX$9QQ{LtL^#j5y6*NfLDNJb(yS@~^ru(_Sk%}5E3{h|oCL8KKT3YnMpi6LOvMbX5xl!;R@xyJoJ!Ch|yRoLezble!V~3 z@RSIz?1&B!u5Q(3f+@{Csvs_gjFy9;;50b);rU;`NgeoU;`|*x*DSIPj8|t~w{Gyk z>=@uARli3jkM^^e@m@Xjyt_FcpCK9UTU|KDueT`zSyVL)80xL>l|A%_rTHr~yj#0s zjUvULnbuBzJVXC6IZoH{*s(HpMg#VW_Tpp4B{~+Z*o-NIs)Xy;abqv%X=^V4PUI2w zc3;TSX*%{@o(>yUz4J>Qy0k+R%_@@BO8FG5^NL~6V6vDKHu&Lu4`~oyPyGg#myU_?3rl%wKG$Qnh^f7Rx6Sg_5=1e$zFKt0ml5zS~pY)$AEPNkR zJRr+a?RnOcxE|W(Gs>3@NoU*G!=z1NGM=N&Ww$g}O)Q_qp!+d6Vu@}Pmbn^)(lIP> za+33v;}g<}Uj}4LdhI0CYV%P{Yg#u9=er(?2@M9LFvu2dm@ioW<^ucRGaoeC_nQZ5 z+kdpp%{VGHcxuS`lER9Ve3%O#a6UbTo8?V0L&_XF%sq!JP%AncV`O`MHfGB69 zaV%30#}ipjx#`x$Wiv22J=ys(9{a|*SFXN&h=>^ojy+~7-T|#Q#n%O7Sx1c@xR_}4 zsk7qiaJqKKEcy0T6+Dg<2vp-{I(p%)cllaRYBy?wTv(9^pNQ%aYqDa1NR}Jo%EivR z-Qy_wk=UIpp}h(~l8EJV{7i6<;PEfTk3s%#N#D#kXxV@2*D}svm(=5(Bk+iAN`K>N zR6NVgC`7*-y)E{HgWi?3F785zKwv+PgRM?8ocxOhUPdj4y}BOJ+=JNQ(ekYoiHTne zaiJkVl-F1{$G&Oi+N#$dGRALZi7?3Q<8MN`Av>+uQsBs8%Sa7Y$T{UJ#~tR3wy?o%RurN?sL=<{i|YxxPrK=pv*pR_s}n z9*EBltY~F2N*w64#bZQ~dGXBB;1St6@6E>U)%u-qCHf5o?jeDDmS$D7_b>Fm+JInG zf&zgg8(otmg&^G6b1P4wEKfI2cw(ctBTF}*v{XOi zL}7y38qNE-v@j|^DM|Jp8tVsas%6wAAqw*ZAJJ4p^ke*^m>e(=AY|Mw|!OC!{s;hXNsO;I5nLwM?@y(9cKwQuo_@#YO=hRHKyw2+Tpd&0)CBI z%r9!04gxd7_l~JC12YFnZRl4jI1gdT1oQA;qV7b*KybXoWB$Kqjs_18R#|i*>5$=w61zDh97`^ z0DpP=T;S+hxqmTg{UFNLF=9u#{O!z7>qeuTwSFBCbey@)GD1^&pkh`?2a4cy#i}`g zfJcdvIy+G)8eCddlo@pxkh8X?rx&|m9gSl0jXs~P`M0_yR?jf$Pn%fZn`n05S#dnW z<*V%iAXtK|_CEAp&X z(z!gIp?QjJUv+849=yCqdGU?NeFm%rDe5^@yY1NSco(>4qFFv`3bk|B>=%47QhhY* zvwu6V(PjbW%5?)W3dkN4GaxkVkS?{k6pQIwFQgS~z`!G;kadH6N?!~O_~yqe%+Nz?R%QYvHp%r$2{ z2i}N-MRd+XU3MI2egBPCA zF9e-bWJ&z+r_bDaPk2@|+-?S<0iN5`AG~KIn#R5V9fpqIrUlrz;%7F9?zqrCH*Of| zs~w9^z4iPVp6ocmBL3l9BO%HJ3nYeY$_Gd3=GA*FH|R&*2B3jyv>rAL_xM{s@esT! zbcxDR+A zfhXtF-gBbjnHQ3}uC=iZpAje%aylP2&Ehn|OQOd01H~^U+-e%y{JQ>aBz^5n&E8B~ z#D9G0%Dm4$8uOGJ_{7!|-Lo6MiF$eRmZW{FB?0U`YNUqXm~VP>vU9!`wz#d*`F=6; zZzdY%0F_uebH+3=H7)uAjcB2PsM!5*y<#jv;1!0TfZa}jZ@U247&QBD*|M$U8eQzQ z7A=MBAUnEt4HT>sDS6K%gCD{u2Q3CT%~Q_z0eF_8_-hpj_?CUQEltxUe0`* zlu3knMWe6sw+FrKRGuOS+2JP#$ICfS_EAPK==JK%rdzVPWnXOnb7-KD6cYBj2-9C# zzMWd)@C#Rx+d}h@7huSyg~dOr3ik)eAEtrzIx!fghPwsg`FUyA=#L;F!9 z*B62E_Oy8!`b6I=S&4ssH;LEn8@+LU0>!34;c=#zI@sQf$2KR6Z$LQp(co@~g(G0V zZl+N*Gu<*u&iE?*ObL+`Wf_}E^epQ6ooI^1H)SQszU#8fiO5*v!>?eAQ}&zT>Kaa? z_M=n#7}e6`UKDfs@I zXGl$MRNYuC7CaOd^|shm-ALTWj4P~_ukiAx9^siroiBX~3QmP~*V8LHk{cQ(Sssq8 zB>G;zc3Me$Wqq>~W$g8?U9H)P&dJ&E1z17UOy_jV&195TAZVnBA$@c%TkYugeE3Wp zVvgQP@4;^u#Pm%;)D~*j9YmRaK(C{2_5X@`??9^G_kaB9I5_4x86oSCSx6}(2gg=2 zLmAn#glO1Xif|ArvyjYe*~chklNH%BdvCt?>HYfre*bdr`+48jecji6&Bx{YS%TTa zRwk&dBZ7{W>9=o0Pt|$^H8XFiAktS~;n=}ik9z3u?7hK` zsrb6MSv53SvZS>A-07U+$gLn6juM*zDmLsBHwmSo_3n$Fx95dB?=rU>&`;-A0LZ7G zph%6kqsy~MvdqvrAlg})oiuQO=LjCON7Q?s5@mE|Ka)}`WKiZU?)0Z^~&9_Pg?_uM0njJDID|kgORqIdw zHO2H-m`7xBW%+9?uzD&tw`*nrZ?9D&{R-hr5I{|Oq$-IX^d_eQRcVgi9QRKj- z*;$P<4ltv18AR1IO>vP_lV1x#EP2IR(f)lo(DEu)LaBf|U8pM#^%WSz0?4v7Eethj zxNcE?|ePJHfe|Q>O8z&?ASHNzel`54H>Del-y^^cA zn4DrJP7t8QP#}|3ehV7a{?1nI6*XhIqpIbkwPu~e05|K5HxXR9^Qxq%Xi zGsGR{kp_+mgCWH501Awki=wEG%1mJ0!p8M9268fY7>V1@Oq2*AGV1!DdcAV0Jgz4Q zrkJo0z~l%*P41(YyS0u#st8b}pIIuik{q7ozZnven=CmNwSV5dbs-F^6%|64DPbZmNLi1*&gHGTX(u;l7wZr?`gA*=C(oRi%IZj>%B*=ndtf?{c^rT-gA(|CVws!SsK z7KK2`E_SYGiXgn|KDV6?(sz0vaXedg>bsOJzTCIj7V-S1+J+x&(~CkNkD6W}*gx^n zwJGOUuj0MYT%eYWjk|-&yK3Gt66wnJdsB52{v>1O0>pNSEv8j(2=?H)GzV% zU+R>u$J?UMuvyt}L_`!KU_NUj#Q=b~p>g&Ti;hqWmR!?K;_YV+7M+!rO$+)sK@yX7 zuUA^)Y$*m zI}KKotvJmKH{E-BG}+xHds-kVQtA4RTGh*Prao2J;~KGl6xoX$_LSm9qrZL_yUNe& zwP>n#T$-F!eJ(p1wT+vK??StA&2k#>mZIFw@_EP+75?%CGp*Ccel^gvSUQJY7rW!` z)Ze=)zO$RaL3{5Hb{_!NbJcJ=noiX}qZbEhMKe_v!&@QCV>B0AM8DrE0m_5LCub*k z0Q**nFNtQYbZlT~`sbbd44mS3*CULLhq^}YXuPyH#?_{Hdyg%@=wKNf_c=}RT2&i% zVh)Hld!>3s6UdA2>1VdcY?7KaHaNXDT5bY_!jm`6q$pGhKM_uXhJ%LY*~+JItO&$T z!}jrg-aO_T`R$R)Z7!hT2k}~&l(;84K|NM-Dm$MTw)0op}MM= z9{(}9$g|t1+qsWypA(11$*j@{oS!K;6t=uw!(l{nuSX5dgwXUf%55bN^ZQ{bNkx`0 zU=0&JK}1nhXupd}L3uviwNSvn=T3xELtvgQmR4dyC-i0{*5FBaUdoi9^k+6l=-S=Epb$u_4hxytlV9pq;p#Ajrv zfBawd{N?Z!MVfR8K$&n*+A0<98^zc5idv5gD+bb~T>F)p-gcOC!ws$vYDo^z3H)1a>suYY^)F}-!GHSH2@ z`;|8JjT^%IH)Ar3EDKYwEdBh{{a961Hl<%F1A}lY>`NKs^rQi5u=&z+ocO*4LQ zNmmjRlSE!4YL~mPrUY94u`C%X^FaWS#7Gfm)jnKBpTY zg#4q8lS2E}H&g{4GSh5v2K~Xd-X9i45lwD4%?eopu$ugPlvF+f_6XKs|BCEGZdCi# zNX>Wu)4QPKJTpKoeq_qz_b z*^Vb1X&#oqp(oL2vyw~__;T_wKZT0;apMr~v=;8quDyEoKoQjHP+2s?+qG+>+I(;T z5Nhg)h$`I*YfNUPXm22nlZQY1*&@-miOnkEMfZt}{+zjt{=uSQezlGz#L$U0TI4P*KJh}8*{6?eht^I^4_D$l!e2OASB9^Th-UJ2EJQ?Uj0_2(Mulp$ zZ`3a7#o4=iu$2nu2v@JkQkF!|CQ=f~NR%m2_XS~3wN1F%*HJuzbQ&D~X8!JDZ{urO zNG|fNGRIxGDAKX+G}au#Z12@oY|NzTJ;UibGclHb^ik6K8=F1PMrCh=LzR~#<)-ta z;YKo}pulw#JZI|1m8fz0OazO^^b#jkJE=d~t2VV%f2Ng%Zxj}t&|<^G*%m(eyK=Nz zaNmR)H`l8yWiX_{qaaW!4S0E7A6mqea95yQ9Nn~VMQWl=>(y&T!g{`xoZjrnhbZqD%3lImm$(H5BN!?KOxs6Z z1S|9O+ScteZ2jyPFZLpL%TB5FfBf`;*ys$J{;7v02e#j=k0<3NCnwdWdve~P!tyO2%mH?CO|oO&>&7dgC#v5O4HRPidwnw%;cml+1E2#wi^tmoPeszc?M z^n5D5xNlmU2F1KWCj9J zsMHC=^Tv|Ts)ht+W8)B)!~TX_lkHgs=Th*Zny@8K%5+1Z_aFIp)g)c!-(C23$1xKk zEG#Y`U+iVJXfDk*yf8VMTZ+wbde`N%k@MjSIT8cXy$?1bG1uE)Zr3Od%JJJB=2^6V zz}O`rbCIi|+xoK?aCGQRu+x6{bPqv? zu&}$f@vMFeXQ?C%@$V=*&%mVfnT5;#ZrAPgQY{(&c^mfibAqv=ty;v+r=_(%r<28$ zlT+DK*}8oN;c}uwf~5wMm(w>YiT4a#3NS4)xlCJ&^+BUl9VER@1qlol!p1Z8kts7Z zVo7~J>-3jgfa8pfpG=9r)94O;c;Hxrx+X)--y|$5D=I9EgselBtdyIP)T@=)SwkLA zqG@<_)cv+$wle2EK~Dc51;t8tyKXW>#7ieP1ET0OXA#F+>~4Zve)=ILhxYm>4ECuP zIL&BUc6zL5$Af&Lf3mXKPODL#8$`gPUSqRKsVC78up?gqVHded>{Kut|73I; z*#meTXxaEtIZj9rzPat9BrM`~n;LzN{KlO0qqOd=dX=mT_IoaY7X_%#E%Iz^URH@A z4gA0_FfK~zWh3?5ZZTNu&cq1S{2TalrTibR-`Tq=XIViC2eJc5CRWXsy%J2OBjB5@xPL&X@%jL`6^XtY|nsSCs)>1G#qA zKXq@6)_#%x$-BRv_%iADr*de76MVp_!2W@-;TLu|1=f(G-Lo3w^d&6$@!5*3_tD5& zUERe$>H$vwnYQ?(zld4Bo!1F&Qn_4L-QO$ni(9(-Kd|LILqL?gpuUOK8<}KTQNcpP zvCL!gQHvfPupVu^f5mOUGl#?Y7bH17DT32yobj~%lKCg&;mpHc7W)4lg)>JEHp%fZ ztI)ib-k$h3?J=DyWLJbBO1RzM(yv3q z|5}~ivFcc_!sTLYYed9n`VU-x99o_YzR^8*IB=Q4#~}XEHc7UN*(!;DTf`;Y6+%dw zCWysl_1oOU&_B8=836Ln7T;M058lBfJa7)g@1B=pdQJWl1bv-QEc{mios*^?bX zQ{q(zkj~tdEMpGv#8$2@ni+I;x4ETE=Wnmb#?i96juf&BFUW6IC z8ZvLqtHOsxzZwN)Sq2rY$Qop4G(6}!mP7{~tLaLlSB|meptpO_9+w@(c746_$D{gi zTMbP@{;#)s`?Kz4=~#Bzh&Y}bJtfB`2(vn;%q-sa@Y4J1D*YUl;2Rg>$g1WGOwIB~ z+^t@NhJoCgymPMGGaqoMg+Svsoupz)t_p_a67RiUOw6AhIGt^Dyg1Dd8&uvUL`n(8 zFCfh5o;%LyT0k!zD^MG8q}x;cg;-L!Z&N|(qwn%VhNJhUb2sU3xuM|C^L#=gK)vs? zZ8l951`d;(G0})SU*34vh~5xHRA>sWxv9s&q}KR=STjb<{Mz0)TD!xbp%XlxIwf41 zc|?=VI(?Yk)I?@sK1m;V=UTC_dH9LJ>s2tTroga zx-si&{ZNmchUJ5Jp_c%OOB(YT=k@Al{fvCEK$#<-`AXykVA2?_-Z1vA>*edFqlrh* zk!A*AQNg_8RK=Tg9&7bJf;UoVU3(B5tx9uGQ}ulF2CM}N%0J>?oc_u@J+2w?)!sC_ z`~TSyqV{kH8u@zNgn-MMnvNkEcW%%4uf01ST6Zo#?%Q*14L$Cw)pWd#^Hzt+==I#hfjZh(?uNZB)j&zN__?25uV|4_UF0uBCm~Zx&OYiYK2T0?sj+Joke zwk<7tv{XJ;yOUA|ikVegcOUZr)tS!5%E&&Ilk3D8MPaicwH)UfpHqJx9tZ_Y)bCl- z$)&1*oru{}qZ@7D0eFbQL{0*6_x?a_dH2ZwnSJ?_GX!;9HdkPCsx0}u}LQM4`Ie}P2z zn_q&u!;Hm+J&Jk*(cKE*5n5hn57-L=DiGRI$3wR5+H~sUCix#e*Be{qK1_v(kY9}q zSRs1%YcJ^}T()APGp|RaoIOuNgH;TaZiPQjd#oZ(uAr&D`XlDY!LF432MTw{U?crz zzcGkYsU2{LrD`5pm4Hr!fbP|KCFd*fqd%`lur&lJ#o7 zBS1j2?5THm0BIeXwg<3eIB0ea$6e^kCZd*0t;0x=+5A3)CxA}c$)Gix&WlzJa$Bxp zXFW#10J2 z1U~4Aj$*+?PqdlB67iY47K5Gi&xhZ#;Gj~aHd^oO{=cBy9Rs%AZt^g|k7Y-s$*kQD>7 zAK3@@0V+6*1RcxLN6W-M^{!D=%|)*24&9`~@-f+2Ux(4!iR5E4*xpv8tOL!P)$20i zqj4<19?@h||GG?tN%&<-Ry!Y$4$K~J{_Lzsg*3EkU+tJ%_f3%jFi}qE9 z96@eDMg4)9Da$BpqDt^gWTkm8c0*k_`6k503)n)t?LS4aW5_elxV>@~1H0Esi z&xr5F%31Eh5vLV^Y7&n@jvgFRXA4xNnO`J$WDO5)tgX8pu>@#O?7BjpA`Wxp9)AwOL+D3gpkwX( zK`+EID5P_kr7@u})hRZi=@{s(p6A@s2n$BCk3RA_zKDF^?YSbce0jmkAUg5rytMRq zUTe7YV^)Z4rs|2lkJsu}+xjQSebX$oo--Mgs7?sE>r@&AG|kC|H@ESXHJ-O8*+jZxD$ti#W^}Pdz=g;2} zicY(dldlUOOY9D%g@JKkL}~?vW;sBINRc?DZXJ=Z!p>JCFO0tn!>y`TFXdb`_5IjPT z%bbg982~pJ!$;+4T%*hyUp@@M3?-W9wXPN6ZDIDfWZyp8)8O8CS5z6_gdnHcsf0sj zPv=WP zA+h(hTA-N3m~N-rKAR=K$~ofGX>qQ~5$@MTnu2~=GPVNI2cbZBSQkCH;2>_QlC$|E zgvjB1K}auQ!)eDK-h;*%TapHbqmc~mU4kLMIEq@ho03CMW@>uBCOmupOWj(Z$n8A* zH<<;UaT-0bKN^DkLBV~0z_O*q|cu2VY%B8#cmxUu>?d4NJP<(45jVPG1JBep@(3X50}@zKFi#@POdU<+^ZS zyCpr1(S0l8RDL`-=Q}2JHtf7CT7PXKU`qtiesVzd=O6i);wVBut5_qD{t$9I(Ph0i zIO_`<4W}(MZ^ICwF;uWcx>r*Fjtij{+$hiVMd5Q~{>=|J4ZkI;71hmSmb^*6m2nfH z)%>2Q0Ig-Mb&Gu^oH`3(yfVG^2?V#nIm5xgO{9j;e%EQ(*|Db&fPjyjzVxO+%rd?< zqK}sLymjc+&<^3V2RI0~=H@UpS%7N9qe_5z4PnvuFR{`zV9Qy@-#qDi_BFIy|9Y21 z_9(p9A}9Mi&v9B21=~3|*Y$sV?U&Ioxu3;xvd-cAcTKwb>%vj}jVwc>=k%Y71#`mh zt4gTzaAK79L;NLV2A?+0m~AV9FZj*>?+a%w*ZZwi^d5>CzstCpRy^b~ZEz{|ql0?f ziSRXJqQGd^HlS|F6J&zl{O$gQ3;owmFH&tMklkfBD7yh=@g~OcG{b|LNb5OtF5Vqv zly(TlqF(tENIX#pFbc$fB+fD&ob;+v@lQsPelWFloIlTEbLBij8x z2b|jq1L+y{0hve+i4Ze80x{ga02DT*g&U4`C%b!mzWW)$qIV-1yk&#h1A%{?6o+!O zy5r$9oLX=IJV*PMf0&9dnU6mN9&zDA8h@z>&#qm0)T>_v#+&%4bHmU|GI9K&?~;n} z@!#=-s9~`B>UA`e)wM#h^q+j z7h^gI6bc!zmo3+$!T=cUjY85f?c-`6n4Tx-2f zom1`Wmxo~z=?|x#wgrmhr_7O%L^QaO_=_12pT^Ys`xKc^Xr3*E_%7c|mN>0a8fR3R zs@Jh-?BIl`uJ}E~8meF!{t-UaDYfpum$4eu#h6|`tmVI?86c<74=VG1Y>oN7oYZj! z=L|te0FaP)bq>?qAiL*%mOOH{?!9UROcS7U|kUDIzjwz zB!L|^JOV8zQji>DXFuvrn-)3v4}GvNbb^bPwb#0M$jP$oNVE{zHRl>Gh25~ZT0cEdJ19S8BpHu#AP1*$~~z)kE+Uij~YYv&SJY#PDYl%fvKi6_X(4e5xMadpqv#vh=n~1 zgz#8`w9x`MHf3EyOUtglZ@kA>Uwl2?^cB_FK7rV@YbITDAP*XtGgoS|pZYeb;`;v$ z5GSr2>ZtjNWHYWi-dHUBv_`f-naX&+FO>Cmmz$k9K$Bx`%wMRqL7;gYvJ!TUiuriR!34NM#;oZ;3$fB{L7Jq z+GAS|sZ*&sufL6%VYOE#7#DARTXrvPe>vLSbypgZ8jn9D`tbnAp5xWUSq|1n#RK$P z9VyRGDRI859srhL?og_{Jz?bs7M}e9e@poQ659T3%@SZFE{>dTCY@nwow|~I zPw{(Rd%s^qI-Hfu?$?Z;NO~4GwK8)8g{z8t4nx)fUoWaLfAR5m~Hjs^$w7txFQOY2pf&Pmnz-wHps+3_nzM! z8Q=Reeh@P5b9OR*oF-d)?vKw$<@*RHAONV7gfLD))K3`>#X(CN*8p03KXjo55$yBLH5i0=i$slSDZbSHtE@q#qu&FYZu@iDzQQMXAd z@huO7XdKSOoIgD=#8)}{^l@E}JF<+Vg#;#tw&g|`+r0Cq@HD^HCPE9C%o~{~a2(-0 zF|_ZqHFHtL&~zWv@O}QkxDZH%ED#@3as5`OZx!3brKi{u14?&sKuL+c^#C44GfwRvY<(Zsj=DV4R$i~Fy`LxG-4``j%|`6NAR8kL0~#0;HJ)dHja084uZJdd+nNGct<^6QJ%Pss+X_k(O$ zLUO7v6D~YJ7g<&;j5J|9@TpIoakW&awOLU4I*F9ITkMq#?SUDfLW~5H=BUrdAKx`- z&XN9I(qR}<9LHj#w%HReyWxANC|9~J#Rh-piuK;$XsF#C$Uvg=o$@Q*$Qv&=|Yl}$4ekAq&b61)!I1w7f4~`iEhEs}`$=rRCa4iut_+}+R#Bul@ zPx_c{0jQGXuGX(KGv|rSKYuX)6y>V7MRZHl__|C;SmAnp7W3G9v9tGE1)i5_Az?;u z73d={xH0#DIx%&CX%=8akPvfAOMRvHmbF{3v{TKA1+Q0Z{5S_c?V_B78YLtNBB?!p#-ew(6}(AapVQk`|PaEI_79Ts|z{Q2-`1Q@Wcx4 zNsfMBED<1TvPGzUep1zmd^k@;vZdRr2$|1hFB-tl=dJLKf`bZJ-sRr$1CS)svPu#P zTn+0eb2HB6iWB)2o1PZ|kqeAp8h6P{+5$cAAXkI38nAM$I@8SgF@FAnw`MseIY5T? zgJ9wojjRhB+|^7r85K7%4YZTXJPUL~op;{VN&A_%>in49-}BR@?QB6JUA3&9Ppy5@ zJ?+sU#@++1(AmvDIDd2i>{3>>AXU!?sGWI*UFguKMW;c=Kkr|>Rm;Z~a#Yo9us`^C zLcD3C5KyO-eF||ap%HH2Ep*(ps?uVw0sH&h5pk}`_DTl2o8TOSFG92z`4!CuEbBQ2 z-#|Lwqs{Sjz2Z2Lg8QGXhUr)VuNf(T;^d&8SV#d>fbLE%1spqYosehF9=BHz8H&w# z6fht6SYGg>fXU&Bbv_c9n7js794m*iv% zBp8WW=)fmx>?4S9Y;k0^eBaGEYY6ps0N(A|c5RMxv)h4ZyyJe$ zix?ads||$os`?Wuikzpm7g|jtgeE)v zQlVs04fC}h-@ht6UUzFL7RM{M?eu2DJ$TD}^Pd15g?hq_8|mfW7Oun)k;CE*Ud9Tu zz-54-2UrW4yJ~%w3=nN}cGEYdT^D`3j)zwFol{>0P9eBl4bU@lFvqlu-@Ms$Y~6s~tZc3TZoAi&z;+ zwofYKj}cxud#>wVJQr(<1Y`N%{DbdheT*j*1QGC&(vMhu|((@t|+g z{S-TLJTeuoIqSK&cQT;jyZ`n0sjT{6HATl&JP0j(@VV#@8JBvjvf^rQ-W{Fv;wCS@ShT=rgzAIr z`!t*=l*AiX`VzyoxLhgM`F0GFD$e?&q-|%c^~rJa77XfUX9y^83;R-S?jPs#bmdC& z*}>3xjPE_!lQE97gSE5cP1Cxgex-zG%P%DEBn1;esY)=p`_*}<28AUNcGJKK!Mo?YrV8yz_jzxTzJ~J*Upw~_=#k7 zo0YyyXsmfQ(fsr?Dx&ha?So1RxE?^m!?wW#x{)17ys1&x}`bk zU4+EFt_ZPEG5OLewl^nAQML~p)kz^gZCZpp@;U=1#^8V}$hpgdYDCj_b9iCoHYJf! z;n;C=LyvOLPyLai?0YvqgmgAQP&bi3dRwKhek*o|DZkK>F~SxmHsYVMZ6=}6`w-#w z^C8*Z(B(~ss=y-%!Y`1+Kk|Lx_{&$k*@g-b${^ku?$AxO;!b03=ereR3@TXV_kaARYVJ{f!c*FM$7WUsVXm}4See8GTitzZ zX07*P0~tJ0NjR7nx(IpA8})cA{V@dRGKEF&-a({q_lRUuXq|nve)ZVR;xAiJS65W74vf#Aqao;Ndz`tdV(!#$#yPCcfaP7>m_AW*M(*Fa zwZwam$1_`sXUclP`u>9Taap7~dtbYpAmn9gB)*W5!~}iTC2@-r&L$p|`bGvp&cKUG zOD7d+^1l72UeYV`Nt0$9w8H4_;g>+q?Tnaf5cDQgs1YYokA9j>4hTpk<(IqA~{+5@8P$OXtFgd1=t+@`DB9njXx8vEI&i-J2^9!@ZJp)v8zYaQ>b*MD;E2MOL=u1X^ppQg84U>e`?K>LQvMb`vMas_swre z`@F#T7LX$nR?`MCA~&B_@7y@}Sx4FJ&Imzvu@rI>X-$_Jgv*()d7U}oqI%!^KIYHy z@WeAzHYX(|+!(vE=^!X~1%kGLJN;ckYp!tw=j2aGsq||aWPI7Dx{WmoD)WfX5BVah z+*eqC6r=X$d#sru=m)?JEpfbd`JRQU1Ot7D5Odi=NPkpDigzj=lneI`?AikXXg`Bu0a18{0<^n&+E-ZEPRvY!f zgbzyRl!$m0%~9>LpcR$#$5li|izCp{xdwWlFw@MN-Fc7+6yUsxVAEKY43-cYZRr?mCa>s3ris4 zI?3Tgsm(vL$EY|*%au8vs@R%?g>k4WL8;>d@PM_Q>Xzbu_DJvsd8h;f_#0z`{*e#! zUd>B3l)h26oh@+4k5Y#dzzUWNppN63m6G#z>@X07qh|4yvK?+}HkirR2vc)6;-Ekbpa|7sOO9ARbd^i)a~+B0K?Ad#D8RK!>ho%}CS zW9))dU}q$S)=UtsuKtcbV$#Io;-EAWAOk+m98&G;3xd@%g57Ta8|fhjXp^3k`*#J^ z@P}P9b(p5CJ%tM3*f+qeEsDNFcJfG2h_X`;y z1k{-dw$(cjv6lXhA!5>8MbsUT>+>Kr`0Xv=uO-(0M=kxZ;&X7&VHp~_Mg=t% z_UZ&i&4XQdBmtU;5GYL~kaK8{m~2h7;YI)o5YZxFcs#h@4ofw@uMMPyHyt{bz@}dQ zn-fsxIbw2^#CGltIA134_(4>^B&CpRH*AsT-OJXVK@cAh8Zc_1tgatIP<{JC`~~iHi2YSL`>GTizzh3%s*uz{Na|8hGM?FS-}4THIlkz 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 From 5a94458019258c69eb6627992bdd46a4087847f8 Mon Sep 17 00:00:00 2001 From: vsoch Date: Thu, 4 Jul 2024 23:23:49 -0600 Subject: [PATCH 3/8] org: moving dyad to be separate module, and organizing into supplementary section Signed-off-by: vsoch --- .../docker/jupyter-launcher.yaml | 5 +-- .../tutorial/01_flux_tutorial.ipynb | 36 +++++++----------- .../tutorial/02_flux_framework.ipynb | 8 ++-- .../03_flux_tutorial_conclusions.ipynb | 2 +- .../configs/workload/dyad_unet3d_demo.yaml | 0 .../dlio_extensions/dyad_torch_data_loader.py | 0 .../{ => supplementary/dyad}/dyad_dlio.ipynb | 2 +- .../dyad/dyad_example1.svg | 0 .../dyad/dyad_example2.svg | 0 .../dyad}/img/dyad-software-stack.png | Bin .../dyad}/img/dyad-unet3d-results.svg | 0 .../dyad}/img/dyad_design.png | Bin 12 files changed, 21 insertions(+), 32 deletions(-) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/dlio_extensions/dyad_torch_data_loader.py (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/dyad_dlio.ipynb (99%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary}/dyad/dyad_example1.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary}/dyad/dyad_example2.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/img/dyad-software-stack.png (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/img/dyad-unet3d-results.svg (100%) rename 2024-RADIUSS-AWS/JupyterNotebook/tutorial/{ => supplementary/dyad}/img/dyad_design.png (100%) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml index 1121928..eb6feb0 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/jupyter-launcher.yaml @@ -13,14 +13,13 @@ - 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 + path: supplementary/dyad/dyad_dlio.ipynb icon: ./flux-icon.png - catalog: Notebook + catalog: Console - title: Flux Framework Portal description: Flux Framework portal for projects, releases, and publication. diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index b9bd3b0..c91aee7 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -38,7 +38,7 @@ "\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", + "* [Supplementary Chapter 1: Using DYAD to accelerate distributed Deep Learning (DL) training](./supplementary/dyad/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!" ] @@ -173,7 +173,9 @@ "tags": [] }, "source": [ - "Did you know you can also get help for a specific command? For example, let's run, e.g. `flux help jobs` to get information on a sub-command:" + "
\n", + "Tip: Did you know you can also get help for a specific command? For example, run, `flux help jobs` to get information on a sub-command.\n", + "
" ] }, { @@ -1170,7 +1172,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 1, "id": "2735b1ca-e761-46be-b509-a86b771628fc", "metadata": {}, "outputs": [ @@ -1180,26 +1182,14 @@ "text": [ "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", + "awk: line 1: syntax error at or near :\n", + "e8cfed35f636\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", + "Jul 05 05:20:32.333883 UTC broker.err[0]: rc2.0: flux tree -N1 -c1 --leaf --prefix=tree.1.1 --njobs=1 -- hostname Exited (rc=1) 0.6s\n", + "awk: line 1: syntax error at or near :\n", + "flux-tree-helper: ERROR: Expecting value: line 1 column 156 (char 155)\n", + "Jul 05 05:20:33.523886 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.4s\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" ] @@ -1718,7 +1708,7 @@ "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", + "In analogy to `top`, Flux provides `flux pstree`. Try it out in the or here in the notebook.\n", "\n", "## Flux proxy\n", "\n", diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb index 15dfe6f..f540653 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb @@ -32,7 +32,7 @@ "* PMIx (for OpenMPI)\n", "* An in-memory content store (useful for preloading data into pods on cloud)\n", "\n", - "When Flux starts, it launches one or more brokers across the resources it manages. By default, Flux will launch one broker per node, but this can be configured (e.g., with the `--test-size` flag to `flux start` shown in [Module 1](./01_flux_tutorial.ipynb)). After launching the brokers, Flux will designate one broker as the \"leader\" and the rest as \"followers\". The leader serves as entrypoint into the Flux instance, and it serves as the starting point for most Flux commands. The distribution of brokers and the \"leader-follower\" designations are shown in the following figure:\n", + "When Flux starts, it launches one or more brokers across the resources it manages. By default, Flux will launch one broker per node, but this can be configured (e.g., with the `--test-size` flag to `flux start` shown in [Chapter 1](./01_flux_tutorial.ipynb)). After launching the brokers, Flux will designate one broker as the \"leader\" and the rest as \"followers\". The leader serves as entrypoint into the Flux instance, and it serves as the starting point for most Flux commands. The distribution of brokers and the \"leader-follower\" designations are shown in the following figure:\n", "\n", "
\n", "\n", @@ -155,7 +155,7 @@ "source": [ "### flux kvs\n", "\n", - "One of the core services built into Flux is the key-value store (KVS). It is used in many other services, including most of Flux's resource management services, the `flux archive` service below, and DYAD (which we will explore in [Module 4](./04_dyad_dlio.ipynb)). These services use the KVS to persistantly store information and retrieve it later (potentially after a restart of Flux).\n", + "One of the core services built into Flux is the key-value store (KVS). It is used in many other services, including most of Flux's resource management services, the `flux archive` service below, and DYAD (which we will explore in [Supplementary Chapter 1](./supplementary/dyad/dyad_dlio.ipynb)). These services use the KVS to persistantly store information and retrieve it later (potentially after a restart of Flux).\n", "\n", "The `flux kvs` command provides a utility to list and manipulate values of the KVS. As a example of using `flux kvs`, let's use the command to examine information saved by the `resource` service." ] @@ -264,7 +264,7 @@ "source": [ "Finally, note that `flux archive` was named `flux filemap` in earlier versions of Flux.\n", "\n", - "`flux kvs` and `flux archive` are two useful, but simple exammples of Flux services. Flux also supports more complex services, including services for runtime data movement, such as DYAD (covered in [Module 4](./04_dyad_dlio.ipynb))." + "`flux kvs` and `flux archive` are two useful, but simple exammples of Flux services. Flux also supports more complex services, including services for runtime data movement, such as DYAD (covered in [Supplementary Chapter 1](./supplementary/dyad/dyad_dlio.ipynb))." ] }, { @@ -278,7 +278,7 @@ "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 finish the tutorial, open [Chapter 3](./03_flux_tutorial.ipynb)." + "To finish the tutorial, open [Chapter 3](./03_flux_tutorial_conclusions.ipynb)." ] } ], diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb index 0f65f03..ff2c464 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb @@ -20,7 +20,7 @@ "* Explained how to manage services with Flux\n", "* Showed examples of Flux services\n", "\n", - "If you are ready for advanced content, you can do the [DYAD and DLIO tutorial](./dyad_dlio.ipynb) and learn about:\n", + "If you are ready for advanced content, you can do the [DYAD and DLIO tutorial](./supplementary/dyad/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", diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dlio_extensions/configs/workload/dyad_unet3d_demo.yaml diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dlio_extensions/dyad_torch_data_loader.py similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dlio_extensions/dyad_torch_data_loader.py rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dlio_extensions/dyad_torch_data_loader.py diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad_dlio.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_dlio.ipynb similarity index 99% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad_dlio.ipynb rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_dlio.ipynb index 703ad68..8d5f583 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad_dlio.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_dlio.ipynb @@ -7,7 +7,7 @@ "tags": [] }, "source": [ - "# Module 3: Using DYAD to accelerate distributed Deep Learning (DL) training\n", + "# Using DYAD to accelerate distributed Deep Learning (DL) training\n", "\n", "Now that we have seen how Flux enables the management and deployment of services, let's look at an example of using DYAD, an advanced Flux service for runtime data movement, in a real world application. Specifically, we will show how DYAD speeds up distributed Deep Learning (DL) training. In this module, we cover these topics:\n", "1. Design of DYAD\n", diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example1.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_example1.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example1.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_example1.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example2.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_example2.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/dyad/dyad_example2.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/dyad_example2.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-software-stack.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad-software-stack.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-software-stack.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad-software-stack.png diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-unet3d-results.svg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad-unet3d-results.svg similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad-unet3d-results.svg rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad-unet3d-results.svg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad_design.png b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad_design.png similarity index 100% rename from 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/dyad_design.png rename to 2024-RADIUSS-AWS/JupyterNotebook/tutorial/supplementary/dyad/img/dyad_design.png From c3012c69ce33a7105c70902a879888e35a92b25c Mon Sep 17 00:00:00 2001 From: vsoch Date: Tue, 16 Jul 2024 18:46:07 -0600 Subject: [PATCH 4/8] radiuss 2024: flatten structure The notebook structure should primarily be organized by command, since this is what the new user will interact with. This change set better does that, and flattens / organizes things a bit better overall. Signed-off-by: vsoch --- .../tutorial/01_flux_tutorial.ipynb | 336 +++++++----------- .../tutorial/02_flux_framework.ipynb | 2 +- 2 files changed, 138 insertions(+), 200 deletions(-) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index c91aee7..36370ad 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -279,6 +279,67 @@ "! flux uptime" ] }, + { + "cell_type": "markdown", + "id": "ec052119", + "metadata": {}, + "source": [ + "## Flux Resources\n", + "\n", + "When you are interacting with Flux, you will commonly want to know what resources are available to you. 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. Let's run `flux resource list` to see the resources available to us in this notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "scenic-chassis", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " STATE NNODES NCORES NGPUS NODELIST\n", + " free 4 40 0 f5af[12550686,12550686,12550686,12550686]\n", + " allocated 0 0 0 \n", + " down 0 0 0 \n" + ] + } + ], + "source": [ + "!flux resource list" + ] + }, + { + "cell_type": "markdown", + "id": "0086e47e", + "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": 2, + "id": "prime-equilibrium", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " STATE UP NNODES NODELIST\n", + " avail \u001b[01;32m ✔\u001b[0;0m 4 f5af[12550686,12550686,12550686,12550686]\n" + ] + } + ], + "source": [ + "# To view status of resources\n", + "!flux resource status" + ] + }, { "cell_type": "markdown", "id": "dee2d6af-43fa-490e-88e9-10f13e660125", @@ -288,7 +349,7 @@ "source": [ "# 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. You can use the `flux` `submit`, `run`, `bulksubmit`, `batch`, and `alloc` commands.\n", "\n", "\n", " \n", @@ -328,10 +389,9 @@ " \n", "
\n", "\n", - "## Submission Client\n", - "### `flux`: the Job Submission Tool\n", + "## flux submit\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. " + "The `flux submit` command submits a job to Flux and prints out the jobid. " ] }, { @@ -416,7 +476,9 @@ "id": "ac798095", "metadata": {}, "source": [ - "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job:" + "## flux run\n", + "\n", + "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job. It's basically doing an interactive submit, because you will be able to watch the output in your terminal, and it will block your terminal until the job completes." ] }, { @@ -516,6 +578,8 @@ "id": "91e9ed6c", "metadata": {}, "source": [ + "## flux bulksubmit\n", + "\n", "The `flux bulksubmit` command enqueues jobs based on a set of inputs which are substituted on the command line, similar to `xargs` and the GNU `parallel` utility, except the jobs have access to the resources of an entire Flux instance instead of only the local system." ] }, @@ -547,7 +611,7 @@ "id": "392a8056-1661-4b76-9ca3-5e536c687e82", "metadata": {}, "source": [ - "The `--cc` option to `submit` makes repeated submission even easier via, `flux submit --cc=IDSET`:" + "The `--cc` option (akin to \"carbon copy\") to `submit` makes repeated submission even easier via, `flux submit --cc=IDSET`:" ] }, { @@ -594,7 +658,7 @@ "source": [ "Try it in the JupyterLab terminal with a progress bar and jobs/s rate report: `flux submit --cc=1-100 --watch --progress --jps hostname`\n", "\n", - "Note that `--wait` is implied by `--watch`." + "Note that `--wait` is implied by `--watch`, meaning that when you are watching jobs, you are also waiting for them to finish." ] }, { @@ -632,7 +696,7 @@ "id": "641f446c-b2e8-40d8-b6bd-eb6b9dba3c71", "metadata": {}, "source": [ - "### `flux watch` to watch jobs\n", + "## flux watch\n", "\n", "Wouldn't it be cool to submit a job and then watch it? Well, yeah! We can do this now with flux watch. Let's run a fun example, and then watch the output. We have sleeps in here interspersed with echos only to show you the live action! 🥞️\n", "Also note a nice trick - you can always use `flux job last` to get the last JOBID.\n", @@ -675,7 +739,9 @@ "id": "3f8c2af2", "metadata": {}, "source": [ - "### Listing job properties with `flux jobs`\n", + "## flux jobs\n", + "\n", + "> Used for listing job properties\n", "\n", "We can now list the jobs in the queue with `flux jobs` and we should see both jobs that we just submitted. Jobs that are instances are colored blue in output, red jobs are failed jobs, and green jobs are those that completed successfully. Note that the JupyterLab notebook may not display these colors. You will be able to see them in the terminal." ] @@ -701,12 +767,32 @@ "!flux jobs" ] }, + { + "cell_type": "markdown", + "id": "f7228e0e-557c-455c-9903-073ef40a56a5", + "metadata": {}, + "source": [ + "You might also want to see \"all\" jobs with `-a`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70dd1459-e21f-46b5-84a4-bd165cf97f4b", + "metadata": {}, + "outputs": [], + "source": [ + "!flux jobs -a" + ] + }, { "cell_type": "markdown", "id": "77ca4277", "metadata": {}, "source": [ - "Since those jobs won't ever exit (and we didn't specify a timelimit), let's cancel them all now and free up the resources." + "## flux cancel\n", + "\n", + "Since some of the jobs we see in the table above won't ever exit (and we didn't specify a timelimit), let's cancel them all now and free up the resources." ] }, { @@ -735,6 +821,8 @@ "id": "544aa0a9", "metadata": {}, "source": [ + "## flux batch\n", + "\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", @@ -1067,7 +1155,7 @@ "id": "f4e525e2-6c89-4c14-9fae-d87a0d4fc574", "metadata": {}, "source": [ - "To list all completed jobs, run `flux jobs -a`:" + "We can again see a list all completed jobs with `flux jobs -a`:" ] }, { @@ -1165,7 +1253,7 @@ "\n", "And for an interesting detail, you can vary the scheduler algorithm or topology within each sub-instance, meaning that you can do some fairly interesting things with scheduling work, and all without stressing the top level system instance. Next, let's look at a prototype tool called `flux-tree` that you can use to see how this works.\n", "\n", - "## Flux tree\n", + "## flux tree\n", "\n", "Flux tree is a prototype tool that allows you to easily submit work to different levels of your flux instance, or more specifically, creating a nested hierarchy of jobs that scale out. Let's run the command, look at the output, and talk about it." ] @@ -1220,11 +1308,11 @@ "or more likely, you would want to use `flux batch` to submit multiple commands within a single flux instance to take advantage of the same\n", "hierarchy. \n", "\n", - "## Flux batch\n", + "## flux batch\n", "\n", - "Next, let's look at an example that doesn't use `flux tree` but instead uses `flux batch`, which is how you will likely interact with your nested instances. Let's start with a batch script `hello-batch.sh`.\n", + "Let's return to flux batch, but now with our new knowledge about flux instances! Flux tree is actually an experimental command that you won't encounter in the wild. Instead, you will likely interact with your nested flux instances with `flux batch`. Let's start with a batch script `hello-batch.sh`.\n", "\n", - "##### hello-batch.sh\n" + "### hello-batch.sh\n" ] }, { @@ -1701,7 +1789,7 @@ "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", "metadata": {}, "source": [ - "# Flux Process and Job Utilities ⚙️\n", + "# 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", @@ -1712,9 +1800,11 @@ "\n", "## Flux proxy\n", "\n", - "### Interacting with a job hierarchy with `flux proxy`\n", + "### flux proxy\n", + "\n", + "> To interact with a job hierarchy\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!" + "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 run the commands below!" ] }, { @@ -1960,7 +2050,9 @@ "id": "73bbc90e", "metadata": {}, "source": [ - "We can then replicate our previous example of submitting multiple heterogeneous jobs and testing that Flux co-schedules them." + "### `flux.job.JobspecV1` to create job specifications\n", + "\n", + "Flux represents work as a standard called the [Jobspec](https://flux-framework.readthedocs.io/projects/flux-rfc/en/latest/spec_25.html). While you could write YAML or JSON, it's much easier to use provided Python functions that take high level metadata (command, resources, etc) to generate them. We can then replicate our previous example of submitting multiple heterogeneous jobs using these Python helpers, and testing that Flux co-schedules them." ] }, { @@ -1979,12 +2071,16 @@ } ], "source": [ + "# Here we create our job specification from a command\n", "compute_jobreq = JobspecV1.from_command(\n", " command=[\"./compute.py\", \"120\"], num_tasks=4, num_nodes=2, cores_per_task=2\n", ")\n", + "\n", + "# This is the \"current working directory\" (cwd)\n", "compute_jobreq.cwd = os.path.expanduser(\"~/flux-workflow-examples/job-submit-api/\")\n", "print(JobID(flux.job.submit(f, compute_jobreq)))\n", "\n", + "# Here is a second I/O job\n", "io_jobreq = JobspecV1.from_command(\n", " command=[\"./io-forwarding.py\", \"120\"], num_tasks=1, num_nodes=1, cores_per_task=1\n", ")\n", @@ -2018,7 +2114,9 @@ "id": "a8051640", "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`:" + "### `FluxExecutor` for bulk submission\n", + "\n", + "We can use the FluxExecutor class to submit large numbers of jobs to Flux. This method uses python's `concurrent.futures` interface. Here is an example snippet from `~/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py`:" ] }, { @@ -2061,70 +2159,13 @@ }, { "cell_type": "markdown", - "id": "ec052119", + "id": "5ee1c49d", "metadata": {}, "source": [ - "# Diving Deeper Into Flux's Internals\n", + "# Deeper Dive into Flux 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", + "## flux queue\n", "\n", - "You can access the topology information that Flux collects with the `flux resource` subcommand:" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "scenic-chassis", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " STATE NNODES NCORES NGPUS NODELIST\n", - " free 3 30 0 993a4f[746854,746854,746854]\n", - " allocated 1 10 0 993a4f746854\n", - " down 0 0 0 \n" - ] - } - ], - "source": [ - "!flux resource list" - ] - }, - { - "cell_type": "markdown", - "id": "0086e47e", - "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": 45, - "id": "prime-equilibrium", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " STATE UP NNODES NODELIST\n", - " avail \u001b[01;32m ✔\u001b[0;0m 4 993a4f[746854,746854,746854,746854]\n" - ] - } - ], - "source": [ - "# To view status of resources\n", - "!flux resource status" - ] - }, - { - "cell_type": "markdown", - "id": "5ee1c49d", - "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:" ] }, @@ -2162,7 +2203,11 @@ "id": "67aa7559", "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)." + "## flux getattr\n", + "\n", + "> Get attributes about your system and environment\n", + "\n", + "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). Here is an example set that you might be interested in looking at:" ] }, { @@ -2239,6 +2284,8 @@ "id": "d74fdfcf", "metadata": {}, "source": [ + "## flux module\n", + "\n", "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`), and we will interact with the `kvs` module in a few cells. For the most part, services are implemented by modules of the same name (e.g., `kvs` implements the `kvs` service and thus the `kvs.lookup` RPC). In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." ] }, @@ -2281,43 +2328,7 @@ "id": "ad7090eb", "metadata": {}, "source": [ - "We can actually unload the Fluxion modules (the scheduler modules from flux-sched) and replace them with `sched-simple` (the scheduler that comes built-into flux-core) as a demonstration of this functionality:" - ] - }, - { - "cell_type": "code", - "execution_count": 49, - "id": "df4bc2d5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Module Idle S Service\n", - "job-info 4 R \n", - "heartbeat 0 R \n", - "job-manager 0 R \n", - "connector-local 0 R \n", - "content-sqlite 3 R content-backing\n", - "kvs 0 R \n", - "resource 0 R \n", - "kvs-watch 4 R \n", - "job-exec 4 R \n", - "sched-simple 0 R sched\n", - "barrier idle R \n", - "job-list 5 R \n", - "content 3 R \n", - "cron idle R \n", - "job-ingest 0 R \n" - ] - } - ], - "source": [ - "!flux module unload sched-fluxion-qmanager\n", - "!flux module unload sched-fluxion-resource\n", - "!flux module load sched-simple\n", - "!flux module list" + "See the [Flux Management Notebook](02_flux_framework.ipynb) for a small tutorial of unloading and reloading the Fluxion (flux scheduler) modules." ] }, { @@ -2325,94 +2336,19 @@ "id": "722c4ecf", "metadata": {}, "source": [ - "We can now reload the Fluxion scheduler, but this time, let's pass some extra arguments to specialize our Flux instance. In particular, let's populate our resource graph with nodes, sockets, and cores and limit the scheduling depth to 4." + "## flux dmesg\n", + "\n", + "If you need some additional help debugging your Flux setup, you might be interested in `flux dmesg`, which is akin to the [Linux dmesg](https://man7.org/linux/man-pages/man1/dmesg.1.html) but delivers messages for Flux." ] }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 7, "id": "c34899ba", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Module Idle S Service\n", - "job-info 6 R \n", - "heartbeat 1 R \n", - "job-manager 0 R \n", - "connector-local 0 R \n", - "content-sqlite 0 R content-backing\n", - "kvs 0 R \n", - "resource 0 R \n", - "kvs-watch 6 R \n", - "job-exec 0 R \n", - "barrier idle R \n", - "job-list 6 R \n", - "sched-fluxion-resource 0 R \n", - "sched-fluxion-qmanager 0 R sched\n", - "content 0 R \n", - "cron idle R \n", - "job-ingest 1 R \n", - "2024-04-12T05:02:45.207563Z sched-fluxion-qmanager.debug[0]: effective queue params (queue=default): queue-depth=4\n" - ] - } - ], - "source": [ - "!flux dmesg -C\n", - "!flux module unload sched-simple\n", - "!flux module load sched-fluxion-resource load-allowlist=node,socket,core\n", - "!flux module load sched-fluxion-qmanager queue-params=queue-depth=4\n", - "!flux module list\n", - "!flux dmesg | grep queue-depth" - ] - }, - { - "cell_type": "markdown", - "id": "ed4b0e04", - "metadata": {}, - "source": [ - "The key-value store (KVS) is a core component of a Flux instance. The `flux kvs` command provides a utility to list and manipulate values of the KVS. Modules of Flux use the KVS to persistently store information and retrieve it later on (potentially after a restart of Flux). One example of KVS use by Flux is the `resource` module, which stores the resource set `R` of the current Flux instance:" - ] - }, - { - "cell_type": "code", - "execution_count": 51, - "id": "nervous-broadcast", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "admin archive job resource\n", - "R eventlog\n", - "\u001b[1;39m{\n", - " \u001b[0m\u001b[34;1m\"version\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m,\n", - " \u001b[0m\u001b[34;1m\"execution\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", - " \u001b[0m\u001b[34;1m\"R_lite\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", - " \u001b[1;39m{\n", - " \u001b[0m\u001b[34;1m\"rank\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"0-3\"\u001b[0m\u001b[1;39m,\n", - " \u001b[0m\u001b[34;1m\"children\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", - " \u001b[0m\u001b[34;1m\"core\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"0-9\"\u001b[0m\u001b[1;39m\n", - " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", - " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", - " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", - " \u001b[0m\u001b[34;1m\"starttime\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m0\u001b[0m\u001b[1;39m,\n", - " \u001b[0m\u001b[34;1m\"expiration\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m0\u001b[0m\u001b[1;39m,\n", - " \u001b[0m\u001b[34;1m\"nodelist\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", - " \u001b[0;32m\"993a4f[746854,746854,746854,746854]\"\u001b[0m\u001b[1;39m\n", - " \u001b[1;39m]\u001b[0m\u001b[1;39m\n", - " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", - "\u001b[1;39m}\u001b[0m\n" - ] - } - ], + "outputs": [], "source": [ - "!flux kvs ls \n", - "!flux kvs ls resource\n", - "!flux kvs get resource.R | jq" + "!flux dmesg" ] }, { @@ -2420,6 +2356,8 @@ "id": "c3920f9e", "metadata": {}, "source": [ + "## flux exec\n", + "\n", "Flux provides a built-in mechanism for executing commands on nodes without requiring a job or resource allocation: `flux exec`. `flux exec` is typically used by sys admins to execute administrative commands and load/unload modules across multiple ranks simultaneously." ] }, diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb index f540653..6d6305f 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb @@ -22,7 +22,7 @@ "
\n", "\n", "
\n", - "Image created by Ian Lumsden for this tutorial
\n", + "Image created by Ian Lumsden for the Flux tutorials\n", "
\n", "\n", "Each broker is a program built on top of the ∅MQ networking library. The broker contains two main components. First, the broker implements Flux-specific networking abstractions over ∅MQ, such as remote-proceedure call (RPC) and publication-subscription (pub-sub). Second, the broker contains several core services, such as PMI (for MPI support), run control support (for enabling automatic startup of other services), and, most importantly, broker module management. The remainder of a Flux broker's functionality comes from broker modules: specially designed services that the broker can deploy in independent OS threads. Some examples of broker modules provided by Flux include:\n", From 74028b2bd7f16555ea1f4f80d6610d95ec7be2a2 Mon Sep 17 00:00:00 2001 From: vsoch Date: Sat, 20 Jul 2024 12:45:04 -0600 Subject: [PATCH 5/8] flux commands Improvement on sections and table for flux commands, and addition of flux accounting to container (likely will not easily work). Signed-off-by: vsoch --- 2024-RADIUSS-AWS/JupyterNotebook/README.md | 35 +- .../JupyterNotebook/docker/Dockerfile.spawn | 14 + .../tutorial/01_flux_tutorial.ipynb | 1156 +++++++++++++---- 3 files changed, 935 insertions(+), 270 deletions(-) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/README.md b/2024-RADIUSS-AWS/JupyterNotebook/README.md index cb39c04..d891233 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/README.md @@ -32,7 +32,40 @@ 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). - +### TODO + +After we add the flux-accounting: +- after flux resource list, to see queues available (flux queue list) + +- more carbon copy examples +- move flux batch aboe hierarchy + - transition into "what if I flux batch in my flux batch" (in my interactive allocation) + - yooo dawg + - check out riken tutorial for example + - drop the tree thing + - drop the throughput thing +- better way to render script in the notebook +- "construct a job submission object, called a jobspec" +- Python, make handle, create job description (jobspec), submit and info (monitor) +- collapse json dump +- add watch / track events for some job + - reproduce cheese / pancakes example here + - how to list jobs + - hot to get output for a job +- figure out way to collapse the last section +- typo at top of chapter 2 +- do a section for flux exec? (show doing something across our "nodes" +- move flux archive into main tutorial +- Plumbing to Porcelain - "the toilet vs. the pipes" 💩️🚽️ +- squash Deep Dive into section above it +- set up flux-accounting and see if it works + - how to specify a bank for a job + - list banks (all) - flux account view-bank --tree + - specify banks - flux account view user $USER + +- Chapter 2: Flux Plumbing 💩️🚽️ + - add flux job submit, show --dry-run + ## 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: diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn index 55f51e1..16a6b2e 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn @@ -79,6 +79,13 @@ RUN git clone https://github.com/flux-framework/dyad.git \ COPY ./flux-tree/* /usr/libexec/flux/cmd/ RUN chmod +x /usr/libexec/flux/cmd/flux-tree* +# Flux accounting +RUN git clone https://github.com/flux-framework/flux-accounting && \ + cd flux-accounting && \ + ./autogen.sh && \ + ./configure --prefix=/usr && \ + make && make install + 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 && \ @@ -119,6 +126,13 @@ COPY ./docker/start.sh /start.sh RUN mkdir -p $HOME/.local/share && \ chmod 777 $HOME/.local/share +# Quick setup of flux-accounting (not working) +# RUN flux start /bin/bash -c "nohup flux account create-db && flux account-service & flux account add-bank root 1" && \ +# flux start flux account add-bank --parent-bank=root default 1 && \ +# flux start flux account add-user --username=jovyan --bank=default && \ +# flux start flux jobtap load mf_priority.so && \ +# flux start flux account-update-db + USER ${NB_USER} CMD ["flux", "start", "--test-size=4", "jupyter", "lab"] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index 36370ad..71bf3ea 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -23,7 +23,7 @@ "\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, 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", + "Flux is a flexible framework for resource management, built for your site. The framework consists of a suite of projects, tools, and libraries that may be used to build site-custom resource managers for High Performance Computing centers and cloud environments. 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", "> I'm ready! How do I do this tutorial? 😁️\n", "\n", @@ -33,7 +33,7 @@ "\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 2: Flux Plumbing](./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", @@ -45,7 +45,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 1, "id": "d71ecd22-8552-4b4d-9bc4-61d86f8d33fe", "metadata": { "tags": [] @@ -111,6 +111,10 @@ "execution_count": 3, "id": "c7d616de-70cd-4090-bd43-ffacb5ade1f6", "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, "tags": [] }, "outputs": [ @@ -180,15 +184,579 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 30, "id": "2e54f640-283a-4523-8dde-9617fd6ef0c5", "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true, "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FLUX-JOBS(1) flux-core FLUX-JOBS(1)\n", + "\n", + "NAME\n", + " flux-jobs - list jobs submitted to Flux\n", + "\n", + "SYNOPSIS\n", + " flux jobs [OPTIONS] [JOBID ...]\n", + "\n", + "DESCRIPTION\n", + " flux jobs is used to list jobs run under Flux. By default only pending\n", + " and running jobs for the current user are listed. Additional jobs and\n", + " information can be listed using options listed below. Alternately,\n", + " specific job ids can be listed on the command line to only list those\n", + " job IDs.\n", + "\n", + "OPTIONS\n", + " -a List jobs in all states, including inactive jobs. This is\n", + " shorthand for --filter=pending,running,inactive.\n", + "\n", + " -A List jobs of all users. This is shorthand for --user=all.\n", + "\n", + " -n, --no-header\n", + " For default output, do not output column headers.\n", + "\n", + " -u, --user=[USERNAME|UID]\n", + " List jobs for a specific username or userid. Specify all for all\n", + " users.\n", + "\n", + " --name=[JOB NAME]\n", + " List jobs with a specific job name.\n", + "\n", + " --queue=[QUEUE]\n", + " List jobs in a specific queue.\n", + "\n", + " -c, --count=N\n", + " Limit output to N jobs (default 1000)\n", + "\n", + " --since=WHEN\n", + " Limit output to jobs that have been active since a given time‐\n", + " stamp. In other words, jobs that are currently pending, cur‐\n", + " rently running, or became inactive since the given timestamp.\n", + " This option implies -a if no other --filter options are speci‐\n", + " fied. If WHEN begins with - character, then the remainder is\n", + " considered to be a an offset in Flux standard duration (RFC 23).\n", + " Otherwise, any datetime expression accepted by the Python ‐\n", + " parsedatetime module is accepted. Examples: \"-6h\", \"-1d\", \"yes‐\n", + " terday\", \"2021-06-21 6am\", \"last Monday\", etc. It is assumed to\n", + " be an error if a timestamp in the future is supplied.\n", + "\n", + " -f, --filter=STATE|RESULT\n", + " List jobs with specific job state or result. Multiple states or\n", + " results can be listed separated by comma. See JOB STATUS below\n", + " for additional information. Defaults to pending,running.\n", + "\n", + " -o, --format=NAME|FORMAT\n", + " Specify a named output format NAME or a format string using\n", + " Python's format syntax. See OUTPUT FORMAT below for field names.\n", + " Named formats may be listed via --format=help. An alternate de‐\n", + " fault format can be set via the FLUX_JOBS_FORMAT_DEFAULT envi‐\n", + " ronment variable. Additional named formats may be registered\n", + " with flux jobs via configuration. See the CONFIGURATION section\n", + " for more details. A configuration snippet for an existing named\n", + " format may be generated with --format=get-config=NAME.\n", + "\n", + " --json Emit data for selected jobs in JSON format. The data for multi‐\n", + " ple matching jobs is contained in a jobs array in the emitted\n", + " JSON object, unless a single job was selected by jobid on the\n", + " command line, in which case a JSON object representing that job\n", + " is emitted on success. With --recursive, each job which is also\n", + " an instance of Flux will will have any recursively listed jobs\n", + " in a jobs array, and so on for each sub-child.\n", + "\n", + " Only the attributes which are available at the time of the flux\n", + " jobs query will be present in the returned JSON object for a\n", + " job. For instance a pending job will not have runtime, waitsta‐\n", + " tus or result keys, among others. A missing key should be con‐\n", + " sidered unavailable.\n", + "\n", + " The --json option is incompatible with --stats and --stats-only,\n", + " and any --format is ignored.\n", + "\n", + " --color[=WHEN]\n", + " Control output coloring. The optional argument WHEN can be\n", + " auto, never, or always. If WHEN is omitted, it defaults to al‐\n", + " ways. Otherwise the default is auto.\n", + "\n", + " --stats\n", + " Output a summary of job statistics before the header. By de‐\n", + " fault shows global statistics. If --queue is specified, shows\n", + " statistics for the specified queue. May be useful in conjunc‐\n", + " tion with utilities like watch(1), e.g.:\n", + "\n", + " $ watch -n 2 flux jobs --stats -f running -c 25\n", + "\n", + " will display a summary of statistics along with the top 25 run‐\n", + " ning jobs, updated every 2 seconds.\n", + "\n", + " Note that all job failures, including canceled and timeout jobs,\n", + " are collectively counted as \"failed\" in --stats.\n", + "\n", + " --stats-only\n", + " Output a summary of job statistics and exit. By default shows\n", + " global statistics. If --queue is specified, shows statistics\n", + " for the specified queue. flux jobs will exit with non-zero exit\n", + " status with --stats-only if there are no active jobs. This al‐\n", + " lows the following loop to work:\n", + "\n", + " $ while flux jobs --stats-only; do sleep 2; done\n", + "\n", + " All options other than --queue are ignored when --stats-only is\n", + " used.\n", + "\n", + " Note that all job failures, including canceled and timeout jobs,\n", + " are collectively counted as \"failed\" in --stats-only.\n", + "\n", + " -R, --recursive\n", + " List jobs recursively. Each child job which is also an instance\n", + " of Flux is prefixed by its jobid \"path\" followed by the list of\n", + " jobs, recursively up to any defined --level. If the --stats op‐\n", + " tion is used, then each child instance in the hierarchy is\n", + " listed with its stats.\n", + "\n", + " --recurse-all\n", + " By default, jobs not owned by the user running flux jobs are\n", + " skipped with --recursive, because normally Flux instances only\n", + " permit the instance owner to connect. This option forces the\n", + " command to attempt to recurse into the jobs of other users. Im‐\n", + " plies --recursive.\n", + "\n", + " -L, --level=N\n", + " With --recursive, stop recursive job listing at level N. Levels\n", + " are counted starting at 0, so flux jobs -R --level=0 is equiva‐\n", + " lent to flux jobs without -R, and --level=1 would limit recur‐\n", + " sive job listing to child jobs of the current instance.\n", + "\n", + " --threads=N\n", + " When flux jobs recursively queries job lists (with --recursive)\n", + " or fetches info for jobs that are also instances (see instance.*\n", + " fields), a pool of threads is used to parallelize the required\n", + " RPCs. Normally, the default number of ThreadPoolExecutor threads\n", + " is used, but by using the --threads, a specific number of\n", + " threads can be chosen.\n", + "\n", + "JOB STATUS\n", + " Jobs may be observed to pass through five job states in Flux: DEPEND,\n", + " PRIORITY, SCHED, RUN, CLEANUP, and INACTIVE (see Flux RFC 21). Under\n", + " the state_single field name, these are abbreviated as D, S, P, R, C,\n", + " and I respectively. For convenience and clarity, the following virtual\n", + " job states also exist: \"pending\", an alias for DEPEND,PRIORITY,SCHED;\n", + " \"running\", an alias for RUN,CLEANUP; \"active\", an alias for \"pend‐\n", + " ing,running\".\n", + "\n", + " After a job has finished and is in the INACTIVE state, it can be marked\n", + " with one of the possible results: COMPLETED, FAILED, CANCELED, TIMEOUT.\n", + " Under the result_abbrev field name, these are abbreviated as CD, F, CA,\n", + " and TO respectively.\n", + "\n", + " The job status is a user friendly mix of both, a job is always in one\n", + " of the following statuses: DEPEND, PRIORITY, SCHED, RUN, CLEANUP, COM‐\n", + " PLETED, FAILED, CANCELED, or TIMEOUT. Under the status_abbrev field\n", + " name, these are abbreviated as D, P, S, R, C, CD, F, CA, and TO respec‐\n", + " tively.\n", + "\n", + "OUTPUT FORMAT\n", + " The --format option can be used to specify an output format to flux\n", + " jobs using Python's string format syntax. For example, the following is\n", + " the format used for the default format:\n", + "\n", + " {id.f58:>12} ?:{queue:<8.8} {username:<8.8} {name:<10.10+} \\\n", + " {status_abbrev:>2.2} {ntasks:>6} {nnodes:>6h} \\\n", + " {contextual_time!F:>8h} {contextual_info}\n", + "\n", + " If a format field is preceded by the special string ?: this will cause\n", + " the field to be removed entirely from output if the result would be an\n", + " empty string or zero value for all jobs in the listing. E.g.:\n", + "\n", + " {id.f58:>12} ?:{exception.type}\n", + "\n", + " would eliminate the EXCEPTION-TYPE column if no jobs in the list re‐\n", + " ceived an exception. (Thus the job queue is only displayed if at least\n", + " one job has a queue assigned in the default format shown above).\n", + "\n", + " As a reminder to the reader, some shells will interpret braces ({ and\n", + " }) in the format string. They may need to be quoted.\n", + "\n", + " The special presentation type h can be used to convert an empty string,\n", + " \"0s\", \"0.0\", \"0:00:00\", or epoch time to a hyphen. For example, nor‐\n", + " mally \"{nodelist}\" would output an empty string if the job has not yet\n", + " run. By specifying, \"{nodelist:h}\", a hyphen would be presented in‐\n", + " stead.\n", + "\n", + " The special suffix + can be used to indicate if a string was truncated\n", + " by including a + character when truncation occurs. If both h and + are\n", + " being used, then the + must appear after the h.\n", + "\n", + " Additionally, the custom job formatter supports a set of special con‐\n", + " version flags. Conversion flags follow the format field and are used to\n", + " transform the value before formatting takes place. Currently, the fol‐\n", + " lowing conversion flags are supported by flux jobs:\n", + "\n", + " !D convert a timestamp field to ISO8601 date and time (e.g.\n", + " 2020-01-07T13:31:00). Defaults to empty string if timestamp\n", + " field does not exist or the timestamp is 0 (i.e epoch time).\n", + "\n", + " !d convert a timestamp to a Python datetime object. This allows\n", + " datetime specific format to be used, e.g. {t_inac‐\n", + " tive!d:%H:%M:%S}. Additionally, width and alignment can be spec‐\n", + " ified after the time format by using two colons (::), e.g.\n", + " {t_inactive!d:%H:%M:%S::>20}. Returns an empty string (or \"-\" if\n", + " the h suffix is used) for an unset timestamp.\n", + "\n", + " !F convert a time duration in floating point seconds to Flux Stan‐\n", + " dard Duration (FSD) string (e.g. {runtime!F}). Defaults to\n", + " empty string if field does not exist.\n", + "\n", + " !H convert a time duration in floating point seconds to hours:min‐\n", + " utes:seconds form (e.g. {runtime!H}). Defaults to empty string\n", + " if time duration field does not exist.\n", + "\n", + " !P convert a floating point number into a percentage fitting in 5\n", + " characters including the \"%\" character. E.g. 0.5 becomes \"50%\"\n", + " 0.015 becomes 1.5%, and 0.0005 becomes 0.05% etc.\n", + "\n", + " As a reminder to the reader, some shells will interpret the exclamation\n", + " point (!) when using a conversion flag. The exclamation point may need\n", + " to be escaped (\\!).\n", + "\n", + " Annotations can be retrieved via the annotations field name. Specific\n", + " keys and sub-object keys can be retrieved separated by a period (\".\").\n", + " For example, if the scheduler has annotated the job with a reason pend‐\n", + " ing status, it can be retrieved via \"{annotations.sched.reason_pend‐\n", + " ing}\".\n", + "\n", + " As a convenience, the field names sched and user can be used as substi‐\n", + " tutions for annotations.sched and annotations.user. For example, a\n", + " reason pending status can be retrieved via \"{sched.reason_pending}\".\n", + "\n", + " The field names that can be specified are:\n", + "\n", + " id job ID\n", + "\n", + " id.f58 job ID in RFC 19 F58 (base58) encoding\n", + "\n", + " id.f58plain\n", + " job ID in RFC 19 F58 encoding with ascii f\n", + "\n", + " id.dec job ID in decimal representation\n", + "\n", + " id.hex job ID in 0x prefix hexadecimal representation\n", + "\n", + " id.dothex\n", + " job ID in dotted hexadecimal representation (xx.xx.xx.xx)\n", + "\n", + " id.words\n", + " job ID in mnemonic encoding\n", + "\n", + " id.emoji\n", + " job ID in emoji encoding\n", + "\n", + " userid job submitter's userid\n", + "\n", + " username\n", + " job submitter's username\n", + "\n", + " urgency\n", + " job urgency\n", + "\n", + " priority\n", + " job priority\n", + "\n", + " dependencies\n", + " list of any currently outstanding job dependencies\n", + "\n", + " status job status (DEPEND, SCHED, RUN, CLEANUP, COMPLETED, FAILED, CAN‐\n", + " CELED, or TIMEOUT)\n", + "\n", + " status_abbrev\n", + " status but in a max 2 character abbreviation\n", + "\n", + " status_emoji\n", + " status but an appropriate emoji instead of job state / result\n", + "\n", + " name job name\n", + "\n", + " cwd job current working directory\n", + "\n", + " queue job queue\n", + "\n", + " project\n", + " job accounting project\n", + "\n", + " bank job accounting bank\n", + "\n", + " ntasks job task count\n", + "\n", + " ncores job core count\n", + "\n", + " duration\n", + " job duration in seconds\n", + "\n", + " nnodes job node count (if job ran / is running), empty string otherwise\n", + "\n", + " ranks job ranks (if job ran / is running), empty string otherwise\n", + "\n", + " nodelist\n", + " job nodelist (if job ran / is running), empty string otherwise\n", + "\n", + " state job state (DEPEND, SCHED, RUN, CLEANUP, INACTIVE)\n", + "\n", + " state_single\n", + " job state as a single character\n", + "\n", + " state_emoji\n", + " job state but an appropriate emoji instead of DEPEND, SCHED,\n", + " RUN, CLEANUP, or INACTIVE\n", + "\n", + " result job result if job is inactive (COMPLETED, FAILED, CANCELED,\n", + " TIMEOUT), empty string otherwise\n", + "\n", + " result_abbrev\n", + " result but in a max 2 character abbreviation\n", + "\n", + " result_emoji\n", + " result but an appropriate emoji instead of COMPLETED, FAILED,\n", + " CANCELED, or TIMEOUT\n", + "\n", + " success\n", + " True of False if job completed successfully, empty string other‐\n", + " wise\n", + "\n", + " waitstatus\n", + " The raw status of the job as returned by waitpid(2) if the job\n", + " exited, otherwise an empty string. Note: waitstatus is the maxi‐\n", + " mum wait status returned by all job shells in a job, which may\n", + " not necessarily indicate the highest task wait status. (The job\n", + " shell exits with the maximum task exit status, unless a task\n", + " died due to a signal, in which case the shell exits with\n", + " 128+signo)\n", + "\n", + " returncode\n", + " The job return code if the job has exited, or an empty string if\n", + " the job is still active. The return code of a job is the highest\n", + " job shell exit code, or negative signal number if the job shell\n", + " was terminated by a signal. If the job was canceled before it\n", + " started, then the returncode is set to the special value -128.\n", + "\n", + " exception.occurred\n", + " True of False if job had an exception, empty string otherwise\n", + "\n", + " exception.severity\n", + " If exception.occurred True, the highest severity, empty string\n", + " otherwise\n", + "\n", + " exception.type\n", + " If exception.occurred True, the highest severity exception type,\n", + " empty string otherwise\n", + "\n", + " exception.note\n", + " If exception.occurred True, the highest severity exception note,\n", + " empty string otherwise\n", + "\n", + " t_submit\n", + " time job was submitted\n", + "\n", + " t_depend\n", + " time job entered depend state\n", + "\n", + " t_run time job entered run state\n", + "\n", + " t_cleanup\n", + " time job entered cleanup state\n", + "\n", + " t_inactive\n", + " time job entered inactive state\n", + "\n", + " runtime\n", + " job runtime\n", + "\n", + " expiration\n", + " time at which job allocation was marked to expire\n", + "\n", + " t_remaining\n", + " If job is running, amount of time remaining before expiration\n", + "\n", + " annotations\n", + " annotations metadata, use \".\" to get specific keys\n", + "\n", + " sched short hand for annotations.sched\n", + "\n", + " user short hand for annotations.user\n", + "\n", + " Field names which are specific to jobs which are also instances of Flux\n", + " include:\n", + "\n", + " instance.stats\n", + " a short string describing current job statistics for the in‐\n", + " stance of the form PD:{pending} R:{running} CD:{successful}\n", + " F:{failed}\n", + "\n", + " instance.stats.total\n", + " total number of jobs in any state in the instance.\n", + "\n", + " instance.utilization\n", + " number of cores currently allocated divided by the total number\n", + " of cores. Can be formatted as a percentage with !P, e.g. {in‐\n", + " stance.utilization!P:>4}.\n", + "\n", + " instance.gpu_utilization\n", + " same as instance.utilization but for gpu resources\n", + "\n", + " instance.progress\n", + " number of inactive jobs divided by the total number of jobs.\n", + " Can be formatted as a percentage with {instance.progress!P:>4}\n", + "\n", + " instance.resources..{ncores,ngpus}\n", + " number of cores, gpus in state state, where state can be all,\n", + " up, down, allocated, or free, e.g. {instance.re‐\n", + " sources.all.ncores}\n", + "\n", + " The following fields may return different information depending on the\n", + " state of the job or other context:\n", + "\n", + " contextual_info\n", + " Returns selected information based on the job's current state.\n", + " If the job is in PRIORITY state, then the string priority-wait\n", + " is returned, if the job is in DEPEND state, then a list of out‐\n", + " standing dependencies is returned, if the job is in SCHED state\n", + " then an estimated time the job will run is returned (if the\n", + " scheduler supports it). Otherwise, the assigned nodelist is re‐\n", + " turned (if resources were assigned).\n", + "\n", + " contextual_info\n", + " Returns the job runtime for jobs in RUN state or later, other‐\n", + " wise the job duration (if set) is returned.\n", + "\n", + " inactive_reason\n", + " If the job is inactive, returns the reason that the job is no\n", + " longer active. Generally speaking, will output \"Exit\", \"Time‐\n", + " out\", \"Canceled\", or signal. If available, other contextual in‐\n", + " formation will also be provided such as the exit returncode or\n", + " cancellation message.\n", + "\n", + "CONFIGURATION\n", + " The flux jobs command supports registration of named output formats in\n", + " configuration files. The command loads configuration files from\n", + " flux-jobs.EXT from the following paths in order of increasing prece‐\n", + " dence:\n", + "\n", + " • $XDG_CONFIG_DIRS/flux or /etc/xdg/flux if XDG_CONFIG_DIRS is not\n", + " set. Note that XDG_CONFIG_DIRS is traversed in reverse order such\n", + " that entries first in the colon separated path are highest prior‐\n", + " ity.\n", + "\n", + " • $XDG_CONFIG_HOME/flux or $HOME/.config/flux if XDG_CONFIG_HOME is\n", + " not set\n", + "\n", + " where EXT can be one of toml, yaml, or json.\n", + "\n", + " If there are multiple flux-jobs.* files found in a directory, then they\n", + " are loaded in lexical order (i.e. .json first, then .toml, then .yaml)\n", + "\n", + " Named formats are registered in a formats table or dictionary with a\n", + " key per format pointing to a table or dictionary with the keys:\n", + "\n", + " format (required) The format string\n", + "\n", + " description\n", + " (optional) A short description of the named format, displayed\n", + " with flux jobs --format=help\n", + "\n", + " If a format name is specified in more than one config file, then the\n", + " last one loaded is used. Due to the order that flux jobs loads config\n", + " files, this allows user configuration to override system configuration.\n", + " It is an error to override any internally defined formats (such as de‐\n", + " fault).\n", + "\n", + " If a format name or string is not specified on the command line the in‐\n", + " ternally defined format default is used.\n", + "\n", + " Example:\n", + "\n", + " # $HOME/.config/flux/flux-jobs.toml\n", + "\n", + " [formats.myformat]\n", + " description = \"My useful format\"\n", + " format = \"\"\"\\\n", + " {id.f58:>12} {name:>8.8} {t_submit!D:<19} \\\n", + " {t_run!D:<19} {t_remaining!F}\\\n", + " \"\"\"\n", + "\n", + " It may be helpful to start with an existing named format by using the\n", + " --format=get-config=NAME option, e.g.:\n", + "\n", + " $ flux jobs --format=get-config=default >> ~/.config/flux/flux-jobs.toml\n", + "\n", + " Be sure to change the name of the format string from default. It is an\n", + " error to redefine the default format string.\n", + "\n", + "EXAMPLES\n", + " The default output of flux jobs will list the pending and running jobs\n", + " of the current user. It is equivalent to:\n", + "\n", + " $ flux jobs --filter=pending,running\n", + "\n", + " To list all pending, running, and inactive jobs, of the current user,\n", + " you can use --filter option or the -a option:\n", + "\n", + " $ flux jobs -a\n", + "\n", + " OR\n", + "\n", + " $ flux jobs --filter=pending,running,inactive\n", + "\n", + " To alter which user's jobs are listed, specify the user with --user:\n", + "\n", + " $ flux jobs --user=flux\n", + "\n", + " Jobs that have finished may be filtered further by specifying if they\n", + " have completed, failed, or were canceled. For example, the following\n", + " will list the jobs that have failed or were canceled:\n", + "\n", + " $ flux jobs --filter=failed,canceled\n", + "\n", + " The --format option can be used to alter the output format or output\n", + " additional information. For example, the following would output all\n", + " jobids for the user in decimal form, and output any annotations the\n", + " scheduler attached to each job:\n", + "\n", + " $ flux jobs -a --format=\"{id} {annotations.sched}\"\n", + "\n", + " The following would output the job id and exception information, so a\n", + " user can learn why a job failed.\n", + "\n", + " $ flux jobs --filter=failed --format=\"{id} {exception.type} {exception.note}\"\n", + "\n", + "RESOURCES\n", + " Flux: http://flux-framework.org\n", + "\n", + " Flux RFC: https://flux-framework.readthedocs.io/projects/flux-rfc\n", + "\n", + "SEE ALSO\n", + " flux-pstree(1)\n", + "\n", + "AUTHOR\n", + " This page is maintained by the Flux community.\n", + "\n", + "COPYRIGHT\n", + " Copyright 2014 Lawrence Livermore National Security, LLC and Flux de‐\n", + " velopers.\n", + "\n", + " SPDX-License-Identifier: LGPL-3.0\n", + "\n", + " Jun 06, 2024 FLUX-JOBS(1)\n" + ] + } + ], "source": [ - "# We have commented this out because the output is huge! Feel free to uncomment (remove the #) and run the command\n", - "#!flux help jobs" + "!flux help jobs" ] }, { @@ -251,93 +819,90 @@ }, { "cell_type": "markdown", - "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", + "id": "ec052119", "metadata": {}, "source": [ - "## Flux uptime\n", + "## Flux Resources\n", "\n", - "Did someone say... [uptime](https://youtu.be/SYRlTISvjww?si=zDlvpWbBljUmZw_Q)? ☝️🕑️\n", + "When you are interacting with Flux, you will commonly want to know what resources are available to you. 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", - "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)." + "You can access the topology information that Flux collects with the `flux resource` subcommand. Let's run `flux resource list` to see the resources available to us in this notebook:" ] }, { "cell_type": "code", - "execution_count": 9, - "id": "1268ed06-e8f4-47a0-af4b-1b93fe3fa1b1", + "execution_count": 1, + "id": "scenic-chassis", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " 04:18:52 run 27m, owner jovyan, depth 0, size 4\n" + " STATE NNODES NCORES NGPUS NODELIST\n", + " free 4 40 0 f5af[12550686,12550686,12550686,12550686]\n", + " allocated 0 0 0 \n", + " down 0 0 0 \n" ] } ], "source": [ - "! flux uptime" + "!flux resource list" ] }, { "cell_type": "markdown", - "id": "ec052119", + "id": "0086e47e", "metadata": {}, "source": [ - "## Flux Resources\n", - "\n", - "When you are interacting with Flux, you will commonly want to know what resources are available to you. 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. Let's run `flux resource list` to see the resources available to us in this notebook:" + "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": 1, - "id": "scenic-chassis", + "execution_count": 2, + "id": "prime-equilibrium", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " STATE NNODES NCORES NGPUS NODELIST\n", - " free 4 40 0 f5af[12550686,12550686,12550686,12550686]\n", - " allocated 0 0 0 \n", - " down 0 0 0 \n" + " STATE UP NNODES NODELIST\n", + " avail \u001b[01;32m ✔\u001b[0;0m 4 f5af[12550686,12550686,12550686,12550686]\n" ] } ], "source": [ - "!flux resource list" + "# To view status of resources\n", + "!flux resource status" ] }, { "cell_type": "markdown", - "id": "0086e47e", + "id": "e6603d7f-dd45-4743-9efb-bf65ba7e2f22", "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`." + "It might also be the case that you need to see queues. Here is how to do that:" ] }, { "cell_type": "code", - "execution_count": 2, - "id": "prime-equilibrium", + "execution_count": 32, + "id": "c7fbe877-c0bf-4296-a20b-21809caa72d7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " STATE UP NNODES NODELIST\n", - " avail \u001b[01;32m ✔\u001b[0;0m 4 f5af[12550686,12550686,12550686,12550686]\n" + " DEFAULTTIME TIMELIMIT NNODES NCORES NGPUS\n", + " inf inf 0-inf 0-inf 0-inf\n" ] } ], "source": [ - "# To view status of resources\n", - "!flux resource status" + "!flux queue list" ] }, { @@ -347,9 +912,9 @@ "tags": [] }, "source": [ - "# Submitting Jobs to Flux 💼️\n", + "# Flux Commands \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. You can use the `flux` `submit`, `run`, `bulksubmit`, `batch`, and `alloc` commands.\n", + "Here are how Flux commands map to a scheduler you are likely familiar with, Slurm. A larger table with similar mappings for LSF, Moab, and Slurm can be [viewed here](https://hpc.llnl.gov/banks-jobs/running-jobs/batch-system-cross-reference-guides). For submitting jobs, you can use the `flux` `submit`, `run`, `bulksubmit`, `batch`, and `alloc` commands.\n", "\n", "\n", " \n", @@ -358,24 +923,29 @@ " \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", " \n", @@ -383,194 +953,224 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - "
Flux
One-off submit of a single job (not interactive)One-off run of a single job (blocking)srunflux submitflux run
One-off submit of a single job (interactive)One-off run of a single job (interactive)srun --ptyflux runflux run -o pty.interactive
Submitting batch jobssbatchflux batchOne-off run of a single job (not blocking)NAflux submit
Submiting interactive jobssallocflux allocBulk submission of jobs (not blocking)NAflux bulksubmit
Watching jobsNAflux watch
Querying the status of jobsflux jobs/flux job info job_id
Cancelling running jobsCanceling running jobsscancelflux cancel
\n", + " \n", + " Submitting batch jobs\n", + " sbatch\n", + " flux batch\n", + " \n", + " \n", + " Allocation for an interactive instance\n", + " salloc\n", + " flux alloc\n", + " \n", + "" + ] + }, + { + "cell_type": "markdown", + "id": "ac798095", + "metadata": {}, + "source": [ + "## flux run\n", "\n", - "## flux submit\n", + "
\n", + "Description: One-off run of a single job (blocking)\n", + "
\n", "\n", - "The `flux submit` command submits a job to Flux and prints out the jobid. " + "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job. It's basically doing an interactive submit, because you will be able to watch the output in your terminal, and it will block your terminal until the job completes." ] }, { "cell_type": "code", - "execution_count": 10, - "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", + "execution_count": 5, + "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ƒF91H7Gc7\n" + "399f5da372b0\n" ] } ], "source": [ - "!flux submit hostname" + "!flux run hostname" ] }, { "cell_type": "markdown", - "id": "a7e4c25e-3ca8-4277-bb70-a0e94bcd223b", + "id": "53357a9d-11d8-4c2d-87d8-c30ae38d01ba", "metadata": {}, "source": [ - "`submit` supports common options like `--nnodes`, `--ntasks`, and `--cores-per-task`. There are short option equivalents (`-N`, `-n`, and `-c`, respectively) of these options as well. `--cores-per-task=1` is the default." + "The output from the previous command is the hostname (a container ID string in this case). If the job exits with a non-zero exit code this will be reported by `flux job attach` (occurs implicitly with `flux run`). For example, execute the following:" ] }, { "cell_type": "code", - "execution_count": 11, - "id": "571d8c3d-b24a-415e-b9ac-f58b99a7e92c", + "execution_count": 6, + "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ƒFCA1pkFH\n" + "flux-job: task(s) exited with exit code 1\n" ] } ], "source": [ - "!flux submit -N1 -n2 sleep inf" + "!flux run /bin/false" ] }, { - "cell_type": "code", - "execution_count": 12, - "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", + "cell_type": "markdown", + "id": "6b2b5c3f-e24a-45a8-a10c-e10bfdbb7b87", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "usage: flux submit [OPTIONS...] COMMAND [ARGS...]\n", - "\n", - "enqueue a job\n", - "\n", - "positional arguments:\n", - " command Job command and arguments\n", - "\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", - " otherwise in Flux standard duration, e.g. 30s,\n", - " 2d, 1.5h\n", - " --urgency=N Set job urgency (0-31), hold=0, default=16,\n", - " expedite=31\n" - ] - } - ], "source": [ - "# Let's peek at the help for flux submit!\n", - "!flux submit --help | head -n 15" + "A job submitted with `run` can be canceled with two rapid `Cltr-C`s in succession, or a user can detach from the job with `Ctrl-C Ctrl-Z`. The user can then re-attach to the job by using `flux job attach JOBID`." ] }, { "cell_type": "markdown", - "id": "ac798095", + "id": "81e5213d", "metadata": {}, "source": [ - "## flux run\n", - "\n", - "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job. It's basically doing an interactive submit, because you will be able to watch the output in your terminal, and it will block your terminal until the job completes." + "`flux submit` and `flux run` also support many other useful flags:" ] }, { "cell_type": "code", - "execution_count": 13, - "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", + "execution_count": 7, + "id": "02032748", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "7db0bdd6f967\n" + "3: 399f5da372b0\n", + "2: 399f5da372b0\n", + "1: 399f5da372b0\n", + "0: 399f5da372b0\n" ] } ], "source": [ - "!flux run hostname" + "!flux run -n4 --label-io --time-limit=5s --env-remove=LD_LIBRARY_PATH hostname" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "f52bb357-a7ce-458d-9c3f-4d664eca4fbd", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncomment and run this help command if you want to see all the flags for flux run\n", + "# !flux run --help" ] }, { "cell_type": "markdown", - "id": "53357a9d-11d8-4c2d-87d8-c30ae38d01ba", + "id": "7c09708a-74a1-4e61-b678-cb337b7df435", "metadata": {}, "source": [ - "The output from the previous command is the hostname (a container ID string in this case). If the job exits with a non-zero exit code this will be reported by `flux job attach` (occurs implicitly with `flux run`). For example, execute the following:" + "## flux submit\n", + "\n", + "
\n", + "Description: One-off run of a single job (not blocking)\n", + "
\n", + "\n", + "\n", + "The `flux submit` command submits a job to Flux and prints out the jobid. " ] }, { "cell_type": "code", - "execution_count": 14, - "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", - "metadata": {}, + "execution_count": 4, + "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "flux-job: task(s) exited with exit code 1\n" + "usage: flux submit [OPTIONS...] COMMAND [ARGS...]\n", + "\n", + "enqueue a job\n", + "\n", + "positional arguments:\n", + " command Job command and arguments\n", + "\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", + " otherwise in Flux standard duration, e.g. 30s,\n", + " 2d, 1.5h\n", + " --urgency=N Set job urgency (0-31), hold=0, default=16,\n", + " expedite=31\n" ] } ], "source": [ - "!flux run /bin/false" + "# Let's peek at the help for flux submit!\n", + "!flux submit --help | head -n 15" ] }, { - "cell_type": "markdown", - "id": "6b2b5c3f-e24a-45a8-a10c-e10bfdbb7b87", + "cell_type": "code", + "execution_count": 2, + "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒScZH3DbD\n" + ] + } + ], "source": [ - "A job submitted with `run` can be canceled with two rapid `Cltr-C`s in succession, or a user can detach from the job with `Ctrl-C Ctrl-Z`. The user can then re-attach to the job by using `flux job attach JOBID`." + "!flux submit hostname" ] }, { "cell_type": "markdown", - "id": "81e5213d", + "id": "a7e4c25e-3ca8-4277-bb70-a0e94bcd223b", "metadata": {}, "source": [ - "`flux submit` and `flux run` also support many other useful flags:" + "`submit` supports common options like `--nnodes`, `--ntasks`, and `--cores-per-task`. There are short option equivalents (`-N`, `-n`, and `-c`, respectively) of these options as well. `--cores-per-task=1` is the default." ] }, { "cell_type": "code", - "execution_count": 15, - "id": "02032748", + "execution_count": 3, + "id": "571d8c3d-b24a-415e-b9ac-f58b99a7e92c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "3: 7db0bdd6f967\n", - "2: 7db0bdd6f967\n", - "1: 7db0bdd6f967\n", - "0: 7db0bdd6f967\n" + "ƒSdrJJshH\n" ] } ], "source": [ - "!flux run -n4 --label-io --time-limit=5s --env-remove=LD_LIBRARY_PATH hostname" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "f52bb357-a7ce-458d-9c3f-4d664eca4fbd", - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment and run this help command if you want to see all the flags for flux run\n", - "# !flux run --help" + "!flux submit -N1 -n2 sleep inf" ] }, { @@ -580,12 +1180,16 @@ "source": [ "## flux bulksubmit\n", "\n", + "
\n", + "Description: Bulk submission of jobs (not blocking)\n", + "
\n", + "\n", "The `flux bulksubmit` command enqueues jobs based on a set of inputs which are substituted on the command line, similar to `xargs` and the GNU `parallel` utility, except the jobs have access to the resources of an entire Flux instance instead of only the local system." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "id": "f0e82702", "metadata": {}, "outputs": [ @@ -593,11 +1197,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒFSHgbfxs\n", - "ƒFSHgbfxt\n", - "ƒFSHgbfxu\n", - "baz\n", + "ƒSqGSA7dh\n", + "ƒSqGSA7di\n", + "ƒSqGSA7dj\n", "bar\n", + "baz\n", "foo\n" ] } @@ -616,39 +1220,12 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "0ea1962b-1831-4bd2-8dab-c61fd710df9c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ƒ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" - ] - } - ], + "outputs": [], "source": [ - "!flux submit --cc=1-10 --watch hostname" + "!flux submit --cc=1-4 --watch hostname" ] }, { @@ -673,7 +1250,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 10, "id": "brazilian-former", "metadata": {}, "outputs": [ @@ -681,8 +1258,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒFZ7C25NP\n", - "ƒFZDMK6HV\n" + "ƒT2VDkrT1\n", + "ƒT2azp48w\n" ] } ], @@ -698,6 +1275,10 @@ "source": [ "## flux watch\n", "\n", + "
\n", + "Description: 👀️ Watching jobs\n", + "
\n", + "\n", "Wouldn't it be cool to submit a job and then watch it? Well, yeah! We can do this now with flux watch. Let's run a fun example, and then watch the output. We have sleeps in here interspersed with echos only to show you the live action! 🥞️\n", "Also note a nice trick - you can always use `flux job last` to get the last JOBID.\n", "Here is an example (not runnable, as notebooks don't support environment variables) for getting and saving a job id:\n", @@ -712,7 +1293,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 11, "id": "5ad231c2-4cdb-4d18-afc2-7cb3a74759c2", "metadata": {}, "outputs": [ @@ -720,11 +1301,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒ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", - "Throw a stack... it makes a smack! 15 blueberry pancakes on the wall! 🥞️\n", + "ƒTR3HXBfD\n", + "25 chocolate chip pancakes on the table... 25 chocolate chip pancakes! 🥞️\n", + "Eat a stack, for a snack, 15 chocolate chip pancakes on the table! 🥄️\n", + "15 chocolate chip pancakes on the table... 15 chocolate chip pancakes! 🥞️\n", + "Throw a stack... it makes a smack! 15 chocolate chip pancakes on the wall! 🥞️\n", "You got some cleaning to do 🧽️\n" ] } @@ -741,14 +1322,16 @@ "source": [ "## flux jobs\n", "\n", - "> Used for listing job properties\n", + "
\n", + "Description: Querying the status of jobs\n", + "
\n", "\n", "We can now list the jobs in the queue with `flux jobs` and we should see both jobs that we just submitted. Jobs that are instances are colored blue in output, red jobs are failed jobs, and green jobs are those that completed successfully. Note that the JupyterLab notebook may not display these colors. You will be able to see them in the terminal." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 12, "id": "institutional-vocabulary", "metadata": {}, "outputs": [ @@ -757,9 +1340,9 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\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" + " ƒT2azp48w jovyan analysis R 1 1 1.267m 399f5da372b0\n", + " ƒT2VDkrT1 jovyan simulation R 2 2 1.271m 399f5da372b[0,0]\n", + " ƒSdrJJshH jovyan sleep R 2 1 2.127m 399f5da372b0\n" ] } ], @@ -792,12 +1375,16 @@ "source": [ "## flux cancel\n", "\n", + "
\n", + "Description: Canceling running jobs\n", + "
\n", + "\n", "Since some of the jobs we see in the table above won't ever exit (and we didn't specify a timelimit), let's cancel them all now and free up the resources." ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 13, "id": "46dd8ec8-6c64-4d8d-9a00-949f5f58c07b", "metadata": {}, "outputs": [ @@ -823,16 +1410,18 @@ "source": [ "## flux batch\n", "\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", + "Description: Submitting batch jobs\n", + "
\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", + "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", - "Note: Flux also provides a `flux alloc` which is an interactive version of `flux batch`, but demonstrating that in a Jupyter notebook is difficult due to the lack of pseudo-terminal." + "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." ] }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 14, "id": "blank-carpet", "metadata": {}, "outputs": [ @@ -840,8 +1429,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒGYBWWT7d\n", - "ƒGYGiwvby\n" + "ƒThKfdhKD\n", + "ƒThRLkwsm\n" ] } ], @@ -860,7 +1449,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 15, "id": "381a3f6c-0da1-4923-801f-486ca5226d3c", "metadata": {}, "outputs": [ @@ -992,7 +1581,7 @@ "flux run -N 2 -n 2 sleep 30\n" ] }, - "execution_count": 24, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -1004,7 +1593,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 16, "id": "edff8993-3c39-4f46-939d-4c8be5739fbc", "metadata": {}, "outputs": [ @@ -1012,20 +1601,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒGbBf664w\n", + "ƒU5u1XQcf\n", " JOBID USER NAME ST NTASKS NNODES TIME INFO\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[01;34m ƒThRLkwsm jovyan ./sleep_b+ R 2 2 51.17s 399f5da372b[0,0]\n", + "\u001b[0;0m\u001b[01;34m ƒThKfdhKD jovyan ./sleep_b+ R 2 2 51.39s 399f5da372b[0,0]\n", "\u001b[0;0m JOBID USER NAME ST NTASKS NNODES TIME INFO\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[01;34m ƒThRLkwsm jovyan ./sleep_b+ R 2 2 51.34s 399f5da372b[0,0]\n", + "\u001b[0;0m\u001b[01;34m ƒThKfdhKD jovyan ./sleep_b+ R 2 2 51.56s 399f5da372b[0,0]\n", "\u001b[0;0m\n", - "ƒGYGiwvby:\n", - " ƒJZWVbu jovyan sleep R 2 2 6.123s 7db0bdd6f[967,967]\n", + "ƒThRLkwsm:\n", + " ƒEgNEfjm jovyan sleep R 2 2 20.11s 399f5da372b[0,0]\n", "\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", + "ƒThKfdhKD:\n", + " ƒEga6ZzX jovyan sleep R 2 2 20.32s 399f5da372b[0,0]\n", + "{\"version\": 1, \"execution\": {\"R_lite\": [{\"rank\": \"3\", \"children\": {\"core\": \"7\"}}], \"nodelist\": [\"399f5da372b0\"], \"starttime\": 1721424338, \"expiration\": 4875020774}}\n", "0: stdout redirected to /tmp/cheese.txt\n", "0: stderr redirected to /tmp/cheese.txt\n" ] @@ -1119,7 +1708,7 @@ "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️" ] }, - "execution_count": 25, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -1160,44 +1749,10 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "id": "df8a8b7c-f475-4a51-8bc6-9983dc9d78ab", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " JOBID USER NAME ST NTASKS NNODES TIME INFO\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" - ] - } - ], + "outputs": [], "source": [ "!flux jobs -a" ] @@ -1212,7 +1767,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 18, "id": "032597d2-4b02-47ea-a5e5-915313cdd7f9", "metadata": {}, "outputs": [ @@ -1221,7 +1776,7 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;31m ƒFKYd97bM jovyan false F 1 1 0.053s 7db0bdd6f967\n", + "\u001b[01;31m ƒSixhuHXu jovyan false F 1 1 0.070s 399f5da372b0\n", "\u001b[0;0m" ] } @@ -1230,6 +1785,34 @@ "!flux jobs -f failed" ] }, + { + "cell_type": "markdown", + "id": "2d3e314e-98eb-487a-ad8e-1442840e37d8", + "metadata": {}, + "source": [ + "## flux alloc\n", + "\n", + "
\n", + "Description: Allocation for an interactive instance\n", + "
\n", + "\n", + "You might want to request an allocation for a set of resources (an allocation) and then attach to the interactively. This is the goal of flux alloc. Since we can't easily do that in a cell, try opening up the and doing: \n", + "\n", + "```bash\n", + "# Look at the resources you have outside of the allocation\n", + "flux resource list\n", + "\n", + "# Request an allocation with 2 \"nodes\" - a subset of what you have in total\n", + "flux alloc -N 2\n", + "\n", + "# See the resources you are given\n", + "flux resource list\n", + "\n", + "# You can exit from the allocation like this!\n", + "exit\n", + "```" + ] + }, { "cell_type": "markdown", "id": "04b405b1-219f-489c-abfc-e2983e82124a", @@ -1317,7 +1900,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 19, "id": "e82863e5-b2a1-456b-9ff1-f669b3525fa1", "metadata": {}, "outputs": [ @@ -1431,7 +2014,7 @@ "flux job wait --all" ] }, - "execution_count": 29, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -1458,7 +2041,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 20, "id": "72358a03-6f1f-4c5e-91eb-cab71883a232", "metadata": {}, "outputs": [ @@ -1466,12 +2049,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒ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" + "ƒY424FfiX\n", + "ƒY424FfiX\n", + "Hello job 1 from 399f5da372b0 💛️\n", + "Hello job 2 from 399f5da372b0 💚️\n", + "Hello job 3 from 399f5da372b0 💙️\n", + "Hello job 4 from 399f5da372b0 💜️\n" ] } ], @@ -1722,7 +2305,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 22, "id": "8640a611-38e4-42b1-a913-89e0c76c8014", "metadata": {}, "outputs": [ @@ -1730,7 +2313,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSRXzaub5\n" + "ƒYRzZSFuy\n" ] } ], @@ -1750,7 +2333,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 23, "id": "2d2b1f0b-e6c2-4583-8068-7c76fa341884", "metadata": {}, "outputs": [ @@ -1759,14 +2342,16 @@ "output_type": "stream", "text": [ ".\n", + "├── ./sub_job1.sh\n", + "│ └── ./sub_job2.sh\n", + "│ └── sleep:R\n", "├── ./hello-batch.sh:CD\n", - "├── 2*[flux-tree-Tpb37xfIP23YjqCChAoJjWshyEHYJob1:F]\n", "├── 2*[./sleep_batch.sh:CD]\n", "├── 4*[echo:CD]\n", "├── sleep:CA\n", "├── simulation:CA\n", "├── analysis:CA\n", - "├── 2*[job-watch.sh:CD]\n", + "├── job-watch.sh:CD\n", "├── 13*[hostname:CD]\n", "└── false:F\n" ] @@ -1786,10 +2371,43 @@ }, { "cell_type": "markdown", - "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", + "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", "metadata": {}, "source": [ "# Process and Job Utilities ⚙️\n", + "\n", + "## Flux uptime\n", + "\n", + "Did someone say... [uptime](https://youtu.be/SYRlTISvjww?si=zDlvpWbBljUmZw_Q)? ☝️🕑️🕺️\n", + "\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": "code", + "execution_count": 31, + "id": "095f2ac3-145b-4cda-8350-7c281f2b2b45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 22:16:17 run 1.8h, owner jovyan, depth 0, size 4\n" + ] + } + ], + "source": [ + "!flux uptime" + ] + }, + { + "cell_type": "markdown", + "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, + "source": [ "## 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", @@ -1847,13 +2465,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": 35, + "execution_count": 24, "id": "third-comment", "metadata": {}, "outputs": [], @@ -1867,7 +2485,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 25, "id": "selective-uganda", "metadata": {}, "outputs": [ @@ -1875,7 +2493,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒKKdeYAGo\n" + "ƒZoXw7Pdq\n" ] } ], @@ -1898,7 +2516,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 26, "id": "ed65cb46-8d8a-41f0-bec1-92b9a89e6db2", "metadata": {}, "outputs": [ @@ -1906,9 +2524,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "🎉️ Hooray, we just submitted ƒKLZWG53M!\n", + "🎉️ Hooray, we just submitted ƒZrqPNeNb!\n", "{\n", - " \"t_depend\": 1720153943.7848454,\n", + " \"t_depend\": 1721425098.682836,\n", " \"t_run\": 0.0,\n", " \"t_cleanup\": 0.0,\n", " \"t_inactive\": 0.0,\n", @@ -1928,8 +2546,8 @@ " \"success\": \"\",\n", " \"result\": \"\",\n", " \"waitstatus\": \"\",\n", - " \"id\": 40488354709504,\n", - " \"t_submit\": 1720153943.7735925,\n", + " \"id\": 72552617607168,\n", + " \"t_submit\": 1721425098.6718118,\n", " \"t_remaining\": 0.0,\n", " \"state\": \"SCHED\",\n", " \"username\": \"jovyan\",\n", @@ -1963,7 +2581,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 27, "id": "5d679897-7054-4f96-b340-7f39245aca89", "metadata": {}, "outputs": [ @@ -1971,8 +2589,8 @@ "name": "stdout", "output_type": "stream", "text": [ - " ƒSRkkNjD9 jovyan compute.py F 1 1 0.014s 993a4f746854\n", - " ƒSRjxR7UT jovyan compute.py F 1 1 0.019s 993a4f746854\n" + " ƒZrqPNeNb jovyan compute.py F 1 1 0.009s 399f5da372b0\n", + " ƒZoXw7Pdq jovyan compute.py F 1 1 0.011s 399f5da372b0\n" ] } ], @@ -1990,7 +2608,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 28, "id": "efa06478", "metadata": {}, "outputs": [ @@ -2137,7 +2755,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 29, "id": "cleared-lawsuit", "metadata": {}, "outputs": [ @@ -2145,10 +2763,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "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" + "bulksubmit_executor: submitted 200 jobs in 0.24s. 831.05job/s\n", + "bulksubmit_executor: First job finished in about 0.254s\n", + "|██████████████████████████████████████████████████████████| 100.0% (278.2 job/s)\n", + "bulksubmit_executor: Ran 200 jobs in 0.9s. 221.8 job/s\n" ] } ], From 9e7c64b70c934c348fa10cbd0579c23123073fa5 Mon Sep 17 00:00:00 2001 From: vsoch Date: Sat, 20 Jul 2024 18:09:02 -0600 Subject: [PATCH 6/8] review radiuss 2024: from flux team on july 19th Signed-off-by: vsoch --- 2024-RADIUSS-AWS/JupyterNotebook/README.md | 42 +- .../JupyterNotebook/docker/Dockerfile.spawn | 2 +- .../tutorial/01_flux_tutorial.ipynb | 2549 ++++++++++------- .../tutorial/02_flux_framework.ipynb | 358 ++- .../03_flux_tutorial_conclusions.ipynb | 15 +- .../tutorial/img/flux-batch.jpg | Bin 0 -> 55655 bytes 6 files changed, 1711 insertions(+), 1255 deletions(-) create mode 100644 2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-batch.jpg diff --git a/2024-RADIUSS-AWS/JupyterNotebook/README.md b/2024-RADIUSS-AWS/JupyterNotebook/README.md index d891233..af522db 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/README.md @@ -31,40 +31,6 @@ 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). - -### TODO - -After we add the flux-accounting: -- after flux resource list, to see queues available (flux queue list) - -- more carbon copy examples -- move flux batch aboe hierarchy - - transition into "what if I flux batch in my flux batch" (in my interactive allocation) - - yooo dawg - - check out riken tutorial for example - - drop the tree thing - - drop the throughput thing -- better way to render script in the notebook -- "construct a job submission object, called a jobspec" -- Python, make handle, create job description (jobspec), submit and info (monitor) -- collapse json dump -- add watch / track events for some job - - reproduce cheese / pancakes example here - - how to list jobs - - hot to get output for a job -- figure out way to collapse the last section -- typo at top of chapter 2 -- do a section for flux exec? (show doing something across our "nodes" -- move flux archive into main tutorial -- Plumbing to Porcelain - "the toilet vs. the pipes" 💩️🚽️ -- squash Deep Dive into section above it -- set up flux-accounting and see if it works - - how to specify a bank for a job - - list banks (all) - flux account view-bank --tree - - specify banks - flux account view user $USER - -- Chapter 2: Flux Plumbing 💩️🚽️ - - add flux job submit, show --dry-run ## Local Usage @@ -1048,3 +1014,11 @@ $ eksctl delete cluster --config-file aws/eksctl-config.yaml --wait In practice, you'll need to start deleting with `eksctl` and then you will see the pod eviction warning (because they were re-created) and you'll need to run the command again, and then it will clean up. + +### Tutorial "would be nice" additions + +- Flux accounting + - after flux resource list, to see queues available (flux queue list) + - how to specify a bank for a job + - list banks (all) - flux account view-bank --tree + - specify banks - flux account view user $USER diff --git a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn index 16a6b2e..ba6be5b 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn +++ b/2024-RADIUSS-AWS/JupyterNotebook/docker/Dockerfile.spawn @@ -126,7 +126,7 @@ COPY ./docker/start.sh /start.sh RUN mkdir -p $HOME/.local/share && \ chmod 777 $HOME/.local/share -# Quick setup of flux-accounting (not working) +# Quick setup of flux-accounting (not working due to needing system service) # RUN flux start /bin/bash -c "nohup flux account create-db && flux account-service & flux account add-bank root 1" && \ # flux start flux account add-bank --parent-bank=root default 1 && \ # flux start flux account add-user --username=jovyan --bank=default && \ diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index 71bf3ea..3a0a267 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -88,33 +88,19 @@ "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/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", - "> Looking for other resources? We got you covered! 🤓️\n", - "\n", - " - [https://flux-framework.org/](https://flux-framework.org/) Flux Framework portal for projects, releases, and publication.\n", - " - [Flux Documentation](https://flux-framework.readthedocs.io/en/latest/).\n", - " - [Flux Framework Cheat Sheet](https://flux-framework.org/cheat-sheet/)\n", - " - [Flux Glossary of Terms](https://flux-framework.readthedocs.io/en/latest/glossary.html)\n", - " - [Flux Comics](https://flux-framework.readthedocs.io/en/latest/comics/fluxonomicon.html) come and meet FluxBird - the pink bird who knows things!\n", - " - [Flux Learning Guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html) learn about what Flux does, how it works, and real research applications \n", - " - [Getting Started with Flux and Go](https://converged-computing.github.io/flux-go/)\n", - " - [Getting Started with Flux in C](https://converged-computing.github.io/flux-c-examples/) *looking for contributors*\n", - "\n", - "To read the Flux manpages and get help, run `flux help`. To get documentation on a subcommand, run, e.g. `flux help config`. Here is an example of running `flux help` right from the notebook. Yes, did you know we are running in a Flux Instance right now?" + "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. To read the Flux manpages and get help, run `flux help`. To get documentation on a subcommand, run, e.g. `flux help config`. Here is an example of running `flux help` right from the notebook. Yes, did you know we are running in a Flux Instance right now?" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "c7d616de-70cd-4090-bd43-ffacb5ade1f6", "metadata": { "collapsed": true, "jupyter": { "outputs_hidden": true }, + "scrolled": true, "tags": [] }, "outputs": [ @@ -184,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 3, "id": "2e54f640-283a-4523-8dde-9617fd6ef0c5", "metadata": { "collapsed": true, @@ -793,7 +779,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "d568de50-f9e0-452f-8364-e52853013d83", "metadata": {}, "outputs": [ @@ -831,7 +817,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "id": "scenic-chassis", "metadata": {}, "outputs": [ @@ -840,8 +826,8 @@ "output_type": "stream", "text": [ " STATE NNODES NCORES NGPUS NODELIST\n", - " free 4 40 0 f5af[12550686,12550686,12550686,12550686]\n", - " allocated 0 0 0 \n", + " free 4 38 0 8660c254a8e[5,5,5,5]\n", + " allocated 1 2 0 8660c254a8e5\n", " down 0 0 0 \n" ] } @@ -860,7 +846,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "id": "prime-equilibrium", "metadata": {}, "outputs": [ @@ -869,7 +855,7 @@ "output_type": "stream", "text": [ " STATE UP NNODES NODELIST\n", - " avail \u001b[01;32m ✔\u001b[0;0m 4 f5af[12550686,12550686,12550686,12550686]\n" + " avail \u001b[01;32m ✔\u001b[0;0m 4 8660c254a8e[5,5,5,5]\n" ] } ], @@ -888,7 +874,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 7, "id": "c7fbe877-c0bf-4296-a20b-21809caa72d7", "metadata": {}, "outputs": [ @@ -957,16 +943,16 @@ " scancel\n", " flux cancel\n", " \n", - " \n", - " Submitting batch jobs\n", - " sbatch\n", - " flux batch\n", - " \n", " \n", " Allocation for an interactive instance\n", " salloc\n", " flux alloc\n", " \n", + " \n", + " Submitting batch jobs\n", + " sbatch\n", + " flux batch\n", + " \n", "" ] }, @@ -986,7 +972,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", "metadata": {}, "outputs": [ @@ -994,7 +980,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "399f5da372b0\n" + "8660c254a8e5\n" ] } ], @@ -1012,7 +998,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", "metadata": {}, "outputs": [ @@ -1046,7 +1032,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "id": "02032748", "metadata": {}, "outputs": [ @@ -1054,10 +1040,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "3: 399f5da372b0\n", - "2: 399f5da372b0\n", - "1: 399f5da372b0\n", - "0: 399f5da372b0\n" + "3: 8660c254a8e5\n", + "2: 8660c254a8e5\n", + "1: 8660c254a8e5\n", + "0: 8660c254a8e5\n" ] } ], @@ -1067,7 +1053,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "id": "f52bb357-a7ce-458d-9c3f-4d664eca4fbd", "metadata": {}, "outputs": [], @@ -1093,14 +1079,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", - "metadata": { - "collapsed": true, - "jupyter": { - "outputs_hidden": true - } - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -1131,7 +1112,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 13, "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", "metadata": {}, "outputs": [ @@ -1139,7 +1120,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒScZH3DbD\n" + "ƒ3VqNqo3Qs\n" ] } ], @@ -1157,7 +1138,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "id": "571d8c3d-b24a-415e-b9ac-f58b99a7e92c", "metadata": {}, "outputs": [ @@ -1165,7 +1146,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSdrJJshH\n" + "ƒ3VqVSHr7q\n" ] } ], @@ -1189,7 +1170,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 15, "id": "f0e82702", "metadata": {}, "outputs": [ @@ -1197,12 +1178,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒSqGSA7dh\n", - "ƒSqGSA7di\n", - "ƒSqGSA7dj\n", + "ƒ3VqabmM3V\n", + "ƒ3VqabmM3W\n", + "ƒ3VqadFLKq\n", + "foo\n", "bar\n", - "baz\n", - "foo\n" + "baz\n" ] } ], @@ -1212,20 +1193,39 @@ }, { "cell_type": "markdown", - "id": "392a8056-1661-4b76-9ca3-5e536c687e82", + "id": "60ba88b4-538d-4eb6-baf9-735581b4d717", "metadata": {}, "source": [ + "### carbon copy\n", + "\n", "The `--cc` option (akin to \"carbon copy\") to `submit` makes repeated submission even easier via, `flux submit --cc=IDSET`:" ] }, + { + "cell_type": "markdown", + "id": "392a8056-1661-4b76-9ca3-5e536c687e82", + "metadata": {}, + "source": [] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "0ea1962b-1831-4bd2-8dab-c61fd710df9c", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3VqhAnAU7\n", + "ƒ3VqhAnAU8\n", + "ƒ3VqhAnAU9\n", + "ƒ3VqhAnAUA\n" + ] + } + ], "source": [ - "!flux submit --cc=1-4 --watch hostname" + "!flux submit --cc=1-4 hostname" ] }, { @@ -1233,9 +1233,29 @@ "id": "27ca3706-8bb4-4fd6-a37c-e6135fb05604", "metadata": {}, "source": [ - "Try it in the JupyterLab terminal with a progress bar and jobs/s rate report: `flux submit --cc=1-100 --watch --progress --jps hostname`\n", + "Try it in the with a progress bar and jobs/s rate report: `flux submit --cc=1-100 --watch --progress --jps hostname`\n", "\n", - "Note that `--wait` is implied by `--watch`, meaning that when you are watching jobs, you are also waiting for them to finish." + "Note that `--wait` is implied by `--watch`, meaning that when you are watching jobs, you are also waiting for them to finish. Here are some other carbon copy commands that are useful to try:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "8e93d8e3-9342-4edd-b262-757355ddfe9d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3Vqogq1L3\n", + "ƒ3Vqogq1L4\n" + ] + } + ], + "source": [ + "# Use flux carbon copy to submit identical jobs with different inputs\n", + "!flux submit --cc=\"1-2\" echo \"Hello I am job {cc}\"" ] }, { @@ -1243,6 +1263,22 @@ "id": "4c5a18ff-8d6a-47e9-a164-931ed1275ef4", "metadata": {}, "source": [ + "Here are some \"carbon copy\" jobs to try in the :\n", + "\n", + "```bash\n", + "# Use flux carbon copy to submit identical jobs with different inputs\n", + "flux submit --cc=\"1-10\" echo \"Hello I am job {cc}\"\n", + "\n", + "# Submits scripts myscript1.sh through myscript10.sh\n", + "flux submit --cc=1-10 myscript{cc}.sh\n", + "\n", + "# Bypass the key value store and write output to file with jobid\n", + "flux submit --output=job-{{id}}.out echo \"This is job {cc}\"\n", + "\n", + "# Use carbon copy to submit identical jobs with different inputs\n", + "flux bulksubmit --dry-run --cc={0} echo {1} ::: a b c ::: 0-1 0-3 0-7\n", + "```\n", + "\n", "Of course, Flux can launch more than just single-node, single-core jobs. We can submit multiple heterogeneous jobs and Flux will co-schedule the jobs while also ensuring no oversubscription of resources (e.g., cores).\n", "\n", "Note: in this tutorial, we cannot assume that the host you are running on has multiple cores, thus the examples below only vary the number of nodes per job. Varying the `cores-per-task` is also possible on Flux when the underlying hardware supports it (e.g., a multi-core node)." @@ -1250,7 +1286,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 18, "id": "brazilian-former", "metadata": {}, "outputs": [ @@ -1258,8 +1294,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒT2VDkrT1\n", - "ƒT2azp48w\n" + "ƒ3VqtrJWFh\n", + "ƒ3VqzNXq8B\n" ] } ], @@ -1293,7 +1329,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 19, "id": "5ad231c2-4cdb-4d18-afc2-7cb3a74759c2", "metadata": {}, "outputs": [ @@ -1301,7 +1337,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒTR3HXBfD\n", + "ƒ3Vr6FWywV\n", "25 chocolate chip pancakes on the table... 25 chocolate chip pancakes! 🥞️\n", "Eat a stack, for a snack, 15 chocolate chip pancakes on the table! 🥄️\n", "15 chocolate chip pancakes on the table... 15 chocolate chip pancakes! 🥞️\n", @@ -1331,7 +1367,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 20, "id": "institutional-vocabulary", "metadata": {}, "outputs": [ @@ -1340,9 +1376,10 @@ "output_type": "stream", "text": [ " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - " ƒT2azp48w jovyan analysis R 1 1 1.267m 399f5da372b0\n", - " ƒT2VDkrT1 jovyan simulation R 2 2 1.271m 399f5da372b[0,0]\n", - " ƒSdrJJshH jovyan sleep R 2 1 2.127m 399f5da372b0\n" + " ƒ3VqzNXq8B jovyan analysis R 1 1 10.49s 8660c254a8e5\n", + " ƒ3VqtrJWFh jovyan simulation R 2 2 10.71s 8660c254a8e[5,5]\n", + " ƒ3VqVSHr7q jovyan sleep R 2 1 11.62s 8660c254a8e5\n", + " ƒnyvM4Nb jovyan sleep R 2 1 5.269h 8660c254a8e5\n" ] } ], @@ -1360,10 +1397,290 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "70dd1459-e21f-46b5-84a4-bd165cf97f4b", - "metadata": {}, - "outputs": [], + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", + " ƒ3VqzNXq8B jovyan analysis R 1 1 10.71s 8660c254a8e5\n", + " ƒ3VqtrJWFh jovyan simulation R 2 2 10.92s 8660c254a8e[5,5]\n", + " ƒ3VqVSHr7q jovyan sleep R 2 1 11.84s 8660c254a8e5\n", + " ƒnyvM4Nb jovyan sleep R 2 1 5.269h 8660c254a8e5\n", + "\u001b[01;32m ƒ3Vr6FWywV jovyan job-watch+ CD 1 1 10.03s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3Vqogq1L3 jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3Vqogq1L4 jovyan echo CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAUA jovyan hostname CD 1 1 0.060s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU9 jovyan hostname CD 1 1 0.050s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU8 jovyan hostname CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU7 jovyan hostname CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqadFLKq jovyan echo CD 1 1 0.025s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqabmM3W jovyan echo CD 1 1 0.025s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqabmM3V jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqNqo3Qs jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqBbUVR9 jovyan hostname CD 4 1 0.017s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3Vq5LFXNf jovyan false F 1 1 0.037s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VpyWEM83 jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VPB8ZEqV jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3V7Tprhqh jovyan echo CD 1 1 0.060s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3V35oKmEo jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mzETcgvB jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mnMLCXPd jovyan echo CD 1 1 0.036s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mfhe5NCX jovyan echo CD 1 1 0.036s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FE jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y2 jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y8 jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xx jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y5 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y7 jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y1 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xs jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xt jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y3 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gd jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y4 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FF jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FG jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xv jovyan sleep CD 1 1 0.117s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FH jovyan sleep CD 1 1 0.073s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xu jovyan sleep CD 1 1 0.117s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FD jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xw jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y6 jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xz jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gc jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gb jovyan sleep CD 1 1 0.087s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xy jovyan sleep CD 1 1 0.086s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gX jovyan sleep CD 1 1 0.101s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QK jovyan sleep CD 1 1 0.109s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QJ jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QD jovyan sleep CD 1 1 0.111s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QG jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gY jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266ga jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gZ jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QL jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QH jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QF jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QE jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QC jovyan sleep CD 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887z jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887y jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QB jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887w jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887x jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887v jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZN jovyan sleep CD 1 1 0.091s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887s jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887u jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEwe8qV jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZL jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887r jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887t jovyan sleep CD 1 1 0.073s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZM jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887q jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEwe8qW jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZG jovyan sleep CD 1 1 0.082s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZF jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9Z9 jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZE jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZB jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZA jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZH jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZK jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZJ jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH6 jovyan sleep CD 1 1 0.081s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZD jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZC jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH3 jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH5 jovyan sleep CD 1 1 0.072s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH2 jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH1 jovyan sleep CD 1 1 0.067s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGw jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGx jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH4 jovyan sleep CD 1 1 0.063s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGz jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGv jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzd jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGy jovyan sleep CD 1 1 0.058s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGt jovyan sleep CD 1 1 0.059s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGu jovyan sleep CD 1 1 0.058s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzb jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzc jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAza jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAze jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGq jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGr jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGp jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGs jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzY jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzZ jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzf jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzi jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGo jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzg jovyan sleep CD 1 1 0.080s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzj jovyan sleep CD 1 1 0.071s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzW jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzX jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiM jovyan sleep CD 1 1 0.112s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiJ jovyan sleep CD 1 1 0.111s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzh jovyan sleep CD 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiK jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiL jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiH jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzT jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzV jovyan sleep CD 1 1 0.092s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzU jovyan sleep CD 1 1 0.092s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiG jovyan sleep CD 1 1 0.087s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiA jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiF jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiD jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiE jovyan sleep CD 1 1 0.070s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRy jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi9 jovyan sleep CD 1 1 0.068s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi8 jovyan sleep CD 1 1 0.066s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiB jovyan sleep CD 1 1 0.065s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiC jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi7 jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRu jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRw jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRt jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRx jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRr jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRv jovyan sleep CD 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRs jovyan sleep CD 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRq jovyan sleep CD 1 1 0.059s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRp jovyan sleep CD 1 1 0.114s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9d jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9Y jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9e jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9b jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9W jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRm jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9f jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9a jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9Z jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9X jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsC jovyan sleep CD 1 1 0.100s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9c jovyan sleep CD 1 1 0.099s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9U jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9T jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9R jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRn jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9S jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9V jovyan sleep CD 1 1 0.091s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRo jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvN jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs5 jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs8 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvQ jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs9 jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs7 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs6 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvP jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvR jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvS jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsB jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvK jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvJ jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvT jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvM jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvL jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvU jovyan sleep CD 1 1 0.071s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsA jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvH jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe7 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvG jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe4 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe6 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvF jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe5 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe8 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMm jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMg jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMf jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMi jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdy jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdv jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMh jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe2 jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe3 jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdw jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdz jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe9 jovyan sleep CD 1 1 0.191s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMp jovyan sleep CD 1 1 0.175s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMj jovyan sleep CD 1 1 0.173s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdx jovyan sleep CD 1 1 0.174s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMq jovyan sleep CD 1 1 0.173s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe1 jovyan sleep CD 1 1 0.174s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMn jovyan sleep CD 1 1 0.171s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMZ jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5G jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5F jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5E jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdu jovyan sleep CD 1 1 0.166s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMo jovyan sleep CD 1 1 0.167s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMk jovyan sleep CD 1 1 0.166s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMd jovyan sleep CD 1 1 0.165s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMb jovyan sleep CD 1 1 0.163s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMe jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMa jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMc jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5D jovyan sleep CD 1 1 0.032s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2Fr5PCm9h jovyan ./sub_job+ CD 1 1 31.58s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒS4xykqnw jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3wSjr2ik jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3wSjr2ij jovyan echo CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rj jovyan hostname CD 1 1 0.030s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Ri jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rh jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rk jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsB jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsA jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgs9 jovyan Hello I a+ F 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsC jovyan Hello I a+ F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2U jovyan echo CD 1 1 0.032s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2T jovyan echo CD 1 1 0.027s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2V jovyan echo CD 1 1 0.024s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQkA jovyan echo CD 1 1 0.024s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQkB jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk9 jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk8 jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk7 jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGgyRTn jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGgyRTm jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiB jovyan hostname CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiC jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiD jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiE jovyan hostname CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhg jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhf jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhd jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhe jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5VSGEC jovyan echo CD 1 1 0.051s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5TxGwq jovyan echo CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5VSGEB jovyan echo CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒm4dLyPD jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒioKWajq jovyan hostname CD 4 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒhFVr6U7 jovyan false F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒgmdsbJF jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m" + ] + } + ], "source": [ "!flux jobs -a" ] @@ -1384,7 +1701,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 22, "id": "46dd8ec8-6c64-4d8d-9a00-949f5f58c07b", "metadata": {}, "outputs": [ @@ -1392,7 +1709,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "flux-cancel: Canceled 3 jobs (0 errors)\n", + "flux-cancel: Canceled 4 jobs (0 errors)\n", " JOBID USER NAME ST NTASKS NNODES TIME INFO\n" ] } @@ -1403,6 +1720,35 @@ "!flux jobs" ] }, + { + "cell_type": "markdown", + "id": "2d3e314e-98eb-487a-ad8e-1442840e37d8", + "metadata": {}, + "source": [ + "## flux alloc\n", + "\n", + "
\n", + "Description: Allocation for an interactive instance\n", + "
\n", + "\n", + "You might want to request an allocation for a set of resources (an allocation) and then attach to the interactively. This is the goal of flux alloc. Since we can't easily do that in a cell, try opening up the and doing: \n", + "\n", + "```bash\n", + "# Look at the resources you have outside of the allocation\n", + "flux resource list\n", + "\n", + "# Request an allocation with 2 \"nodes\" - a subset of what you have in total\n", + "flux alloc -N 2\n", + "\n", + "# See the resources you are given\n", + "flux resource list\n", + "\n", + "# You can exit from the allocation like this!\n", + "exit\n", + "```\n", + "When you want to automate this, submitting work to an allocation, you would use `flux batch`." + ] + }, { "cell_type": "markdown", "id": "544aa0a9", @@ -1421,7 +1767,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 23, "id": "blank-carpet", "metadata": {}, "outputs": [ @@ -1429,8 +1775,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒThKfdhKD\n", - "ƒThRLkwsm\n" + "ƒ3Vw1mYfjD\n", + "ƒ3Vw6xW9wD\n" ] } ], @@ -1444,15 +1790,69 @@ "id": "da98bfa1", "metadata": {}, "source": [ - "The contents of `sleep_batch.sh`:" + "Take a quick look at [sleep_batch.sh](sleep_batch.sh) to see what we are about to run." ] }, { "cell_type": "code", - "execution_count": 15, - "id": "381a3f6c-0da1-4923-801f-486ca5226d3c", + "execution_count": 24, + "id": "edff8993-3c39-4f46-939d-4c8be5739fbc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3VwC9Te9D\n", + " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", + "\u001b[01;34m ƒ3Vw6xW9wD jovyan ./sleep_b+ R 2 2 0.368s 8660c254a8e[5,5]\n", + "\u001b[0;0m\u001b[01;34m ƒ3Vw1mYfjD jovyan ./sleep_b+ R 2 2 0.572s 8660c254a8e[5,5]\n", + "\u001b[0;0m JOBID USER NAME ST NTASKS NNODES TIME INFO\n", + "\u001b[01;34m ƒ3Vw6xW9wD jovyan ./sleep_b+ R 2 2 0.536s 8660c254a8e[5,5]\n", + "\u001b[0;0m\u001b[01;34m ƒ3Vw1mYfjD jovyan ./sleep_b+ R 2 2 0.741s 8660c254a8e[5,5]\n", + "\u001b[0;0m\n", + "ƒ3Vw6xW9wD:\n", + "\n", + "ƒ3Vw1mYfjD:\n" + ] + } + ], + "source": [ + "# Here we are submitting a job that generates output, and asking to write it to /tmp/cheese.txt\n", + "!flux submit --out /tmp/cheese.txt echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\"\n", + "\n", + "# This will show us JOBIDs\n", + "!flux jobs\n", + "\n", + "# We can even see jobs in sub-instances with \"-R\" (for recursive)\n", + "!flux jobs -R" + ] + }, + { + "cell_type": "markdown", + "id": "7f2b135c-ece7-45f7-b25d-dc90ba5f44f7", + "metadata": {}, + "source": [ + "### `flux job`\n", + "\n", + "Let's next inspect the last job we ran with `flux job info` and target the last job identifier with `flux job last`. " + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "429eb39d-d19c-4170-9707-ca8c3b2bfe87", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"version\": 1, \"execution\": {\"R_lite\": [{\"rank\": \"2\", \"children\": {\"core\": \"7\"}}], \"nodelist\": [\"8660c254a8e5\"], \"starttime\": 1721520196, \"expiration\": 4875116178}}\n", + "0: stdout redirected to /tmp/cheese.txt\n", + "0: stderr redirected to /tmp/cheese.txt\n" + ] + }, { "data": { "text/html": [ @@ -1530,853 +1930,773 @@ ".output_html .vg { color: #19177C } /* Name.Variable.Global */\n", ".output_html .vi { color: #19177C } /* Name.Variable.Instance */\n", ".output_html .vm { color: #19177C } /* Name.Variable.Magic */\n", - ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
#!/bin/bash\n",
-       "#FLUX: --nodes=2\n",
-       "#FLUX: --nslots=2\n",
-       "#FLUX: --cores-per-slot=1\n",
-       "\n",
-       "echo "Starting my batch job"\n",
-       "echo "Print the resources allocated to this batch job"\n",
-       "flux resource list\n",
-       "\n",
-       "echo "Use sleep to emulate a parallel program"\n",
-       "echo "Run the program at a total of 2 processes each requiring"\n",
-       "echo "1 core. These processes are equally spread across 2 nodes."\n",
-       "flux run -N 2 -n 2 sleep 30\n",
-       "flux run -N 2 -n 2 sleep 30\n",
+       ".output_html .il { color: #666666 } /* Literal.Number.Integer.Long */
Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n",
        "
\n" ], "text/latex": [ "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", - "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}nodes=2}\n", - "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}nslots=2}\n", - "\\PY{c+c1}{\\PYZsh{}FLUX: \\PYZhy{}\\PYZhy{}cores\\PYZhy{}per\\PYZhy{}slot=1}\n", - "\n", - "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Starting my batch job\\PYZdq{}}\n", - "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Print the resources allocated to this batch job\\PYZdq{}}\n", - "flux\\PY{+w}{ }resource\\PY{+w}{ }list\n", - "\n", - "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Use sleep to emulate a parallel program\\PYZdq{}}\n", - "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}Run the program at a total of 2 processes each requiring\\PYZdq{}}\n", - "\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}1 core. These processes are equally spread across 2 nodes.\\PYZdq{}}\n", - "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }\\PYZhy{}n\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", - "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }\\PYZhy{}n\\PY{+w}{ }\\PY{l+m}{2}\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", + "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n", "\\end{Verbatim}\n" ], "text/plain": [ - "#!/bin/bash\n", - "#FLUX: --nodes=2\n", - "#FLUX: --nslots=2\n", - "#FLUX: --cores-per-slot=1\n", - "\n", - "echo \"Starting my batch job\"\n", - "echo \"Print the resources allocated to this batch job\"\n", - "flux resource list\n", - "\n", - "echo \"Use sleep to emulate a parallel program\"\n", - "echo \"Run the program at a total of 2 processes each requiring\"\n", - "echo \"1 core. These processes are equally spread across 2 nodes.\"\n", - "flux run -N 2 -n 2 sleep 30\n", - "flux run -N 2 -n 2 sleep 30\n" + "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️" ] }, - "execution_count": 15, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ + "# Note here we are using flux job last to see the last job id\n", + "# The \"R\" here asks for the resource spec\n", + "!flux job info $(flux job last) R\n", + "\n", + "# When we attach it will direct us to our output file\n", + "!flux job attach $(flux job last)\n", + "\n", + "# And we can look at the output file to see our expected output!\n", "from IPython.display import Code\n", - "Code(filename='sleep_batch.sh', language='bash')" + "Code(filename='/tmp/cheese.txt', language='text')" ] }, { - "cell_type": "code", - "execution_count": 16, - "id": "edff8993-3c39-4f46-939d-4c8be5739fbc", + "cell_type": "markdown", + "id": "f4e525e2-6c89-4c14-9fae-d87a0d4fc574", "metadata": {}, + "source": [ + "We can again see a list all completed jobs with `flux jobs -a`:" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "df8a8b7c-f475-4a51-8bc6-9983dc9d78ab", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ƒU5u1XQcf\n", " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;34m ƒThRLkwsm jovyan ./sleep_b+ R 2 2 51.17s 399f5da372b[0,0]\n", - "\u001b[0;0m\u001b[01;34m ƒThKfdhKD jovyan ./sleep_b+ R 2 2 51.39s 399f5da372b[0,0]\n", - "\u001b[0;0m JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;34m ƒThRLkwsm jovyan ./sleep_b+ R 2 2 51.34s 399f5da372b[0,0]\n", - "\u001b[0;0m\u001b[01;34m ƒThKfdhKD jovyan ./sleep_b+ R 2 2 51.56s 399f5da372b[0,0]\n", - "\u001b[0;0m\n", - "ƒThRLkwsm:\n", - " ƒEgNEfjm jovyan sleep R 2 2 20.11s 399f5da372b[0,0]\n", - "\n", - "ƒThKfdhKD:\n", - " ƒEga6ZzX jovyan sleep R 2 2 20.32s 399f5da372b[0,0]\n", - "{\"version\": 1, \"execution\": {\"R_lite\": [{\"rank\": \"3\", \"children\": {\"core\": \"7\"}}], \"nodelist\": [\"399f5da372b0\"], \"starttime\": 1721424338, \"expiration\": 4875020774}}\n", - "0: stdout redirected to /tmp/cheese.txt\n", - "0: stderr redirected to /tmp/cheese.txt\n" + "\u001b[01;34m ƒ3Vw6xW9wD jovyan ./sleep_b+ R 2 2 0.998s 8660c254a8e[5,5]\n", + "\u001b[0;0m\u001b[01;34m ƒ3Vw1mYfjD jovyan ./sleep_b+ R 2 2 1.202s 8660c254a8e[5,5]\n", + "\u001b[0;0m\u001b[01;32m ƒ3VwC9Te9D jovyan echo CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[37m ƒnyvM4Nb jovyan sleep CA 2 1 5.269h 8660c254a8e5\n", + "\u001b[0;0m\u001b[37m ƒ3VqVSHr7q jovyan sleep CA 2 1 12.04s 8660c254a8e5\n", + "\u001b[0;0m\u001b[37m ƒ3VqzNXq8B jovyan analysis CA 1 1 10.91s 8660c254a8e5\n", + "\u001b[0;0m\u001b[37m ƒ3VqtrJWFh jovyan simulation CA 2 2 11.12s 8660c254a8e[5,5]\n", + "\u001b[0;0m\u001b[01;32m ƒ3Vr6FWywV jovyan job-watch+ CD 1 1 10.03s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3Vqogq1L3 jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3Vqogq1L4 jovyan echo CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAUA jovyan hostname CD 1 1 0.060s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU9 jovyan hostname CD 1 1 0.050s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU8 jovyan hostname CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqhAnAU7 jovyan hostname CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqadFLKq jovyan echo CD 1 1 0.025s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqabmM3W jovyan echo CD 1 1 0.025s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqabmM3V jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqNqo3Qs jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VqBbUVR9 jovyan hostname CD 4 1 0.017s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3Vq5LFXNf jovyan false F 1 1 0.037s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VpyWEM83 jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VPB8ZEqV jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3V7Tprhqh jovyan echo CD 1 1 0.060s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3V35oKmEo jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mzETcgvB jovyan echo CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mnMLCXPd jovyan echo CD 1 1 0.036s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2mfhe5NCX jovyan echo CD 1 1 0.036s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FE jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y2 jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y8 jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xx jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y5 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y7 jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y1 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xs jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xt jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y3 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gd jovyan sleep CD 1 1 0.118s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y4 jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FF jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FG jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xv jovyan sleep CD 1 1 0.117s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FH jovyan sleep CD 1 1 0.073s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xu jovyan sleep CD 1 1 0.117s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF545FD jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xw jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5y6 jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xz jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gc jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gb jovyan sleep CD 1 1 0.087s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF3a5xy jovyan sleep CD 1 1 0.086s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gX jovyan sleep CD 1 1 0.101s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QK jovyan sleep CD 1 1 0.109s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QJ jovyan sleep CD 1 1 0.107s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QD jovyan sleep CD 1 1 0.111s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QG jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gY jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266ga jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEF266gZ jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QL jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QH jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QF jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QE jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QC jovyan sleep CD 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887z jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887y jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEzc7QB jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887w jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887x jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887v jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZN jovyan sleep CD 1 1 0.091s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887s jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887u jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEwe8qV jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZL jovyan sleep CD 1 1 0.077s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887r jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887t jovyan sleep CD 1 1 0.073s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZM jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEy887q jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEwe8qW jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZG jovyan sleep CD 1 1 0.082s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZF jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9Z9 jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZE jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZB jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZA jovyan sleep CD 1 1 0.094s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZH jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZK jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZJ jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH6 jovyan sleep CD 1 1 0.081s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZD jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEvA9ZC jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH3 jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH5 jovyan sleep CD 1 1 0.072s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH2 jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH1 jovyan sleep CD 1 1 0.067s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGw jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGx jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAH4 jovyan sleep CD 1 1 0.063s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGz jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGv jovyan sleep CD 1 1 0.083s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzd jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGy jovyan sleep CD 1 1 0.058s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGt jovyan sleep CD 1 1 0.059s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGu jovyan sleep CD 1 1 0.058s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzb jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzc jovyan sleep CD 1 1 0.108s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAza jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAze jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGq jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGr jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGp jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGs jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzY jovyan sleep CD 1 1 0.088s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzZ jovyan sleep CD 1 1 0.085s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzf jovyan sleep CD 1 1 0.084s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzi jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEtgAGo jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzg jovyan sleep CD 1 1 0.080s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzj jovyan sleep CD 1 1 0.071s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzW jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzX jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiM jovyan sleep CD 1 1 0.112s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiJ jovyan sleep CD 1 1 0.111s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzh jovyan sleep CD 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiK jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiL jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiH jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzT jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzV jovyan sleep CD 1 1 0.092s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEsCAzU jovyan sleep CD 1 1 0.092s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiG jovyan sleep CD 1 1 0.087s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiA jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiF jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiD jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiE jovyan sleep CD 1 1 0.070s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRy jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi9 jovyan sleep CD 1 1 0.068s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi8 jovyan sleep CD 1 1 0.066s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiB jovyan sleep CD 1 1 0.065s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBiC jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEqiBi7 jovyan sleep CD 1 1 0.064s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRu jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRw jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRt jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRx jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRr jovyan sleep CD 1 1 0.053s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRv jovyan sleep CD 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRs jovyan sleep CD 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRq jovyan sleep CD 1 1 0.059s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRp jovyan sleep CD 1 1 0.114s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9d jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9Y jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9e jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9b jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9W jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRm jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9f jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9a jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9Z jovyan sleep CD 1 1 0.123s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9X jovyan sleep CD 1 1 0.122s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsC jovyan sleep CD 1 1 0.100s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9c jovyan sleep CD 1 1 0.099s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9U jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9T jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9R jovyan sleep CD 1 1 0.095s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRn jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9S jovyan sleep CD 1 1 0.093s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEnkD9V jovyan sleep CD 1 1 0.091s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEpECRo jovyan sleep CD 1 1 0.090s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvN jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs5 jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs8 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvQ jovyan sleep CD 1 1 0.105s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs9 jovyan sleep CD 1 1 0.103s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs7 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDs6 jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvP jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvR jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvS jovyan sleep CD 1 1 0.104s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsB jovyan sleep CD 1 1 0.078s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvK jovyan sleep CD 1 1 0.079s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvJ jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvT jovyan sleep CD 1 1 0.075s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvM jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvL jovyan sleep CD 1 1 0.076s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvU jovyan sleep CD 1 1 0.071s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEmGDsA jovyan sleep CD 1 1 0.069s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvH jovyan sleep CD 1 1 0.074s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe7 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvG jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe4 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe6 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEQWPvF jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe5 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe8 jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMm jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMg jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMf jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMi jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdy jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdv jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMh jovyan sleep CD 1 1 0.193s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe2 jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe3 jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdw jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdz jovyan sleep CD 1 1 0.192s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe9 jovyan sleep CD 1 1 0.191s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMp jovyan sleep CD 1 1 0.175s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMj jovyan sleep CD 1 1 0.173s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdx jovyan sleep CD 1 1 0.174s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMq jovyan sleep CD 1 1 0.173s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qe1 jovyan sleep CD 1 1 0.174s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMn jovyan sleep CD 1 1 0.171s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMZ jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5G jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5F jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5E jovyan sleep CD 1 1 0.168s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEP2Qdu jovyan sleep CD 1 1 0.166s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMo jovyan sleep CD 1 1 0.167s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMk jovyan sleep CD 1 1 0.166s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMd jovyan sleep CD 1 1 0.165s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMb jovyan sleep CD 1 1 0.163s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMe jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMa jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEMYRMc jovyan sleep CD 1 1 0.162s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2eEEL4S5D jovyan sleep CD 1 1 0.032s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2Fr5PCm9h jovyan ./sub_job+ CD 1 1 31.58s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒS4xykqnw jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3wSjr2ik jovyan echo CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3wSjr2ij jovyan echo CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rj jovyan hostname CD 1 1 0.030s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Ri jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rh jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3mYvC1Rk jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsB jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsA jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgs9 jovyan Hello I a+ F 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsC jovyan Hello I a+ F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2U jovyan echo CD 1 1 0.032s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2T jovyan echo CD 1 1 0.027s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGjwQ2V jovyan echo CD 1 1 0.024s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQkA jovyan echo CD 1 1 0.024s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQkB jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk9 jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk8 jovyan echo CD 1 1 0.023s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGiTQk7 jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGgyRTn jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ3VGgyRTm jovyan echo CD 1 1 0.018s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiB jovyan hostname CD 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiC jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiD jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒ2rpm1UiE jovyan hostname CD 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhg jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhf jovyan hostname CD 1 1 0.016s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhd jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒxah9Lhe jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5VSGEC jovyan echo CD 1 1 0.051s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5TxGwq jovyan echo CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒp5VSGEB jovyan echo CD 1 1 0.047s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒm4dLyPD jovyan hostname CD 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒioKWajq jovyan hostname CD 4 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒhFVr6U7 jovyan false F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;32m ƒgmdsbJF jovyan hostname CD 1 1 0.013s 8660c254a8e5\n", + "\u001b[0;0m" ] - }, + } + ], + "source": [ + "!flux jobs -a" + ] + }, + { + "cell_type": "markdown", + "id": "3e415ecc-f451-4909-a2bf-351a639cd7fa", + "metadata": {}, + "source": [ + "To restrict the output to failed (i.e., jobs that exit with nonzero exit code, time out, or are canceled or killed) jobs, run:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "032597d2-4b02-47ea-a5e5-915313cdd7f9", + "metadata": {}, + "outputs": [ { - "data": { - "text/html": [ - "
Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", + "\u001b[01;31m ƒ3Vq5LFXNf jovyan false F 1 1 0.037s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsB jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsA jovyan Hello I a+ F 1 1 0.014s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgs9 jovyan Hello I a+ F 1 1 0.015s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒ3cZKNgsC jovyan Hello I a+ F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m\u001b[01;31m ƒhFVr6U7 jovyan false F 1 1 0.012s 8660c254a8e5\n", + "\u001b[0;0m" + ] } ], "source": [ - "# Here we are submitting a job that generates output, and asking to write it to /tmp/cheese.txt\n", - "!flux submit --out /tmp/cheese.txt echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\"\n", - "\n", - "# This will show us JOBIDs\n", - "!flux jobs\n", - "\n", - "# We can even see jobs in sub-instances with \"-R\" (for recursive)\n", - "!flux jobs -R\n", + "!flux jobs -f failed" + ] + }, + { + "cell_type": "markdown", + "id": "6bc17bac-2fc4-4418-8939-e930f9929976", + "metadata": {}, + "source": [ + "### flux submit from within a batch\n", "\n", - "# You could copy a JOBID from above and paste it in the line below to examine the job's resources and output\n", - "# or get the last jobid with \"flux job last\" (this is what we will do here)\n", - "# JOBID=\"ƒFoRYVpt7\"\n", + "Next open up [hello-batch.sh](hello-batch.sh) to see an example of using `flux batch` to submit jobs within the instance, and then wait for them to finish. This script is going to:\n", "\n", - "# Note here we are using flux job last to see the last one\n", - "# The \"R\" here asks for the resource spec\n", - "!flux job info $(flux job last) R\n", + "1. Create a flux instance with the top level resources you specify\n", + "2. Submit jobs to the scheduler controlled by the broker of that sub-instance\n", + "3. Run the four jobs, with `--flags=waitable` and `flux job wait --all` to wait for the output file\n", + "4. Within the batch script, you can add `--wait` or `--flags=waitable` to individual jobs, and use `flux queue drain` to wait for the queue to drain, _or_ `flux job wait --all` to wait for the jobs you flagged to finish. \n", "\n", - "# When we attach it will direct us to our output file\n", - "!flux job attach $(flux job last)\n", + "Note that when you submit a batch job, you'll get a job id back for the _batch job_, and usually when you look at the output of that with `flux job attach $jobid` you will see the output file(s) where the internal contents are written. Since we want to print the output file easily to the terminal, we are waiting for the batch job by adding the `--flags=waitable` and then waiting for it. Let's try to run our batch job now." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "72358a03-6f1f-4c5e-91eb-cab71883a232", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3VwkUsydR\n", + "ƒ3VwkUsydR\n", + "Hello job 1 from 8660c254a8e5 💛️\n", + "Hello job 2 from 8660c254a8e5 💚️\n", + "Hello job 3 from 8660c254a8e5 💙️\n", + "Hello job 4 from 8660c254a8e5 💜️\n" + ] + } + ], + "source": [ + "! flux batch --flags=waitable --out /tmp/flux-batch.out -N2 ./hello-batch.sh\n", + "! flux job wait\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", + "id": "75c0ae3f-2813-4ae8-83be-00be3df92a4b", + "metadata": {}, + "source": [ + "Each of `flux batch` and `flux alloc` hints at creating a Flux instance. How deep can we go into that rabbit hole, perhaps for jobs and workflows with nested logic or more orchestration complexity?\n", "\n", - "# And we can look at the output file to see our expected output!\n", - "from IPython.display import Code\n", - "Code(filename='/tmp/cheese.txt', language='text')" + "" ] }, { "cell_type": "markdown", - "id": "f4e525e2-6c89-4c14-9fae-d87a0d4fc574", + "id": "04b405b1-219f-489c-abfc-e2983e82124a", "metadata": {}, "source": [ - "We can again see a list all completed jobs with `flux jobs -a`:" + "### 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 (left), where all jobs go to a central job queue or database. Note that our maximum job throughput is one job per second. The throughput is limited by the workload manager's ability to process a single job. We can improve upon this by simply adding another level, perhaps with three instances. For example, let's say we create a flux allocation or batch that has control of some number of child nodes. We might launch three new instances (each with its own scheduler and queue, right image) at that level two, and all of a sudden, we get a throughput of 1x3, or three jobs per second.\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + "
\n", + "\n", + "All of a sudden, the throughout can increase exponentially because we are essentially submitting to different schedulers. The example above is not impressive, but our [learning guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html#fully-hierarchical-resource-management-techniques) (Figure 10) has a beautiful example of how it can scale, done via an actual experiment. We were able to submit 500 jobs/second using only three levels, vs. close to 1 job/second with one level. For an interesting detail, you can vary the scheduler algorithm or topology within each sub-instance, meaning that you can do some fairly interesting things with scheduling work, and all without stressing the top level system instance. \n", + "\n", + "Now that we understand nested instances, let's look at another batch example that better uses them. Here we have two job scripts:\n", + "\n", + "- [sub_job1.sh](sub_job1.sh): Is going to be run with `flux batch` and submit sub_job2.sh\n", + "- [sub_job2.sh](sub_job2.sh): Is going to be submit by sub_job1.sh.\n", + "\n", + "Take a look at each script to see how they work, and then submit it!" ] }, { "cell_type": "code", - "execution_count": null, - "id": "df8a8b7c-f475-4a51-8bc6-9983dc9d78ab", + "execution_count": 29, + "id": "8640a611-38e4-42b1-a913-89e0c76c8014", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3Vxb9eQBy\n" + ] + } + ], "source": [ - "!flux jobs -a" + "!flux batch -N1 ./sub_job1.sh" ] }, { "cell_type": "markdown", - "id": "3e415ecc-f451-4909-a2bf-351a639cd7fa", + "id": "b29c3a4a-2b77-4ab9-8e0c-9f5228e61016", "metadata": {}, "source": [ - "To restrict the output to failed (i.e., jobs that exit with nonzero exit code, time out, or are canceled or killed) jobs, run:" + "And now that we've submitted, let's look at the hierarchy for all the jobs we just ran. Here is how to try flux pstree, which normally can show jobs in an instance, but it has limited functionality given we are in a notebook! So instead of just running the single command, let's add \"-a\" to indicate \"show me ALL jobs.\"\n", + "More complex jobs and in a different environment would have deeper nesting. You can [see examples here](https://flux-framework.readthedocs.io/en/latest/jobs/hierarchies.html?h=pstree#flux-pstree-command)." ] }, { "cell_type": "code", - "execution_count": 18, - "id": "032597d2-4b02-47ea-a5e5-915313cdd7f9", + "execution_count": 30, + "id": "2d2b1f0b-e6c2-4583-8068-7c76fa341884", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " JOBID USER NAME ST NTASKS NNODES TIME INFO\n", - "\u001b[01;31m ƒSixhuHXu jovyan false F 1 1 0.070s 399f5da372b0\n", - "\u001b[0;0m" + ".\n", + "├── ./sub_job1.sh\n", + "├── ./sleep_batch.sh\n", + "│ └── sleep:R\n", + "├── ./sleep_batch.sh\n", + "│ └── sleep:R\n", + "├── ./hello-batch.sh:CD\n", + "├── 28*[echo:CD]\n", + "├── 2*[sleep:CA]\n", + "├── analysis:CA\n", + "├── simulation:CA\n", + "├── job-watch.sh:CD\n", + "├── 22*[hostname:CD]\n", + "├── 2*[false:F]\n", + "├── 200*[sleep:CD]\n", + "├── 4*[compute.py:F]\n", + "├── ./sub_job1.sh:CD\n", + "├── Hello I am job 3:F\n", + "├── Hello I am job 2:F\n", + "├── Hello I am job 1:F\n", + "└── Hello I am job 4:F\n" ] } ], "source": [ - "!flux jobs -f failed" + "!flux pstree -a" ] }, { "cell_type": "markdown", - "id": "2d3e314e-98eb-487a-ad8e-1442840e37d8", + "id": "7724130f-b0db-4ccf-a01e-98907b9a27ca", "metadata": {}, "source": [ - "## flux alloc\n", - "\n", - "
\n", - "Description: Allocation for an interactive instance\n", - "
\n", - "\n", - "You might want to request an allocation for a set of resources (an allocation) and then attach to the interactively. This is the goal of flux alloc. Since we can't easily do that in a cell, try opening up the and doing: \n", - "\n", - "```bash\n", - "# Look at the resources you have outside of the allocation\n", - "flux resource list\n", - "\n", - "# Request an allocation with 2 \"nodes\" - a subset of what you have in total\n", - "flux alloc -N 2\n", - "\n", - "# See the resources you are given\n", - "flux resource list\n", - "\n", - "# You can exit from the allocation like this!\n", - "exit\n", - "```" + "You can also try a more detailed view with `flux pstree -a -X`!" ] }, { "cell_type": "markdown", - "id": "04b405b1-219f-489c-abfc-e2983e82124a", + "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", "metadata": {}, "source": [ - "# 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", - "\n", - "![img/single-submit.png](img/single-submit.png)\n", - "\n", - "The throughput is limited by the workload manager's ability to process a single job. We can improve upon this by simply adding another level, perhaps with three instances. For example, let's say we create a flux allocation or batch that has control of some number of child nodes. We might launch three new instances (each with its own scheduler and queue) at that level two, and all of a sudden, we get a throughput of 1x3, or three jobs per second. \n", - "\n", - "![img/instance-submit.png](img/instance-submit.png)\n", - "\n", + "# Process, Monitoring, and Job Utilities ⚙️\n", "\n", - "All of a sudden, the throughout can increase exponentially because we are essentially submitting to different schedulers. The example above is not impressive, but our [learning guide](https://flux-framework.readthedocs.io/en/latest/guides/learning_guide.html#fully-hierarchical-resource-management-techniques) (Figure 10) has a beautiful example of how it can scale, done via an actual experiment. We were able to submit 500 jobs/second using only three levels, vs. close to 1 job/second with one level. \n", + "## flux exec 👊️\n", "\n", - "![img/scaled-submit.png](img/scaled-submit.png)\n", - "\n", - "And for an interesting detail, you can vary the scheduler algorithm or topology within each sub-instance, meaning that you can do some fairly interesting things with scheduling work, and all without stressing the top level system instance. Next, let's look at a prototype tool called `flux-tree` that you can use to see how this works.\n", - "\n", - "## flux tree\n", + "
\n", + "Description: Execute commands across ranks\n", + "
\n", "\n", - "Flux tree is a prototype tool that allows you to easily submit work to different levels of your flux instance, or more specifically, creating a nested hierarchy of jobs that scale out. Let's run the command, look at the output, and talk about it." + "Have you ever wanted a quick way to execute a command to all of your nodes in a flux instance? It might be to create a directory, or otherwise interact with a file. This can be hugely useful in environments where you don't have a shared filesystem, for example. This is a job for flux exec! Here is a toy example to execute the command to every rank (`-r all`) to print." ] }, { "cell_type": "code", - "execution_count": 1, - "id": "2735b1ca-e761-46be-b509-a86b771628fc", + "execution_count": 31, + "id": "df8e5a5d-76aa-4151-a25f-4d8f3aa4a738", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "flux-job: task(s) exited with exit code 1\n", - "flux-job: task(s) exited with exit code 1\n", - "awk: line 1: syntax error at or near :\n", - "e8cfed35f636\n", - "flux-tree-helper: ERROR: Expecting value: line 1 column 160 (char 159)\n", - "Jul 05 05:20:32.333883 UTC broker.err[0]: rc2.0: flux tree -N1 -c1 --leaf --prefix=tree.1.1 --njobs=1 -- hostname Exited (rc=1) 0.6s\n", - "awk: line 1: syntax error at or near :\n", - "flux-tree-helper: ERROR: Expecting value: line 1 column 156 (char 155)\n", - "Jul 05 05:20:33.523886 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.4s\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" + "Hello from a flux rank!\n", + "Hello from a flux rank!\n", + "Hello from a flux rank!\n", + "Hello from a flux rank!\n" ] } ], "source": [ - "!flux tree -T2x2 -J 4 -N 1 -c 4 -o ./tree.out -Q easy:fcfs hostname\n", - "! cat ./tree.out" + "!flux exec -r all echo \"Hello from a flux rank!\"" ] }, { "cell_type": "markdown", - "id": "9d5fe7a0-af54-4c90-be6f-75f50c918dea", + "id": "768c05fe-461e-4f88-bb3d-c74f9d8bc217", "metadata": {}, "source": [ - "In the above, we are running `flux-tree` and looking at the output file. What is happening is that the `flux tree` command is creating a hierarchy of instances. Based on their names you can tell that:\n", - "\n", - " - `2x2` in the command is the topology\n", - " - It says to create two flux instances, and make them each spawn two more.\n", - " - `tree` is the root\n", - " - `tree.1` is the first instance\n", - " - `tree.2` is the second instance\n", - " - `tree.1.1` and `tree.1.2` refer to the nested instances under `tree.1`\n", - " - `tree.2.1` and `tree.2.2` refer to the nested instances under `tree.2`\n", - " \n", - "And we provided the command `hostname` to this script, but a more complex example would generate more interested hierarchies,\n", - "and with differet functionality for each. Note that although this is just a dummy prototype, you could use `flux-tree` for actual work,\n", - "or more likely, you would want to use `flux batch` to submit multiple commands within a single flux instance to take advantage of the same\n", - "hierarchy. \n", - "\n", - "## flux batch\n", - "\n", - "Let's return to flux batch, but now with our new knowledge about flux instances! Flux tree is actually an experimental command that you won't encounter in the wild. Instead, you will likely interact with your nested flux instances with `flux batch`. Let's start with a batch script `hello-batch.sh`.\n", - "\n", - "### hello-batch.sh\n" + "You can also use `-x` to exclude ranks. For example, we often do custom actions on the main or \"leader\" rank, and just want to issue commands to the workers." ] }, { "cell_type": "code", - "execution_count": 19, - "id": "e82863e5-b2a1-456b-9ff1-f669b3525fa1", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
#!/bin/bash\n",
-       "\n",
-       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-1.out echo "Hello job 1 from $(hostname) 💛️"\n",
-       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-2.out echo "Hello job 2 from $(hostname) 💚️"\n",
-       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-3.out echo "Hello job 3 from $(hostname) 💙️"\n",
-       "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-4.out echo "Hello job 4 from $(hostname) 💜️"\n",
-       "# Wait for the jobs to finish\n",
-       "flux job wait --all\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", - "\n", - "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}1.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 1 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💛️}\\PY{l+s+s2}{\\PYZdq{}}\n", - "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}2.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 2 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💚️}\\PY{l+s+s2}{\\PYZdq{}}\n", - "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}3.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 3 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💙️}\\PY{l+s+s2}{\\PYZdq{}}\n", - "flux\\PY{+w}{ }submit\\PY{+w}{ }\\PYZhy{}\\PYZhy{}flags\\PY{o}{=}waitable\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }\\PYZhy{}\\PYZhy{}output\\PY{o}{=}/tmp/hello\\PYZhy{}batch\\PYZhy{}4.out\\PY{+w}{ }\\PY{n+nb}{echo}\\PY{+w}{ }\\PY{l+s+s2}{\\PYZdq{}}\\PY{l+s+s2}{Hello job 4 from }\\PY{k}{\\PYZdl{}(}hostname\\PY{k}{)}\\PY{l+s+s2}{ 💜️}\\PY{l+s+s2}{\\PYZdq{}}\n", - "\\PY{c+c1}{\\PYZsh{} Wait for the jobs to finish}\n", - "flux\\PY{+w}{ }job\\PY{+w}{ }\\PY{n+nb}{wait}\\PY{+w}{ }\\PYZhy{}\\PYZhy{}all\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "#!/bin/bash\n", - "\n", - "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-1.out echo \"Hello job 1 from $(hostname) 💛️\"\n", - "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-2.out echo \"Hello job 2 from $(hostname) 💚️\"\n", - "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-3.out echo \"Hello job 3 from $(hostname) 💙️\"\n", - "flux submit --flags=waitable -N1 --output=/tmp/hello-batch-4.out echo \"Hello job 4 from $(hostname) 💜️\"\n", - "# Wait for the jobs to finish\n", - "flux job wait --all" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from IPython.display import Code\n", - "Code(filename='hello-batch.sh', language='bash')" - ] - }, - { - "cell_type": "markdown", - "id": "6bc17bac-2fc4-4418-8939-e930f9929976", - "metadata": {}, - "source": [ - "We would provide this script to run with `flux batch` that is going to:\n", - "\n", - "1. Create a flux instance with the top level resources you specify\n", - "2. Submit jobs to the scheduler controlled by the broker of that sub-instance\n", - "3. Run the four jobs, with `--flags=waitable` and `flux job wait --all` to wait for the output file\n", - "4. Within the batch script, you can add `--wait` or `--flags=waitable` to individual jobs, and use `flux queue drain` to wait for the queue to drain, _or_ `flux job wait --all` to wait for the jobs you flagged to finish. \n", - "\n", - "Note that when you submit a batch job, you'll get a job id back for the _batch job_, and usually when you look at the output of that with `flux job attach $jobid` you will see the output file(s) where the internal contents are written. Since we want to print the output file easily to the terminal, we are waiting for the batch job by adding the `--flags=waitable` and then waiting for it. Let's try to run our batch job now." - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "72358a03-6f1f-4c5e-91eb-cab71883a232", + "execution_count": 32, + "id": "3a9f7e0d-edf4-459e-93ac-463ce0635e2a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ƒY424FfiX\n", - "ƒY424FfiX\n", - "Hello job 1 from 399f5da372b0 💛️\n", - "Hello job 2 from 399f5da372b0 💚️\n", - "Hello job 3 from 399f5da372b0 💙️\n", - "Hello job 4 from 399f5da372b0 💜️\n" + "Hello from everyone except the lead (0) rank!\n", + "Hello from everyone except the lead (0) rank!\n", + "Hello from everyone except the lead (0) rank!\n" ] } ], "source": [ - "! flux batch --flags=waitable --out /tmp/flux-batch.out -N2 ./hello-batch.sh\n", - "! flux job wait\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" + "! flux exec -r all -x 0 echo \"Hello from everyone except the lead (0) rank!\"" ] }, { "cell_type": "markdown", - "id": "75c0ae3f-2813-4ae8-83be-00be3df92a4b", + "id": "05404084-55df-4067-9512-e4ef16ca272e", "metadata": {}, "source": [ - "Excellent! Now let's look at another batch example. Here we have two job scripts:\n", - "\n", - "- sub_job1.sh: Is going to be run with `flux batch` and submit sub_job2.sh\n", - "- sub_job2.sh: Is going to be submit by sub_job1.sh.\n", - "\n", - "You can see that below." + "Here is a similar example, but asking to execute only on rank 2, and to have it print the rank." ] }, { "cell_type": "code", - "execution_count": 31, - "id": "2e6976f8-dbb6-405e-a06b-47c571aa1cdf", + "execution_count": 33, + "id": "e9507c7b-de5c-4129-9a99-c943614a9ba2", "metadata": {}, "outputs": [ { - "data": { - "text/html": [ - "
#!/bin/bash\n",
-       "\n",
-       "flux batch -N1 ./sub_job2.sh\n",
-       "flux queue drain\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", - "\n", - "flux\\PY{+w}{ }batch\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }./sub\\PYZus{}job2.sh\n", - "flux\\PY{+w}{ }queue\\PY{+w}{ }drain\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "#!/bin/bash\n", - "\n", - "flux batch -N1 ./sub_job2.sh\n", - "flux queue drain\n" - ] - }, - "execution_count": 31, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] } ], "source": [ - "Code(filename='sub_job1.sh', language='bash')" + "!flux exec -r 2 flux getattr rank " ] }, - { - "cell_type": "code", - "execution_count": 32, - "id": "a0719cc9-6bf2-4285-b5d7-6cc534fc364c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
#!/bin/bash\n",
-       "\n",
-       "flux run -N1 sleep 30\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{c+ch}{\\PYZsh{}!/bin/bash}\n", - "\n", - "flux\\PY{+w}{ }run\\PY{+w}{ }\\PYZhy{}N1\\PY{+w}{ }sleep\\PY{+w}{ }\\PY{l+m}{30}\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "#!/bin/bash\n", - "\n", - "flux run -N1 sleep 30\n" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], + { + "cell_type": "markdown", + "id": "9ccb6f4d-cbff-4f0a-98b1-59d5a99ee58f", + "metadata": {}, "source": [ - "Code(filename='sub_job2.sh', language='bash')" + "And of course, we could do the same to print for all ranks! This is a derivative of the first example we showed you." ] }, { "cell_type": "code", - "execution_count": 22, - "id": "8640a611-38e4-42b1-a913-89e0c76c8014", + "execution_count": 34, + "id": "6a9de119-abc4-4917-a339-2010ccc7b9b7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "ƒYRzZSFuy\n" + "0\n", + "3\n", + "2\n", + "1\n" ] } ], "source": [ - "# Submit it!\n", - "!flux batch -N1 ./sub_job1.sh" + "!flux exec flux getattr rank" ] }, { "cell_type": "markdown", - "id": "b29c3a4a-2b77-4ab9-8e0c-9f5228e61016", + "id": "b2676cbc-e883-4d72-a719-67bc46182270", "metadata": {}, "source": [ - "And now that we've submit, let's look at the hierarchy for all the jobs we just ran. Here is how to try flux pstree, which normally can show jobs in an instance, but it has limited functionality given we are in a notebook! So instead of just running the single command, let's add \"-a\" to indicate \"show me ALL jobs.\"\n", - "More complex jobs and in a different environment would have deeper nesting. You can [see examples here](https://flux-framework.readthedocs.io/en/latest/jobs/hierarchies.html?h=pstree#flux-pstree-command)." + "You can imagine that `flux exec` is hugely useful in the context of batch jobs, and specific use cases with files, such as using `flux archive`, discussed next." + ] + }, + { + "cell_type": "markdown", + "id": "be923293-6fa1-4a4e-a3b4-8d462d021919", + "metadata": {}, + "source": [ + "## flux archive 📚️\n", + "\n", + "
\n", + "Description: Create file and content archives to access later and between ranks\n", + "
\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", + "When using `flux archive`, we first have to create an named archive. In the code below, we will create a text file and then save it into an archive using `flux archive`. Note that, for larger files, you can speed up the creation and extraction of archives by using the `--mmap` flag." ] }, { "cell_type": "code", - "execution_count": 23, - "id": "2d2b1f0b-e6c2-4583-8068-7c76fa341884", + "execution_count": 35, + "id": "3928d581-9815-4f7b-98cb-72d6a804813d", + "metadata": {}, + "outputs": [], + "source": [ + "!echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\" > shared-file.txt\n", + "!flux archive create --name myarchive --directory $(pwd) shared-file.txt" + ] + }, + { + "cell_type": "markdown", + "id": "1341da82-b8f0-445c-b335-6a10271994d9", + "metadata": {}, + "source": [ + "When we run this code, we are creating an archive in the leader broker. Now that the archive is created, we will want to extract its contents onto the other nodes of our cluster. To do this, we first need to ensure that the directory that we will extract into exists on those nodes. This can be done using `flux exec`. The `flux exec` command will execute a command on the nodes associated with specified brokers. Let's use `flux exec` to run `mkdir` on all the nodes of our cluster except the leader broker's node." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "2bf40c7b-3ca3-4e4f-b21c-4e843c7562a6", + "metadata": {}, + "outputs": [], + "source": [ + "!flux exec -r all -x 0 mkdir -p $(pwd)" + ] + }, + { + "cell_type": "markdown", + "id": "9913e925-aefc-400e-9ff3-0f541f9c3ed2", + "metadata": {}, + "source": [ + "The flags provided to `flux exec` do the following:\n", + "* `-r all`: run across all brokers in the Flux instance\n", + "* `-x 0`: don't runn on broker 0 (i.e., the leader broker)\n", + "\n", + "Now that the directory has been created on all our nodes, we can extract the archive onto those nodes by combining `flux exec` and `flux archive extract`." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "72567af7-aa40-46b7-be43-c9e8124c1c7e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - ".\n", - "├── ./sub_job1.sh\n", - "│ └── ./sub_job2.sh\n", - "│ └── sleep:R\n", - "├── ./hello-batch.sh:CD\n", - "├── 2*[./sleep_batch.sh:CD]\n", - "├── 4*[echo:CD]\n", - "├── sleep:CA\n", - "├── simulation:CA\n", - "├── analysis:CA\n", - "├── job-watch.sh:CD\n", - "├── 13*[hostname:CD]\n", - "└── false:F\n" + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "[1-3]: Exit 1\n" ] } ], "source": [ - "!flux pstree -a" + "!flux exec -r all -x 0 flux archive extract --name myarchive --directory $(pwd) shared-file.txt" ] }, { "cell_type": "markdown", - "id": "7724130f-b0db-4ccf-a01e-98907b9a27ca", + "id": "8b35f8a6-869b-4f4f-874a-074919dfcc51", "metadata": {}, "source": [ - "You can also try a more detailed view with `flux pstree -a -X`!" + "Finally, when we're done with the archive, we can remove it with `flux archive remove`." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "38472bab-c7b9-409b-9058-734527898eb7", + "metadata": {}, + "outputs": [], + "source": [ + "!flux archive remove --name myarchive" ] }, { "cell_type": "markdown", - "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", + "id": "76fa64ca-ebde-4c5f-a505-7ca2b0173f98", + "metadata": {}, + "source": [ + "Finally, note that `flux archive` was named `flux filemap` in earlier versions of Flux." + ] + }, + { + "cell_type": "markdown", + "id": "32e110ce-db7a-4066-81f7-7191c1968496", "metadata": {}, "source": [ - "# Process and Job Utilities ⚙️\n", + "## flux uptime\n", "\n", - "## Flux uptime\n", + "
\n", + "Description: Show how long this flux instance has been running\n", + "
\n", "\n", "Did someone say... [uptime](https://youtu.be/SYRlTISvjww?si=zDlvpWbBljUmZw_Q)? ☝️🕑️🕺️\n", "\n", @@ -2385,7 +2705,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 39, "id": "095f2ac3-145b-4cda-8350-7c281f2b2b45", "metadata": {}, "outputs": [ @@ -2393,7 +2713,7 @@ "name": "stdout", "output_type": "stream", "text": [ - " 22:16:17 run 1.8h, owner jovyan, depth 0, size 4\n" + " 00:03:20 run 5.3h, owner jovyan, depth 0, size 4\n" ] } ], @@ -2404,60 +2724,287 @@ { "cell_type": "markdown", "id": "03e2ae62-3e3b-4c82-a0c7-4c97ff1376d2", - "metadata": { - "jp-MarkdownHeadingCollapsed": true - }, + "metadata": {}, "source": [ - "## Flux top \n", + "## flux top \n", + "\n", + "
\n", + "Description: Show a table of real-time Flux processes\n", + "
\n", + "\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 or here in the notebook.\n", + "## flux pstree \n", + "\n", + "
\n", + "Description: Show a flux process tree (and see nesting in instances)\n", + "
\n", "\n", - "## Flux proxy\n", + "In analogy to `top`, Flux provides `flux pstree`. Try it out in the or here in the notebook.\n", "\n", - "### flux proxy\n", + "## flux proxy\n", "\n", - "> To interact with a job hierarchy\n", + "
\n", + "Description: Interact with a job hierarchy\n", + "
\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 run the commands below!" ] }, { "cell_type": "markdown", - "id": "a609b2f8-e24d-40c7-b022-ce02e91a49f8", + "id": "523e04b6-7427-4c77-8eb0-b5b8998c6224", + "metadata": {}, + "source": [ + "## flux queue\n", + "\n", + "
\n", + "Description: Interact with and inspect Flux queues\n", + "
\n", + "\n", + "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": 40, + "id": "800de4eb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Job submission is disabled: maintenance outage\n", + "Job submission is enabled\n", + "usage: flux-queue [-h] {status,list,enable,disable,start,stop,drain,idle} ...\n", + "\n", + "options:\n", + " -h, --help show this help message and exit\n", + "\n", + "subcommands:\n", + "\n", + " {status,list,enable,disable,start,stop,drain,idle}\n" + ] + } + ], + "source": [ + "!flux queue disable \"maintenance outage\"\n", + "!flux queue enable\n", + "!flux queue -h" + ] + }, + { + "cell_type": "markdown", + "id": "e958b3ce-9220-48ad-8f3e-f76c8d6a800c", + "metadata": {}, + "source": [ + "## flux getattr\n", + "\n", + "
\n", + "Description: Get attributes about your system and environment\n", + "
\n", + "\n", + "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). Here is an example set that you might be interested in looking at:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "biblical-generic", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "4\n", + "local:///tmp/flux-iwjuLe/local-0\n", + "broker.boot-method simple\n", + "broker.critical-ranks 0\n", + "broker.mapping [[0,1,4,1]]\n", + "broker.pid 8\n", + "broker.quorum 4\n", + "broker.quorum-timeout 1m\n", + "broker.rc1_path /etc/flux/rc1\n", + "broker.rc3_path /etc/flux/rc3\n", + "broker.starttime 1721501121.61\n", + "conf.shell_initrc /etc/flux/shell/initrc.lua\n", + "conf.shell_pluginpath /usr/lib/flux/shell/plugins\n", + "config.path -\n", + "content.backing-module content-sqlite\n", + "content.hash sha1\n", + "hostlist 8660c254a8e[5,5,5,5]\n", + "instance-level 0\n", + "jobid -\n", + "local-uri local:///tmp/flux-iwjuLe/local-0\n", + "log-critical-level 2\n", + "log-filename -\n", + "log-forward-level 7\n", + "log-level 7\n", + "log-ring-size 1024\n", + "log-stderr-level 3\n", + "log-stderr-mode leader\n", + "parent-kvs-namespace -\n", + "parent-uri -\n", + "rank 0\n", + "rundir /tmp/flux-iwjuLe\n", + "security.owner 1000\n", + "size 4\n", + "statedir -\n", + "tbon.child_rcvhwm 0\n", + "tbon.connect_timeout 30s\n", + "tbon.descendants 3\n", + "tbon.endpoint ipc:///tmp/flux-iwjuLe/tbon-0\n", + "tbon.level 0\n", + "tbon.maxlevel 1\n", + "tbon.parent-endpoint -\n", + "tbon.prefertcp 0\n", + "tbon.tcp_user_timeout 20s\n", + "tbon.topo kary:32\n", + "tbon.torpid_max 30s\n", + "tbon.torpid_min 5s\n", + "tbon.zmq_io_threads 1\n", + "tbon.zmqdebug 0\n", + "version 0.63.0-5-g0ddc3d9e8\n" + ] + } + ], + "source": [ + "!flux getattr rank\n", + "!flux getattr size\n", + "!flux getattr local-uri\n", + "!flux setattr log-stderr-level 3\n", + "!flux lsattr -v" + ] + }, + { + "cell_type": "markdown", + "id": "e78568c0-7b78-4a1c-aa7f-40d7ea43620f", + "metadata": {}, + "source": [ + "## flux module\n", + "\n", + "
\n", + "Description: Manage Flux extension modules\n", + "
\n", + "\n", + "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`), and we will interact with the `kvs` module in a few cells. For the most part, services are implemented by modules of the same name (e.g., `kvs` implements the `kvs` service and thus the `kvs.lookup` RPC). In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "spatial-maintenance", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-exec 2 R \n", + "heartbeat 0 R \n", + "job-list 2 R \n", + "sched-fluxion-qmanager 2 R sched\n", + "content-sqlite 1 R content-backing\n", + "resource 1 R \n", + "job-ingest 2 R \n", + "content 1 R \n", + "job-info 5 R \n", + "kvs-watch 5 R \n", + "sched-fluxion-resource 2 R \n", + "kvs 1 R \n", + "cron idle R \n", + "job-manager 0 R \n", + "barrier idle R \n", + "connector-local 0 R 1000-shell-f3Vw1mYfjD,1000-shell-f3Vw6xW9wD\n" + ] + } + ], + "source": [ + "!flux module list" + ] + }, + { + "cell_type": "markdown", + "id": "a4c7d50b-50d2-4190-a42e-9b13f1f30380", + "metadata": {}, + "source": [ + "See the [Flux Management Notebook](02_flux_framework.ipynb) for a small tutorial of unloading and reloading the Fluxion (flux scheduler) modules." + ] + }, + { + "cell_type": "markdown", + "id": "5fea958f-e12c-4229-b8a6-e40dcfbd0692", + "metadata": {}, + "source": [ + "## flux dmesg\n", + "\n", + "
\n", + "Description: View Flux system messages\n", + "
\n", + "\n", + "\n", + "If you need some additional help debugging your Flux setup, you might be interested in `flux dmesg`, which is akin to the [Linux dmesg](https://man7.org/linux/man-pages/man1/dmesg.1.html) but delivers messages for Flux." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "c34899ba", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32m2024-07-20T22:56:18.760174Z\u001b[0m \u001b[33mbroker.debug[0]\u001b[0m: \u001b[34mrmmod sched-simple\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.760532Z\u001b[0m \u001b[33mbroker.debug[0]\u001b[0m: \u001b[34mmodule sched-simple exited\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.760597Z\u001b[0m \u001b[33mresource.debug[0]\u001b[0m: \u001b[34maborted 1 resource.acquire(s)\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.760615Z\u001b[0m \u001b[33mjob-manager.debug[0]\u001b[0m: \u001b[34malloc: stop due to disconnect: Success\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.879655Z\u001b[0m \u001b[33mbroker.debug[0]\u001b[0m: \u001b[34minsmod sched-fluxion-resource\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.879925Z\u001b[0m \u001b[33msched-fluxion-resource.info[0]\u001b[0m: version 0.34.0-38-g0fad5268\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.879954Z\u001b[0m \u001b[33msched-fluxion-resource.debug[0]\u001b[0m: \u001b[34mmod_main: resource module starting\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880319Z\u001b[0m \u001b[33msched-fluxion-resource.warning[0]\u001b[0m: \u001b[1mcreate_reader: allowlist unsupported\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880472Z\u001b[0m \u001b[33msched-fluxion-resource.debug[0]\u001b[0m: \u001b[34mresource graph datastore loaded with rv1exec reader\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880477Z\u001b[0m \u001b[33msched-fluxion-resource.info[0]\u001b[0m: populate_resource_db: loaded resources from core's resource.acquire\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880519Z\u001b[0m \u001b[33msched-fluxion-resource.debug[0]\u001b[0m: \u001b[34mresource status changed (rankset=[all] status=DOWN)\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880522Z\u001b[0m \u001b[33msched-fluxion-resource.debug[0]\u001b[0m: \u001b[34mresource status changed (rankset=[0-3] status=UP)\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.880523Z\u001b[0m \u001b[33msched-fluxion-resource.debug[0]\u001b[0m: \u001b[34mmod_main: resource graph database loaded\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998112Z\u001b[0m \u001b[33mbroker.debug[0]\u001b[0m: \u001b[34minsmod sched-fluxion-qmanager\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998336Z\u001b[0m \u001b[33msched-fluxion-qmanager.info[0]\u001b[0m: version 0.34.0-38-g0fad5268\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998452Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34mservice_register\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998472Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34menforced policy (queue=default): fcfs\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998477Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34meffective queue params (queue=default): queue-depth=4\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998478Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34meffective policy params (queue=default): default\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998726Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34mhandshaking with sched-fluxion-resource completed\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:18.998919Z\u001b[0m \u001b[33mjob-manager.debug[0]\u001b[0m: \u001b[34mscheduler: hello\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:19.000882Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34mrequeue success (queue=default id=1750450831360)\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:19.001022Z\u001b[0m \u001b[33mjob-manager.debug[0]\u001b[0m: \u001b[34mscheduler: ready unlimited\u001b[0m\n", + "\u001b[32m2024-07-20T22:56:19.001141Z\u001b[0m \u001b[33msched-fluxion-qmanager.debug[0]\u001b[0m: \u001b[34mhandshaking with job-manager completed\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:03.486096Z\u001b[0m \u001b[33mkvs.debug[0]\u001b[0m: \u001b[34maggregated 2 transactions (2 ops)\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:03.487165Z\u001b[0m \u001b[33mkvs.debug[0]\u001b[0m: \u001b[34maggregated 2 transactions (2 ops)\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:03.749285Z\u001b[0m \u001b[33mkvs.debug[0]\u001b[0m: \u001b[34maggregated 3 transactions (3 ops)\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:03.752634Z\u001b[0m \u001b[33mkvs.debug[0]\u001b[0m: \u001b[34maggregated 2 transactions (2 ops)\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:15.324218Z\u001b[0m \u001b[33mjob-exec.debug[0]\u001b[0m: \u001b[34mexec aborted: id=ƒ3VqVSHr7q\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:15.324274Z\u001b[0m \u001b[33mjob-exec.debug[0]\u001b[0m: \u001b[34mexec aborted: id=ƒ3VqtrJWFh\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:15.324296Z\u001b[0m \u001b[33mjob-exec.debug[0]\u001b[0m: \u001b[34mexec aborted: id=ƒ3VqzNXq8B\u001b[0m\n", + "\u001b[32m2024-07-21T00:03:15.324309Z\u001b[0m \u001b[33mjob-exec.debug[0]\u001b[0m: \u001b[34mexec aborted: id=ƒnyvM4Nb\u001b[0m\n" + ] + } + ], "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? 😎️" + "!flux dmesg" ] }, { @@ -2466,12 +3013,16 @@ "metadata": {}, "source": [ "# 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:" + "Flux also provides first-class python bindings which can be used to submit jobs programmatically. \n", + "\n", + "### `flux.job.JobspecV1` to create job specifications\n", + "\n", + "Flux represents work as a standard called the [Jobspec](https://flux-framework.readthedocs.io/projects/flux-rfc/en/latest/spec_25.html). While you could write YAML or JSON, it's much easier to use provided Python functions that take high level metadata (command, resources, etc) to generate them. We can then replicate our previous example of submitting multiple heterogeneous jobs using these Python helpers, and testing that Flux co-schedules them." ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 44, "id": "third-comment", "metadata": {}, "outputs": [], @@ -2485,7 +3036,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 45, "id": "selective-uganda", "metadata": {}, "outputs": [ @@ -2493,16 +3044,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒZoXw7Pdq\n" + "ƒ3Vyv4d99Z\n" ] } ], "source": [ - "f = flux.Flux() # connect to the running Flux instance\n", + "# connect to the running Flux instance\n", + "f = flux.Flux()\n", + "\n", + "# Create the Jobspec from a command to run a python script, and specify resources\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", + ")\n", + "\n", + "# This is the \"current working directory\" (cwd)\n", + "compute_jobreq.cwd = os.path.expanduser(\"~/flux-tutorial/flux-workflow-examples/job-submit-api/\")\n", + "\n", + "# When we submit, we get back the job identifier (JobID)\n", "print(JobID(flux.job.submit(f,compute_jobreq)).f58) # submit and print out the jobid (in f58 format)" ] }, @@ -2511,12 +3069,14 @@ "id": "0c4b260f-f08a-46ae-ad66-805911a857a7", "metadata": {}, "source": [ + "Once we create the job, when we submit it in Python we get back a job identifier or jobid. We can then interact with the Flux handle, a connection to Flux, to get information about that job.\n", + "\n", "### `flux.job.get_job(handle, jobid)` to get job info" ] }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 46, "id": "ed65cb46-8d8a-41f0-bec1-92b9a89e6db2", "metadata": {}, "outputs": [ @@ -2524,64 +3084,36 @@ "name": "stdout", "output_type": "stream", "text": [ - "🎉️ Hooray, we just submitted ƒZrqPNeNb!\n", - "{\n", - " \"t_depend\": 1721425098.682836,\n", - " \"t_run\": 0.0,\n", - " \"t_cleanup\": 0.0,\n", - " \"t_inactive\": 0.0,\n", - " \"duration\": 0.0,\n", - " \"expiration\": 0.0,\n", - " \"name\": \"compute.py\",\n", - " \"cwd\": \"/home/jovyan/flux-tutorial/flux-workflow-examples/job-submit-api/\",\n", - " \"queue\": \"\",\n", - " \"project\": \"\",\n", - " \"bank\": \"\",\n", - " \"ntasks\": 1,\n", - " \"ncores\": 1,\n", - " \"nnodes\": 1,\n", - " \"priority\": 16,\n", - " \"ranks\": \"\",\n", - " \"nodelist\": \"\",\n", - " \"success\": \"\",\n", - " \"result\": \"\",\n", - " \"waitstatus\": \"\",\n", - " \"id\": 72552617607168,\n", - " \"t_submit\": 1721425098.6718118,\n", - " \"t_remaining\": 0.0,\n", - " \"state\": \"SCHED\",\n", - " \"username\": \"jovyan\",\n", - " \"userid\": 1000,\n", - " \"urgency\": 16,\n", - " \"runtime\": 0.0,\n", - " \"status\": \"SCHED\",\n", - " \"returncode\": \"\",\n", - " \"dependencies\": [],\n", - " \"annotations\": {},\n", - " \"exception\": {\n", - " \"occurred\": \"\",\n", - " \"severity\": \"\",\n", - " \"type\": \"\",\n", - " \"note\": \"\"\n", - " }\n", - "}\n" + "🎉️ Hooray, we just submitted ƒ3VyvraktF!\n", + "\n", + "{\"t_depend\": 1721520202.4164655, \"t_run\": 0.0, \"t_cleanup\": 0.0, \"t_inactive\": 0.0, \"duration\": 0.0, \"expiration\": 0.0, \"name\": \"compute.py\", \"cwd\": \"/home/jovyan/flux-tutorial/flux-workflow-examples/job-submit-api/\", \"queue\": \"\", \"project\": \"\", \"bank\": \"\", \"ntasks\": 1, \"ncores\": 1, \"nnodes\": 1, \"priority\": 16, \"ranks\": \"\", \"nodelist\": \"\", \"success\": \"\", \"result\": \"\", \"waitstatus\": \"\", \"id\": 320116914913280, \"t_submit\": 1721520202.4053006, \"t_remaining\": 0.0, \"state\": \"SCHED\", \"username\": \"jovyan\", \"userid\": 1000, \"urgency\": 16, \"runtime\": 0.0, \"status\": \"SCHED\", \"returncode\": \"\", \"dependencies\": [], \"annotations\": {}, \"exception\": {\"occurred\": \"\", \"severity\": \"\", \"type\": \"\", \"note\": \"\"}}\n" ] } ], "source": [ - "# This is a new command to get info about your job from the id!\n", - "fluxjob = flux.job.submit(f,compute_jobreq)\n", + "# Let's submit again to retrieve (and save) the job identifier\n", + "fluxjob = flux.job.submit(f, compute_jobreq)\n", "fluxjobid = JobID(fluxjob.f58)\n", - "print(f\"🎉️ Hooray, we just submitted {fluxjobid}!\")\n", + "print(f\"🎉️ Hooray, we just submitted {fluxjobid}!\\n\")\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))" + "print(json.dumps(jobinfo))" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 47, + "id": "810b72b9-2de2-4b62-9330-252eede22abb", + "metadata": {}, + "outputs": [], + "source": [ + "### `flux jobs`" + ] + }, + { + "cell_type": "code", + "execution_count": 48, "id": "5d679897-7054-4f96-b340-7f39245aca89", "metadata": {}, "outputs": [ @@ -2589,8 +3121,12 @@ "name": "stdout", "output_type": "stream", "text": [ - " ƒZrqPNeNb jovyan compute.py F 1 1 0.009s 399f5da372b0\n", - " ƒZoXw7Pdq jovyan compute.py F 1 1 0.011s 399f5da372b0\n" + " ƒ3VyvraktF jovyan compute.py F 1 1 0.014s 8660c254a8e5\n", + " ƒ3Vyv4d99Z jovyan compute.py F 1 1 0.020s 8660c254a8e5\n", + " ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", + " ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", + " ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", + " ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n" ] } ], @@ -2608,9 +3144,15 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 49, "id": "efa06478", - "metadata": {}, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, "outputs": [ { "name": "stdout", @@ -2663,52 +3205,9 @@ "print(compute_jobreq.dumps(indent=2))" ] }, - { - "cell_type": "markdown", - "id": "73bbc90e", - "metadata": {}, - "source": [ - "### `flux.job.JobspecV1` to create job specifications\n", - "\n", - "Flux represents work as a standard called the [Jobspec](https://flux-framework.readthedocs.io/projects/flux-rfc/en/latest/spec_25.html). While you could write YAML or JSON, it's much easier to use provided Python functions that take high level metadata (command, resources, etc) to generate them. We can then replicate our previous example of submitting multiple heterogeneous jobs using these Python helpers, and testing that Flux co-schedules them." - ] - }, - { - "cell_type": "code", - "execution_count": 40, - "id": "industrial-privacy", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "ƒKf7Aiz4P\n", - "ƒKf7rkefh\n" - ] - } - ], - "source": [ - "# Here we create our job specification from a command\n", - "compute_jobreq = JobspecV1.from_command(\n", - " command=[\"./compute.py\", \"120\"], num_tasks=4, num_nodes=2, cores_per_task=2\n", - ")\n", - "\n", - "# This is the \"current working directory\" (cwd)\n", - "compute_jobreq.cwd = os.path.expanduser(\"~/flux-workflow-examples/job-submit-api/\")\n", - "print(JobID(flux.job.submit(f, compute_jobreq)))\n", - "\n", - "# Here is a second I/O job\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-workflow-examples/job-submit-api/\")\n", - "print(JobID(flux.job.submit(f, io_jobreq)))" - ] - }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 50, "id": "pregnant-creativity", "metadata": {}, "outputs": [ @@ -2716,10 +3215,12 @@ "name": "stdout", "output_type": "stream", "text": [ - " ƒ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" + " ƒ3VyvraktF jovyan compute.py F 1 1 0.014s 8660c254a8e5\n", + " ƒ3Vyv4d99Z jovyan compute.py F 1 1 0.020s 8660c254a8e5\n", + " ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", + " ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", + " ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", + " ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n" ] } ], @@ -2734,7 +3235,7 @@ "source": [ "### `FluxExecutor` for bulk submission\n", "\n", - "We can use the FluxExecutor class to submit large numbers of jobs to Flux. This method uses python's `concurrent.futures` interface. Here is an example snippet from `~/flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py`:" + "We can use the FluxExecutor class to submit large numbers of jobs to Flux. This method uses python's `concurrent.futures` interface. Here is an example snippet from [flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py](flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py)." ] }, { @@ -2744,18 +3245,18 @@ "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", + " 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": 29, + "execution_count": 51, "id": "cleared-lawsuit", "metadata": {}, "outputs": [ @@ -2763,259 +3264,79 @@ "name": "stdout", "output_type": "stream", "text": [ - "bulksubmit_executor: submitted 200 jobs in 0.24s. 831.05job/s\n", - "bulksubmit_executor: First job finished in about 0.254s\n", - "|██████████████████████████████████████████████████████████| 100.0% (278.2 job/s)\n", - "bulksubmit_executor: Ran 200 jobs in 0.9s. 221.8 job/s\n" + "bulksubmit_executor: submitted 200 jobs in 0.16s. 1246.61job/s\n", + "bulksubmit_executor: First job finished in about 0.172s\n", + "|██████████████████████████████████████████████████████████| 100.0% (298.2 job/s)\n", + "bulksubmit_executor: Ran 200 jobs in 0.8s. 249.6 job/s\n" ] } ], "source": [ - "# Submit a FluxExecutor based script.\n", + "# Submit the FluxExecutor based script.\n", "%run ./flux-workflow-examples/async-bulk-job-submit/bulksubmit_executor.py -n200 /bin/sleep 0" ] }, { "cell_type": "markdown", - "id": "5ee1c49d", + "id": "e5e39506-7f89-4be2-880e-fc21cfe33548", "metadata": {}, "source": [ - "# Deeper Dive into Flux Internals 🧐️\n", + "### `flux.event_watch` to watch events\n", "\n", - "## flux queue\n", - "\n", - "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:" + "If you want to get the output of a job (or more generally, stream events) you can do that as follows. Let's submit a quick job, and then look at the output.\n" ] }, { "cell_type": "code", - "execution_count": 46, - "id": "800de4eb", + "execution_count": 53, + "id": "b24124f8-0faf-4e99-83a5-bd983300fda6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Job submission is disabled: maintenance outage\n", - "Job submission is enabled\n", - "usage: flux-queue [-h] {status,list,enable,disable,start,stop,drain,idle} ...\n", - "\n", - "optional arguments:\n", - " -h, --help show this help message and exit\n", - "\n", - "subcommands:\n", - "\n", - " {status,list,enable,disable,start,stop,drain,idle}\n" + "1721520256.00717: header {'version': 1, 'encoding': {'stdout': 'UTF-8', 'stderr': 'UTF-8'}, 'count': {'stdout': 1, 'stderr': 1}, 'options': {}}\n", + "1721520256.01083: data {'stream': 'stderr', 'rank': '0', 'eof': True}\n", + "1721520256.01085: data {'stream': 'stdout', 'rank': '0', 'data': 'Flux Plumbing 💩️🚽️\\n'}\n", + "1721520256.01087: data {'stream': 'stdout', 'rank': '0', 'eof': True}\n" ] } ], "source": [ - "!flux queue disable \"maintenance outage\"\n", - "!flux queue enable\n", - "!flux queue -h" - ] - }, - { - "cell_type": "markdown", - "id": "67aa7559", - "metadata": {}, - "source": [ - "## flux getattr\n", - "\n", - "> Get attributes about your system and environment\n", + "# Create the Jobspec from a command to run a python script, and specify resources\n", + "jobspec = JobspecV1.from_command(\n", + " command=[\"echo\", \"Flux Plumbing 💩️🚽️\"], num_tasks=1, num_nodes=1, cores_per_task=1)\n", + "jobid = flux.job.submit(f, jobspec)\n", "\n", - "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). Here is an example set that you might be interested in looking at:" - ] - }, - { - "cell_type": "code", - "execution_count": 47, - "id": "biblical-generic", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "4\n", - "local:///tmp/flux-rWMT6G/local-0\n", - "broker.boot-method simple\n", - "broker.critical-ranks 0-1\n", - "broker.mapping [[0,1,4,1]]\n", - "broker.pid 8\n", - "broker.quorum 4\n", - "broker.quorum-timeout 1m\n", - "broker.rc1_path /etc/flux/rc1\n", - "broker.rc3_path /etc/flux/rc3\n", - "broker.starttime 1712894811.07\n", - "conf.shell_initrc /etc/flux/shell/initrc.lua\n", - "conf.shell_pluginpath /usr/lib/flux/shell/plugins\n", - "config.path -\n", - "content.backing-module content-sqlite\n", - "content.hash sha1\n", - "hostlist 993a4f[746854,746854,746854,746854]\n", - "instance-level 0\n", - "jobid -\n", - "local-uri local:///tmp/flux-rWMT6G/local-0\n", - "log-critical-level 2\n", - "log-filename -\n", - "log-forward-level 7\n", - "log-level 7\n", - "log-ring-size 1024\n", - "log-stderr-level 3\n", - "log-stderr-mode leader\n", - "parent-kvs-namespace -\n", - "parent-uri -\n", - "rank 0\n", - "rundir /tmp/flux-rWMT6G\n", - "security.owner 1000\n", - "size 4\n", - "statedir -\n", - "tbon.connect_timeout 30s\n", - "tbon.descendants 3\n", - "tbon.endpoint ipc:///tmp/flux-rWMT6G/tbon-0\n", - "tbon.level 0\n", - "tbon.maxlevel 2\n", - "tbon.parent-endpoint -\n", - "tbon.prefertcp 0\n", - "tbon.tcp_user_timeout 20s\n", - "tbon.topo kary:2\n", - "tbon.torpid_max 30s\n", - "tbon.torpid_min 5s\n", - "tbon.zmqdebug 0\n", - "version 0.61.1\n" - ] - } - ], - "source": [ - "!flux getattr rank\n", - "!flux getattr size\n", - "!flux getattr local-uri\n", - "!flux setattr log-stderr-level 3\n", - "!flux lsattr -v" - ] - }, - { - "cell_type": "markdown", - "id": "d74fdfcf", - "metadata": {}, - "source": [ - "## flux module\n", + "# Give some time to run and finish\n", + "import time\n", + "time.sleep(5)\n", "\n", - "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`), and we will interact with the `kvs` module in a few cells. For the most part, services are implemented by modules of the same name (e.g., `kvs` implements the `kvs` service and thus the `kvs.lookup` RPC). In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." - ] - }, - { - "cell_type": "code", - "execution_count": 48, - "id": "spatial-maintenance", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Module Idle S Service\n", - "job-info 4 R \n", - "sched-fluxion-resource 4 R \n", - "heartbeat 1 R \n", - "job-manager 1 R \n", - "connector-local 0 R \n", - "content-sqlite 2 R content-backing\n", - "kvs 2 R \n", - "resource 2 R \n", - "kvs-watch 4 R \n", - "job-exec 4 R \n", - "barrier idle R \n", - "job-list 4 R \n", - "content 2 R \n", - "sched-fluxion-qmanager 4 R sched\n", - "cron idle R \n", - "job-ingest 4 R \n" - ] - } - ], - "source": [ - "!flux module list" - ] - }, - { - "cell_type": "markdown", - "id": "ad7090eb", - "metadata": {}, - "source": [ - "See the [Flux Management Notebook](02_flux_framework.ipynb) for a small tutorial of unloading and reloading the Fluxion (flux scheduler) modules." + "for line in flux.job.event_watch(f, jobid, \"guest.output\"):\n", + " print(line)" ] }, { "cell_type": "markdown", - "id": "722c4ecf", + "id": "432a6b44-4a37-4b75-9035-ade107def5de", "metadata": {}, "source": [ - "## flux dmesg\n", + "### `flux.job.job_list` to list jobs\n", "\n", - "If you need some additional help debugging your Flux setup, you might be interested in `flux dmesg`, which is akin to the [Linux dmesg](https://man7.org/linux/man-pages/man1/dmesg.1.html) but delivers messages for Flux." + "Finally, it can be really helpful to get an entire listing of jobs. You can do that as follows. Note that the `job_list` is creating a remote procedure call (rpc) and we call `get` to retrieve the output." ] }, { "cell_type": "code", - "execution_count": 7, - "id": "c34899ba", - "metadata": {}, + "execution_count": null, + "id": "b0d109d8-8586-4b91-bbfc-89e523199707", + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ - "!flux dmesg" - ] - }, - { - "cell_type": "markdown", - "id": "c3920f9e", - "metadata": {}, - "source": [ - "## flux exec\n", - "\n", - "Flux provides a built-in mechanism for executing commands on nodes without requiring a job or resource allocation: `flux exec`. `flux exec` is typically used by sys admins to execute administrative commands and load/unload modules across multiple ranks simultaneously." - ] - }, - { - "cell_type": "code", - "execution_count": 52, - "id": "e9507c7b-de5c-4129-9a99-c943614a9ba2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2\n" - ] - } - ], - "source": [ - "!flux exec -r 2 flux getattr rank # only execute on rank 2" - ] - }, - { - "cell_type": "code", - "execution_count": 53, - "id": "6a9de119-abc4-4917-a339-2010ccc7b9b7", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0\n", - "1\n", - "3\n", - "2\n" - ] - } - ], - "source": [ - "!flux exec flux getattr rank # execute on all ranks" + "flux.job.job_list(f).get()" ] }, { diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb index 6d6305f..e4030be 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb @@ -8,12 +8,15 @@ "
\n", "
\n", "\n", - "# Chapter 2: Using Flux to manage and deploy distributed services\n", + "# Chapter 2: Flux Plumbing 💩️🚽️\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", + "> How to get to Porcelain? You start with Plumbing, of course - \"the toilet vs. the pipes\"\n", + "\n", + "Now that we have learned about basic flux commands, and hierarchical scheduling and its benefits, let's dive deeper into the structure of the individual Flux instances that comprise a hierarchy and talk about some additional \"plumbing\" that helps Flux to run. In this module, we cover:\n", "1. The structure of Flux instances\n", - "2. Management of Flux services\n", - "3. Examples of services in Flux (`flux kvs` and `flux archive`)\n", + "2. Flux modules\n", + "3. Examples `flux kvs` that powers a lot of higher level commands\n", + "4. Advanced job specification interaction with flux job\n", "\n", "## The structure of Flux instances\n", "\n", @@ -27,9 +30,9 @@ "\n", "Each broker is a program built on top of the ∅MQ networking library. The broker contains two main components. First, the broker implements Flux-specific networking abstractions over ∅MQ, such as remote-proceedure call (RPC) and publication-subscription (pub-sub). Second, the broker contains several core services, such as PMI (for MPI support), run control support (for enabling automatic startup of other services), and, most importantly, broker module management. The remainder of a Flux broker's functionality comes from broker modules: specially designed services that the broker can deploy in independent OS threads. Some examples of broker modules provided by Flux include:\n", "* Job scheduling (both [traditional and hierarchical](./02_flux_scheduling.ipynb))\n", - "* Fluxion (Flux's advanced graph-based scheduler)\n", + "* [Fluxion](https://github.com/flux-framework/flux-sched) (Flux's advanced graph-based scheduler)\n", "* Banks and accounting (for system-wide deployments of Flux)\n", - "* PMIx (for OpenMPI)\n", + "* [PMIx](https://github.com/openpmix/openpmix) (for OpenMPI)\n", "* An in-memory content store (useful for preloading data into pods on cloud)\n", "\n", "When Flux starts, it launches one or more brokers across the resources it manages. By default, Flux will launch one broker per node, but this can be configured (e.g., with the `--test-size` flag to `flux start` shown in [Chapter 1](./01_flux_tutorial.ipynb)). After launching the brokers, Flux will designate one broker as the \"leader\" and the rest as \"followers\". The leader serves as entrypoint into the Flux instance, and it serves as the starting point for most Flux commands. The distribution of brokers and the \"leader-follower\" designations are shown in the following figure:\n", @@ -40,7 +43,7 @@ "Image created by Vanessa Sochat for Flux Framework Components documentation\n", "
\n", "\n", - "After launching the brokers and designating a leader, Flux uses the brokers' network abstractions to connect the brokers together into what we call the \"tree-based overlay network\", or TBON for short. This network is shown in the figure below. This overlay network connects brokers together in a pre-defined tree-based topology (e.g., *k*-ary and binomial). Whenever brokers or instances of distributed services running on top of the brokers need to communicate, they can send messages up and down this tree-structured network. This tree-structured network is used over alternative designs (e.g., all-to-all networks used by MPI) because it provides better scalability (by minimizing communication), security, and fault tolerance for a service-focused framework. More information about these benefits and Flux's overall design can be found in our [publications](https://flux-framework.org/publications/) (particularly our [2014 paper on Flux](https://ieeexplore.ieee.org/document/7103433) presented at ICPP).\n", + "After launching the brokers and designating a leader, Flux uses the brokers' network abstractions to connect the brokers together into what we call the \"tree-based overlay network\" or TBON for short. This network is shown in the figure below. This overlay network connects brokers together in a pre-defined tree-based topology (e.g., *k*-ary and binomial). Whenever brokers or instances of distributed services running on top of the brokers need to communicate, they can send messages up and down this tree-structured network. This tree-structured network is used over alternative designs (e.g., all-to-all networks used by MPI) because it provides better scalability (by minimizing communication), security, and fault tolerance for a service-focused framework. More information about these benefits and Flux's overall design can be found in our [publications](https://flux-framework.org/publications/) (particularly our [2014 paper on Flux](https://ieeexplore.ieee.org/document/7103433) presented at ICPP).\n", "\n", "
\n", "\n", @@ -48,25 +51,46 @@ "Image created by Vanessa Sochat for Flux Framework Components documentation\n", "
\n", "\n", - "### How Flux instances support services\n", - "\n", - "Services in Flux are implemented as broker modules that can be deployed across one or more brokers. Once deployed, these services can leverage the other components of the broker, including message routing over the TBON and services provided by other broker modules. As a result, broker modules allow for the creation of composable, easily deployable services for Flux instances." + "Flux functionality can be extended with modules, which you might think of like services. For Flux instances, additional services are typically implemented as broker modules that can be deployed across one or more brokers. Once deployed, these services can leverage the other components of the broker, including message routing over the TBON and services provided by other broker modules. As a result, broker modules allow for the creation of composable, easily deployable services for Flux instances." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Management of Flux services\n", + "## Flux Modules\n", "\n", - "To manage and query services, Flux provides the `flux module` command. The sub-commands provided by `flux module` can be seen by running the cell below." + "To manage and query modules, Flux provides the `flux module` command. The sub-commands provided by `flux module` can be seen by running the cell below." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 2, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Usage: flux-module COMMAND [OPTIONS]\n", + " -h, --help Display this message.\n", + "\n", + "flux module subcommands:\n", + " list List loaded modules\n", + " remove Unload module\n", + " load Load module\n", + " reload Reload module\n", + " stats Display stats on module\n", + " debug Get/set module debug flags\n" + ] + } + ], "source": [ "!flux module --help" ] @@ -86,9 +110,39 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 3, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-exec idle R \n", + "heartbeat 1 R \n", + "job-list idle R \n", + "sched-fluxion-resource idle R \n", + "content-sqlite idle R content-backing\n", + "resource idle R \n", + "job-ingest idle R \n", + "content idle R \n", + "job-info idle R \n", + "sched-fluxion-qmanager idle R sched\n", + "kvs-watch idle R \n", + "kvs idle R \n", + "cron idle R \n", + "job-manager idle R \n", + "barrier idle R \n", + "connector-local 0 R \n" + ] + } + ], "source": [ "!flux module list" ] @@ -97,14 +151,43 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Users and system administrators can easily load and unload services using the `flux module load` and `flux module remove` commands. To show this, let's unload Fluxion (Flux's graph-based scheduler) and replace it with the built-in simple scheduler." + "Users and system administrators can easily load and unload modules using the `flux module load` and `flux module remove` commands. To show this, let's unload Fluxion (Flux's graph-based scheduler) and replace it with the built-in simple scheduler." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-exec idle R \n", + "heartbeat 0 R \n", + "job-list idle R \n", + "content-sqlite idle R content-backing\n", + "resource 0 R \n", + "job-ingest idle R \n", + "content 0 R \n", + "job-info idle R \n", + "kvs-watch idle R \n", + "kvs 0 R \n", + "cron idle R \n", + "job-manager 0 R \n", + "sched-simple 0 R sched\n", + "barrier idle R \n", + "connector-local 0 R \n" + ] + } + ], "source": [ "!flux module remove sched-fluxion-qmanager\n", "!flux module remove sched-fluxion-resource\n", @@ -116,9 +199,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In this code block, we unload the 2 services that comprise Fluxion: `sched-fluxion-qmanager` and `sched-fluxion-resource`. Next, we load the simple scheduler (`sched-simple`), and, finally, we look at the running servicees. We now see that Fluxion is not available, and the simple scheduler is.\n", - "\n", - "Next, let's reload Fluxion, but, this time, let's pass some extra arguments to specialize our Flux instance. In particular, we will limit the scheduling depth to 4 and populate Fluxion's resource graph with:\n", + "In this code block, we unload the 2 services that comprise Fluxion: `sched-fluxion-qmanager` and `sched-fluxion-resource`. Next, we load the simple scheduler (`sched-simple`), and, finally, we look at the running servicees. We now see that Fluxion is not available, and the simple scheduler is. Next, let's reload Fluxion, but, this time, let's pass some extra arguments to specialize our Flux instance. In particular, we will limit the scheduling depth to 4 and populate Fluxion's resource graph with:\n", "* Nodes\n", "* Sockets\n", "* Cores" @@ -126,9 +207,39 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 5, + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Module Idle S Service\n", + "job-exec idle R \n", + "heartbeat 1 R \n", + "job-list idle R \n", + "sched-fluxion-qmanager 0 R sched\n", + "content-sqlite idle R content-backing\n", + "resource 0 R \n", + "job-ingest idle R \n", + "content 0 R \n", + "job-info idle R \n", + "kvs-watch idle R \n", + "sched-fluxion-resource 0 R \n", + "kvs 0 R \n", + "cron idle R \n", + "job-manager 0 R \n", + "barrier idle R \n", + "connector-local 0 R \n" + ] + } + ], "source": [ "# Run flux dmesg to make sure sched-simple has no more work before unloading\n", "!flux dmesg -C\n", @@ -138,17 +249,6 @@ "!flux module list" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Examples of services in Flux\n", - "\n", - "In this section, we will cover two services that expand Flux's usefulness to diverse applications:\n", - "1. `flux kvs`\n", - "2. `flux archive`" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -189,82 +289,139 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 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", + "## flux jobspec generation\n", "\n", - "When using `flux archive`, we first have to create an named archive. In the code below, we will create a text file and then save it into an archive using `flux archive`. Note that, for larger files, you can speed up the creation and extraction of archives by using the `--mmap` flag." + "Underlying much interaction with jobs is the creation of job specifications. When you use the command line or Python SDK and submit from a command or script, under the hood (back to that plumbing reference) we are creating a job specification \"Jobspec\" that is passed further through Flux. The command `flux submit` makes it possible to provide a similar command, but instead of running it, to generate the jobspec. Let's do that now. We will generate and view a Jobspec for a simple \"hello world\" job. We do that by adding `--dry-run`." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!echo \"Sweet dreams 🌚️ are made of cheese, who am I to diss a brie? 🧀️\" > shared-file.txt\n", - "!flux archive create --name myarchive --directory $(pwd) shared-file.txt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When we run this code, we are creating an archive in the leader broker. Now that the archive is created, we will want to extract its contents onto the other nodes of our cluster. To do this, we first need to ensure that the directory that we will extract into exists on those nodes. This can be done using `flux exec`. The `flux exec` command will execute a command on the nodes associated with specified brokers. Let's use `flux exec` to run `mkdir` on all the nodes of our cluster except the leader broker's node." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux exec -r all -x 0 mkdir -p $(pwd)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The flags provided to `flux exec` do the following:\n", - "* `-r all`: run across all brokers in the Flux instance\n", - "* `-x 0`: don't runn on broker 0 (i.e., the leader broker)\n", - "\n", - "Now that the directory has been created on all our nodes, we can extract the archive onto those nodes by combining `flux exec` and `flux archive extract`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 16, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"resources\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"type\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"slot\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"count\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"with\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"type\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"core\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"count\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"label\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"task\"\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"tasks\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"command\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m[\n", + " \u001b[0;32m\"echo\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0;32m\"hello\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0;32m\"potato\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0;32m\"🥔️🍠️\"\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"slot\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"task\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"count\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"per_slot\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m]\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"attributes\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"system\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"duration\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m0\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"environment\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"SHELL\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/bin/bash\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_MODULE_PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/lib/flux/modules\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"HOSTNAME\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"8660c254a8e5\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_START_URI\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"local:///tmp/flux-iwjuLe/start\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"NB_UID\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"1000\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"PWD\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/home/jovyan\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"JPY_SESSION_NAME\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/home/jovyan/02_flux_framework.ipynb\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"MANPATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/share/man\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_CONNECTOR_PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/lib/flux/connectors\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"_\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/bin/flux\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"HOME\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/home/jovyan\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"LANG\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"C.UTF-8\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FORCE_COLOR\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"1\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"PYDEVD_USE_FRAME_EVAL\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"NO\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"JUPYTER_APP_LAUNCHER_PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/local/share/jupyter/lab/jupyter_app_launcher/\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"CLICOLOR\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"1\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"CLICOLOR_FORCE\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"1\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"JPY_PARENT_PID\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"159\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"PYTHONPATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/lib/flux/python3.10\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"TERM\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"xterm-color\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"GIT_PAGER\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"cat\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"SHLVL\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"2\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"PAGER\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"cat\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_URI\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"local:///tmp/flux-iwjuLe/local-0\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"MPLBACKEND\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"module://matplotlib_inline.backend_inline\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"NB_USER\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"jovyan\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"LUA_CPATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/lib/lua/5.2/?.so;;;\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_EXEC_PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/libexec/flux/cmd\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"FLUX_URI_RESOLVE_LOCAL\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"t\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"LUA_PATH\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/usr/share/lua/5.2/?.lua;;;\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"BASE_IMAGE\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"jammy\"\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"cwd\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;32m\"/home/jovyan\"\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"shell\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"options\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"rlimit\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[1;39m{\n", + " \u001b[0m\u001b[34;1m\"cpu\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"fsize\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"data\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"stack\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m8388608\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"core\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"nofile\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1048576\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"as\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"rss\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"nproc\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m-1\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m\n", + " \u001b[1;39m}\u001b[0m\u001b[1;39m,\n", + " \u001b[0m\u001b[34;1m\"version\"\u001b[0m\u001b[1;39m: \u001b[0m\u001b[0;39m1\u001b[0m\u001b[1;39m\n", + "\u001b[1;39m}\u001b[0m\n" + ] + } + ], "source": [ - "!flux exec -r all -x 0 flux archive extract --name myarchive --directory $(pwd) shared-file.txt" + "! flux submit --dry-run echo hello potato 🥔️🍠️ > potato-job.txt\n", + "! cat potato-job.txt | jq" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Finally, when we're done with the archive, we can remove it with `flux archive remove`." + "You'll notice there is a lot of content in there! At this point you could write this to file (as we did, saving to `potato-job.txt`, edit it, and provide it directly to `flux job submit` to run. Let's try that now." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!flux archive remove --name myarchive" - ] - }, - { - "cell_type": "markdown", + "execution_count": 17, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒ3VPB8ZEqV\n", + "hello potato 🥔️🍠️\n" + ] + } + ], "source": [ - "Finally, note that `flux archive` was named `flux filemap` in earlier versions of Flux.\n", - "\n", - "`flux kvs` and `flux archive` are two useful, but simple exammples of Flux services. Flux also supports more complex services, including services for runtime data movement, such as DYAD (covered in [Supplementary Chapter 1](./supplementary/dyad/dyad_dlio.ipynb))." + "! flux job submit ./potato-job.txt\n", + "! flux job attach $(flux job last)" ] }, { @@ -274,9 +431,10 @@ "# 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", + "1. The structure of Flux instances \n", + "2. How to load and unload modules in Flux\n", + "3. An example flux module `flux kvs`\n", + "4. Interacting with job specifications `Jobspec`s\n", "\n", "To finish the tutorial, open [Chapter 3](./03_flux_tutorial_conclusions.ipynb)." ] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb index ff2c464..8eae6e9 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb @@ -8,17 +8,14 @@ "
\n", "\n", "\n", - "# Chapter 3: Lessons learned, next steps, and discussion\n", + "# Chapter 3: You Finished!\n", "# This concludes the Flux tutorial! 😄️\n", "\n", "In this tutorial, we:\n", - "* Introduced Flux\n", - "* Showed how to get started with Flux\n", + "* Introduced Flux, and showed you how to get started\n", "* Showed how to perform traditional batch scheduling with Flux\n", "* Showed how to perform hierarchical scheduling with Flux\n", - "* 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 structure of Flux instances and Flux modules\n", "\n", "If you are ready for advanced content, you can do the [DYAD and DLIO tutorial](./supplementary/dyad/dyad_dlio.ipynb) and learn about:\n", "* Describing the design of DYAD, a Flux service for runtime data movement\n", @@ -53,6 +50,12 @@ " - [Getting Started with Flux and Go](https://converged-computing.github.io/flux-go/)\n", " - [Getting Started with Flux in C](https://converged-computing.github.io/flux-c-examples/) *looking for contributors*\n", "\n", + "We also have talks and recent publications or work related to Flux in the cloud:\n", + "\n", + " - [Flux Alongside User-Space Kubernetes](https://arxiv.org/abs/2406.06995): A possible future for running Kubernetes in user space on a traditional HPC cluster (with Flux)!\n", + " - [The Flux Operator](https://flux-framework.org/flux-operator/getting_started/user-guide.html): For deploying an entire Flux cluster in seconds in Kubernetes.\n", + " - [Fluence, a scheduler-plugin for Kubernetes](https://github.com/flux-framework/flux-k8s): to schedule pods with Fluxion.\n", + "\n", "We've also got resources for learning about DYAD!\n", "* [DYAD's ReadTheDocs page](https://dyad.readthedocs.io/en/latest/)\n", "* [DYAD's GitHub repository](https://github.com/flux-framework/dyad)\n", diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-batch.jpg b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/img/flux-batch.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f7282bb421312ed2cd85cb12cd6ea3a1ffdfbd6a GIT binary patch literal 55655 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 literal 0 HcmV?d00001 From 835cd68b9dc59818e1d688edf66ecbe4234a07c6 Mon Sep 17 00:00:00 2001 From: vsoch Date: Fri, 26 Jul 2024 19:37:20 -0600 Subject: [PATCH 7/8] feedback: jacob review items Signed-off-by: vsoch --- .../tutorial/01_flux_tutorial.ipynb | 394 +++++++++++------- .../tutorial/02_flux_framework.ipynb | 10 +- .../03_flux_tutorial_conclusions.ipynb | 4 +- .../flux-workflow-examples/bulksubmit/0.sh | 3 + .../flux-workflow-examples/bulksubmit/1.sh | 3 + .../flux-workflow-examples/bulksubmit/2.sh | 3 + .../flux-workflow-examples/bulksubmit/3.sh | 3 + .../flux-workflow-examples/bulksubmit/4.sh | 3 + .../flux-workflow-examples/bulksubmit/5.sh | 3 + .../flux-workflow-examples/bulksubmit/6.sh | 3 + 10 files changed, 268 insertions(+), 161 deletions(-) create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/0.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/1.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/2.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/3.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/4.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/5.sh create mode 100755 2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/6.sh diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb index 3a0a267..90fd9da 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/01_flux_tutorial.ipynb @@ -25,10 +25,6 @@ " \n", "Flux is a flexible framework for resource management, built for your site. The framework consists of a suite of projects, tools, and libraries that may be used to build site-custom resource managers for High Performance Computing centers and cloud environments. 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", - "> 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 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 3 chapters, each of which has a notebook:\n", @@ -45,7 +41,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 22, "id": "d71ecd22-8552-4b4d-9bc4-61d86f8d33fe", "metadata": { "tags": [] @@ -86,9 +82,23 @@ "tags": [] }, "source": [ + "
\n", + "\n", "# 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/2024-RADIUSS-AWS). You can also find python examples in the `flux-workflow-examples` directory from the sidebar navigation in this JupyterLab instance. To read the Flux manpages and get help, run `flux help`. To get documentation on a subcommand, run, e.g. `flux help config`. Here is an example of running `flux help` right from the notebook. Yes, did you know we are running in a Flux Instance right now?" + "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. " + ] + }, + { + "cell_type": "markdown", + "id": "ae33fef6-278c-4996-8534-fd15e548b338", + "metadata": { + "tags": [] + }, + "source": [ + "
\n", + "Tip: Did you know you can get help for flux or a flux command? For example, try \"flux help\" and \"flux help jobs\"\n", + "
" ] }, { @@ -156,18 +166,6 @@ "!flux help" ] }, - { - "cell_type": "markdown", - "id": "ae33fef6-278c-4996-8534-fd15e548b338", - "metadata": { - "tags": [] - }, - "source": [ - "
\n", - "Tip: Did you know you can also get help for a specific command? For example, run, `flux help jobs` to get information on a sub-command.\n", - "
" - ] - }, { "cell_type": "code", "execution_count": 3, @@ -756,53 +754,6 @@ "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." ] }, - { - "cell_type": "markdown", - "id": "70e3df1d-32c9-4996-b6f7-2fa85f4c02ad", - "metadata": { - "tags": [] - }, - "source": [ - "# Creating Flux Instances\n", - "\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 that mimics a multi-node instance can be started locally with simply:\n", - "\n", - "```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:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "d568de50-f9e0-452f-8364-e52853013d83", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "4\n" - ] - } - ], - "source": [ - "!flux start --test-size=4 flux getattr size" - ] - }, - { - "cell_type": "markdown", - "id": "e693f2d9-651f-4f58-bf53-62528caa83d9", - "metadata": {}, - "source": [ - "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." - ] - }, { "cell_type": "markdown", "id": "ec052119", @@ -841,7 +792,7 @@ "id": "0086e47e", "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`." + "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/projects/flux-core/en/latest/guide/admin.html#configuration). Flux provides a more standard interface to listing available resources that works regardless of the resource input source: `flux resource`." ] }, { @@ -898,6 +849,8 @@ "tags": [] }, "source": [ + "
\n", + "\n", "# Flux Commands \n", "\n", "Here are how Flux commands map to a scheduler you are likely familiar with, Slurm. A larger table with similar mappings for LSF, Moab, and Slurm can be [viewed here](https://hpc.llnl.gov/banks-jobs/running-jobs/batch-system-cross-reference-guides). For submitting jobs, you can use the `flux` `submit`, `run`, `bulksubmit`, `batch`, and `alloc` commands.\n", @@ -964,7 +917,7 @@ "## flux run\n", "\n", "
\n", - "Description: One-off run of a single job (blocking)\n", + "Description: Running a single job (blocking)\n", "
\n", "\n", "The `flux run` command submits a job to Flux (similar to `flux submit`) but then attaches to the job with `flux job attach`, printing the job's stdout/stderr to the terminal and exiting with the same exit code as the job. It's basically doing an interactive submit, because you will be able to watch the output in your terminal, and it will block your terminal until the job completes." @@ -972,7 +925,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 19, "id": "52d26496-dd1f-44f7-bb10-8a9b4b8c9c80", "metadata": {}, "outputs": [ @@ -980,7 +933,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "8660c254a8e5\n" + "749a39b51885\n" ] } ], @@ -998,7 +951,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 20, "id": "fa40cb98-a138-4771-a7ef-f1860dddf7db", "metadata": {}, "outputs": [ @@ -1070,16 +1023,16 @@ "## flux submit\n", "\n", "
\n", - "Description: One-off run of a single job (not blocking)\n", + "Description: Running a single job (not blocking)\n", "
\n", "\n", "\n", - "The `flux submit` command submits a job to Flux and prints out the jobid. " + "The `flux submit` command submits a job to Flux and prints out the jobid." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 1, "id": "cc2bddee-f454-4674-80d4-4a39c5f1bee2", "metadata": {}, "outputs": [ @@ -1112,7 +1065,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 2, "id": "8a5e7d41-1d8d-426c-8198-0ad4a57e7d04", "metadata": {}, "outputs": [ @@ -1120,7 +1073,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "ƒ3VqNqo3Qs\n" + "ƒckWM1ZXM\n" ] } ], @@ -1128,6 +1081,66 @@ "!flux submit hostname" ] }, + { + "cell_type": "markdown", + "id": "809292e5-3f24-4528-916f-8733d065de47", + "metadata": {}, + "source": [ + "But how does one get output? To quickly see output (which will block the terminal if the job is still running) after a submit, you can do:\n", + "\n", + "```bash\n", + "flux job attach $(flux job last)\n", + "```\n", + "\n", + "To provide a custom path to an output or error file, you can provide `--out` and `--err`, respectively. Let's try those both now." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "38a4da7f-2b84-4c67-9da1-02435005d392", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒckWM1ZXM\n", + "749a39b51885\n" + ] + } + ], + "source": [ + "# What was the last job id again?\n", + "! flux job last\n", + "\n", + "# Attach to the last job id that was submitted (will block if still running and stream output)\n", + "! flux job attach $(flux job last)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "89a851d3-0179-4e5e-9e20-93bc11b5056f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ƒfeTb2bBm\n", + "Did a polar bear with a soft drink write this...?! 🐻‍❄️🥤️😎️ \n" + ] + } + ], + "source": [ + "# Now let's submit another one, and give it the same output and error file\n", + "! flux submit --out /tmp/hola-cola.txt --err /tmp/hola-cola.txt echo \"Did a polar bear with a soft drink write this...?! 🐻‍❄️🥤️😎️ \"\n", + "\n", + "# Take a look!\n", + "! cat /tmp/hola-cola.txt" + ] + }, { "cell_type": "markdown", "id": "a7e4c25e-3ca8-4277-bb70-a0e94bcd223b", @@ -1162,7 +1175,7 @@ "## flux bulksubmit\n", "\n", "
\n", - "Description: Bulk submission of jobs (not blocking)\n", + "Description: Submitting jobs in bulk (not blocking)\n", "
\n", "\n", "The `flux bulksubmit` command enqueues jobs based on a set of inputs which are substituted on the command line, similar to `xargs` and the GNU `parallel` utility, except the jobs have access to the resources of an entire Flux instance instead of only the local system." @@ -1201,12 +1214,6 @@ "The `--cc` option (akin to \"carbon copy\") to `submit` makes repeated submission even easier via, `flux submit --cc=IDSET`:" ] }, - { - "cell_type": "markdown", - "id": "392a8056-1661-4b76-9ca3-5e536c687e82", - "metadata": {}, - "source": [] - }, { "cell_type": "code", "execution_count": 16, @@ -1270,18 +1277,48 @@ "flux submit --cc=\"1-10\" echo \"Hello I am job {cc}\"\n", "\n", "# Submits scripts myscript1.sh through myscript10.sh\n", - "flux submit --cc=1-10 myscript{cc}.sh\n", + "flux submit --cc=0-6 flux-workflow-examples/bulksubmit/{cc}.sh\n", "\n", "# Bypass the key value store and write output to file with jobid\n", - "flux submit --output=job-{{id}}.out echo \"This is job {cc}\"\n", + "flux submit --cc=1-10 --output=job-{{id}}.out echo \"This is job {cc}\"\n", "\n", "# Use carbon copy to submit identical jobs with different inputs\n", - "flux bulksubmit --dry-run --cc={0} echo {1} ::: a b c ::: 0-1 0-3 0-7\n", + "flux bulksubmit --dry-run --cc={1} echo {0} ::: a b c ::: 0-1 0-3 0-7\n", "```\n", "\n", - "Of course, Flux can launch more than just single-node, single-core jobs. We can submit multiple heterogeneous jobs and Flux will co-schedule the jobs while also ensuring no oversubscription of resources (e.g., cores).\n", - "\n", - "Note: in this tutorial, we cannot assume that the host you are running on has multiple cores, thus the examples below only vary the number of nodes per job. Varying the `cores-per-task` is also possible on Flux when the underlying hardware supports it (e.g., a multi-core node)." + "Of course, Flux can launch more than just single-node, single-core jobs. We can submit multiple heterogeneous jobs and Flux will co-schedule the jobs while also ensuring no oversubscription of resources (e.g., cores). Let's run the second example here, and add a clever trick to ask for output as we submit the jobs. This is a fun one, I promise!" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "2f089be5-6d32-40db-b9e9-328e5200b754", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Once upon a time... 📗️\n", + "There was a little duck 🦆️\n", + "Her name was pizzaquack 🍕️\n", + "She was very fond of cheese 🧀️\n", + "And running Flux 🌀️\n", + "And so she ran Flux, while she ate her cheese 😋️\n", + "And was so happy! The end. 🌈️\n" + ] + } + ], + "source": [ + "! for jobid in $(flux submit --cc=0-6 /bin/bash flux-workflow-examples/bulksubmit/{cc}.sh); do flux job attach ${jobid}; done" + ] + }, + { + "cell_type": "markdown", + "id": "6d3623b2-ca25-4d42-8e43-0c8e038464b4", + "metadata": {}, + "source": [ + "Note: in this tutorial, we cannot assume that the host you are running on has multiple cores, thus the examples below only vary the number of nodes per job. Varying the `cores-per-task` is also possible on Flux when the underlying hardware supports it (e.g., a multi-core node). Let's run the middle example - it's a fun one, I promise!" ] }, { @@ -1731,7 +1768,7 @@ "Description: Allocation for an interactive instance\n", "\n", "\n", - "You might want to request an allocation for a set of resources (an allocation) and then attach to the interactively. This is the goal of flux alloc. Since we can't easily do that in a cell, try opening up the and doing: \n", + "You might want to request an allocation for a set of resources (an allocation) and then attach to them interactively. This is the goal of flux alloc. Since we can't easily do that in a cell, try opening up the and doing: \n", "\n", "```bash\n", "# Look at the resources you have outside of the allocation\n", @@ -2347,9 +2384,7 @@ "id": "75c0ae3f-2813-4ae8-83be-00be3df92a4b", "metadata": {}, "source": [ - "Each of `flux batch` and `flux alloc` hints at creating a Flux instance. How deep can we go into that rabbit hole, perhaps for jobs and workflows with nested logic or more orchestration complexity?\n", - "\n", - "" + "Each of `flux batch` and `flux alloc` hints at creating a Flux instance. How deep can we go into that rabbit hole, perhaps for jobs and workflows with nested logic or more orchestration complexity?" ] }, { @@ -2377,7 +2412,7 @@ "Now that we understand nested instances, let's look at another batch example that better uses them. Here we have two job scripts:\n", "\n", "- [sub_job1.sh](sub_job1.sh): Is going to be run with `flux batch` and submit sub_job2.sh\n", - "- [sub_job2.sh](sub_job2.sh): Is going to be submit by sub_job1.sh.\n", + "- [sub_job2.sh](sub_job2.sh): Is going to be submitted by sub_job1.sh.\n", "\n", "Take a look at each script to see how they work, and then submit it!" ] @@ -2455,17 +2490,40 @@ "You can also try a more detailed view with `flux pstree -a -X`!" ] }, + { + "cell_type": "code", + "execution_count": 37, + "id": "72567af7-aa40-46b7-be43-c9e8124c1c7e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", + "[1-3]: Exit 1\n" + ] + } + ], + "source": [ + "!flux exec -r all -x 0 flux archive extract --name myarchive --directory $(pwd) shared-file.txt" + ] + }, { "cell_type": "markdown", "id": "eda1a33c-9f9e-4ba0-a013-e97601f79e41", "metadata": {}, "source": [ + "
\n", + "\n", "# Process, Monitoring, and Job Utilities ⚙️\n", "\n", "## flux exec 👊️\n", "\n", "
\n", - "Description: Execute commands across ranks\n", + "Description: Executing commands across ranks\n", "
\n", "\n", "Have you ever wanted a quick way to execute a command to all of your nodes in a flux instance? It might be to create a directory, or otherwise interact with a file. This can be hugely useful in environments where you don't have a shared filesystem, for example. This is a job for flux exec! Here is a toy example to execute the command to every rank (`-r all`) to print." @@ -2591,7 +2649,7 @@ "## flux archive 📚️\n", "\n", "
\n", - "Description: Create file and content archives to access later and between ranks\n", + "Description: Creating file and content archives to access later and between ranks\n", "
\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", @@ -2640,27 +2698,6 @@ "Now that the directory has been created on all our nodes, we can extract the archive onto those nodes by combining `flux exec` and `flux archive extract`." ] }, - { - "cell_type": "code", - "execution_count": 37, - "id": "72567af7-aa40-46b7-be43-c9e8124c1c7e", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", - "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", - "flux-archive: shared-file.txt: write: Attempt to overwrite existing file\n", - "[1-3]: Exit 1\n" - ] - } - ], - "source": [ - "!flux exec -r all -x 0 flux archive extract --name myarchive --directory $(pwd) shared-file.txt" - ] - }, { "cell_type": "markdown", "id": "8b35f8a6-869b-4f4f-874a-074919dfcc51", @@ -2695,7 +2732,7 @@ "## flux uptime\n", "\n", "
\n", - "Description: Show how long this flux instance has been running\n", + "Description: Showing how long a flux instance has been running\n", "
\n", "\n", "Did someone say... [uptime](https://youtu.be/SYRlTISvjww?si=zDlvpWbBljUmZw_Q)? ☝️🕑️🕺️\n", @@ -2729,17 +2766,17 @@ "## flux top \n", "\n", "
\n", - "Description: Show a table of real-time Flux processes\n", + "Description: Showing a table of real-time Flux processes\n", "
\n", "\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", + "Flux provides a feature-full version of `top` for nested Flux instances and jobs. In the 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", "\n", "
\n", - "Description: Show a flux process tree (and see nesting in instances)\n", + "Description: Showing a flux process tree (and seeing nesting in instances)\n", "
\n", "\n", "In analogy to `top`, Flux provides `flux pstree`. Try it out in the or here in the notebook.\n", @@ -2747,10 +2784,30 @@ "## flux proxy\n", "\n", "
\n", - "Description: Interact with a job hierarchy\n", + "Description: Interacting with a job hierarchy\n", "
\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 run the commands below!" + "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. From the run the commands below!\n", + "\n", + "```bash\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", + "```" ] }, { @@ -2761,7 +2818,7 @@ "## flux queue\n", "\n", "
\n", - "Description: Interact with and inspect Flux queues\n", + "Description: Interacting with and inspecting Flux queues\n", "
\n", "\n", "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:" @@ -2804,7 +2861,7 @@ "## flux getattr\n", "\n", "
\n", - "Description: Get attributes about your system and environment\n", + "Description: Getting attributes about your system and environment\n", "
\n", "\n", "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). Here is an example set that you might be interested in looking at:" @@ -2895,10 +2952,10 @@ "## flux module\n", "\n", "
\n", - "Description: Manage Flux extension modules\n", + "Description: Managing Flux extension modules\n", "
\n", "\n", - "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`), and we will interact with the `kvs` module in a few cells. For the most part, services are implemented by modules of the same name (e.g., `kvs` implements the `kvs` service and thus the `kvs.lookup` RPC). In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." + "Services within a Flux instance are implemented by modules. To query and manage broker modules, use `flux module`. Modules that we have already directly interacted with in this tutorial include `resource` (via `flux resource`), `job-ingest` (via `flux` and the Python API) `job-list` (via `flux jobs`) and `job-manager` (via `flux queue`). For the most part, services are implemented by modules of the same name. In some circumstances, where multiple implementations for a service exist, a module of a different name implements a given service (e.g., in this instance, `sched-fluxion-qmanager` provides the `sched` service and thus `sched.alloc`, but in another instance `sched-simple` might provide the `sched` service)." ] }, { @@ -2951,7 +3008,7 @@ "## flux dmesg\n", "\n", "
\n", - "Description: View Flux system messages\n", + "Description: Viewing Flux system messages\n", "
\n", "\n", "\n", @@ -3007,11 +3064,61 @@ "!flux dmesg" ] }, + { + "cell_type": "markdown", + "id": "70e3df1d-32c9-4996-b6f7-2fa85f4c02ad", + "metadata": { + "tags": [] + }, + "source": [ + "### flux start\n", + "\n", + "
\n", + "Description: Interactively starting a set of resources\n", + "
\n", + "\n", + "Sometimes you need to interactively start a set of compute resources. We call this subset a flux instance. You can launch jobs under this instance, akin to how you've done above! In fact, this entire tutorial is started (to give you 4 faux nodes) with a `flux start` command: \n", + "\n", + "```bash\n", + "flux start --test-size=4\n", + "```\n", + "\n", + "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. This is really neat because it means you can launch Flux under other resource managers where it is not installed as the system workload manager. You can also execute \"one off\" commands to it, for example, to see the instance size:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d568de50-f9e0-452f-8364-e52853013d83", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4\n" + ] + } + ], + "source": [ + "!flux start --test-size=4 flux getattr size" + ] + }, + { + "cell_type": "markdown", + "id": "e693f2d9-651f-4f58-bf53-62528caa83d9", + "metadata": {}, + "source": [ + "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." + ] + }, { "cell_type": "markdown", "id": "997faffc", "metadata": {}, "source": [ + "
\n", + "\n", "# Python Submission API 🐍️\n", "Flux also provides first-class python bindings which can be used to submit jobs programmatically. \n", "\n", @@ -3102,13 +3209,11 @@ ] }, { - "cell_type": "code", - "execution_count": 47, - "id": "810b72b9-2de2-4b62-9330-252eede22abb", + "cell_type": "markdown", + "id": "197ee252-dfc9-4256-8d45-df40718c5c3f", "metadata": {}, - "outputs": [], "source": [ - "### `flux jobs`" + "You can now run `flux jobs` to see the jobs that we submit from Python." ] }, { @@ -3205,29 +3310,6 @@ "print(compute_jobreq.dumps(indent=2))" ] }, - { - "cell_type": "code", - "execution_count": 50, - "id": "pregnant-creativity", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " ƒ3VyvraktF jovyan compute.py F 1 1 0.014s 8660c254a8e5\n", - " ƒ3Vyv4d99Z jovyan compute.py F 1 1 0.020s 8660c254a8e5\n", - " ƒ2YnijmLwy jovyan compute.py F 1 1 0.031s 8660c254a8e5\n", - " ƒ2YiqfxNdm jovyan compute.py F 1 1 0.012s 8660c254a8e5\n", - " ƒ2YYgVHnyV jovyan compute.py F 1 1 0.062s 8660c254a8e5\n", - " ƒ2YYE7Ja9d jovyan compute.py F 1 1 0.048s 8660c254a8e5\n" - ] - } - ], - "source": [ - "!flux jobs -a | grep compute" - ] - }, { "cell_type": "markdown", "id": "a8051640", @@ -3350,7 +3432,7 @@ "1. Submitting jobs with Flux\n", "2. The Flux Hierarchy\n", "3. Flux Process and Job Utilities\n", - "4. Deeper Dive into Flux Internals\n", + "4. Python Submission API\n", "\n", "To continue with the tutorial, open [Chapter 2](./02_flux_framework.ipynb)" ] diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb index e4030be..1ae33bc 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/02_flux_framework.ipynb @@ -18,6 +18,8 @@ "3. Examples `flux kvs` that powers a lot of higher level commands\n", "4. Advanced job specification interaction with flux job\n", "\n", + "
\n", + "\n", "## The structure of Flux instances\n", "\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", @@ -29,7 +31,7 @@ "
\n", "\n", "Each broker is a program built on top of the ∅MQ networking library. The broker contains two main components. First, the broker implements Flux-specific networking abstractions over ∅MQ, such as remote-proceedure call (RPC) and publication-subscription (pub-sub). Second, the broker contains several core services, such as PMI (for MPI support), run control support (for enabling automatic startup of other services), and, most importantly, broker module management. The remainder of a Flux broker's functionality comes from broker modules: specially designed services that the broker can deploy in independent OS threads. Some examples of broker modules provided by Flux include:\n", - "* Job scheduling (both [traditional and hierarchical](./02_flux_scheduling.ipynb))\n", + "* Job scheduling (both traditional and hierarchical)\n", "* [Fluxion](https://github.com/flux-framework/flux-sched) (Flux's advanced graph-based scheduler)\n", "* Banks and accounting (for system-wide deployments of Flux)\n", "* [PMIx](https://github.com/openpmix/openpmix) (for OpenMPI)\n", @@ -58,6 +60,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "
\n", + "\n", "## Flux Modules\n", "\n", "To manage and query modules, Flux provides the `flux module` command. The sub-commands provided by `flux module` can be seen by running the cell below." @@ -99,7 +103,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "While going through [Module 2](./02_flux_scheduling.ipynb), we've already encountered some built-in services provided by Flux, such as:\n", + "Some examples of Flux modules include:\n", "* `job-ingest` (used by Flux submission commands like `flux batch` and `flux run`)\n", "* `job-list` (used by `flux jobs`)\n", "* `sched-fluxion-qmanager` (used by `flux tree`)\n", @@ -289,6 +293,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "
\n", + "\n", "## flux jobspec generation\n", "\n", "Underlying much interaction with jobs is the creation of job specifications. When you use the command line or Python SDK and submit from a command or script, under the hood (back to that plumbing reference) we are creating a job specification \"Jobspec\" that is passed further through Flux. The command `flux submit` makes it possible to provide a similar command, but instead of running it, to generate the jobspec. Let's do that now. We will generate and view a Jobspec for a simple \"hello world\" job. We do that by adding `--dry-run`." diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb index 8eae6e9..e89ec3d 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/03_flux_tutorial_conclusions.ipynb @@ -31,7 +31,7 @@ "\n", "> Where can I learn to set this up on my own?\n", "\n", - "If you're interested in installing Flux on your cluster, take a look at the [system instance instructions](https://flux-framework.readthedocs.io/en/latest/adminguide.html). If you are interested in running Flux on Kubernetes, check out the [Flux Operator](https://github.com/flux-framework/flux-operator). \n", + "If you're interested in installing Flux on your cluster, take a look at the [system instance instructions](https://flux-framework.readthedocs.io/projects/flux-core/en/latest/guide/admin.html). If you are interested in running Flux on Kubernetes, check out the [Flux Operator](https://github.com/flux-framework/flux-operator). \n", "\n", "> How can I run this tutorial on my own?\n", "\n", @@ -60,8 +60,6 @@ "* [DYAD's ReadTheDocs page](https://dyad.readthedocs.io/en/latest/)\n", "* [DYAD's GitHub repository](https://github.com/flux-framework/dyad)\n", "* [eScience 2022 Short Paper](https://dyad.readthedocs.io/en/latest/_downloads/27090817b034a89b76e5538e148fea9e/ShortPaper_2022_eScience_LLNL.pdf)\n", - "* [SC 2023 ACM Student Research Competition Extended Abstract](https://github.com/flux-framework/dyad/blob/main/docs/_static/ExtendedAbstract_2023_SC_ACM_SRC_DYAD.pdf)\n", - "* [IPDPS 2024 HiCOMB Workshop Paper](https://github.com/flux-framework/dyad/blob/main/docs/_static/Paper_2024_IPDPS_HiCOMB_DYAD.pdf)\n", "\n", "And, of course, you can always 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!\n", "\n", diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/0.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/0.sh new file mode 100755 index 0000000..a11e231 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/0.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Once upon a time... 📗️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/1.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/1.sh new file mode 100755 index 0000000..f958b58 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/1.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "There was a little duck 🦆️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/2.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/2.sh new file mode 100755 index 0000000..fe6930b --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/2.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Her name was pizzaquack 🍕️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/3.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/3.sh new file mode 100755 index 0000000..7ba6b82 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/3.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "She was very fond of cheese 🧀️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/4.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/4.sh new file mode 100755 index 0000000..089c949 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/4.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "And running Flux 🌀️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/5.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/5.sh new file mode 100755 index 0000000..3c00920 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/5.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "And so she ran Flux, while she ate her cheese 😋️" diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/6.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/6.sh new file mode 100755 index 0000000..9233634 --- /dev/null +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/bulksubmit/6.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "And was so happy! The end. 🌈️" From b00ccf1342f629c1224aee6163d2349d008c768f Mon Sep 17 00:00:00 2001 From: vsoch Date: Fri, 9 Aug 2024 14:29:48 -0600 Subject: [PATCH 8/8] flux-workflow-examples: update content conduit still does not compile, and a note was added about that. Signed-off-by: vsoch --- .../tutorial/flux-workflow-examples/README.md | 45 +++++----- .../async-bulk-job-submit/README.md | 72 +++++++++------ .../comms-module/README.md | 85 +++++++++++++++--- .../data-conduit/README.md | 36 ++++++-- .../data-conduit/conduit.c | 2 +- .../hierarchical-launching/README.md | 24 ++--- .../hierarchical-launching/ensemble.sh | 2 +- .../hierarchical-launching/parent.sh | 2 +- .../job-cancel/README.md | 21 +++-- .../job-ensemble/README.md | 58 +++++------- .../job-ensemble/ensemble.sh | 6 +- .../job-status-control/README.md | 28 +++--- .../job-submit-api/README.md | 85 ++++++++++-------- .../job-submit-cli/README.md | 90 ++++++++----------- .../job-submit-wait/README.md | 63 +++++++------ .../kvs-python-bindings/README.md | 36 ++++---- .../synchronize-events/README.md | 39 ++++---- 17 files changed, 392 insertions(+), 302 deletions(-) diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md index 9208b12..892bcb0 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/README.md @@ -1,73 +1,72 @@ -**WARNING** +# Flux Workflow Examples -This repository has been archived. It is no longer maintained and it is -likely the examples do not work or are no longer good or suggested -examples. - -Please look elswhere for examples. - -**Flux Workflow Examples** +This contents used to be hosted at [flux-framework/flux-workflow-examples](https://github.com/flux-framework/flux-workflow-examples) and has been moved here for annual updates paired with the Flux Tutorials. The examples contained here demonstrate and explain some simple use-cases with Flux, and make use of Flux's command-line interface (CLI), Flux's C library, and the Python and Lua bindings to the C library. -**Requirements** +## Requirements The examples assume that you have installed: 1. A recent version of Flux - 2. Python 3.6+ - 3. Lua 5.1+ -**_1. [CLI: Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-cli)_** +You can also use an interactive container locally, binding this directory to the container: + +```bash +docker run -it -v $(pwd):/home/fluxuser/flux-workflow-examples fluxrm/flux-sched:jammy +cd /home/fluxuser/flux-workflow-examples/ +``` + +**_1. [CLI: Job Submission](job-submit-cli)_** Launch a flux instance and schedule/launch compute and io-forwarding jobs on separate nodes using the CLI -**_2. [Python: Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-api)_** +**_2. [Python: Job Submission](job-submit-api)_** Schedule/launch compute and io-forwarding jobs on separate nodes using the Python bindings -**_3. [Python: Job Submit/Wait](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-submit-wait)_** +**_3. [Python: Job Submit/Wait](job-submit-wait)_** Submit jobs and wait for them to complete using the Flux Python bindings -**_4. [Python: Asynchronous Bulk Job Submission](https://github.com/flux-framework/flux-workflow-examples/tree/master/async-bulk-job-submit)_** +**_4. [Python: Asynchronous Bulk Job Submission](async-bulk-job-submit)_** Asynchronously submit jobspec files from a directory and wait for them to complete in any order -**_5. [Python: Tracking Job Status and Events](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-status-control)_** +**_5. [Python: Tracking Job Status and Events](job-status-control)_** Submit job bundles, get event updates, and wait until all jobs complete -**_6. [Python: Job Cancellation](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-cancel)_** +**_6. [Python: Job Cancellation](job-cancel)_** Cancel a running job -**_7. [Lua: Use Events](https://github.com/flux-framework/flux-workflow-examples/tree/master/synchronize-events)_** +**_7. [Lua: Use Events](synchronize-events)_** Use events to synchronize compute and io-forwarding jobs running on separate nodes -**_8. [Python: Simple KVS Example](https://github.com/flux-framework/flux-workflow-examples/tree/master/kvs-python-bindings)_** +**_8. [Python: Simple KVS Example](kvs-python-bindings)_** Use KVS Python interfaces to store user data into KVS -**_9. [CLI/Lua: Job Ensemble Submitted with a New Flux Instance](https://github.com/flux-framework/flux-workflow-examples/tree/master/job-ensemble)_** +**_9. [CLI/Lua: Job Ensemble Submitted with a New Flux Instance](job-ensemble)_** Submit job bundles, print live job events, and exit when all jobs are complete -**_10. [CLI: Hierarchical Launching](https://github.com/flux-framework/flux-workflow-examples/tree/master/hierarchical-launching)_** +**_10. [CLI: Hierarchical Launching](hierarchical-launching)_** Launch a large number of sleep 0 jobs -**_11. [C/Lua: Use a Flux Comms Module](https://github.com/flux-framework/flux-workflow-examples/tree/master/comms-module)_** +**_11. [C/Lua: Use a Flux Comms Module](comms-module)_** Use a Flux Comms Module to communicate with job elements -**_12. [C/Python: A Data Conduit Strategy](https://github.com/flux-framework/flux-workflow-examples/tree/master/data-conduit)_** +**_12. [C/Python: A Data Conduit Strategy](data-conduit)_** Attach to a job that receives OS time data from compute jobs diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md index 719af07..c612e51 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/async-bulk-job-submit/README.md @@ -1,7 +1,6 @@ -## Python Asynchronous Bulk Job Submission +# Python Asynchronous Bulk Job Submission -Parts (a) and (b) demonstrate different implementations of the same basic use-case---submitting -large numbers of jobs to Flux. For simplicity, in these examples all of the jobs are identical. +Parts (a) and (b) demonstrate different implementations of the same basic use-case---submitting large numbers of jobs to Flux. For simplicity, in these examples all of the jobs are identical. In part (a), we use the `flux.job.submit_async` and `flux.job.wait` functions to submit jobs and wait for them. In part (b), we use the `FluxExecutor` class, which offers a higher-level interface. It is important to note that @@ -10,49 +9,59 @@ The executor's futures fulfill in the background and callbacks added to the futu be invoked by different threads; the `submit_async` futures do not fulfill in the background, callbacks are always invoked by the same thread that added them, and sharing the futures among threads is not supported. -### Setup - Downloading the Files +## Setup - Downloading the Files If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/async-bulk-job-submit ``` -### Part (a) - Using `submit_async` +## Part (a) - Using `submit_async` -#### Description: Asynchronously submit jobspec files from a directory and wait for them to complete in any order +### Description: Asynchronously submit jobspec files from a directory and wait for them to complete in any order 1. Allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` 2. Make a **jobs** directory: -`mkdir jobs` +```bash +mkdir /tmp/jobs +``` -3. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: +3. If you are running Slurm, launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 4. Store the jobspec of a `sleep 0` job in the **jobs** directory: -`flux mini run --dry-run -n1 sleep 0 > jobs/0.json` +```bash +flux run --dry-run -n1 sleep 0 > /tmp/jobs/0.json +``` 5. Copy the jobspec of **job0** 1024 times to create a directory of 1025 `sleep 0` jobs: -``for i in `seq 1 1024`; do cp jobs/0.json jobs/${i}.json; done`` +```bash +for i in `seq 1 1024`; do cp /tmp/jobs/0.json /tmp/jobs/${i}.json; done +``` 6. Run the **bulksubmit.py** script and pass all jobspec in the **jobs** directory as an argument with a shell glob `jobs/*.json`: -`./bulksubmit.py jobs/*.json` - +```bash +./bulksubmit.py /tmp/jobs/*.json ``` +```console bulksubmit: Starting... -bulksubmit: submitted 1025 jobs in 3.04s. 337.09job/s -bulksubmit: First job finished in about 3.089s -|██████████████████████████████████████████████████████████| 100.0% (29.4 job/s) -bulksubmit: Ran 1025 jobs in 34.9s. 29.4 job/s +bulksubmit: submitted 1025 jobs in 0.43s. 2392.93job/s +bulksubmit: First job finished in about 0.521s +|██████████████████████████████████████████████████████████| 100.0% (274.3 job/s) +bulksubmit: Ran 1025 jobs in 3.7s. 274.3 job/s ``` ### Notes to Part (a) @@ -65,7 +74,7 @@ bulksubmit: Ran 1025 jobs in 34.9s. 29.4 job/s ```python if h.reactor_run() < 0: - h.fatal_error("reactor start failed") + h.fatal_error("reactor start failed") ``` The reactor will return automatically when there are no more outstanding RPC responses, i.e., all jobs have been submitted. @@ -81,19 +90,24 @@ If continuing from part (a), skip to step 3. 1. Allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` 2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Run the **bulksubmit_executor.py** script and pass the command (`/bin/sleep 0` in this example) and the number of times to run it (default is 100): -`./bulksubmit_executor.py -n200 /bin/sleep 0` - +```bash +./bulksubmit_executor.py -n200 /bin/sleep 0 ``` -bulksubmit_executor: submitted 200 jobs in 0.45s. 441.15job/s -bulksubmit_executor: First job finished in about 1.035s -|██████████████████████████████████████████████████████████| 100.0% (24.9 job/s) -bulksubmit_executor: Ran 200 jobs in 8.2s. 24.4 job/s +```console +bulksubmit_executor: submitted 200 jobs in 0.18s. 1087.27job/s +bulksubmit_executor: First job finished in about 0.248s +|██████████████████████████████████████████████████████████| 100.0% (229.8 job/s) +bulksubmit_executor: Ran 200 jobs in 1.0s. 199.6 job/s ``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md index 3acdc5c..6f1456c 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/comms-module/README.md @@ -1,36 +1,93 @@ -### Using a Flux Comms Module +# Using a Flux Comms Module -#### Description: Use a Flux comms module to communicate with job elements +## Description: Use a Flux comms module to communicate with job elements -##### Setup +### Setup If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/comms-module ``` -##### Execution +### Execution + +If you need to get an allocation on Slurm: -1. `salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` -2. Point to `flux-core`'s `pkgconfig` directory: +Point to `flux-core`'s `pkgconfig` directory: | Shell | Command | | ----- | ---------- | | tcsh | `setenv PKG_CONFIG_PATH /lib/pkgconfig` | | bash/zsh | `export PKG_CONFIG_PATH='/lib/pkgconfig'` | -3. `make` +This might look like this in the container: + +```bash +export PKG_CONFIG_PATH=/usr/lib/pkgconfig +``` -4. Add the directory of the modules to `FLUX_MODULE_PATH`; if the module was +Then build the module (if you don't have permission, copy to /tmp) + +```bash +cp -R ./comms-module /tmp/comms-module +cd /tmp/comms-module +make +``` + +Add the directory of the modules to `FLUX_MODULE_PATH`; if the module was built in the current dir: -`export FLUX_MODULE_PATH=${FLUX_MODULE_PATH}:$(pwd)` +```bash +flux module load ioapp.so +flux module load capp.so +export FLUX_MODULE_PATH=${FLUX_MODULE_PATH}:$(pwd) +``` -5. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +Now let's try it! If you need to run flux start under Slurm: -6. `flux submit -N 2 -n 2 ./compute.lua 120` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` + +Try running flux with the module on the path. + +```bash +flux run -N 1 -n 2 ./compute.lua 120 +flux run -N 1 -n 2 ./io-forwarding.lua 120 +``` +Notice that the module is loaded (at the bottom): -7. `flux submit -N 1 -n 1 ./io-forwarding.lua 120` +```console +Try `flux-module load --help' for more information. +Module Idle S Sendq Recvq Service +heartbeat 1 R 0 0 +resource 0 R 0 0 +job-ingest 0 R 0 0 +kvs-watch 0 R 0 0 +sched-fluxion-resource 0 R 0 0 +cron idle R 0 0 +barrier idle R 0 0 +job-exec 0 R 0 0 +job-list idle R 0 0 +kvs 0 R 0 0 +content-sqlite 0 R 0 0 content-backing +job-info 0 R 0 0 +job-manager 0 R 0 0 +sched-fluxion-qmanager 0 R 0 0 sched +content 0 R 0 0 +connector-local 0 R 0 0 1002-shell-f3Lv2Zd3tj,1002-shell-f3N2WmZB5H +ioapp 83 R 0 0 +Block until we hear go message from the an io forwarder +``` + +If you run them together, they work together: + +```bash +flux submit -N 1 -n 2 ./compute.lua 120 +flux run -N 1 -n 2 ./io-forwarding.lua 120 +``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md index a68aedb..3a9a927 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/README.md @@ -1,29 +1,47 @@ -## A Data Conduit Strategy +# A Data Conduit Strategy -### Description: Use a data stream to send packets through +**Note that this module script does not compile and needs an update** -#### Setup + +## Description: Use a data stream to send packets through + +### Setup If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/data-conduit ``` -#### Execution +### Execution -1. Allocate three nodes from a resource manager: +If you are using Slurm, allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` -2. Point to `flux-core`'s `pkgconfig` directory: +Point to `flux-core`'s `pkgconfig` directory: | Shell | Command | | ----- | ---------- | | tcsh | `setenv PKG_CONFIG_PATH /lib/pkgconfig` | | bash/zsh | `export PKG_CONFIG_PATH='/lib/pkgconfig'` | +This might look like this in the container: + +```bash +export PKG_CONFIG_PATH=/usr/lib/pkgconfig +``` + +Then build the module (if you don't have permission, copy to /tmp) + +```bash +cp -R ./data-conduit /tmp/data-conduit +cd /tmp/data-conduit +make +``` + 3. `make` 4. Add the directory of the modules to `FLUX_MODULE_PATH`, if the module was built in the current directory: diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c index 790f84f..9e6f446 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/data-conduit/conduit.c @@ -45,7 +45,7 @@ static struct conduit_ctx *getctx (flux_t *h) return ctx; } -/* Foward the received JSON string to the datastore.py */ +/* Forward the received JSON string to the datastore.py */ static int conduit_send (flux_t *h, const char *json_str) { int rc = -1; diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md index 7f9c7ca..ff33747 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/README.md @@ -1,25 +1,30 @@ -## Hierarchical Launching +# Hierarchical Launching -### Description: Launch an ensemble of sleep 0 tasks +## Description: Launch an ensemble of sleep 0 tasks -#### Setup +### Setup If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/hierarchical-launching ``` -#### Execution +### Execution -1. `salloc -N3 -ppdebug` +If you need to start flux on a Slurm cluster: -2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +salloc -N3 -ppdebug +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` -3. `./parent.sh` +Start the parent instance +```bash +./parent.sh ``` +```console Mon Nov 18 15:31:08 PST 2019 13363018989568 13365166473216 @@ -28,7 +33,6 @@ First Level Done Mon Nov 18 15:34:13 PST 2019 ``` - ### Notes - You can increase the number of jobs by increasing `NCORES` in `parent.sh` and diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh index 0edca81..efd987b 100755 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/ensemble.sh @@ -4,7 +4,7 @@ NJOBS=750 MAXTIME=$(expr ${NJOBS} + 2) for i in `seq 1 ${NJOBS}`; do - flux mini submit --nodes=1 --ntasks=1 --cores-per-task=1 sleep 0 + flux submit --nodes=1 --ntasks=1 --cores-per-task=1 sleep 0 done flux jobs diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh index 84ef464..19d74e3 100755 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/hierarchical-launching/parent.sh @@ -5,7 +5,7 @@ NCORES=3 date for i in `seq 1 ${NCORES}`; do - flux mini submit -N 1 -n 1 flux start ./ensemble.sh + flux submit -N 1 -n 1 flux start ./ensemble.sh done flux queue drain diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md index af1d3b8..2c9c0cf 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-cancel/README.md @@ -1,25 +1,24 @@ -## Job Cancellation +# Job Cancellation -### Description: Cancel a running job +## Description: Cancel a running job -#### Setup +### Setup If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/job-cancel ``` -#### Execution - -1. Launch the submitter script: +### Execution -`./submitter.py $(flux resource list -no {ncores} --state=up)` - -_note: for older versions of Flux, you might need to instead run: `./submitter.py $(flux hwloc info | awk '{print $3}')`_ +Launch the submitter script: +```bash +python3 ./submitter.py $(flux resource list -no {ncores} --state=up) ``` + +```console Submitted 1st job: 2241905819648 Submitted 2nd job: 2258951471104 diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md index 3f303ea..99361f5 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/README.md @@ -1,8 +1,8 @@ -### Job Ensemble Submitted with a New Flux Instance +# Job Ensemble Submitted with a New Flux Instance -#### Description: Launch a flux instance and submit one instance of an io-forwarding job and 50 compute jobs, each spanning the entire set of nodes. +## Description: Launch a flux instance and submit one instance of an io-forwarding job and 50 compute jobs, each spanning the entire set of nodes. -#### Setup +### Setup If you haven't already, download the files and change your working directory: @@ -11,47 +11,37 @@ $ git clone https://github.com/flux-framework/flux-workflow-examples.git $ cd flux-workflow-examples/job-ensemble ``` -#### Execution +### Execution -1. `salloc -N3 -ppdebug` +If you need a Slurm allocation: -2. `cat ensemble.sh` +```bash +salloc -N3 -ppdebug +# Take a look at the script first +cat ensemble.sh ``` -#!/usr/bin/env sh +Here is how to run under Slurm: -NJOBS=10 -MAXTIME=$(expr ${NJOBS} + 2) -JOBIDS="" - -JOBIDS=$(flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua ${MAXTIME}) -for i in `seq 1 ${NJOBS}`; do - JOBIDS="${JOBIDS} $(flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 1)" -done +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out ./ensemble.sh +``` -flux jobs -flux queue drain +Or without: -# print mock-up prevenance data -for i in ${JOBIDS}; do - echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - echo "Jobid: ${i}" - KVSJOBID=$(flux job id --from=dec --to=kvs ${i}) - flux kvs get ${KVSJOBID}.R | jq -done +```bash +flux start -o,-S,log-filename=out ./ensemble.sh ``` -3. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out ./ensemble.sh` - ``` -JOBID USER NAME STATE NTASKS NNODES RUNTIME RANKS -1721426247680 fluxuser compute.lu RUN 4 2 0.122s [1-2] -1718322462720 fluxuser compute.lu RUN 4 2 0.293s [0,2] -1715201900544 fluxuser compute.lu RUN 4 2 0.481s [0-1] -1712299442176 fluxuser compute.lu RUN 4 2 0.626s [1-2] -1709296320512 fluxuser compute.lu RUN 4 2 0.885s [0,2] -1706293198848 fluxuser compute.lu RUN 4 2 1.064s [0-1] -1691378253824 fluxuser io-forward RUN 1 1 1.951s 0 +JOBID USER NAME STATE NTASKS NNODES RUNTIME +1721426247680 fluxuser compute.lu RUN 4 2 0.122s +1718322462720 fluxuser compute.lu RUN 4 2 0.293s +1715201900544 fluxuser compute.lu RUN 4 2 0.481s +1712299442176 fluxuser compute.lu RUN 4 2 0.626s +1709296320512 fluxuser compute.lu RUN 4 2 0.885s +1706293198848 fluxuser compute.lu RUN 4 2 1.064s +1691378253824 fluxuser io-forward RUN 1 1 1.951s ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Jobid: 1691378253824 { diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh index 8c76593..9468ec6 100755 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-ensemble/ensemble.sh @@ -4,9 +4,9 @@ NJOBS=10 MAXTIME=$(expr ${NJOBS} + 2) JOBIDS="" -JOBIDS=$(flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua ${MAXTIME}) +JOBIDS=$(flux submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua ${MAXTIME}) for i in `seq 1 ${NJOBS}`; do - JOBIDS="${JOBIDS} $(flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 1)" + JOBIDS="${JOBIDS} $(flux submit --nodes=1 --ntasks=1 --cores-per-task=2 ./compute.lua 1)" done flux jobs @@ -16,6 +16,6 @@ flux queue drain for i in ${JOBIDS}; do echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" echo "Jobid: ${i}" - KVSJOBID=$(flux job id --from=dec --to=kvs ${i}) + KVSJOBID=$(flux job id --to=kvs ${i}) flux kvs get ${KVSJOBID}.R | jq done diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md index bbd3704..02ad14c 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-status-control/README.md @@ -1,31 +1,35 @@ -## Using Flux Job Status and Control API +# Using Flux Job Status and Control API -### Description: Submit job bundles, get event updates, and wait until all jobs complete +## Description: Submit job bundles, get event updates, and wait until all jobs complete -#### Setup +### Setup If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/job-status-control ``` -#### Execution +### Execution 1. Allocate three nodes from a resource manager: -`salloc -N3 -p pdebug` +```bash +salloc -N3 -p pdebug +``` -2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: +2. If needed, launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Run the bookkeeper executable along with the number of jobs to be submitted (if no size is specified, 6 jobs are submitted: 3 instances of **compute.py**, and 3 instances of **io-forwarding,py**): -`./bookkeeper.py 2` - +```bash +python3 ./bookkeeper.py 2 ``` +```console bookkeeper: all jobs submitted bookkeeper: waiting until all jobs complete job 39040581632 triggered event 'submit' @@ -49,8 +53,6 @@ job 39040581632 triggered event 'clean' bookkeeper: all jobs completed ``` ---- - ### Notes - The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md index 12cf931..cfcd17e 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-api/README.md @@ -1,99 +1,108 @@ -## Job Submit API +# Job Submit API To run the following examples, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/job-submit-api ``` -### Part(a) - Using a direct job.submit RPC +## Part(a) - Using a direct job.submit RPC -#### Description: Schedule and launch compute and io-forwarding jobs on separate nodes +### Description: Schedule and launch compute and io-forwarding jobs on separate nodes 1. Allocate three nodes from a resource manager: -`salloc -N3 -p pdebug` +```bash +salloc -N3 -p pdebug +``` 2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Run the submitter executable: -`./submitter.py` +```bash +python3 ./submitter.py +``` 4. List currently running jobs: -`flux jobs` - +```bash +flux jobs ``` -JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS -ƒ5W8gVwm moussa1 io-forward R 1 1 19.15s 2 -ƒ5Vd2kJs moussa1 compute.py R 4 2 19.18s [0-1] +```console +JOBID USER NAME ST NTASKS NNODES RUNTIME +ƒ5W8gVwm fluxuser io-forward R 1 1 19.15s +ƒ5Vd2kJs fluxuser compute.py R 4 2 19.18s ``` 5. Information about jobs, such as the submitted job specification, an eventlog, and the resource description format **R** are stored in the KVS. The data can be queried via the `job-info` module via the `flux job info` command. For example, to fetch **R** for a job which has been allocated resources: -`flux job info ƒ5W8gVwm R` - +```bash +flux job info ƒ5W8gVwm R ``` +```console {"version":1,"execution":{"R_lite":[{"rank":"2","children":{"core":"0"}}]}} ``` - -`flux job info ƒ5Vd2kJs R` - +```bash +flux job info ƒ5Vd2kJs R ``` +```console {"version":1,"execution":{"R_lite":[{"rank":"0-1","children":{"core":"0-3"}}]}} ``` -### Part(b) - Using a direct job.submit RPC +## Part(b) - Using a direct job.submit RPC -#### Description: Schedule and launch both compute and io-forwarding jobs across all nodes +### Schedule and launch both compute and io-forwarding jobs across all nodes 1. Allocate three nodes from a resource manager: -`salloc -N3 -p pdebug` +```bash +salloc -N3 -p pdebug +``` 2. Launch another Flux instance on the current allocation: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Run the second submitter executable: -`./submitter2.py` +```bash +python3 ./submitter2.py +``` 4. List currently running jobs: -`flux jobs` - +```bash +flux jobs ``` -JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS -ƒctYadhh moussa1 io-forward R 3 3 3.058s [0-2] -ƒct1StnT moussa1 compute.py R 6 3 3.086s [0-2] +```console +JOBID USER NAME ST NTASKS NNODES RUNTIME +ƒctYadhh fluxuser io-forward R 3 3 3.058s +ƒct1StnT fluxuser compute.py R 6 3 3.086s ``` 5. Fetch **R** for the jobs that have been allocated resources: -`flux job info ƒctYadhh R` - +```bash +flux job info $(flux job last) jobspec ``` +```console {"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} ``` - -`flux job info ƒct1StnT R` - -``` -{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} +```console +{"resources": [{"type": "node", "count": 3, "with": [{"type": "slot", "count": 1, "with": [{"type": "core", "count": 1}], "label": "task"}]}], "tasks": [{"command": ["./io-forwarding.py", "120"], "slot": "task", "count": {"per_slot": 1}}], "attributes": {"system": {"duration": 0, "cwd": "/home/fluxuser/flux-workflow-examples/job-submit-api"}}, "version": 1} ``` ---- - ### Notes - `f = flux.Flux()` creates a new Flux handle which can be used to connect to and interact with a Flux instance. - - The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: ```python compute_jobreq = JobspecV1.from_command( diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md index b50e71a..0a34979 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-cli/README.md @@ -1,81 +1,61 @@ -## Job Submit CLI +# Job Submit CLI To run the following examples, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```console $ cd flux-workflow-examples/job-submit-cli ``` -### Part(a) - Partitioning Schedule - -#### Description: Launch a flux instance and schedule/launch compute and io-forwarding jobs on separate nodes - -1. `salloc -N3 -ppdebug` - -2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` - -3. `flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 120` +## Example -4. `flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120` +### Launch a flux instance and submit compute and io-forwarding jobs -5. List running jobs: +If you need an allocation: -`flux jobs` - -``` -JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS -ƒ3ETxsR9H moussa1 io-forward R 1 1 2.858s 2 -ƒ38rBqEWT moussa1 compute.lu R 4 2 15.6s [0-1] +```bash +salloc -N3 -ppdebug +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out ``` -6. Get information about job: +To submit -`flux job info ƒ3ETxsR9H R` +```bash +# if you have more than one node... +flux submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 120 -``` -{"version":1,"execution":{"R_lite":[{"rank":"2","children":{"core":"0-1"}}]}} +# and if not! +flux submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120 ``` -`flux job info ƒ38rBqEWT R` +Attach to watch output: +```bash +# Control +C then Control+Z to detach +flux job attach $(flux job last) ``` -{"version":1,"execution":{"R_lite":[{"rank":"0-1","children":{"core":"0-3"}}]}} -``` - -### Part(b) - Overlapping Schedule - -#### Description: Launch a flux instance and schedule/launch both compute and io-forwarding jobs across all nodes -1. `salloc -N3 -ppdebug` - -2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` - -3. `flux mini submit --nodes=3 --ntasks=6 --cores-per-task=2 ./compute.lua 120` - -4. `flux mini submit --nodes=3 --ntasks=3 --cores-per-task=1 ./io-forwarding.lua 120` - -5. List jobs in KVS: - -`flux jobs` +List running jobs: +```bash +flux jobs ``` -JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS -ƒ3ghmgCpw moussa1 io-forward R 3 3 16.91s [0-2] -ƒ3dSybfQ3 moussa1 compute.lu R 6 3 24.3s [0-2] - +```console +JOBID USER NAME ST NTASKS NNODES RUNTIME +ƒ3ETxsR9H fluxuser io-forward R 1 1 2.858s +ƒ38rBqEWT fluxuser compute.lu R 4 2 15.6s ``` -6. Get information about job: - -`flux job info ƒ3ghmgCpw R` - -``` -{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"4"}}]}} -``` +Get information about job: -`flux job info ƒ3dSybfQ3 R` +```bash +flux job info $(flux job last) R +flux job info $(flux job last) jobspec +flux job info $(flux job last) eventlog +flux job info $(flux job last) guest.output +# Example with flux job id +flux job info ƒ3ETxsR9H R ``` -{"version":1,"execution":{"R_lite":[{"rank":"0-2","children":{"core":"0-3"}}]}} +```console +{"version": 1, "execution": {"R_lite": [{"rank": "0", "children": {"core": "5-7"}}], "nodelist": ["674f16a501e5"], "starttime": 1723225494, "expiration": 4876808372}} ``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md index 1f8745e..c4fbd5d 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/job-submit-wait/README.md @@ -1,29 +1,33 @@ -## Python Job Submit/Wait +# Python Job Submit/Wait To run the following examples, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```bash $ cd flux-workflow-examples/job-submit-wait ``` -### Part(a) - Python Job Submit/Wait +## Part(a) - Python Job Submit/Wait -#### Description: Submit jobs asynchronously and wait for them to complete in any order +### Description: Submit jobs asynchronously and wait for them to complete in any order -1. Allocate three nodes from a resource manager: +1. If needed, allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` 2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Submit the **submitter_wait_any.py** script, along with the number of jobs you want to run (if no argument is passed, 10 jobs are submitted): -`./submitter_wait_any.py 10` - +```bash +python3 ./submitter_wait_any.py 10 ``` +```console submit: 46912591450240 compute_jobspec submit: 46912591450912 compute_jobspec submit: 46912591451080 compute_jobspec @@ -46,25 +50,28 @@ wait: 46912591451080 Success wait: 46912591362984 Success ``` ---- - -### Part(b) - Python Job Submit/Wait (Sliding Window) +## Part(b) - Python Job Submit/Wait (Sliding Window) -#### Description: Asynchronously submit jobs and keep at most a number of those jobs active +### Description: Asynchronously submit jobs and keep at most a number of those jobs active 1. Allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` 2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Submit the **submitter_sliding_window.py** script, along with the number of jobs you want to run and the size of the window (if no argument is passed, 10 jobs are submitted and the window size is 2 jobs): -`./submitter_sliding_window.py 10 3` - +```bash +python3 ./submitter_sliding_window.py 10 3 ``` +```console submit: 5624175788032 submit: 5624611995648 submit: 5625014648832 @@ -87,25 +94,29 @@ wait: 5986882420736 Success wait: 6164435697664 Success ``` ---- -### Part(c) - Python Job Submit/Wait (Specific Job ID) +## Part(c) - Python Job Submit/Wait (Specific Job ID) -#### Description: Asynchronously submit jobs, block/wait for specific jobs to complete +### Description: Asynchronously submit jobs, block/wait for specific jobs to complete 1. Allocate three nodes from a resource manager: -`salloc -N3 -ppdebug` +```bash +salloc -N3 -ppdebug +``` 2. Launch a Flux instance on the current allocation by running `flux start` once per node, redirecting log messages to the file `out` in the current directory: -`srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +```bash +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +``` 3. Submit the **submitter_wait_in_order.py** script, along with the number of jobs you want to run (if no argument is passed, 10 jobs are submitted): -`./submitter_wait_in_order.py 10` - +```bash +python3 ./submitter_wait_in_order.py 10 ``` +```console submit: 46912593818008 compute_jobspec submit: 46912593818176 compute_jobspec submit: 46912593818344 compute_jobspec @@ -128,8 +139,6 @@ wait: 46912593819128 Error: job returned exit code 1 wait: 46912593819296 Error: job returned exit code 1 ``` ---- - ### Notes - The following constructs a job request using the **JobspecV1** class with customizable parameters for how you want to utilize the resources allocated for your job: diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md index 0a67026..5c3aa22 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/kvs-python-bindings/README.md @@ -1,56 +1,62 @@ -## KVS Python Binding Example +# KVS Python Binding Example -### Description: Use the KVS Python interface to store user data into KVS +## Description: Use the KVS Python interface to store user data into KVS If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```console $ cd flux-workflow-examples/kvs-python-bindings ``` 1. Launch a Flux instance by running `flux start`, redirecting log messages to the file `out` in the current directory: -`flux start -s 1 -o,-S,log-filename=out` +```bash +flux start -s 1 -o,-S,log-filename=out +``` 2. Submit the Python script: -`flux mini submit -N 1 -n 1 ./kvsput-usrdata.py` - +```bash +flux submit -N 1 -n 1 ./kvsput-usrdata.py ``` +```console 6705031151616 ``` 3. Attach to the job and view output: -`flux job attach 6705031151616` - +```bash +flux job attach $(flux job last) ``` +```console hello world hello world again ``` 4. Each job is run within a KVS namespace. `FLUX_KVS_NAMESPACE` is set, which is automatically read and used by the KVS operations in the handle. To take a look at the job's KVS, convert its job ID to KVS: -`flux job id --from=dec --to=kvs 6705031151616` - +```bash +flux job id --to=kvs $(flux job last) ``` +```console job.0000.0619.2300.0000 ``` 5. The keys for this job will be put at the root of the namespace, which is mounted under "guest". To get the value stored under the first key "usrdata": -`flux kvs get job.0000.0619.2300.0000.guest.usrdata` - +```bash +flux kvs get job.0000.0619.2300.0000.guest.usrdata ``` +```bash "hello world" ``` 6. Get the value stored under the second key "usrdata2": -`flux kvs get job.0000.0619.2300.0000.guest.usrdata2` - +```bash +flux kvs get job.0000.0619.2300.0000.guest.usrdata2 ``` +```console "hello world again" ``` diff --git a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md index 3fa4b53..641be09 100644 --- a/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md +++ b/2024-RADIUSS-AWS/JupyterNotebook/tutorial/flux-workflow-examples/synchronize-events/README.md @@ -1,41 +1,44 @@ -### Using Events with Separate Nodes +# Using Events with Separate Nodes -#### Description: Using events to synchronize compute and io-forwarding jobs running on separate nodes +## Description: Using events to synchronize compute and io-forwarding jobs running on separate nodes If you haven't already, download the files and change your working directory: -``` -$ git clone https://github.com/flux-framework/flux-workflow-examples.git +```console $ cd flux-workflow-examples/synchronize-events ``` -1. `salloc -N3 -ppdebug` - -2. `srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out` +Ask for a Slurm allocation, if relevant: -3. `flux mini submit --nodes=2 --ntasks=4 --cores-per-task=2 ./compute.lua 120` - -**Output -** `225284456448` +```bash +salloc -N3 -ppdebug +srun --pty --mpi=none -N3 flux start -o,-S,log-filename=out +flux submit --nodes=1 --ntasks=4 --cores-per-task=2 ./compute.lua 120 +``` -4. `flux mini submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120` +And: -**Output -** `344889229312` +```bash +flux submit --nodes=1 --ntasks=1 --cores-per-task=2 ./io-forwarding.lua 120 +``` 5. List running jobs: -`flux jobs` - +```bash +flux jobs +``` ``` JOBID USER NAME ST NTASKS NNODES RUNTIME RANKS -ƒA4TgT7d moussa1 io-forward R 1 1 4.376s 2 -ƒ6vEcj7M moussa1 compute.lu R 4 2 11.51s [0-1] +ƒA4TgT7d fluxuser io-forward R 1 1 4.376s 2 +ƒ6vEcj7M fluxuser compute.lu R 4 2 11.51s [0-1] ``` 6. Attach to running or completed job output: -`flux job attach ƒ6vEcj7M` - +```bash +flux job attach ƒ6vEcj7M ``` +```console Block until we hear go message from the an io forwarder Block until we hear go message from the an io forwarder Recv an event: please proceed