[RLlib] Fix AlgorithmConfig.to_dict() for New API Stack - #63695
Conversation
Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
f7efa84 to
33a2a7e
Compare
There was a problem hiding this comment.
Code Review
This pull request updates AlgorithmConfig.to_dict() to dynamically overwrite the legacy train_batch_size key with total_train_batch_size and explicitly expose new API variables when using the new API stack. However, adding total_train_batch_size to the returned dictionary will cause update_from_dict() to crash with an AttributeError because total_train_batch_size is a read-only property without a setter.
| config["train_batch_size"] = self.total_train_batch_size | ||
|
|
||
| # 2. Expose the new API variables explicitly so they don't return "Not found" | ||
| config["total_train_batch_size"] = self.total_train_batch_size |
There was a problem hiding this comment.
Adding total_train_batch_size to the dictionary returned by to_dict() will cause update_from_dict() (and consequently from_dict()) to crash with an AttributeError.\n\n### Why this happens:\n1. to_dict() now injects "total_train_batch_size" into the returned dictionary.\n2. When restoring a config or updating it from a dictionary (e.g., during Tune trial creation or checkpoint restoration), update_from_dict() iterates over the dictionary keys and calls setattr(self, key, value) for any keys not explicitly handled.\n3. total_train_batch_size is a read-only property on AlgorithmConfig (it has a getter but no @total_train_batch_size.setter).\n4. In Python, calling setattr() on a read-only property raises an AttributeError: can't set attribute.\n\n### How to fix:\nTo prevent this crash, you must either:\n- Add a dummy setter for total_train_batch_size in AlgorithmConfig (around line 4291) that ignores the value or handles it appropriately:\n python\n @total_train_batch_size.setter\n def total_train_batch_size(self, value: int) -> None:\n pass\n \n- Or, avoid adding total_train_batch_size to the dictionary in to_dict() if it is not strictly required by external legacy APIs.
…e_from_dict crash Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
8cc7d60 to
3ef559a
Compare
|
I thought heavily about adding a dummy setter, but I realized that's an anti-pattern that could lead to silent failures. Instead, I went with the cleaner approach: I omitted the read-only total_train_batch_size key entirely. |
…API stack When enable_rl_module_and_learner=True, to_dict() was injecting 'train_batch_size_per_learner' as a computed value into the dictionary. This caused a lossy round-trip through update_from_dict(): the property setter would permanently pin _train_batch_size_per_learner, breaking the dynamic inference link from train_batch_size and num_learners. Fix: only overwrite the legacy 'train_batch_size' key with the computed total_train_batch_size. The private backing field '_train_batch_size_per_learner' (which may be None for inferred configs) is already serialized correctly via vars(self) and round-trips safely. Fixes: ray-project#63669 Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 02f8ce9. Configure here.
pseudo-rnd-thoughts
left a comment
There was a problem hiding this comment.
Thanks for the PR @AyushKashyapII
Could you add a test to check the roundtripping of to_dict
Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
87e1a78 to
463d244
Compare
|
Thanks for the review! Let me know if you need any other tests added! |
6c9dd26 to
bb3c545
Compare
Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
bb3c545 to
4694437
Compare
|
@AyushKashyapII The test is failing with |
Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
|
Hey @pseudo-rnd-thoughts Thanks for catching that!Just pushed a fix for both issues: Updated the test assertion–it was incorrectly looking for the public property we just removed from to_dict(), so I swapped it to check the private variable instead. |
pseudo-rnd-thoughts
left a comment
There was a problem hiding this comment.
Test looks good but the if statements don't make sense to me
| # If not set explicitly, try to infer the value. | ||
| if self._train_batch_size_per_learner is None: | ||
| if self.train_batch_size is None: |
There was a problem hiding this comment.
I think this should raise an error. When would this occur?
There was a problem hiding this comment.
This scenario actually occurs when an algorithm explicitly unsets train_batch_size because it uses a completely different batching logic. For example, DreamerV3 sets train_batch_size = None and uses its own batch_size_B and batch_length_T variables instead. If we raise an error here, calling config.to_dict() on a DreamerV3 config will crash because to_dict() attempts to evaluate total_train_batch_size for everything using the new API stack. However, you are right that for standard algorithms we should fail loudly. I've updated the PR to raise a ValueError here and catch the error inside to_dict() so that algorithms with non-standard batching still work.
| """Returns the effective total train batch size. | ||
|
|
||
| New API stack: `train_batch_size_per_learner` * [effective num Learners]. | ||
|
|
||
| @OldAPIStack: User never touches `train_batch_size_per_learner` or | ||
| `num_learners`) -> `train_batch_size`. | ||
| """ | ||
| if self.train_batch_size_per_learner is None: |
fe37009 to
41241ea
Compare
Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
41241ea to
8cfc328
Compare
|
Hi @pseudo-rnd-thoughts , I wanted to follow up on this PR. The review feedback has been incorporated and the PR is currently approved. If the implementation looks good, would it be possible to move it forward for merge? If there are any remaining concerns or changes you'd like to see, I'm happy to address them. Thanks again for your help and review.. |
pseudo-rnd-thoughts
left a comment
There was a problem hiding this comment.
Just one last comment
| if self.enable_rl_module_and_learner: | ||
| try: | ||
| config["train_batch_size"] = self.total_train_batch_size | ||
| except ValueError: |
There was a problem hiding this comment.
When could this ValueError happen?
There was a problem hiding this comment.
its raised by the @Property getters when both _train_batch_size_per_learner and train_batch_size are None or neither batch size has been configured.
This happens when to_dict() is called on a partially-configured AlgorithmConfig before the user has explicitly set any batch size (like maybe during initialization, or serialization before .validate() is called). It also happens for algorithms that intentionally leave standard batch sizes unset (like DreamerV3).
The try/except lets to_dict() handle this gracefully .
|
Hi @pseudo-rnd-thoughts , following up on this PR. The review comments have been resolved and approved. Is there anything else needed before it can be merged? |
…63695) ## Why are these changes needed? This PR fixes ray-project#63669. Currently, when a user is on the New API Stack (`enable_rl_module_and_learner=True`) and sets `train_batch_size_per_learner`, `config.to_dict()` returns misleading data. Because `to_dict()` relies on `vars(self)`, it dumps the stale legacy `train_batch_size` attribute (e.g., 4000) and fails to expose the new calculated properties, leading to incorrect experiment logging. **What this PR does:** - Updates `AlgorithmConfig.to_dict()` to explicitly check if `enable_rl_module_and_learner` is True. - Dynamically overwrites the legacy `train_batch_size` key with the true `self.total_train_batch_size`. - Injects `total_train_batch_size` and `train_batch_size_per_learner` into the dictionary so they no longer return as "Not found". - Updates the method docstring to warn users about this dynamic replacement. ## Related issue number Fixes ray-project#63669 --------- Signed-off-by: Ayush KAshyap <kashyap11ayush02@gmail.com>
## Description
`linux://rllib:examples/ray_tune/appo_hyperparameter_tune` was failing
with
```
==================== Test output for //rllib:examples/ray_tune/appo_hyperparameter_tune:
Traceback (most recent call last):
File "rllib/examples/ray_tune/appo_hyperparameter_tune.py", line 155, in <module>
tuner = tune.Tuner(
^^^^^^^^^^^
File "/rayci/python/ray/tune/tuner.py", line 135, in __init__
self._local_tuner = TunerInternal(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/tune/impl/tuner_internal.py", line 161, in __init__
self.param_space = param_space
^^^^^^^^^^^^^^^^
File "/rayci/python/ray/tune/impl/tuner_internal.py", line 502, in param_space
param_space = param_space.to_dict()
^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/rllib/algorithms/algorithm_config.py", line 753, in to_dict
config["train_batch_size"] = self.total_train_batch_size
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/rllib/algorithms/algorithm_config.py", line 4321, in total_train_batch_size
return self.train_batch_size_per_learner * (self.num_learners or 1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for *: 'Integer' and 'int'
================================================================================
```
#63695 introduced a property for
`total_train_batch_size` however this causes an issue for RLlib + Ray
Tune.
This PR largely reverts the #63695 and adds a check that a Tune
parameter is usable
Signed-off-by: Mark Towers <mark@anyscale.com>
Co-authored-by: Mark Towers <mark@anyscale.com>
## Description
`linux://rllib:examples/ray_tune/appo_hyperparameter_tune` was failing
with
```
==================== Test output for //rllib:examples/ray_tune/appo_hyperparameter_tune:
Traceback (most recent call last):
File "rllib/examples/ray_tune/appo_hyperparameter_tune.py", line 155, in <module>
tuner = tune.Tuner(
^^^^^^^^^^^
File "/rayci/python/ray/tune/tuner.py", line 135, in __init__
self._local_tuner = TunerInternal(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/tune/impl/tuner_internal.py", line 161, in __init__
self.param_space = param_space
^^^^^^^^^^^^^^^^
File "/rayci/python/ray/tune/impl/tuner_internal.py", line 502, in param_space
param_space = param_space.to_dict()
^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/rllib/algorithms/algorithm_config.py", line 753, in to_dict
config["train_batch_size"] = self.total_train_batch_size
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/rayci/python/ray/rllib/algorithms/algorithm_config.py", line 4321, in total_train_batch_size
return self.train_batch_size_per_learner * (self.num_learners or 1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: unsupported operand type(s) for *: 'Integer' and 'int'
================================================================================
```
#63695 introduced a property for
`total_train_batch_size` however this causes an issue for RLlib + Ray
Tune.
This PR largely reverts the #63695 and adds a check that a Tune
parameter is usable
Signed-off-by: Mark Towers <mark@anyscale.com>
Co-authored-by: Mark Towers <mark@anyscale.com>
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>


Why are these changes needed?
This PR fixes #63669.
Currently, when a user is on the New API Stack (
enable_rl_module_and_learner=True) and setstrain_batch_size_per_learner,config.to_dict()returns misleading data. Becauseto_dict()relies onvars(self), it dumps the stale legacytrain_batch_sizeattribute (e.g., 4000) and fails to expose the new calculated properties, leading to incorrect experiment logging.What this PR does:
AlgorithmConfig.to_dict()to explicitly check ifenable_rl_module_and_learneris True.train_batch_sizekey with the trueself.total_train_batch_size.total_train_batch_sizeandtrain_batch_size_per_learnerinto the dictionary so they no longer return as "Not found".Related issue number
Fixes #63669