2025, Oct 31 05:32
RLlib CTDE में PrioritizedEpisodeReplayBuffer फेल: EpisodeReplayBuffer से स्थिर ट्रेनिंग गाइड
RLlib में CTDE/CTCE पर जटिल observations के साथ PrioritizedEpisodeReplayBuffer से क्रैश? समाधान: EpisodeReplayBuffer, सही sequence सेटिंग्स और स्थिर SAC कॉन्फ़िग।
RLlib के साथ Centralized Training, Decentralized Execution (CTDE) एक शक्तिशाली पैटर्न है, लेकिन मल्टी-एजेंट ग्रुपिंग और रीप्ले बफर्स के आसपास का ग्लू-कोड अक्सर ऑब्जर्वेशन की संरचना के प्रति अप्रत्याशित रूप से संवेदनशील होता है। यह गाइड उस व्यावहारिक फेल्योर मोड से गुजरती है जो Ray RLlib के GroupAgentsWrapper, CTDE के लिए एक कस्टम RLModule, और PrioritizedEpisodeReplayBuffer को मिलाने पर सामने आता है, और एक ऐसी कॉन्फ़िगरेशन दिखाती है जो मॉडल लॉजिक या एनवायरनमेंट की सेमांटिक्स बदले बिना स्थिरता से ट्रेन होती है।
समस्या का अवलोकन
ValueFunctionAPI, TargetNetworkAPI और QNetAPI को लागू करने वाला एक कस्टम RLModule, PPO, APPO और SAC के साथ इस्तेमाल किया जा रहा है। GroupAgentsWrapper के जरिए एक मल्टी-एजेंट एनवायरनमेंट को सिंगल-एजेंट इंटरफेस में समूहित किया गया है। टेस्ट एनवायरनमेंट (Rock–Paper–Scissors का एक वेरिएंट) में ray.tune.Tuner के तहत PPO/APPO/SAC के साथ ट्रेनिंग सफल रहती है। लेकिन समान observation-space संरचना वाले वास्तविक एनवायरनमेंट में, PrioritizedEpisodeReplayBuffer इस्तेमाल करने पर ट्रेनिंग असफल हो जाती है। पहला स्पष्ट लक्षण single_agent_episode.concat_episode में एक assertion है:
assert np.all(other.observations[0] == self.observations[-1]) — ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
डिबगिंग के लिए उस assertion को बायपास करने पर ट्रेनिंग कुछ आगे बढ़ती है, और फिर सैंपलिंग के दौरान prioritized episode बफर के सेगमेंट ट्री के भीतर गहराई में out-of-range prefix-sum assertion के साथ फेल हो जाती है।
ग्रुप की गई ऑब्जर्वेशन संरचना टेस्ट और वास्तविक दोनों सेटिंग्स में एक जैसी है: मल्टी-एजेंट Dict({"agent1": Box, "agent2": Box}) को सिंगल-एजेंट Dict({"grouped": Tuple(Box, Box)}) में बदला जाता है। इसके बावजूद, prioritized बफर वाला पाथ केवल वास्तविक एनवायरनमेंट में टूटता है।
पुनरुत्पादन योग्य उदाहरण
नीचे दिया गया एनवायरनमेंट काम कर रहे Rock–Paper–Scissors टेस्ट का प्रतिबिंब है। यह वही प्रोग्राम लॉजिक बनाए रखता है, बस प्रतीक और नाम अलग हैं।
class RpsArena(MultiAgentEnv):
    MOVE_R = 0
    MOVE_P = 1
    MOVE_S = 2
    OUTCOME = {
        (MOVE_R, MOVE_R): (0, 0),
        (MOVE_R, MOVE_P): (-1, 1),
        (MOVE_R, MOVE_S): (1, -1),
        (MOVE_P, MOVE_R): (1, -1),
        (MOVE_P, MOVE_P): (0, 0),
        (MOVE_P, MOVE_S): (-1, 1),
        (MOVE_S, MOVE_R): (-1, 1),
        (MOVE_S, MOVE_P): (1, -1),
        (MOVE_S, MOVE_S): (0, 0),
    }
    def __init__(self, env_config=None):
        super().__init__()
        self.agent_names = ["p1", "p2"]
        self.agents = self.possible_agents = self.agent_names
        self.observation_spaces = self.action_spaces = gym.spaces.Dict({
            "p1": gym.spaces.Box(low=0, high=2, shape=(1,)),
            "p2": gym.spaces.Box(low=0, high=2, shape=(1,)),
        })
        self.turns = 0
    def reset(self, *, seed=None, options=None):
        self.turns = 0
        return {
            "p1": np.array([0.0], dtype=np.float32),
            "p2": np.array([0.0], dtype=np.float32),
        }, {}
    def step(self, action_dict):
        self.turns += 1
        m1 = int(action_dict["p1"].item())
        m2 = int(action_dict["p2"].item())
        obs = {
            "p1": np.array([m2], dtype=np.float32),
            "p2": np.array([m1], dtype=np.float32),
        }
        r1, r2 = self.OUTCOME[m1, m2]
        rew = {"p1": r1, "p2": r2}
        dones = {"__all__": bool(self.turns >= 10)}
        truncs = {"__all__": bool(self.turns >= 10)}
        return obs, rew, dones, truncs, {}
