Repository navigation
Conversation
RewardClipping declares clamp_min and clamp_max as optional (None by default, and RewardClippingConfig exposes them as optional fields), and _apply_transform has branches for a one-sided clip. The constructor, however, passes both values to torch.as_tensor, so RewardClipping(clamp_max=1.0) or RewardClipping(clamp_min=0.0) raises "RuntimeError: Could not infer dtype of NoneType" before the transform is built. Keep a missing bound as None, bound the reward spec on the open side by the extreme value of the reward dtype (as ClipTransform does), print None in __repr__, and raise a TypeError when neither bound is given. The docstring now names the real arguments (clamp_min/clamp_max, not clip_min/clip_max), documents in_keys/out_keys and has an example.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/rl/4622
Note: Links to docs will display an error until the docs builds have been completed.
|
coder-jayp
left a comment
There was a problem hiding this comment.
LGTM, thank you for the contribution!
The fix cleanly addresses single-sided clipping across the constructor, spec transformation, and string representation, while adding proper validation and docstring updates. The regression tests in test_reward_transforms.py cover both single-bound configurations and spec bounds thoroughly.
| self.register_buffer("clamp_min", clamp_min_tensor) | ||
| self.register_buffer("clamp_max", clamp_max_tensor) | ||
| if clamp_min is None and clamp_max is None: | ||
| raise TypeError( |
There was a problem hiding this comment.
I think it would be better to have this as a ValueError instead.
There was a problem hiding this comment.
Done in 31b4786: RewardClipping() with no bound now raises ValueError, and test_no_bound_raises checks for it.
| ) | ||
|
|
||
| def __repr__(self) -> str: | ||
| clamp_min = None if self.clamp_min is None else f"{float(self.clamp_min):4.4f}" |
There was a problem hiding this comment.
I'd prefer to stick with significant figures so can we use :.4g. cc @bsprenger what do you think?
There was a problem hiding this comment.
Done in 31b4786: __repr__ now formats both bounds with :.4g (so clamp_max=1e-5 prints as 1e-05 instead of 0.0000), and a new test_repr covers it.
theap06
left a comment
There was a problem hiding this comment.
Thanks for contributing! Few nitpicks on my end but overall looks great!
…ping repr Address review on pytorch#4622: RewardClipping() with neither clamp_min nor clamp_max now raises ValueError instead of TypeError, and __repr__ formats the bounds with :.4g instead of :4.4f, so a bound such as 1e-5 is no longer printed as 0.0000. Add a test for the repr.
Description
RewardClippingdefaultsclamp_minandclamp_maxtoNone,RewardClippingConfigexposesboth as optional, and
_apply_transformalready has a branch for each one-sided case. Theconstructor, however, calls
torch.as_tensoron both values, soRewardClipping(clamp_max=1.0)and
RewardClipping(clamp_min=0.0)raiseRuntimeError: Could not infer dtype of NoneType.This PR:
None(registered as aNonebuffer);as
ClipTransformdoes, socheck_env_specspasses;Nonefor a missing bound in__repr__instead of callingfloat(None), and printsthe bounds to 4 significant figures (
:.4g);ValueErrorwhen neither bound is given (same message style asClipTransform;RewardClipping()raisedRuntimeErrorbefore);clip_min/clip_maxinstead of the real argumentnames, documents
in_keys/out_keys, and adds an example.Two-sided clipping is unchanged.
flowchart LR subgraph Before A1["RewardClipping(clamp_max=1.0)"] --> B1["torch.as_tensor(None)"] --> C1["RuntimeError"] end subgraph After A2["RewardClipping(clamp_max=1.0)"] --> B2["clamp_min buffer = None"] B2 --> C2["_apply_transform: reward.clamp_max(1.0)"] B2 --> D2["reward spec: Bounded(finfo.min, 1.0)"] endOutput of the same script on
mainand on this branch (CPU, Windows):Motivation and Context
One-sided reward clipping (for example, flooring large negative penalties) is advertised by the
signature and by
RewardClippingConfig, but has never worked. The linked issue has the minimal reproduction.Types of changes
Checklist
Testing
TestRewardClipping.test_one_sided_bound(lower-only and upper-only): clips four hand-pickedrewards on one side only, then checks the reward spec bounds of a
TransformedEnvand runscheck_env_specs.test_no_bound_raiseschecks theValueError. All three cases fail onmainwith
RuntimeError: Could not infer dtype of NoneTypeand pass on this branch.test_reprchecks the printed bounds (4 significant figures,
Nonefor a missing bound).pytest test/transforms/test_reward_transforms.py -k TestRewardClipping: 16 passed, 1 skipped(the existing "no inverse" skip), on Windows 11, Python 3.11, torch 2.14.1+cpu,
tensordict 0.15.3. The whole file: 285 passed, 7 skipped.
pre-commit run --fileson both files: all hooks pass (autoflakeandcheck-docstring-argsrun by hand, since their entries expect Linux executable names).Fixes #4621