Repository navigation
[Bug]: n_steps > 1 is silently ignored when replay_buffer_class is passed explicitly #2274
Description
🐛 Bug
When replay_buffer_class is passed explicitly to an off-policy algorithm, n_steps > 1
is silently ignored and the algorithm trains with 1-step returns.
In OffPolicyAlgorithm._setup_model(), both the n-step buffer selection and its validation
live inside the if self.replay_buffer_class is None: branch. When the user supplies the
class, neither runs: self.n_steps keeps its value, but the buffer is not an
NStepReplayBuffer, so sample() returns discounts=None and train() falls back to
discounts = self.gamma. No exception and no warning are raised.
To Reproduce
1. n_steps is accepted but never applied when the buffer class is given explicitly
from stable_baselines3 import SAC
from stable_baselines3.common.buffers import ReplayBuffer
model = SAC("MlpPolicy", "Pendulum-v1", n_steps=3, buffer_size=200,
replay_buffer_class=ReplayBuffer)
print(model.n_steps, type(model.replay_buffer).__name__)3 ReplayBuffer
n_steps=3 is stored on the model, but the buffer built cannot compute n-step returns.
sample() therefore returns discounts=None, and train() falls back to
discounts = self.gamma, i.e. 1-step returns. Nothing is raised or warned.
2. The same configuration is rejected or accepted depending only on whether the class is named
from stable_baselines3 import SAC
from stable_baselines3.common.envs import BitFlippingEnv
from stable_baselines3.her import HerReplayBuffer
env_kwargs = dict(n_bits=4, continuous=True)
try:
SAC("MultiInputPolicy", BitFlippingEnv(**env_kwargs), n_steps=3, buffer_size=200)
except AssertionError as e:
print("no explicit class ->", type(e).__name__ + ":", e)
m = SAC("MultiInputPolicy", BitFlippingEnv(**env_kwargs), n_steps=3, buffer_size=200,
replay_buffer_class=HerReplayBuffer, learning_starts=10)
print("explicit HerReplayBuffer -> no error;", type(m.replay_buffer).__name__, "n_steps =", m.n_steps)no explicit class -> AssertionError: N-step returns are not supported for Dict observation spaces yet.
explicit HerReplayBuffer -> no error; HerReplayBuffer n_steps = 3
Relevant log output / Error message
No error and no warning are raised in the silent cases -- that is the bug.
The only exception produced anywhere in the repro is:
AssertionError: N-step returns are not supported for Dict observation spaces yet.
It comes from the case that already behaves CORRECTLY: a Dict observation space with
n_steps=3 and no explicit replay_buffer_class. It is shown to highlight the inconsistency,
not because it is the problem being reported.System Info
No response
Checklist
- My issue does not relate to a custom gym environment. (Use the custom gym env template instead)
- I have checked that there is no similar issue in the repo
- I have read the documentation
- I have provided a minimal and working example to reproduce the bug
- I've used the markdown code blocks for both code and stack traces.
Activity
Koustav-github commentedon Aug 11, 2026
I'd be happy to work on this if you agree with the direction — could you assign it to me?
Proposed fix: move the n-step check out of the
if self.replay_buffer_class is None:branchin
_setup_model(), so it applies to the resolved buffer class regardless of where it came from:n_steps > 1+ Dict observation space → raise (currently only raised on the default path)n_steps > 1+ a class that is not anNStepReplayBuffersubclass → raise, naming the classn_steps > 1+ anNStepReplayBuffersubclass → forwardn_steps/gamma, as the default path already doesThe last case uses
issubclassrather than an identity check on purpose, so a user subclass ofNStepReplayBufferkeeps working — today such a subclass silently receives the buffer's owndefaults (
n_steps=3,gamma=0.99) instead of the values passed to the algorithm.One open question I'd like your call on: the existing Dict-obs check is an
assert. I'd leantowards
ValueErrorso both rejections share a type and survivepython -O, but that changesthe exception type on an error path — happy to keep
assertif you prefer the smaller diff.This has been the behaviour since 2.7.0, when
n_stepswas introduced; nothing about theinteraction with
replay_buffer_classhas changed since.Koustav-github commentedon Aug 12, 2026
Draft PR opened at #2275 with tests and a changelog entry — no hurry at all, just flagging it. Happy to adjust the
assertvsValueErrorchoice (and anything else) whenever you're back.araffin commentedon Aug 15, 2026
Hello,
n_stepsparameter is only meant to be used with the default replay buffer settings.If you pass
replay_buffer_class=MyCustomBuffer, you must passreplay_buffer_kwargs={"n_steps": 3}and then it should work.Instead of changing the way
n_stepsbehave, I would prefer to emit a warning whenn_steps > 1and a custom class is passed (replay_buffer_classnot None).Koustav-github commentedon Aug 15, 2026
Verified —
replay_buffer_class=NStepReplayBufferwithreplay_buffer_kwargs={"n_steps": 3}does compute n-step returns (
discountsis not None), so the supported path works as youdescribe. Dropping the behaviour change; I'll rework the PR as documentation plus the warning
you suggested.
Noting the documentation label — the current docstring is:
It names
NStepReplayBuffer, but doesn't say thatn_stepsis ignored whenreplay_buffer_classis passed, or that
replay_buffer_kwargsis the way to configure a custom buffer. I'd proposeexpanding it across
off_policy_algorithm.py,dqn.py,sac.py,td3.pyandddpg.pyto makeboth explicit. Happy to adjust the wording or the set of files.
One question that affects both the docs and the warning text:
gammaisn't forwarded either. Withthe buffer keeps its own default
gamma=0.99while the algorithm uses0.95, so n-step returnsget discounted differently from the critic target. Should the docs and warning tell users to pass
both
n_stepsandgammainreplay_buffer_kwargs?araffin commentedon Aug 15, 2026
Please don't use a LLM when replying to issues.
probably, but I would only trigger it when
n_steps > 1Koustav-github commentedon Aug 15, 2026
Alright, on it.
Koustav-github commentedon Aug 15, 2026
hey @araffin,
kindly check the commits now, warning is now triggered when n_steps > 1 and a custom class is passed, instead of changing the way n_steps behave.
Koustav-github commentedon Aug 16, 2026
hey @araffin,
I have addressed the issues and resolved it the one that u asked for in the last conversation on the pr branch. #2275
Koustav-github commentedon Aug 16, 2026
hey @araffin,
i saw the "llm generated" tag on pr #2275 given by u. So, its better to say upfront, yes, I have used llm to root-cause the issue and draft them, but regarding "tell your human", I was always here and I do understand that
this trained in 1 step (silently) because n_steps/gamma is only forwarded in the auto-selection path and nothing warns.
and Thanks for approving the changes. :)
Koustav-github commentedon Aug 16, 2026
Thanks @araffin for merging the pr, happy to contribute to SB3 :))))))