diff --git a/episodes/01-introduction.md b/episodes/01-introduction.md new file mode 100644 index 0000000..bfefa6a --- /dev/null +++ b/episodes/01-introduction.md @@ -0,0 +1,241 @@ +--- +title: "Running commands with Maestro" +teaching: 30 +exercises: 30 +--- + +::: questions +- "How do I run a simple command with Maestro?" +::: + +:::objectives +- "Create a Maestro YAML file" +::: + + +## What is the workflow I'm interested in? + +In this lesson we will make an experiment that takes an application which runs +in parallel and investigate it's scalability. To do that we will need to gather +data, in this case that means running the application multiple times with +different numbers of CPU cores and recording the execution time. Once we've +done that we need to create a visualisation of the data to see how it compares +against the ideal case. + +From the visualisation we can then decide at what scale it +makes most sense to run the application at in production to maximise the use of +our CPU allocation on the system. + +We could do all of this manually, but there are useful tools to help us manage +data analysis pipelines like we have in our experiment. Today we'll learn about +one of those: Maestro. + +In order to get started with Maestro, let's begin by taking a simple command +and see how we can run that via Maestro. Let's choose the command `hostname` +which prints out the name of the host where the command is executed: + +```bash +janeh@pascal83:~$ hostname +``` +```output +pascal83 +``` + +That prints out the result but Maestro relies on files to know the status of +your workflow, so let's redirect the output to a file: + +```bash +janeh@pascal83:~$ hostname > hostname_login.txt +``` + +## Writing a Maestro YAML + +Edit a new text file named `hostname.yaml`. + +Contents of `hostname.yaml`: + +```yml +description: + name: Hostnames + description: Report a node's hostname. + +study: + - name: hostname-login + description: Write the login node's hostname to a file + run: + cmd: | + hostname > hostname_login.txt +``` + +::: callout + +## Key points about this file + +1. The name of `hostname.yaml` is not very important; it gives us information + about file contents and type, but maestro will behave the same if you rename + it to `hostname` or `foo.txt`. +1. The file specifies fields in a hierarchy. For example, `name`, `description`, + and `run` are all passed to `study` and are at the same level in the hierarchy. + `description` and `study` are both at the top level in the hierarchy. +1. Indentation indicates the hierarchy and should be consistent. For example, all + the fields passed directly to `study` are indented relative to `study` and + their indentation is all the same. +1. The commands executed during the study are given under `cmd`. Starting this + entry with `|` and a newline character allows us to specify multiple commands. +1. The example YAML file above is pretty minimal; all fields shown are required. +1. The names given to `study` can include letters, numbers, and special characters. + + +::: + +Back in the shell we'll run our new rule. At this point, we may see an error if +a required field is missing or if our indentation is inconsistent. + +```bash +$ maestro run hostname.yaml +``` + +::: callout + +## `bash: maestro: command not found...` + +If your shell tells you that it cannot find the command `maestro` then we need +to make the software available somehow. In our case, this means activating the +python virtual environment where maestro is installed. +```bash +source /usr/global/docs/training/janeh/maestro_venv/bin/activate +``` + +You can tell this command has already been run when `(maestro_venv)` appears +before your command prompt: + + +```bash +janeh@pascal83:~$ source /usr/global/docs/training/janeh/maestro_venv/bin/activate +(maestro_venv) janeh@pascal83:~$ +``` + +Now that the `maestro_venv` virtual environment has been activated, the `maestro` +command should be available, but let's double check + +```bash +(maestro_venv) janeh@pascal83:~$ which maestro +``` +```output +/usr/global/docs/training/janeh/maestro_venv/bin/maestro +``` +::: + + +## Running maestro + +Once you have `maestro` available to you, run `maestro run hostname.yaml` +and enter `y` when prompted + +```bash +(maestro_venv) janeh@pascal83:~$ maestro run hostname.yaml +[2024-03-20 15:39:34: INFO] INFO Logging Level -- Enabled +[2024-03-20 15:39:34: WARNING] WARNING Logging Level -- Enabled +[2024-03-20 15:39:34: CRITICAL] CRITICAL Logging Level -- Enabled +[2024-03-20 15:39:34: INFO] Loading specification -- path = hostname.yaml +[2024-03-20 15:39:34: INFO] Directory does not exist. Creating directories to /g/g0/janeh/Hostnames_20240320-153934/logs +[2024-03-20 15:39:34: INFO] Adding step 'hostname-login' to study 'Hostnames'... +[2024-03-20 15:39:34: INFO] +------------------------------------------ +Submission attempts = 1 +Submission restart limit = 1 +Submission throttle limit = 0 +Use temporary directory = False +Hash workspaces = False +Dry run enabled = False +Output path = /g/g0/janeh/Hostnames_20240320-153934 +------------------------------------------ +Would you like to launch the study? [yn] y +Study launched successfully. +``` + +and look at the outputs. You should have a new directory whose name includes a +date and timestamp and that starts with the `name` given under `description` +at the top of `hostname.yaml`. + +In that directory will be a subdirectory for every `study` run from +`hostname.yaml`. The subdirectories for each study include all output files +for that study + +```bash +(maestro_venv) janeh@pascal83:~$ cd Hostnames_20240320-153934/ +(maestro_venv) janeh@pascal83:~/Hostnames_20240320-153934$ ls +``` +```output +batch.info Hostnames.pkl Hostnames.txt logs status.csv +hostname-login Hostnames.study.pkl hostname.yaml meta +``` +```bash +(maestro_venv) janeh@pascal83:~/Hostnames_20240320-153934$ cd hostname-login/ +(maestro_venv) janeh@pascal83:~/Hostnames_20240320-153934/hostname-login$ ls +```output +hostname-login.2284862.err hostname-login.2284862.out hostname-login.sh hostname_login.txt +``` + +::: challenge + +To which file will the login node's hostname, `pascal83`, be written? + +1. hostname-login.2284862.err +2. hostname-login.2284862.out +3. hostname-login.sh +4. hostname_login.txt + +:::::: solution +(4) hostname_login.txt + +In the original `hostname.yaml` file that we ran, we specified that +hostname would be written to `hostname_login.txt`, and this is where +we'll see that output, if the run worked! +:::::: +::: + +::: challenge + +This one is tricky! In the example above, `pascal83` was written to +`.../Hostnames_{date}_{time}/hostname-login/hostname_login.txt`. + +Where would `Hello` be written for the following YAML? + +```yml +description: + name: MyHello + description: Report a node's hostname. + +study: + - name: give-salutation + description: Write the login node's hostname to a file + run: + cmd: | + echo "hello" > greeting.txt +``` + + +1. `.../give-salutation_{date}_{time}/greeting/greeting.txt` +2. `.../greeting_{date}_{time}/give_salutation/greeting.txt` +3. `.../MyHello_{date}_{time}/give-salutation/greeting.txt` +4. `.../MyHello_{date}_{time}/greeting/greeting.txt` + +:::::: solution + +(3) `.../MyHello_{date}_{time}/give-salutation/greeting.txt` + +The toplevel folder created starts with the `name` field under `description`; here, that's `MyHello`. +Its subdirectory is named after the `study`; here, that's `give-salutation`. +The file created is `greeting.txt` and this stores the output of `echo "hello"`. + +:::::: +::: + +::: keypoints + +- "You execute `maestro run` with a YAML file including information about your run." +- "Your run includes a description and at least one study (a step in your run)." +- "Your maestro run creates a directory with subdirectories and outputs for each study." + +::: diff --git a/episodes/02-maestro_on_the_cluster.md b/episodes/02-maestro_on_the_cluster.md new file mode 100644 index 0000000..a14be16 --- /dev/null +++ b/episodes/02-maestro_on_the_cluster.md @@ -0,0 +1,190 @@ +--- +title: "Running Maestro on the cluster" +teaching: 30 +exercises: 20 +--- + +::: objectives + +- "Define rules to run locally and on the cluster" + +::: + +# How do I run Maestro on the cluster? + +What happens when we want to run on the cluster ("to run a batch job")rather +than the login node? The cluster we are using uses Slurm, and Maestro has +built in support for Slurm. We just need to tell Maestro which resources we +need Slurm to grab for our run. + +First, we need to add a `batch` block to our YAML file, where we'll provide the +names of the machine, bank, and queue in which your jobs should run. + +```yml +batch: + type: slurm + host: pascal # enter the machine you'll run on + bank: lc # enter the bank to charge + queue: pvis # enter the partition in which your job should run +``` + +Second, we need to specify the number of nodes, number of processes, and walltime +for *each study* to be run from our YAML file. This information goes under each +study's `run` field: + +```yml +(...) + run: + cmd: | + hostname >> hostname.txt + nodes: 1 + procs: 1 + walltime: "00:00:30" +``` + +Here we specify 1 node, 1 process, and a time limit of 30 seconds. **Note** that +the format of the walltime includes quotation marks -- "::". + +With these changes, our updated YAML file might look like + +```yml +description: + name: Hostnames + description: Report a node's hostname. + +batch: + type: slurm + host: pascal # machine to run on + bank: lc # bank + queue: pvis # partition + +study: + - name: hostname-login + description: Write the login node's hostname to a file + run: + cmd: | + hostname > hostname_login.txt + - name: hostname_batch + description: Write the node's hostname to a file + run: + cmd: | + hostname >> hostname.txt + nodes: 1 + procs: 1 + walltime: "00:00:30" +``` + +Note that we left the rule `hostname-login` as is. Because we do not specify any info for slurm under this original step's `run` field -- like nodes, processes, or walltime -- this step will continue running on the login node and only `hostname_batch` will be handed off to slurm. + +::: challenge +## Running on the cluster + +Modify your YAML file, `hostname.yaml` to execute `hostname` on the _cluster_. +Run with 1 node and 1 process using the bank `guest` on the partition +`psummer` on `quartz`. + +If you run this multiple times, do you always run on the same node? +(Is the hostname printed always the same?) + +:::::: solution + +The contents of `hostname.yaml` should look something like: + +```yml +description: + name: Hostnames + description: Report a node's hostname. + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: psummer # partition + +study: + - name: hostname-login + description: Write the login node's hostname to a file + run: + cmd: | + hostname > hostname_login.txt + - name: hostname_batch + description: Write the node's hostname to a file + run: + cmd: | + hostname >> hostname.txt + nodes: 1 + procs: 1 + walltime: "00:00:30" + +``` + +When you run this job, a directory called `Hostname_...` will be created. If you look in the subdirectory `hostname_batch`, you'll find a file called `hostname.txt` with info about the compute node where the `hostname` command ran. If you run the job multiple times, you will probably land on different nodes; this means you'll see different node numbers in different hostname.txt files. If you see the same number more than once, don't worry! If you get an answer other than `pascal83`, you're doing it correctly. :) + +:::::: + +::: + +## Outputs from a batch job + +When running in batch, `maestro run...` will create a new directory with the +same naming scheme as seen in episode 1, and that directory will contain +subdirectories for all studies. The `hostname_batch` subdirectory has four +output files, but this time the file ending with extension `.sh` is a slurm +submission script + +```bash +(maestro_venv) janeh@pascal83:~/Hostnames_20240320-170150/hostname_batch$ ls +hostname.err hostname.out hostname.slurm.sh hostname.txt +(maestro_venv) janeh@pascal83:~/Hostnames_20240320-170150/hostname_batch$ cat hostname.slurm.sh +#!/bin/bash +#SBATCH --nodes=1 +#SBATCH --partition=pvis +#SBATCH --account=lc +#SBATCH --time=00:00:30 +#SBATCH --job-name="hostname" +#SBATCH --output="hostname.out" +#SBATCH --error="hostname.err" +#SBATCH --comment "Write the node's hostname to a file" + +hostname > hostname.txt +``` + +Maestro uses the info from your YAML file to write this script and then +submits it to the scheduler for you. Soon after you run on the cluster via +`maestro run hostname.yaml`, you should be able to see the job +running or finishing up in the queue with the command `squeue -u > amdahl.out + nodes: 1 + procs: 1 + walltime: "00:00:30" +``` + +Exact wording for names and descriptions is not important, but should +help you to remember what this file and its study are doing. + +:::::: +::: + +::: challenge + +After checking that `amdahl.yaml` looks similar to the solution above, +run `maestro run amdahl.yaml`. Then, update the number of `nodes` and +`procs` each to `2`. You should also increase the walltime a bit, to +a minute or minute and a half. Then rerun `maestro run amdahl.yaml`. +How does the output change? How did you expect it to change? + +*Hint* Remember that if you run `squeue -u `, you can +see the node(s) assigned to your slurm job. + +::::::solution + +In your output files (`amdahl.out` if using the script in the last +solution), you probably see output looking something like + +``` +Doing 30.000000 seconds of 'work' on 1 processor, + which should take 30.000000 seconds with 0.800000 parallel proportion of the workload. + + Hello, World! I am process 0 of 1 on pascal17. I will do all the serial 'work' for 5.324555 seconds. + Hello, World! I am process 0 of 1 on pascal17. I will do parallel 'work' for 22.349517 seconds. + +Total execution time (according to rank 0): 27.755552 seconds +``` + +Notice that this output refers to only "1 processor" and mentions +only `pascal17`, even in the job that requested and received two +nodes. You will likely see a different number, but most you will +still see only a single processor mentioned in the output. If you +ran `squeue -u ` while the job was in queue, you should +have seen two unique node numbers assigned to your job. + +So what's going on? If your job were really *using* both nodes +that were assigned to it, then both processes would have written +to `amdahl.out`. + +:::::: +::: + +## Maestro and MPI + +We didn't really run an MPI application in the last section as we only ran on +one processor. How do we request to run on multiple processors for a single +step? + +The answer is that we have to tell Slurm that we want to use MPI. In the Intro +to HPC lesson, the episodes introducing Slurm and running parallel jobs showed +that commands to run in parallel need to use `srun`. `srun` talks to MPI and +allows multiple processors to coordinate work. A call to `srun` might look +something like + +```bash +srun -N {# of nodes} -n {number of processes} amdahl >> amdahl.out +``` + +To make this easier, Maestro offers the shorthand `$(LAUNCHER)`. Maestro +will replace instances of `$(LAUNCHER)` with a call to `srun`, specifying +as many nodes and processes we've already told Slurm we want to use. + +::: challenge + +Update `amdahl.yaml` to include `$(LAUNCHER)` before the call to `amdahl` +in your study's `run` field. Re-run maestro with the updated YAML and +explore the outputs. How many nodes are mentioned in `amdahl.out`? +In the Slurm submission script created by Maestro (included in the same +subdirectory as `amdahl.out`), what text was used to replace `$(LAUNCHER)`? + +:::::: solution + +The updated YAML should look something like + +```yml +description: + name: Amdahl + description: Run a parallel program + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: pbatch # partition + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl >> amdahl.out + nodes: 2 + procs: 2 + walltime: "00:01:30" +``` + +Your output file `Amdahl_.../amdahl/amdahl.out` should include +"Doing 30.000000 seconds of 'work' on 2 processors" and the submission +script `Amdahl_.../amdahl/amdahl.slurm.sh` should include the line +"srun -n 2 -N 2 amdahl >> amdahl.out". Maestro substituted +`srun -n 2 -N 2` for `$(LAUNCHER)`! + +:::::: +::: + +::: callout +## Commenting Maestro YAML files + +In the solution from the last challenge, the line beginning `#` is a comment line. Hopefully +you are already in the habit of adding comments to your own scripts. Good comments make any +script more readable, and this is just as true with our YAML files. + +::: + + +## Customizing amdahl output + +Another thing about our application `amdahl` is that we ultimately want to +process the output to generate our scaling plot. The output right now is useful +for reading but makes processing harder. `amdahl` has an option that actually +makes this easier for us. To see the `amdahl` options we can use + +```bash +(maestro_venv) janeh@pascal83:~$ amdahl --help +``` +```output +usage: amdahl [-h] [-p [PARALLEL_PROPORTION]] [-w [WORK_SECONDS]] [-t] [-e] + +options: + -h, --help show this help message and exit + -p [PARALLEL_PROPORTION], --parallel-proportion [PARALLEL_PROPORTION] + Parallel proportion should be a float between 0 and 1 + -w [WORK_SECONDS], --work-seconds [WORK_SECONDS] + Total seconds of workload, should be an integer greater than 0 + -t, --terse Enable terse output + -e, --exact Disable random jitter +``` +The option we are looking for is `--terse`, and that will make `amdahl` print +output in a format that is much easier to process, JSON. JSON format in a file +typically uses the file extension `.json` so let's add that option to our +`shell` command _and_ change the file format of the `output` to match our new +command: + +```yml +description: + name: Amdahl + description: Run a parallel program + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: pbatch # partition + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse >> amdahl.json + nodes: 2 + procs: 2 + walltime: "00:01:30" +``` + +There was another parameter for `amdahl` that caught my eye. `amdahl` has an +option `--parallel-proportion` (or `-p`) which we might be interested in +changing as it changes the behaviour of the code,and therefore has an impact on +the values we get in our results. Let's try specifying a parallel proportion +of 90%: + +```yml +description: + name: Amdahl + description: Run a parallel program + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: pbatch # partition + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p .9 >> amdahl.json + nodes: 2 + procs: 2 + walltime: "00:01:30" +``` + +Our current directory is probably starting to fill up with directories +starting with `Amdahl_...`, distinguished only by dates and timestamps. +It's probably best to group runs into separate folders to keep things tidy. +One way we can do this is by specifying an `env` section in our YAML +file with a variable called `OUTPUT_PATH` specified in this format: + +```yml +env: + variables: + OUTPUT_PATH: ./Episode3 +``` + +This `env` block goes above our `study` block. In this case, directories +created by runs using this `OUTPUT_PATH` will all be grouped inside the +directory `Episode3`, to help us group runs by where we are in the lesson. + + +::: challenge + +Create a YAML file for a value of `-p` of 0.999 (the default value is 0.8) +for the case where we have a single node and 6 parallel processes. +Directories for subsequent runs should be grouped into a shared parent +directory (for example, `Episode3`, as above). + +:::::: solution + +```yml +description: + name: Amdahl + description: Run a parallel program + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: pbatch # partition + +env: + variables: + OUTPUT_PATH: ./Episode3 + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p .999 >> amdahl.json + nodes: 1 + procs: 6 + walltime: "00:01:30" +``` + +:::::: +::: + +## Dry-run (`--dry`) mode + +It's often useful to run Maestro in `--dry` mode, which causes Maestro to create scripts +and the directory structure without actually running jobs. You will see this parameter +if you run `maestro run --help`. + +::: challenge + +Do a dry-run using the script created in the last challenge. This should help you +verify that a new directory gets created for runs from this episode. + +:::::: solution + +After running + +```bash +maestro run --dry amdahl.yaml +``` +a directory path of the form `Episode3/Amdahl_{DATE}_{TIME}/amdahl` should +be created. + +:::::: +::: + + +::: keypoints + +- "Adding `$(LAUNCHER)` before commands signals to Maestro to use MPI via `srun`." +- "New Maestro runs can be grouped within a new directory specified by the environment +variable `OUTPUT_PATH`" +- You can use `--dry` to verify that the expected directory structure and scripts +are created by a given Maestro YAML file. + +::: diff --git a/episodes/04-placeholders.md b/episodes/04-placeholders.md new file mode 100644 index 0000000..dc05775 --- /dev/null +++ b/episodes/04-placeholders.md @@ -0,0 +1,235 @@ +--- +title: "Placeholders" +teaching: 40 +exercises: 30 +--- + +::: questions +- "How do I make a generic rule?" +::: + +::: objectives +- "Learn to use variables as placeholders" +- "Learn to run many similar Maestro runs at once" +::: + +::: callout +## D.R.Y. (Don't Repeat Yourself) + +In many programming languages, the bulk of the language features are +there to allow the programmer to describe long-winded computational +routines as short, expressive, beautiful code. Features in Python, +R, or Java, such as user-defined variables and functions are useful in +part because they mean we don't have to write out (or think about) +all of the details over and over again. This good habit of writing +things out only once is known as the "Don't Repeat Yourself" +principle or D.R.Y. +::: + +Maestro YAML files are a form of code and, in any code, repetition can +lead to problems (e.g. we rename a data file in one part of the YAML +but forget to rename it elsewhere). + +In this episode, we'll set ourselves up with ways to avoid repeating +ourselves by using *environment variables* as *placeholders*. + + +## Placeholders + +At the end of our last episode, our YAML file contained the sections + +```yml +(...) + +env: + variables: + OUTPUT_PATH: ./Episode3 + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p .999 >> amdahl.json + nodes: 1 + procs: 6 + walltime: "00:01:30" +``` + +Here we were already using a placeholder -- `$(LAUNCHER)` -- which held +the place and was later swapped out for a call to `srun` specifying +the nodes and tasks wanted. + +Let's create another environment variable in the `variables` second under +`env`. We can define a new parallel proportion as `P: .999`. Then, under +`run`'s `cmd`, we can call this environment variable with the syntax +`$(P)`. `$(P)` holds the place of and will be substituted by `.999` when +Maestro creates a Slurm submission script for us + +```yml +(...) + +env: + variables: + P: .999 + OUTPUT_PATH: ./Episode4 + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p $(P) >> amdahl.json + nodes: 1 + procs: 6 + walltime: "00:01:30" +``` + +(Note that the `OUTPUT_PATH` was also updated to reflect the current +episode.) + +It may also be helpful to create a variable for our output file, like this: + +```yml +(...) + +env: + variables: + P: .999 + OUTPUT: amdahl.json + OUTPUT_PATH: ./Episode4 + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p $(P) >> $(OUTPUT) + nodes: 1 + procs: 6 + walltime: "00:01:30" +``` + +## Maestro's global.parameters + +We're almost ready to perform our scaling study -- to see how the amount of work per processor +changes as we use more processors in the job. One way to do this would be to update the line + +```yml + procs: 6 +``` + +and to manually re-run `maestro run...` several times with different numbers of processes. + +An alternative is to avoid repeating ourselves by defining a **parameter** that lists multiple +values of tasks and runs a separate job for each value. We do this by adding a +`global.parameters` section at the bottom of the script. We then list individual parameters +within this section. Each parameter must include a list of its values and a label, using the +following syntax: + + +```yml +global.parameters: + TASKS: + values: [2, 4, 8, 18, 24, 36] + label: TASKS.%% +``` + +Note that the parameter is `TASKS` and that its label starts with the same name, but is followed +by a `.%%`. + +We would then update the line under `run` -> `cmd` defining `procs` to include the name +of the parameter enclosed in `$()`: + +```yml + procs: $(TASKS) +``` + +The full YAML file will look like + +```yml +description: + name: Amdahl + description: Run a parallel program + +batch: + type: slurm + host: quartz # machine to run on + bank: guest # bank + queue: pbatch # partition + +env: + variables: + P: .999 + OUTPUT: amdahl.json + OUTPUT_PATH: ./Episode4 + +study: + - name: amdahl + description: run in parallel + run: + # Here's where we include our MPI wrapper: + cmd: | + $(LAUNCHER) amdahl --terse -p $(P) >> $(OUTPUT) + nodes: 1 + procs: $(TASKS) + walltime: "00:01:30" + +global.parameters: + TASKS: + values: [2, 4, 8, 18, 24, 36] + label: TASKS.%% +``` + +::: challenge + +Run `maestro run --dry amdahl.yaml` using the above YAML file +and investigate the resulting directory structure. How does +the list of task values under `global.parameters` change the +output directory organization? + +::::::solution + +Under your current working directory, you should see a directory structure +created with the following format -- `Episode4/Amdahl_-