Two-Step CPU-GPU Workflow #
In this tutorial, you create a reusable two-step workflow. A CPU job converts temperatures from Celsius to Fahrenheit, then a GPU job calculates their average and saves the results to iRODS. Set up the cluster scripts once, then run the workflow repeatedly from the LEXIS Platform with a different input dataset for each execution.
Both jobs use GenericCommandTemplate to run scripts stored on the shared
project filesystem.
Note
You need a LEXIS Platform project with CPU and GPU resources, SSH access to the selected HPC system and permission to write to its shared project directory.
Required Values#
Before starting, identify the values that you will substitute throughout this tutorial:
YOUR_PROJECT_SHORTNAMEandYOUR_COMPUTATION_RESOURCEUse the project and computation resource identifiers described in Values Used in Custom Workflow YAML. If separate resources provide the CPU and GPU node types, use the appropriate resource name in each YAML job.
YOUR_PROJECT_DIRThe full path to a shared project directory on the selected HPC system. On Karolina, for example, use
/scratch/project/PROJECT_ID. Store the workflow scripts and shared intermediate files in this directory. Do not use your home directory: workflow jobs are launched by a service account that can access LEXIS Platform project directories, but not your personal home directory.
Prepare the Cluster Scripts#
On your local computer, create a folder named cpu-gpu-workflow for the
four script files:
mkdir cpu-gpu-workflow
cd cpu-gpu-workflow
The module commands below use the module names available on Karolina. When using another HPC system, replace them with the commands that provide Python and CuPy on that system.
First create a wrapper that loads Python before starting the CPU program:
#!/usr/bin/env bash
set -e
# Load Python
module load Python
# Run the CPU program
python3 YOUR_PROJECT_DIR/cpu-gpu-workflow/cpu_prepare.py
# Copy its output to the shared project directory for the GPU job
mkdir -p YOUR_PROJECT_DIR/cpu-gpu-workflow/intermediate
cp intermediate/temperatures.txt YOUR_PROJECT_DIR/cpu-gpu-workflow/intermediate/
Save it as cpu_prepare.sh in the local cpu-gpu-workflow folder.
The CPU script converts every temperature from Celsius to Fahrenheit and
writes the result to the job’s intermediate directory.
The wrapper then copies the same result to the shared project directory for
the GPU job:
#!/usr/bin/env python3
from pathlib import Path
celsius = [
float(line)
for line in Path("temperatures.txt").read_text().splitlines()
]
fahrenheit = [temperature * 1.8 + 32 for temperature in celsius]
output = "\n".join(str(temperature) for temperature in fahrenheit) + "\n"
output_dir = Path("intermediate")
output_dir.mkdir(exist_ok=True)
(output_dir / "temperatures.txt").write_text(output)
print(f"Converted {len(celsius)} temperatures")
Save it as cpu_prepare.py in the same local folder.
The GPU wrapper loads CuPy, copies the intermediate file into the job’s working directory and starts the GPU program:
#!/usr/bin/env bash
set -e
# Load CuPy
module load CuPy
# Copy the CPU output into this job's working directory
cp YOUR_PROJECT_DIR/cpu-gpu-workflow/intermediate/temperatures.txt .
# Run the GPU program
python3 YOUR_PROJECT_DIR/cpu-gpu-workflow/gpu_reduce.py
Save it as gpu_reduce.sh. The wrapper runs this CuPy program:
#!/usr/bin/env python3
from pathlib import Path
import cupy as cp
temperatures = cp.loadtxt("temperatures.txt")
average = float(cp.mean(temperatures).get())
output_dir = Path("result")
output_dir.mkdir(exist_ok=True)
result = f"Average temperature: {average:.3f} F\n"
(output_dir / "average.txt").write_text(result)
print(result, end="")
Save it as gpu_reduce.py in the same local folder. The folder should now
contain:
cpu-gpu-workflow/
|-- cpu_prepare.py
|-- cpu_prepare.sh
|-- gpu_reduce.py
`-- gpu_reduce.sh
Return to the directory containing cpu-gpu-workflow and upload the whole
folder. Replace USERNAME with your cluster username and
YOUR_PROJECT_DIR with the full shared project directory. The following
example uses Karolina; use the login address of your selected HPC system when
running elsewhere:
cd ..
scp -r cpu-gpu-workflow USERNAME@karolina.it4i.cz:YOUR_PROJECT_DIR/
Finally, log in to the system and make the uploaded scripts executable:
ssh USERNAME@karolina.it4i.cz
cd YOUR_PROJECT_DIR/cpu-gpu-workflow/
chmod 750 *.sh *.py
Prepare the Input Dataset#
Create a folder named cpu-gpu-input containing temperatures.txt.
Enter one temperature in degrees Celsius per line:
12
15.5
18
20
22.5
Create the archive from inside the folder so that it does not add an extra directory level:
cd cpu-gpu-input
zip ../cpu-gpu-input.zip temperatures.txt
Upload cpu-gpu-input.zip in Data Management/Datasets and enable
unpacking, so that temperatures.txt is at the root of the staged dataset.
Name the dataset CPU GPU Input. See
Data Management for the complete dataset upload procedure.
The workflow definition below uses ddi://~, so the input remains
unselected until each workflow execution. To bind a workflow to a specific
dataset instead, see Finding a Dataset DDI URI.
Create the Workflow#
Open Workflows from the main menu, select Custom Workflow and choose the
YAML code editor. See Create Custom LEXIS Platform workflow for the complete
portal procedure. Start with the following definition, which uses Karolina’s
qcpu and qgpu node types as an example. For another HPC system, replace
the location and node types with values supported by that system:
id: My_2_step_workflow
desc: Convert temperatures on a CPU node and average them on a GPU node
project_shortname: YOUR_PROJECT_SHORTNAME
jobs:
Job1_Prepare:
requirements:
policy: preferred
command_template_name: GenericCommandTemplate
node_type_name: qcpu
locations:
- location_name: Karolina
location_resource: YOUR_COMPUTATION_RESOURCE
walltime_limit: 300
max_cores: 128
template_parameters:
userScriptPath: YOUR_PROJECT_DIR/cpu-gpu-workflow/cpu_prepare.sh
environment_variables: {}
data_inputs:
- source: ddi://~
target: ./
data_outputs:
# Preserve the intermediate result as an iRODS dataset.
- source: intermediate/
target: ddi://~
metadata:
title: Fahrenheit temperatures
access: project
Job2_Compute:
requirements:
policy: preferred
command_template_name: GenericCommandTemplate
node_type_name: qgpu
locations:
- location_name: Karolina
location_resource: YOUR_COMPUTATION_RESOURCE
walltime_limit: 300
max_cores: 16
template_parameters:
userScriptPath: YOUR_PROJECT_DIR/cpu-gpu-workflow/gpu_reduce.sh
environment_variables: {}
depends_on:
- Job1_Prepare
data_inputs: []
data_outputs:
- source: result/
target: ddi://~
metadata:
title: Average temperature
access: project
metadata:
start_date: "2026-07-01T00:00:00.000Z"
catchup: false
Review the translated workflow and click Create Workflow.
Run the Workflow#
Open the workflow details and click Create Workflow Execution. Check the
selected HPC resource and input dataset, then create the execution. See
Execution of LEXIS Platform Workflows for the complete execution procedure.
LEXIS Platform first submits Job1_Prepare to a CPU node. After it finishes, it
submits Job2_Compute to a GPU node.
Open each task in the execution graph to inspect its HPC job log. The CPU log reports how many temperatures were converted. The GPU log prints:
Average temperature: 63.680 F
Check the Results#
The execution has two output datasets in iRODS:
Fahrenheit temperaturescontains the converted values intemperatures.txt.Average temperaturecontains the calculated average inaverage.txt.