class RpsGrouped(MultiAgentEnv):
    def __init__(self, env_config=None):
        super().__init__()
        base = RpsArena(env_config)
        tuple_obs = self._to_tuple_space(base.observation_spaces)
        tuple_act = self._to_tuple_space(base.action_spaces)
        self.core = base.with_agent_groups(
            groups={"pack": ["p1", "p2"]},
            obs_space=tuple_obs,
            act_space=tuple_act,
        )
        self.agent_names = ["pack"]
        self.agents = self.possible_agents = self.agent_names
        self.orig_ids = base.agent_names
        self.observation_space = gym.spaces.Dict({"pack": tuple_obs})
        self.action_space = gym.spaces.Dict({"pack": tuple_act})
    def reset(self, *, seed=None, options=None):
        obs, info = self.core.reset(seed=seed, options=options)
        grouped = {k: tuple(v) for k, v in obs.items()}
        return grouped, info
    def step(self, action_dict):
        obs, rew, dones, truncs, info = self.core.step(action_dict)
        grouped = {k: tuple(v) for k, v in obs.items()}
        total_rew = sum(rew.values())
        return grouped, total_rew, dones["__all__"], truncs["__all__"], info
    @staticmethod
    def _to_tuple_space(dspace: gym.spaces.Dict) -> gym.spaces.Tuple:
        keys = sorted(dspace.keys())
        return gym.spaces.Tuple(tuple(dspace[k] for k in keys))
