quacc icon indicating copy to clipboard operation
quacc copied to clipboard

Support Parsl special options: `parsl_resource_specification`, `stdout`, `stderr`, and `walltime`

Open Andrew-S-Rosen opened this issue 3 months ago • 1 comments

What new feature would you like to see?

As shown here, we need to be able to support Parsl "special options" with @job decorators: namely, parsl_resource_specification, stdout, stderr, and walltime.

Originally mentioned in https://github.com/Quantum-Accelerators/quacc/discussions/1881.

Andrew-S-Rosen avatar Mar 17 '24 02:03 Andrew-S-Rosen

Comment from Parsl team:

YB: Here's how I see it from the HTEX perspective. An app behaves mostly like a python function, but with some additional parsl behavior (like walltime limits, parsl_resource_spec... ). If the issue is that you do not want to/can't modify a user supplied function, you can always wrap your function with a wrapper that takes the parsl_resource_specification and internally calls the user supplied function.

AR: Yeah, the issue is that it would not be practical or feasible to modify the underlying functions. Interesting idea about the wrapper. That seems like a potentially viable path forward, but my concern would be that this would then mess up things with monitoring (e.g. the function name would be the wrapped function name and such). I guess that might be solvable with a @functools.wraps kind of thing?

Andrew-S-Rosen avatar Mar 28 '24 21:03 Andrew-S-Rosen

Following yesterday's deleted comment, if you do the following change in the job decorator you get what you want. There is no additional assumptions about the parameter that _func takes. The only problem is what you already reported above: what is being launched is function job.<locals>.wrapper which can be problematic for some situations (monitoring...), doing wrapper.__name__ = _func.__name__ does not not seem to have any impact.


elif SETTINGS.WORKFLOW_ENGINE == "parsl":
    from parsl import python_app
    stdout = kwargs.pop("stdout", None)
    stderr = kwargs.pop("stderr", None)
    walltime = kwargs.pop("walltime", None)
    parsl_resource_specification = kwargs.pop("parsl_resource_specification", None)
    def wrapper(
        *f_args,
        stdout=stdout,
        stderr=stderr,
        walltime=walltime,
        parsl_resource_specification=parsl_resource_specification,
        **f_kwargs,
    ):
        return _func(*f_args, **f_kwargs)
    return python_app(wrapper, **kwargs)
config = Config(
    executors=[
        HighThroughputExecutor(
            label="htex_local",
            cores_per_worker=1,
            max_workers=1,
            provider=LocalProvider(
                channel=LocalChannel(),
                init_blocks=1,
                max_blocks=1,
            ),
        )
    ],
)

resource_spec = {
    "num_nodes": 2,
    "ranks_per_node": 2,
    "num_ranks": 4,
}

parsl.load(config)

@job(parsl_resource_specification=resource_spec)
def test_mpi_apps():
    from os import environ

    return environ.get("PARSL_RANKS_PER_NODE")

future = test_mpi_apps()

print(future.result())

This should print 2

tomdemeyere avatar May 04 '24 11:05 tomdemeyere

Yup, that makes perfect sense to me. It's possible that @functools.wraps around the wrapper could fix the function metadata (should probably be used either way).

Feel free to open a PR if you would like. Otherwise, I appreciate your comment here and will get to it one day. :)

Andrew-S-Rosen avatar May 04 '24 18:05 Andrew-S-Rosen