Skip to content

Commit 4150ee0

Browse files
sawenzelclaude
andcommitted
Count backfill memory in the memory gate of the new runner
This fixes a problem in the memory admission of the new workflow runner and adds unit tests. - The default tier ignored memory booked by backfill tasks, so `--mem-limit` could be exceeded. - `fits_default` and `mem_free_default` now include the backfill memory. - The backfill memory factor now defaults to 1.0 and can be set with `--backfill-mem-factor`. - The simulator default is aligned with the runner. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 1d96c11 commit 4150ee0

6 files changed

Lines changed: 39 additions & 7 deletions

File tree

‎MC/workflow_runner/o2dpg_runner/cli.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def build_parser() -> argparse.ArgumentParser:
6060
p.add_argument("--dynamic-resources", dest="dynamic_resources", action="store_true")
6161
p.add_argument("--optimistic-resources", dest="optimistic_resources", action="store_true")
6262
p.add_argument("--n-backfill", dest="n_backfill", type=int, default=1)
63+
p.add_argument("--backfill-mem-factor", dest="backfill_mem_factor", type=float, default=1.0,
64+
help="memory over-commit allowed for backfill tasks, as a factor of --mem-limit")
6365
p.add_argument("--mem-limit", type=float, default=default_mem, help="in MB")
6466
p.add_argument("--cpu-limit", type=float, default=8)
6567

@@ -135,6 +137,7 @@ def _args_to_config(ns: argparse.Namespace) -> RunnerConfig:
135137
mem_limit=ns.mem_limit,
136138
cpu_limit=ns.cpu_limit,
137139
n_backfill=ns.n_backfill,
140+
backfill_mem_factor=ns.backfill_mem_factor,
138141
update_resources=ns.update_resources,
139142
dynamic_resources=ns.dynamic_resources,
140143
optimistic_resources=ns.optimistic_resources,

‎MC/workflow_runner/o2dpg_runner/config.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class RunnerConfig:
2121
mem_limit: float = 0.0 # MB; 0 means "auto from psutil"
2222
cpu_limit: float = 8.0
2323
n_backfill: int = 1
24+
backfill_mem_factor: float = 1.0
2425
update_resources: Optional[str] = None
2526
dynamic_resources: bool = False
2627
optimistic_resources: bool = False

‎MC/workflow_runner/o2dpg_runner/executor.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def __init__(
112112
mem_limit=config.mem_limit,
113113
procs_parallel_max=config.maxjobs,
114114
n_backfill_max=config.n_backfill,
115+
backfill_mem_factor=config.backfill_mem_factor,
115116
dynamic_resources=config.dynamic_resources,
116117
optimistic_resources=config.optimistic_resources,
117118
)

‎MC/workflow_runner/o2dpg_runner/resources.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def __init__(
182182
procs_parallel_max: int = 100,
183183
n_backfill_max: int = 1,
184184
backfill_cpu_factor: float = 1.5,
185-
backfill_mem_factor: float = 1.5,
185+
backfill_mem_factor: float = 1.0,
186186
dynamic_resources: bool = False,
187187
optimistic_resources: bool = False,
188188
):
@@ -308,12 +308,12 @@ def cpu_free_default(self) -> float:
308308
return self.boundaries.cpu_limit - self.cpu_booked
309309

310310
def mem_free_default(self) -> float:
311-
return self.boundaries.mem_limit - self.mem_booked
311+
return self.boundaries.mem_limit - self.mem_booked - self.mem_booked_backfill
312312

313313
def fits_default(self, res: TaskResources) -> bool:
314314
return (
315315
self.cpu_booked + res.cpu_assigned <= self.boundaries.cpu_limit
316-
and self.mem_booked + res.mem_assigned <= self.boundaries.mem_limit
316+
and self.mem_booked + self.mem_booked_backfill + res.mem_assigned <= self.boundaries.mem_limit
317317
)
318318

