11import argparse
2- import ast
32import datetime
43import getpass
54import os
65import pathlib
7- import re
8- import sys
96import tempfile
107import typing
118import warnings
1916optional_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-
10619def 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-
0 commit comments