Carlos Alberto da Costa Filho
03/31/2026, 10:52 PMMarvin
03/31/2026, 10:52 PMMarvin
03/31/2026, 10:55 PMfrom prefect import flow, task
from ray import tune
@task
def run_tune():
def trainable(config):
# your training logic here
acc = 1.0 - config["lr"] # placeholder metric
return {"accuracy": acc}
analysis = tune.run(
trainable,
config={"lr": tune.grid_search([0.001, 0.01, 0.1])},
num_samples=1,
stop={"training_iteration": 1},
)
return analysis.best_result
@flow
def hpo_flow():
return run_tune()
if __name__ == "__main__":
hpo_flow()
- If you want Prefect tasks to execute on a Ray cluster while you run Tune inside a task, use RayTaskRunner and ensure worker env has Prefect configured:
from prefect import flow, task
from prefect_ray.task_runners import RayTaskRunner
from ray import tune
@task
def run_tune():
# same as above
...
@flow(
task_runner=RayTaskRunner(
address="ray://<your-ray-head>:10001",
init_kwargs={
"runtime_env": {
# ensure Prefect + any deps exist on Ray workers
"pip": ["prefect>=3", "prefect-ray>=0.4", "ray[tune]"],
# make sure workers can reach your API if using Prefect Cloud/Server
"env_vars": {"PREFECT_API_URL": "https://<your-api>/api"},
}
},
)
)
def hpo_flow():
return run_tune()
Notes and caveats
- RayTaskRunner focuses on parallelizing Prefect tasks; it doesn’t manage Tune trials or Train backends natively.
- When running on a remote Ray cluster, make sure Prefect and your Prefect API settings are available to Ray workers (see the env_vars/runtime_env pattern above). One of the linked issues tracks an env propagation pitfall.
- For Ray Train, use the same pattern: build your Trainer inside a Prefect task and return metrics/checkpoints.
Would you like me to open a feature request for a first‑class Ray Tune/Train integration on your behalf, or share a more complete template tailored to your environment (local, K8s, Ray address, storage)?Carlos Alberto da Costa Filho
03/31/2026, 10:58 PMMarvin
03/31/2026, 10:58 PMMarvin
03/31/2026, 10:59 PMfrom prefect import flow, task
from prefect_ray.task_runners import RayTaskRunner
from prefect_ray.context import remote_options
# one training run = one Prefect task
@task
def train_one_config(cfg: dict) -> dict:
# do your single-trial training here, using GPUs if allocated
# return metrics + anything needed to choose the best
# e.g., train(model, cfg) ...
accuracy = 0.9 # placeholder
return {"metrics": {"accuracy": accuracy}, "config": cfg}
@flow(
task_runner=RayTaskRunner(
address="ray://<your-ray-head>:10001",
init_kwargs={
"runtime_env": {
# ensure workers have what they need
"pip": ["prefect>=3", "prefect-ray>=0.4", "torch", "numpy"], # add deps as needed
# if using Prefect Cloud/Server, propagate settings so Ray workers can call back
"env_vars": {"PREFECT_API_URL": "https://<your-prefect-api>/api"},
}
},
)
)
def hpo_as_prefect_tasks(n_trials: int = 12):
# build your search space (grid or random)
import random
configs = [
{"lr": 10 ** random.uniform(-4, -1), "batch_size": random.choice([32, 64, 128])}
for _ in range(n_trials)
]
# per-trial resource allocation on Ray workers
with remote_options(num_cpus=4, num_gpus=1):
futs = train_one_config.map(configs)
# collect results and pick best
results = [f.result() for f in futs]
best = max(results, key=lambda r: r["metrics"]["accuracy"])
return best
if __name__ == "__main__":
print(hpo_as_prefect_tasks())
Option B — Ray Tune owns trials (Prefect wraps the whole HPO as one task/flow)
- You call Tune (or Train) inside a single Prefect task/flow, and let it launch/manage trials.
- Pros: you get Tune’s schedulers, search algorithms, early stopping, trial pruning, etc.
- Cons: trials will not appear as individual Prefect tasks (they’re Ray trials), though you can still persist best metrics/artifacts back to Prefect at the end.
Example (Ray Tune inside a Prefect task)
from prefect import flow, task
from ray import tune
@task
def run_tune():
def trainable(cfg):
# training loop
# tune.report(metric=...) inside loop if you want schedulers like ASHA
return {"accuracy": 0.9}
analysis = tune.run(
trainable,
config={"lr": tune.grid_search([1e-4, 1e-3, 1e-2]),
"batch_size": tune.choice([32, 64, 128])},
num_samples=1,
stop={"training_iteration": 5},
)
return analysis.best_result
@flow
def hpo_with_tune():
return run_tune()
Key trade-off
- If “each config must be a Prefect task” is a hard requirement, choose Option A. You’ll parallelize via RayTaskRunner and keep full Prefect visibility per trial, but you give up Tune’s adaptive/early‑stopping orchestration.
- If you need advanced HPO features (ASHA, PBT, search algorithms), choose Option B and let Tune manage trials end‑to‑end.
Caveats and tips
- On remote Ray clusters, make sure Prefect is reachable from Ray workers (set PREFECT_API_URL in RayTaskRunner.init_kwargs.runtime_env.env_vars).
- If you want per‑trial GPUs, use the remote_options context (as shown) around the submit/map calls.Marvin
03/31/2026, 10:59 PMCarlos Alberto da Costa Filho
03/31/2026, 11:13 PMMarvin
03/31/2026, 11:13 PMMarvin
03/31/2026, 11:15 PMMarvin
03/31/2026, 11:15 PMCarlos Alberto da Costa Filho
03/31/2026, 11:32 PMMarvin
03/31/2026, 11:32 PMMarvin
03/31/2026, 11:32 PMCarlos Alberto da Costa Filho
03/31/2026, 11:33 PMMarvin
03/31/2026, 11:33 PMMarvin
03/31/2026, 11:33 PMMarvin
03/31/2026, 11:33 PMCarlos Alberto da Costa Filho
03/31/2026, 11:35 PMMarvin
03/31/2026, 11:35 PMMarvin
03/31/2026, 11:36 PMMarvin
03/31/2026, 11:36 PMCarlos Alberto da Costa Filho
03/31/2026, 11:38 PMMarvin
03/31/2026, 11:38 PMMarvin
03/31/2026, 11:39 PMMarvin
03/31/2026, 11:39 PM