319319
def fits_backfill(

‎MC/workflow_runner/o2dpg_runner/tests/test_resources.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,30 @@ def test_at_proc_cap():
135135
rm.resources[i].nice_value = rm.nice_default
136136
rm.book(i, rm.nice_default)
137137
assert rm.at_proc_cap()
138+
139+
140+
def test_default_gate_counts_memory_booked_in_backfill():
141+
"""Memory held by a backfill task must count against the default-tier gate."""
142+
rm = _make_rm(cpu=8, mem=16000)
143+
rm.add_task("a", None, 1, 1, 11000)
144+
rm.add_task("b", None, 1, 1, 11000)
145+
rm.resources[0].nice_value = rm.nice_backfill
146+
rm.book(0, rm.nice_backfill)
147+
assert rm.mem_booked == 0 and rm.mem_booked_backfill == 11000
148+
assert not rm.fits_default(rm.resources[1])
149+
assert rm.mem_free_default() == 5000
150+
151+
152+
def test_backfill_mem_factor_default_does_not_overcommit_memory():
153+
rm = _make_rm(cpu=8, mem=16000)
154+
rm.add_task("a", None, 1, 1, 9000)
155+
rm.add_task("b", None, 1, 1, 9000)
156+
rm.resources[0].nice_value = rm.nice_default
157+
rm.book(0, rm.nice_default)
158+
assert not rm.fits_backfill(rm.resources[1])
159+
rm15 = _make_rm(cpu=8, mem=16000, backfill_mem_factor=1.5)
160+
rm15.add_task("a", None, 1, 1, 9000)
161+
rm15.add_task("b", None, 1, 1, 9000)
162+
rm15.resources[0].nice_value = rm15.nice_default
163+
rm15.book(0, rm15.nice_default)
164+
assert rm15.fits_backfill(rm15.resources[1])

‎MC/workflow_runner/o2dpg_schedule_simulator.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def _build_rm(
261261
cpu_overrides: Optional[Dict[int, float]] = None,
262262
n_backfill_max: int = 0,
263263
backfill_cpu_factor: float = 1.5,
264-
backfill_mem_factor: float = 1.5,
264+
backfill_mem_factor: float = 1.0,
265265
maxjobs: int = 10_000,
266266
) -> Tuple[ResourceManager, Set[int]]:
267267
"""Fresh ResourceManager with no backfill tier and unlimited job slots.
@@ -374,7 +374,7 @@ def simulate(
374374
backfill_model: str = "off",
375375
n_backfill: int = 1,
376376
backfill_cpu_factor: float = 1.5,
377-
backfill_mem_factor: float = 1.5,
377+
backfill_mem_factor: float = 1.0,
378378
backfill_slowdown_factor: float = 1.15,
379379
maxjobs: int = 10_000,
380380
) -> SimResult:
@@ -802,7 +802,7 @@ def optimize_workers(
802802
backfill_model: str = "off",
803803
n_backfill: int = 1,
804804
backfill_cpu_factor: float = 1.5,
805-
backfill_mem_factor: float = 1.5,
805+
backfill_mem_factor: float = 1.0,
806806
backfill_slowdown_factor: float = 1.15,
807807
maxjobs: int = 10_000,
808808
) -> Tuple[Dict[str, int], float]:
@@ -911,7 +911,7 @@ def build_parser() -> argparse.ArgumentParser:
911911
help="Maximum concurrent backfill tasks when backfill simulation is enabled.")
912912
p.add_argument("--backfill-cpu-factor", type=float, default=1.5, metavar="X",
913913
help="Total CPU oversubscription factor allowed for backfill admission.")
914-
p.add_argument("--backfill-mem-factor", type=float, default=1.5, metavar="X",
914+
p.add_argument("--backfill-mem-factor", type=float, default=1.0, metavar="X",
915915
help="Total memory oversubscription factor allowed for backfill admission.")
916916
p.add_argument("--backfill-slowdown-factor", type=float, default=1.15, metavar="X",
917917
help="Walltime multiplier applied to backfill tasks in "

0 commit comments

Comments
 (0)