Keep the elasticity batch overrides out of the caller's config dict - #8329
Keep the elasticity batch overrides out of the caller's config dict#8329alanhuangyoo wants to merge 2 commits into
Conversation
DeepSpeedConfig stores the dict it is handed by reference, and deepspeedai#8289 established that parsing must not write back into it -- the caller owns that dict and may reuse it afterwards. The elasticity branch still does, two lines above the comment that says otherwise: it assigns train_batch_size, train_micro_batch_size_per_gpu and gradient_accumulation_steps into self._param_dict before the copy is taken. A caller that passes a config with elasticity enabled gets three keys back that it never set, and print_user_config() then reports them as though the user had. Collect the overrides and apply them to the copy instead. All three are top-level keys, so the existing shallow copy is enough to keep them off the caller's dict, and the parsed values are unchanged. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
ebarkhordar
left a comment
There was a problem hiding this comment.
I ran the double-parse case against b0dee8d and its merge base 32e301f in a clean container. The change looks right to me. The re-parse sentence in the description has the direction backwards.
It says a config "that was rejected on the first pass would be accepted on a re-parse". The guard at config.py:764 raises when the batch keys are present, so it is the other way round: the first parse succeeds and injects them, and the second parse of the same dict is the one that fails.
Elasticity on, ignore_non_elastic_batch_info unset, DeepSpeedConfig(d) twice on the same dict:
32e301ffa parse 1 OK, caller dict gains the 3 batch keys
parse 2 ElasticityConfigError: One or more batch related parameters were found in your ds_config (...)
b0dee8df2 parse 1 OK, caller dict unchanged
parse 2 OK
That is a stronger case for the fix than the description makes: on master, re-parsing a dict you just parsed is a hard error, and its text asks the user to remove three keys they never wrote.
It is also unpinned. test_elasticity_leaves_caller_config_untouched sets ignore_non_elastic_batch_info: True, which switches that guard off, so it cannot reach the path. Leaving the flag out and calling DeepSpeedConfig(config_dict) twice covers it.
I exercised only the dict branch, not the json path.
The test set ignore_non_elastic_batch_info, which switches off the guard that rejects batch parameters under elasticity -- the one the injected keys trip. With the flag on, the second parse cannot fail, so the test would have passed with or without the write-back. Drop the flag and parse the same dict twice. On master the first parse injects the three keys and the second is rejected with a message naming parameters the caller never wrote. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
|
You are right on both, thanks — and the repro is better than what I wrote. I had the direction backwards. The guard at Description rewritten around that: on master, re-parsing a dict you just parsed is a hard error whose message names three keys the caller never wrote. The test was unpinned exactly as you say — Left the json branch alone — same as you, I only exercised the dict path. |
|
Confirmed at It stops at line 293, so the second parse is unreached at base, which matches your reading of it as the backstop rather than the primary assertion. I only ran the dict path, same as you. |
|
The red on That lane passed on The lane is green on master, so this looks like a flake in that test rather than a broken lane. Happy to rebase if a re-run is easier than taking my word for it. The rest of the lanes on this PR are sitting in |
DeepSpeedConfigkeeps the dict it is handed by reference:#8289 established that parsing must not write back into it — the caller owns that dict and may reuse it after initialization.
The elasticity branch still does, two lines above the comment that says otherwise:
A caller that enables elasticity gets three keys back that it never set.
print_user_config()dumpsself._param_dict, so it then reports them as though the user had written them.It also makes the dict unparseable a second time. The elasticity path rejects those keys in the input unless
ignore_non_elastic_batch_infois set:The first parse succeeds and injects them; the second parse of the same dict trips that guard, and its message asks the user to remove three keys they never wrote.
The fix
Collect the overrides and apply them to the copy. All three are top-level keys, so the existing shallow copy keeps them off the caller's dict.
Test
DeepSpeedConfig(config_dict)twice on an elasticity config, withignore_non_elastic_batch_infoleft out so the guard is live:before
after
Parsed values are unchanged either way (
train_batch_size=4 micro=2 gas=2), so this only removes the write-back.test_elasticity_leaves_caller_config_untouchedsits next to #8289'stest_max_grad_norm_leaves_caller_config_untouchedand covers both symptoms. On master it fails at