कस्टम CTDE मॉड्यूल के साथ असफल होने वाला पाथ ट्रिगर करने वाली SAC कॉन्फ़िगरेशन कुछ ऐसी दिखती है:
train_cfg = (
    SACConfig()
    .environment(RpsGrouped, env_config={})
    .framework("torch")
    .rl_module(
        rl_module_spec=RLModuleSpec(
            module_class=CtdeModule,
            observation_space=RpsGrouped.observation_space,
            action_space=RpsGrouped.action_space,
        )
    )
    .training(
        twin_q=True,
        replay_buffer_config={"type": "PrioritizedEpisodeReplayBuffer"},
    )
    .evaluation(evaluation_config=SACConfig.overrides(exploration=False))
)
असल में गड़बड़ क्या है
फेल्योर एपिसोड्स को जोड़ते समय, और बाद में prioritized episode बफर के सेगमेंट ट्री से सैंपलिंग करते वक्त दिखाई देता है। दर्ज पैटर्न यह है कि CTCE और CTDE में इस्तेमाल होने पर, कस्टम RLModule के साथ मिलकर PrioritizedEpisodeReplayBuffer जटिल observations को संभाल नहीं पाता। वही मॉडल और observation-space संरचना ग्रुप किए गए Rock–Paper–Scissors टेस्ट में काम करती है, लेकिन वास्तविक एनवायरनमेंट में नहीं—हालांकि दोनों सिंगल-एजेंट इंटरफेस पर Dict({"grouped": Tuple(Box, Box)}) ही एक्सपोज़ करते हैं।
कारगर समाधान
PrioritizedEpisodeReplayBuffer से हटते ही समस्या दूर हो जाती है। CTCE और CTDE के लिए EpisodeReplayBuffer का उपयोग करें। DTDE के लिए MultiAgentEpisodeReplayBuffer लें। साथ ही, replay_buffer_config में replay_sequence_length को 1, replay_burn_in को 0, और replay_zero_init_states को True पर सेट करें, और कलेक्शन को complete episodes मोड में चलाएँ। इन बदलावों के साथ, एनवायरनमेंट या RLModule APIs में कुछ बदले बिना ट्रेनिंग सही ढंग से आगे बढ़ती है।
CTDE के लिए समायोजित SAC सेटअप इस प्रकार है:
stable_cfg = (
    SACConfig()
    .environment(RpsGrouped, env_config={})
    .framework("torch")
    .rl_module(
        rl_module_spec=RLModuleSpec(
            module_class=CtdeModule,
            observation_space=RpsGrouped.observation_space,
            action_space=RpsGrouped.action_space,
        )
    )
    .training(
        twin_q=True,
        replay_buffer_config={
            "type": "EpisodeReplayBuffer",
            "replay_sequence_length": 1,
            "replay_burn_in": 0,
            "replay_zero_init_states": True,
        },
    )
    .env_runners(batch_mode="complete_episodes")
    .evaluation(evaluation_config=SACConfig.overrides(exploration=False))
)
यदि आप DTDE में काम कर रहे हैं, तो EpisodeReplayBuffer की जगह MultiAgentEpisodeReplayBuffer लगाएँ और sequence-length, burn-in, तथा zero-init सेटिंग्स वही रखें।
यह क्यों मायने रखता है
RLlib में मल्टी-एजेंट पाइपलाइन्स लगातार एपिसोड सिलाई और रीप्ले सैंपलिंग पर निर्भर करती हैं। जब observations संरचित रूप ले लेते हैं—जैसे arrays का Tuple और वह भी Dict में—तो कुछ बफर इम्प्लीमेंटेशन कस्टम मॉड्यूल प्लंबिंग के साथ इस आकृति के प्रति संवेदनशील हो सकते हैं। CTCE, CTDE या DTDE में जटिल observations पर कौन से रीप्ले बफर भरोसेमंद हैं, यह जानना घंटों की डिबगिंग बचाता है और उन सूक्ष्म क्रैश से बचाता है जो ट्रेनिंग के देर से चरणों में सामने आते हैं।
व्यावहारिक निष्कर्ष
यदि जटिल observations और कस्टम RLModule के साथ काम करते समय single_agent_episode.concat_episode में assertions दिखें या prioritized episode बफर में सैंपलिंग एरर आएँ, तो सबसे पहले PrioritizedEpisodeReplayBuffer को बदलकर देखें। CTCE और CTDE के लिए EpisodeReplayBuffer उपयुक्त है, जबकि DTDE में MultiAgentEpisodeReplayBuffer बेहतर रहता है। replay_sequence_length को 1, replay_burn_in को 0, replay_zero_init_states को True पर सेट करें, और बैचों को complete episodes के रूप में कलेक्ट करें। यह संयोजन आपके एनवायरनमेंट लॉजिक या CTDE मॉड्यूल को छुए बिना ट्रेनिंग लूप को स्थिर रखता है।
यह लेख StackOverflow पर प्रश्न और Nelson Salazar द्वारा दिए गए उत्तर पर आधारित है, दोनों ही Nelson Salazar के हैं।