Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions deepspeed/autotuning/autotuner.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@ def get_best_space_record(self, space_name):
space_num_exps = 0
for (exp, metric_val, num_exps) in space_records:
space_num_exps += num_exps
if metric_val is None:
# a run that did not produce a metric (e.g. OOM) is not a valid candidate
continue
Comment on lines +723 to +725

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Guard the caller when every fast metric is absent

When a fast micro-batch pass records only one None metric (for example, a user-specified micro-batch list whose run writes a null metric), this branch now makes get_best_space_record() return None; tune_space() then unconditionally evaluates fast_best_record[0]['name'] at line 586. That turns a case which previously completed with a single such record into a 'NoneType' object is not subscriptable failure, so the caller must avoid dereferencing the missing record.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this was a real regression. Fixed in 7d770d0: fast_best_record[0]['name'] now goes through the same None-safe ternary as fast_best_mbs and fast_best_metric_val on the lines right above it.

if best_space_record is None or metric_val > best_space_record[1]:
best_space_record = (exp, metric_val)
if best_space_record:
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/autotuning/test_autotuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,27 @@ def test_autotuner_resources(tmpdir, active_resources):
assert expected_num_gpus == tuner.exp_num_gpus


def test_get_best_space_record_ignores_runs_without_a_metric(tmpdir):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the required sign-off trailer

Commit 23e6d10f3f945c85542953b0531273181fdf8150 is a non-merge commit, but its message has no Signed-off-by: trailer, violating the repository's mandatory commit requirement; add the author sign-off before this change is merged.

AGENTS.md reference: AGENTS.md:L8-L8

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it's pointing at a commit that isn't part of this PR (23e6d10f3f9...). The actual commit here is 5c25751, which does have a Signed-off-by trailer, you can see it with git log or the commits tab.

# A run that fails to produce a metric (e.g. OOM) is recorded with metric_val=None.
# get_best_space_record used to crash with "TypeError: '>' not supported between
# instances of 'NoneType' and 'NoneType'" whenever every run in a space had no
# metric, discarding all previously gathered results for the whole tuning run.
config_dict = {"autotuning": {"enabled": True, "exps_dir": os.path.join(tmpdir, 'exps_dir'), "arg_mappings": {}}}
config_path = create_config_from_dict(tmpdir, config_dict)
args = dsrun.parse_args(args=f'--autotuning {TUNE_OPTION} foo.py --deepspeed_config {config_path}'.split())
tuner = Autotuner(args=args, active_resources={"worker-0": [0, 1]})

tuner.update_records("z0_space", {"name": "exp1"}, None, 1)
tuner.update_records("z0_space", {"name": "exp2"}, None, 1)
assert tuner.get_best_space_record("z0_space") is None

tuner.update_records("z0_space", {"name": "exp3"}, 3.5, 1)
best = tuner.get_best_space_record("z0_space")
assert best[0]["name"] == "exp3"
assert best[1] == 3.5
assert best[2] == 3


def test_get_val_by_key_searches_all_nested_subdicts():
# get_val_by_key must mirror its sibling set_val_by_key: both walk every
# nested subdict, not just the first one. Here 'device' lives in the SECOND
Expand Down
Loading