Skip to content

Commit a78a403

Browse files
committed
Merge branch 'main' into aybchan/fern-migration
2 parents 35c8f70 + d85ee15 commit a78a403

16 files changed

Lines changed: 1412 additions & 380 deletions

.github/triage/jax_toolbox_triage/args.py

Lines changed: 51 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import argparse
2-
import ast
32
import datetime
43
import getpass
54
import os
65
import pathlib
7-
import re
8-
import sys
96
import tempfile
107
import typing
118
import warnings
@@ -19,90 +16,6 @@
1916
optional_software = ["flax", "maxtext", "transformer-engine"]
2017

2118

22-
_INFO_LOG_ARGUMENTS_RE = re.compile(
23-
r"^\[INFO\]\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+Arguments:\s*$"
24-
)
25-
_INFO_LOG_ARGUMENT_RE = re.compile(
26-
r"^\[INFO\]\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s+"
27-
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*):\s*(?P<value>.*)$"
28-
)
29-
30-
31-
def logged_arg_value(s: str) -> typing.Any:
32-
try:
33-
return ast.literal_eval(s)
34-
except (SyntaxError, ValueError):
35-
return s
36-
37-
38-
def load_args_from_info_log(info_log: pathlib.Path) -> typing.Dict[str, str]:
39-
ret: typing.Dict[str, str] = {}
40-
in_arguments = False
41-
with info_log.open("r", encoding="utf-8") as f:
42-
for line in f:
43-
if not in_arguments:
44-
if _INFO_LOG_ARGUMENTS_RE.match(line):
45-
in_arguments = True
46-
continue
47-
match = _INFO_LOG_ARGUMENT_RE.match(line)
48-
if not match:
49-
if ret:
50-
break
51-
continue
52-
ret[match.group("name")] = match.group("value")
53-
if not ret:
54-
raise Exception(f"Could not read arguments from {info_log}")
55-
return ret
56-
57-
58-
def parser_actions_by_dest(parser: argparse.ArgumentParser) -> typing.Dict[str, argparse.Action]:
59-
return {
60-
action.dest: action
61-
for action in parser._actions
62-
if action.dest != argparse.SUPPRESS
63-
}
64-
65-
66-
def provided_arg_dests(
67-
parser: argparse.ArgumentParser, args: typing.Sequence[str]
68-
) -> typing.Set[str]:
69-
option_actions = {
70-
option: action for action in parser._actions for option in action.option_strings
71-
}
72-
ret: typing.Set[str] = set()
73-
i = 0
74-
while i < len(args):
75-
arg = args[i]
76-
if arg == "--":
77-
if i + 1 < len(args):
78-
ret.add("test_command")
79-
break
80-
option = arg.split("=", 1)[0]
81-
action = option_actions.get(option)
82-
if action is None:
83-
ret.add("test_command")
84-
break
85-
ret.add(action.dest)
86-
if "=" in arg or action.nargs == 0:
87-
i += 1
88-
elif action.nargs is None:
89-
i += 2
90-
elif isinstance(action.nargs, int):
91-
i += 1 + action.nargs
92-
else:
93-
i += 1
94-
return ret
95-
96-
97-
def coerce_logged_arg(
98-
action: argparse.Action, value: str
99-
) -> typing.Any:
100-
value = logged_arg_value(value)
101-
if isinstance(value, str) and action.type is not None:
102-
return action.type(value)
103-
return value
104-
105-
10619
def parse_cherry_picks(s: str) -> typing.Dict[str, typing.List[str]]:
10720
ret: typing.Dict[str, typing.List[str]] = {}
10821
for part in s.split(","):
@@ -185,6 +98,16 @@ def parse_args(args=None) -> argparse.Namespace:
18598
`date` will be substituted, e.g. ghcr.io/nvidia/jax:{container}-{date} for
18699
the JAX-Toolbox public nightlies.""",
187100
)
101+
parser.add_argument("--metric-name", type=str, help="Example: tflops_per_sec")
102+
parser.add_argument(
103+
"--passing-metric",
104+
action="append",
105+
type=float,
106+
help="Good value for the metric",
107+
)
108+
parser.add_argument(
109+
"--failing-metric", action="append", type=float, help="Bad value for the metric"
110+
)
188111
parser.add_argument(
189112
"--output-prefix",
190113
default=None,
@@ -215,16 +138,9 @@ def parse_args(args=None) -> argparse.Namespace:
215138
container. Careful use of this option, along with --start-date, --end-date and
216139
--threshold-days, allows the container-level search to be skipped.""",
217140
)
218-
parser.add_argument(
219-
"--no-skip-precondition-checks",
220-
dest="skip_precondition_checks",
221-
action="store_false",
222-
default=False,
223-
help="Re-enable precondition checks when restarting from a run that skipped them.",
224-
)
225141
parser.add_argument(
226142
"test_command",
227-
nargs="*",
143+
nargs="+",
228144
help="""
229145
Command to execute inside the container. This should be as targeted as
230146
possible.""",
@@ -367,13 +283,6 @@ def parse_args(args=None) -> argparse.Namespace:
367283
action="store_true",
368284
help="Exclude transformer-engine from the list of optional software to triage.",
369285
)
370-
version_search_args.add_argument(
371-
"--no-exclude-transformer-engine",
372-
dest="exclude_transformer_engine",
373-
action="store_false",
374-
default=False,
375-
help="Include transformer-engine when restarting from a run that excluded it.",
376-
)
377286
version_search_args.add_argument(
378287
"--transformer-engine-ccache-env",
379288
action="append",
@@ -417,9 +326,15 @@ def parse_args(args=None) -> argparse.Namespace:
417326
default="main",
418327
help="The name of the main branch (e.g. main) to derive cherry-picks from",
419328
)
420-
raw_args = sys.argv[1:] if args is None else list(args)
421-
provided_args = provided_arg_dests(parser, raw_args)
422-
args = parser.parse_args(args=raw_args)
329+
parser.add_argument(
330+
"--container-registry",
331+
type=str,
332+
help="""
333+
Remote registry for use with the plugin backend. This can optionally
334+
include a tag prefix, i.e. gitlab.com/USER/containers and
335+
gitlab.com/USER/containers:PREFIX- are both valid.""",
336+
)
337+
args = parser.parse_args(args=args)
423338
if args.restart:
424339
if args.output_prefix is None:
425340
raise Exception("--restart requires --output-prefix")
@@ -429,27 +344,43 @@ def parse_args(args=None) -> argparse.Namespace:
429344
raise Exception(
430345
f"--output-prefix must contain summary.json: {summary_file}"
431346
)
432-
info_log = args.output_prefix / "info.log"
433-
if not info_log.exists():
434-
raise Exception(f"--output-prefix must contain info.log: {info_log}")
435-
actions_by_dest = parser_actions_by_dest(parser)
436-
for arg, value in load_args_from_info_log(info_log).items():
437-
if arg in provided_args or arg not in actions_by_dest:
438-
continue
439-
setattr(args, arg, coerce_logged_arg(actions_by_dest[arg], value))
440347
else:
441348
if args.output_prefix is None:
442349
args.output_prefix = pathlib.Path(
443350
datetime.datetime.now().strftime("triage-%Y-%m-%d-%H-%M-%S")
444351
)
445-
if not args.test_command:
446-
raise Exception("test_command must be passed or restored from info.log")
447-
352+
args.optional_software = optional_software.copy()
353+
if args.exclude_transformer_engine:
354+
args.optional_software.remove("transformer-engine")
448355
assert args.container_runtime in {
449356
"docker",
450357
"pyxis",
451358
"local",
359+
"plugin",
452360
}, args.container_runtime
361+
362+
# Metric-based triage checks
363+
container_search_options_passed = (
364+
args.container is not None
365+
or args.start_date is not None
366+
or args.end_date is not None
367+
)
368+
if args.metric_name is not None:
369+
if container_search_options_passed:
370+
raise Exception(
371+
"--metric-name is only supported in version-level search; use "
372+
"--{passing,failing}-{container,versions}."
373+
)
374+
if not args.passing_metric or not args.failing_metric:
375+
raise Exception(
376+
"You should pass seed metric values via --passing-metric and "
377+
"--failing-metric if --metric-name is passed."
378+
)
379+
if (args.passing_metric or args.failing_metric) and args.metric_name is None:
380+
raise Exception(
381+
"--metric-name must be passed if --passing-metric or --failing-metric is."
382+
)
383+
453384
args.workaround_buggy_container = set(args.workaround_buggy_container)
454385
# --{passing,failing}-commits are deprecated aliases for --{passing,failing}-versions.
455386
for prefix in ["passing", "failing"]:
@@ -506,9 +437,7 @@ def parse_args(args=None) -> argparse.Namespace:
506437
# If the container-level search is being skipped, because a valid combination
507438
# of --{passing,failing}-{versions,container} is passed, then no container-level
508439
# search options should be passed.
509-
assert (
510-
args.container is None and args.start_date is None and args.end_date is None
511-
), (
440+
assert not container_search_options_passed, (
512441
"No container-level search options should be passed if the passing/failing"
513442
" containers/versions have been passed explicitly."
514443
)
@@ -530,13 +459,7 @@ def parse_args(args=None) -> argparse.Namespace:
530459
else:
531460
# None of --{passing,failing}-{versions,container} were passed, make sure the
532461
# compulsory arguments for the container-level search were passed
533-
assert (
534-
args.container is not None
535-
), "--container must be passed for the container-level search"
536-
537-
args.optional_software = optional_software.copy()
538-
if args.exclude_transformer_engine:
539-
args.optional_software.remove("transformer-engine")
540-
462+
assert args.container is not None, (
463+
"--container must be passed for the container-level search"
464+
)
541465
return args
542-
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# syntax=docker/dockerfile:1
2+
ARG BASE_IMAGE
3+
ARG BUILD_CMD
4+
ARG GIT_CMD
5+
FROM ${BASE_IMAGE}
6+
ARG BUILD_CMD
7+
ARG GIT_CMD
8+
RUN sh -c "${GIT_CMD}"
9+
RUN --mount=type=cache,target=/root/.cache sh -c "${BUILD_CMD}"

.github/triage/jax_toolbox_triage/docker.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ def __init__(
2424

2525
def __enter__(self):
2626
self._logger.debug(f"Launching {self}")
27-
have_gpus = shutil.which("nvidia-smi") is not None
27+
have_gpus = (
28+
shutil.which("nvidia-smi") is not None
29+
and subprocess.run(["nvidia-smi"]).returncode == 0
30+
)
2831
if not have_gpus:
2932
self._logger.warning("No GPUs detected!")
30-
gpu_args = ["--gpus=all"] if have_gpus else []
33+
gpu_args = (
34+
["--gpus=all"] if have_gpus else ["-e", "NVIDIA_VISIBLE_DEVICES=void"]
35+
)
3136
result = run_and_log(
3237
[
3338
"docker",
@@ -93,6 +98,7 @@ def exists(self) -> bool:
9398
"""
9499
Check if the given container exists.
95100
"""
101+
# TODO: check without pulling; docker manifest inspect
96102
result = run_and_log(
97103
["docker", "pull", self._url],
98104
logger=self._logger,

0 commit comments

Comments
 (0)