Submit jobs¶
This guide covers the request you pass to Submitor.submit_job(): command,
resources, scheduling policy, execution environment, retries, and
dependencies.
Choose a command form¶
Exactly one command form is required.
| Form | Use it for | Shell interpreted? |
|---|---|---|
argv=[...] |
Programs with explicit arguments | No |
command="..." |
A one-line shell expression | Yes |
script=Script... |
Multi-line or existing shell scripts | Yes |
Argument vector¶
Prefer argv for ordinary programs:
Each item is one argument. Wildcards, $VARS, pipes, and redirects are not
expanded.
Shell command¶
Use command when shell syntax is the point:
Script¶
Use an inline script for a short multi-step workflow:
import molq as mq
job = queue.submit_job(
script=mq.Script.inline(
"""\
set -euo pipefail
python preprocess.py
python train.py
"""
)
)
Or submit an existing file:
Describe resources, scheduling, and execution¶
molq keeps three concerns separate:
| Object | Describes | Typical fields |
|---|---|---|
JobResources |
Hardware and time | CPU, memory, GPU, wall time |
JobScheduling |
Queue policy | partition, account, QoS, dependency |
JobExecution |
Process environment | cwd, name, env, output paths |
import molq as mq
job = queue.submit_job(
argv=["python", "train.py"],
resources=mq.JobResources(
cpu_count=8,
memory=mq.Memory.gb(32),
gpu_count=1,
time_limit=mq.Duration.hours(4),
),
scheduling=mq.JobScheduling(
partition="gpu",
account="project123",
qos="normal",
),
execution=mq.JobExecution(
cwd="/scratch/alice/project",
job_name="train-resnet",
env={"OMP_NUM_THREADS": "8"},
),
)
Values remain typed in Python:
The selected scheduler validates unsupported fields before submission. For example, a local job cannot request a SLURM partition.
Reuse an allocation you have used before¶
Every successful submission records its partition / account / QoS / reservation combination against the cluster. Recalling them is purely local — no query goes to the scheduler:
for allocation in queue.remembered_allocations(limit=5):
print(
allocation.partition,
allocation.account,
allocation.use_count,
)
Results come back most-recently-used first. This is what lets a submission UI
offer "the accounts you actually charge to" without asking the cluster, and
it survives retention cleanup — the memory lives apart from the jobs table,
so expiring old records does not erase it.
A configuration with none of those four fields set is not worth remembering and is skipped.
Set reusable defaults¶
Defaults belong to the Submitor; values supplied to submit_job() override
them field by field.
defaults = mq.SubmitorDefaults(
resources=mq.JobResources(
cpu_count=4,
memory=mq.Memory.gb(16),
time_limit=mq.Duration.hours(2),
),
scheduling=mq.JobScheduling(partition="compute"),
)
with mq.Submitor(target=cluster, defaults=defaults) as queue:
job = queue.submit_job(
argv=["python", "analyse.py"],
resources=mq.JobResources(cpu_count=8),
)
This job uses eight CPUs while inheriting memory, time, and partition. Configuration profiles can supply the same defaults; see Configuration.
Retry failures¶
max_attempts counts the first submission. A value of 3 means at most two
retries.
policy = mq.RetryPolicy(
max_attempts=3,
retry_on_states=(mq.JobState.FAILED, mq.JobState.TIMED_OUT),
retry_on_exit_codes=(1, 137),
backoff=mq.RetryBackoff(
mode="exponential",
initial_seconds=10,
maximum_seconds=120,
),
)
job = queue.submit_job(
argv=["python", "train.py"],
retry=policy,
)
final_record = job.wait()
Each retry is a new persisted attempt with its own job ID. The original handle follows the family to its latest attempt. Inspect all attempts with:
Add job dependencies¶
Dependencies use molq job IDs, not scheduler IDs. They are supported by SLURM, PBS, and LSF, but not by the local scheduler.
prepare = queue.submit_job(argv=["python", "prepare.py"])
train = queue.submit_job(
argv=["python", "train.py"],
after_success=[prepare.job_id],
)
cleanup = queue.submit_job(
argv=["python", "cleanup.py"],
after=[train.job_id],
)
| Keyword | Child becomes eligible after the parent… |
|---|---|
after_success |
succeeds |
after_failure |
fails, is cancelled, times out, or is lost |
after_started |
starts running |
after |
reaches any terminal state |
Each keyword accepts a list, so a job can wait for several upstream jobs.
For programmatically built schedules, the equivalent representation is a tuple
of DependencyRef objects in JobScheduling.dependencies.
Find logs and artifacts¶
Unless paths are overridden, molq creates a job directory below the resolved working directory:
The resolved paths are stored in JobRecord.metadata:
record = job.wait()
print(record.metadata["molq.job_dir"])
print(record.metadata["molq.stdout_path"])
print(record.metadata["molq.stderr_path"])
For remote destinations, use queue.fetch_logs() or
queue.fetch_artifacts() to copy results to the local machine. See
Monitor jobs.
A complete HPC submission¶
import molq as mq
cluster = mq.Cluster("dardel", "slurm", host="dardel")
with mq.Submitor(target=cluster) as queue:
job = queue.submit_job(
argv=["python", "train.py", "--seed", "42"],
resources=mq.JobResources(
cpu_count=8,
memory=mq.Memory.gb(32),
gpu_count=1,
time_limit=mq.Duration.hours(4),
),
scheduling=mq.JobScheduling(
partition="gpu",
account="project123",
),
execution=mq.JobExecution(
cwd="/cfs/klemming/scratch/a/alice/my-run",
job_name="train-42",
),
retry=mq.RetryPolicy(max_attempts=2),
metadata={"experiment": "baseline"},
)
print(job.job_id)