comrad-bench 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comrad/curriculum/__init__.py +30 -0
- comrad/curriculum/base.py +133 -0
- comrad/curriculum/learning_progress.py +136 -0
- comrad/curriculum/observer.py +100 -0
- comrad/curriculum/omni.py +74 -0
- comrad/curriculum/plr.py +208 -0
- comrad/curriculum/sequential.py +72 -0
- comrad/curriculum/uniform.py +20 -0
- comrad/enjoy.py +22 -0
- comrad/envs/__init__.py +0 -0
- comrad/envs/action_space.py +513 -0
- comrad/envs/doom_gym.py +766 -0
- comrad/envs/doom_params.py +252 -0
- comrad/envs/multi_wad_env.py +171 -0
- comrad/envs/multiagent/__init__.py +0 -0
- comrad/envs/multiagent/doom_multiagent.py +224 -0
- comrad/envs/multiagent/doom_multiagent_wrapper.py +592 -0
- comrad/envs/wad_catalog.py +46 -0
- comrad/examples/WandB_Scraper/plot_wandb.py +230 -0
- comrad/examples/agent_run_env.py +50 -0
- comrad/examples/inspect_obs.py +28 -0
- comrad/examples/single_player.py +21 -0
- comrad/examples/vizdoom.pyi +3233 -0
- comrad/examples/you_run_env.py +192 -0
- comrad/misc/resource_greed.py +90 -0
- comrad/models/__init__.py +0 -0
- comrad/models/doom_model.py +47 -0
- comrad/models/happo_model.py +376 -0
- comrad/models/mappo_model.py +85 -0
- comrad/models/qmix_model.py +415 -0
- comrad/models/qplex_mixer.py +443 -0
- comrad/record_topdown_heatmap.py +1299 -0
- comrad/record_video.py +262 -0
- comrad/scenarios/ammo_carrier.cfg +39 -0
- comrad/scenarios/ammo_carrier.wad +0 -0
- comrad/scenarios/armory_siege.cfg +41 -0
- comrad/scenarios/armory_siege.wad +0 -0
- comrad/scenarios/batch_armory/armory_0.wad +0 -0
- comrad/scenarios/batch_armory/armory_1.wad +0 -0
- comrad/scenarios/batch_armory/armory_10.wad +0 -0
- comrad/scenarios/batch_armory/armory_11.wad +0 -0
- comrad/scenarios/batch_armory/armory_12.wad +0 -0
- comrad/scenarios/batch_armory/armory_13.wad +0 -0
- comrad/scenarios/batch_armory/armory_14.wad +0 -0
- comrad/scenarios/batch_armory/armory_15.wad +0 -0
- comrad/scenarios/batch_armory/armory_16.wad +0 -0
- comrad/scenarios/batch_armory/armory_17.wad +0 -0
- comrad/scenarios/batch_armory/armory_18.wad +0 -0
- comrad/scenarios/batch_armory/armory_19.wad +0 -0
- comrad/scenarios/batch_armory/armory_2.wad +0 -0
- comrad/scenarios/batch_armory/armory_20.wad +0 -0
- comrad/scenarios/batch_armory/armory_21.wad +0 -0
- comrad/scenarios/batch_armory/armory_22.wad +0 -0
- comrad/scenarios/batch_armory/armory_23.wad +0 -0
- comrad/scenarios/batch_armory/armory_3.wad +0 -0
- comrad/scenarios/batch_armory/armory_4.wad +0 -0
- comrad/scenarios/batch_armory/armory_5.wad +0 -0
- comrad/scenarios/batch_armory/armory_6.wad +0 -0
- comrad/scenarios/batch_armory/armory_7.wad +0 -0
- comrad/scenarios/batch_armory/armory_8.wad +0 -0
- comrad/scenarios/batch_armory/armory_9.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_0.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_1.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_10.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_11.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_12.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_13.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_14.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_15.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_16.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_17.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_18.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_19.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_2.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_20.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_21.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_22.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_23.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_24.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_25.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_26.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_27.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_28.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_29.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_3.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_30.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_31.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_32.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_33.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_34.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_35.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_36.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_37.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_38.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_39.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_4.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_40.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_41.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_42.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_43.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_44.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_45.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_46.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_47.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_48.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_49.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_5.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_50.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_51.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_52.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_53.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_54.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_55.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_56.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_57.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_58.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_59.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_6.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_60.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_61.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_62.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_63.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_64.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_65.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_7.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_8.wad +0 -0
- comrad/scenarios/batch_lava_maze/lava_maze_9.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_intro.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_mastery.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_bridge_progress.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_intro.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_mastery.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_movers.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_easy_spacing.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_default.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_endgame.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_extended.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_intro.wad +0 -0
- comrad/scenarios/batch_platform_chain_curriculum/platform_chain_hard_tighter.wad +0 -0
- comrad/scenarios/coop_health_gathering.cfg +31 -0
- comrad/scenarios/coop_health_gathering.wad +0 -0
- comrad/scenarios/coop_puzzle.cfg +39 -0
- comrad/scenarios/coop_puzzle.wad +0 -0
- comrad/scenarios/dumb_enemies.cfg +35 -0
- comrad/scenarios/dumb_enemies.wad +0 -0
- comrad/scenarios/foraging_commons.cfg +36 -0
- comrad/scenarios/foraging_commons.wad +0 -0
- comrad/scenarios/lava_maze.cfg +41 -0
- comrad/scenarios/lava_maze.wad +0 -0
- comrad/scenarios/lava_maze_simple.cfg +32 -0
- comrad/scenarios/lava_maze_simple.wad +0 -0
- comrad/scenarios/lavapit.cfg +46 -0
- comrad/scenarios/lavapit.wad +0 -0
- comrad/scenarios/platform_chain.cfg +38 -0
- comrad/scenarios/platform_chain.wad +0 -0
- comrad/scenarios/platform_chain_easy.cfg +38 -0
- comrad/scenarios/platform_chain_easy.wad +0 -0
- comrad/scenarios/rhythm_sync.cfg +49 -0
- comrad/scenarios/rhythm_sync.wad +0 -0
- comrad/scenarios/smart_enemies.cfg +36 -0
- comrad/scenarios/smart_enemies.wad +0 -0
- comrad/scenarios/stag_hunt_arena.cfg +39 -0
- comrad/scenarios/stag_hunt_arena.wad +0 -0
- comrad/scenarios/stealth_labyrinth.cfg +45 -0
- comrad/scenarios/stealth_labyrinth.wad +0 -0
- comrad/templates/asdf.sh +24 -0
- comrad/templates/run.sh +364 -0
- comrad/templates/run_algo.sh +84 -0
- comrad/templates/run_bench.sh +77 -0
- comrad/tests/__init__.py +0 -0
- comrad/tests/test_doom_utils_additional_input.py +60 -0
- comrad/tests/test_lava_maze_action_mask.py +86 -0
- comrad/tests/test_record_topdown_heatmap.py +271 -0
- comrad/tests/test_train_all.py +118 -0
- comrad/tests/test_train_all_scaling.py +168 -0
- comrad/train.py +228 -0
- comrad/train_all.py +368 -0
- comrad/utils/__init__.py +0 -0
- comrad/utils/doom_render.py +35 -0
- comrad/utils/doom_utils.py +644 -0
- comrad/utils/recording_actions.py +29 -0
- comrad/utils/video_uploader.py +51 -0
- comrad/utils/wad_utils.py +55 -0
- comrad/wrappers/__init__.py +0 -0
- comrad/wrappers/action_mask.py +73 -0
- comrad/wrappers/additional_input.py +101 -0
- comrad/wrappers/agent_id_wrapper.py +42 -0
- comrad/wrappers/exploration.py +56 -0
- comrad/wrappers/multiplayer_stats.py +61 -0
- comrad/wrappers/observation_space.py +75 -0
- comrad/wrappers/reward_shaping.py +262 -0
- comrad/wrappers/scenario_wrappers/__init__.py +18 -0
- comrad/wrappers/scenario_wrappers/ammo_carrier.py +231 -0
- comrad/wrappers/scenario_wrappers/armory_siege.py +264 -0
- comrad/wrappers/scenario_wrappers/coop_health_gathering.py +124 -0
- comrad/wrappers/scenario_wrappers/coop_puzzle.py +131 -0
- comrad/wrappers/scenario_wrappers/dumb_enemies.py +135 -0
- comrad/wrappers/scenario_wrappers/foraging_commons.py +147 -0
- comrad/wrappers/scenario_wrappers/gathering_reward_shaping.py +44 -0
- comrad/wrappers/scenario_wrappers/lava_maze.py +303 -0
- comrad/wrappers/scenario_wrappers/lava_maze_simple.py +300 -0
- comrad/wrappers/scenario_wrappers/lavapit_reward_shaping.py +235 -0
- comrad/wrappers/scenario_wrappers/mwh_reward_shaping.py +79 -0
- comrad/wrappers/scenario_wrappers/pitfall_reward_shaping.py +93 -0
- comrad/wrappers/scenario_wrappers/platform_chain.py +115 -0
- comrad/wrappers/scenario_wrappers/rhythm_sync.py +111 -0
- comrad/wrappers/scenario_wrappers/rhythm_sync_dense.py +327 -0
- comrad/wrappers/scenario_wrappers/smart_enemies.py +132 -0
- comrad/wrappers/scenario_wrappers/stag_hunt_arena.py +128 -0
- comrad/wrappers/scenario_wrappers/stealth_labyrinth.py +157 -0
- comrad/wrappers/shared_reward.py +55 -0
- comrad/wrappers/step_human_input.py +37 -0
- comrad/wrappers/video_recorder.py +347 -0
- comrad_bench-1.0.0.dist-info/METADATA +229 -0
- comrad_bench-1.0.0.dist-info/RECORD +384 -0
- comrad_bench-1.0.0.dist-info/WHEEL +5 -0
- comrad_bench-1.0.0.dist-info/licenses/LICENSE +21 -0
- comrad_bench-1.0.0.dist-info/top_level.txt +2 -0
- sample_factory/__init__.py +9 -0
- sample_factory/algo/__init__.py +0 -0
- sample_factory/algo/evaluators/__init__.py +0 -0
- sample_factory/algo/evaluators/default_evaluator.py +0 -0
- sample_factory/algo/learning/__init__.py +0 -0
- sample_factory/algo/learning/batcher.py +271 -0
- sample_factory/algo/learning/learner.py +1322 -0
- sample_factory/algo/learning/learner_dqn.py +450 -0
- sample_factory/algo/learning/learner_happo.py +818 -0
- sample_factory/algo/learning/learner_qmix.py +1254 -0
- sample_factory/algo/learning/learner_worker.py +178 -0
- sample_factory/algo/learning/rnn_utils.py +158 -0
- sample_factory/algo/runners/__init__.py +0 -0
- sample_factory/algo/runners/runner.py +774 -0
- sample_factory/algo/runners/runner_parallel.py +65 -0
- sample_factory/algo/runners/runner_serial.py +27 -0
- sample_factory/algo/sampling/__init__.py +0 -0
- sample_factory/algo/sampling/batched_sampling.py +466 -0
- sample_factory/algo/sampling/evaluation_sampling_api.py +315 -0
- sample_factory/algo/sampling/inference_worker.py +524 -0
- sample_factory/algo/sampling/non_batched_sampling.py +709 -0
- sample_factory/algo/sampling/rollout_worker.py +308 -0
- sample_factory/algo/sampling/sampler.py +296 -0
- sample_factory/algo/sampling/sampling_utils.py +70 -0
- sample_factory/algo/sampling/stats.py +31 -0
- sample_factory/algo/sampling/sync_sampling_api.py +65 -0
- sample_factory/algo/utils/__init__.py +0 -0
- sample_factory/algo/utils/action_distributions.py +350 -0
- sample_factory/algo/utils/agent_policy_mapping.py +62 -0
- sample_factory/algo/utils/context.py +50 -0
- sample_factory/algo/utils/env_info.py +151 -0
- sample_factory/algo/utils/epsilon_schedule.py +50 -0
- sample_factory/algo/utils/gymnasium_utils.py +128 -0
- sample_factory/algo/utils/heartbeat.py +35 -0
- sample_factory/algo/utils/joint_replay_buffer.py +296 -0
- sample_factory/algo/utils/joint_sequence_replay_buffer.py +225 -0
- sample_factory/algo/utils/make_env.py +388 -0
- sample_factory/algo/utils/misc.py +44 -0
- sample_factory/algo/utils/model_sharing.py +170 -0
- sample_factory/algo/utils/multiprocessing_utils.py +41 -0
- sample_factory/algo/utils/optimizers.py +189 -0
- sample_factory/algo/utils/replay_buffer.py +390 -0
- sample_factory/algo/utils/rl_utils.py +117 -0
- sample_factory/algo/utils/running_mean_std.py +152 -0
- sample_factory/algo/utils/shared_buffers.py +248 -0
- sample_factory/algo/utils/spaces/__init__.py +0 -0
- sample_factory/algo/utils/spaces/discretized.py +14 -0
- sample_factory/algo/utils/tensor_dict.py +156 -0
- sample_factory/algo/utils/tensor_utils.py +47 -0
- sample_factory/algo/utils/torch_utils.py +66 -0
- sample_factory/cfg/__init__.py +0 -0
- sample_factory/cfg/arguments.py +617 -0
- sample_factory/cfg/cfg.py +1187 -0
- sample_factory/cfg/configurable.py +6 -0
- sample_factory/enjoy.py +292 -0
- sample_factory/envs/__init__.py +0 -0
- sample_factory/envs/create_env.py +46 -0
- sample_factory/envs/env_utils.py +142 -0
- sample_factory/envs/env_wrappers.py +457 -0
- sample_factory/envs/pettingzoo_envs.py +79 -0
- sample_factory/eval.py +119 -0
- sample_factory/export_onnx.py +202 -0
- sample_factory/huggingface/__init__.py +0 -0
- sample_factory/huggingface/huggingface_utils.py +154 -0
- sample_factory/huggingface/load_from_hub.py +27 -0
- sample_factory/huggingface/push_to_hub.py +61 -0
- sample_factory/launcher/__init__.py +0 -0
- sample_factory/launcher/launcher_utils.py +6 -0
- sample_factory/launcher/run.py +77 -0
- sample_factory/launcher/run_description.py +194 -0
- sample_factory/launcher/run_ngc.py +72 -0
- sample_factory/launcher/run_processes.py +142 -0
- sample_factory/launcher/run_slurm.py +173 -0
- sample_factory/model/__init__.py +0 -0
- sample_factory/model/action_parameterization.py +76 -0
- sample_factory/model/actor_critic.py +351 -0
- sample_factory/model/core.py +86 -0
- sample_factory/model/decoder.py +35 -0
- sample_factory/model/encoder.py +242 -0
- sample_factory/model/model_factory.py +60 -0
- sample_factory/model/model_utils.py +93 -0
- sample_factory/model/utils.py +17 -0
- sample_factory/pbt/__init__.py +0 -0
- sample_factory/pbt/population_based_training.py +415 -0
- sample_factory/tests/__init__.py +0 -0
- sample_factory/tests/algo/__init__.py +0 -0
- sample_factory/tests/algo/happo/__init__.py +0 -0
- sample_factory/tests/algo/happo/conftest.py +111 -0
- sample_factory/tests/algo/happo/test_critic_rnn.py +347 -0
- sample_factory/tests/algo/happo/test_critic_rnn_training.py +420 -0
- sample_factory/tests/algo/happo/test_helpers.py +112 -0
- sample_factory/tests/algo/happo/test_learner.py +328 -0
- sample_factory/tests/algo/happo/test_loss.py +384 -0
- sample_factory/tests/algo/happo/test_model.py +280 -0
- sample_factory/tests/algo/mappo/__init__.py +0 -0
- sample_factory/tests/algo/mappo/conftest.py +56 -0
- sample_factory/tests/algo/mappo/test_critic.py +176 -0
- sample_factory/tests/algo/mappo/test_model.py +138 -0
- sample_factory/tests/algo/mappo/test_rnn_edges.py +171 -0
- sample_factory/tests/algo/qmix/__init__.py +0 -0
- sample_factory/tests/algo/qmix/conftest.py +179 -0
- sample_factory/tests/algo/qmix/test_buffer.py +332 -0
- sample_factory/tests/algo/qmix/test_forward_equivalence.py +399 -0
- sample_factory/tests/algo/qmix/test_learner.py +458 -0
- sample_factory/tests/algo/qmix/test_qplex_compound_actions.py +512 -0
- sample_factory/tests/algo/qmix/test_rnn_buffer.py +189 -0
- sample_factory/tests/algo/qmix/test_rnn_core.py +390 -0
- sample_factory/tests/algo/qmix/test_rnn_loss.py +365 -0
- sample_factory/tests/algo/qmix/test_rnn_model.py +545 -0
- sample_factory/tests/algo/qmix/test_transitions.py +277 -0
- sample_factory/tests/algo/qplex/test_qatten_regress.py +31 -0
- sample_factory/tests/algo/qplex/test_qplex.py +893 -0
- sample_factory/tests/algo/qplex/test_qplex_cfg_validation.py +56 -0
- sample_factory/tests/algo/qplex/test_qplex_compound_convergence.py +487 -0
- sample_factory/tests/algo/test_action_distributions.py +259 -0
- sample_factory/tests/algo/test_joint_replay_buffer_rng.py +83 -0
- sample_factory/tests/algo/test_learner.py +172 -0
- sample_factory/tests/algo/test_model.py +64 -0
- sample_factory/tests/algo/test_model_builder.py +60 -0
- sample_factory/tests/algo/test_pbt.py +25 -0
- sample_factory/tests/algo/test_rms.py +68 -0
- sample_factory/tests/algo/test_rnn.py +64 -0
- sample_factory/tests/algo/test_spaces.py +21 -0
- sample_factory/tests/algo/test_tensordict.py +67 -0
- sample_factory/tests/envs/__init__.py +0 -0
- sample_factory/tests/envs/test_batched_sampling_true_objective.py +81 -0
- sample_factory/tests/envs/test_doom_gym_terminal_info.py +56 -0
- sample_factory/tests/envs/test_doom_multiagent_wrapper.py +259 -0
- sample_factory/tests/envs/tuple_action_envs/__init__.py +0 -0
- sample_factory/tests/envs/tuple_action_envs/test_mixed_dist_env_batched.py +137 -0
- sample_factory/tests/envs/tuple_action_envs/test_mixed_dist_env_non_batched.py +101 -0
- sample_factory/tests/envs/tuple_action_envs/test_two_discrete_action_dist_env_batched.py +144 -0
- sample_factory/tests/envs/tuple_action_envs/test_two_discrete_action_dist_env_non_batched.py +113 -0
- sample_factory/tests/envs/utils.py +63 -0
- sample_factory/tests/envs/vizdoom/__init__.py +0 -0
- sample_factory/tests/envs/vizdoom/test_vizdoom_basic.py +84 -0
- sample_factory/tests/envs/vizdoom/test_vizdoom_multiplayer.py +85 -0
- sample_factory/tests/examples/__init__.py +0 -0
- sample_factory/tests/examples/test_example.py +178 -0
- sample_factory/tests/examples/test_example_multi.py +64 -0
- sample_factory/tests/examples/test_example_sampler.py +31 -0
- sample_factory/tests/export_onnx_utils.py +68 -0
- sample_factory/tests/test_comrad_curriculum.py +595 -0
- sample_factory/tests/test_comrad_train_resume.py +155 -0
- sample_factory/tests/test_launcher.py +189 -0
- sample_factory/tests/test_precheck.py +26 -0
- sample_factory/tests/test_utils.py +60 -0
- sample_factory/tests/test_video_recorder.py +444 -0
- sample_factory/tests/test_video_uploader.py +107 -0
- sample_factory/tests/utils.py +14 -0
- sample_factory/train.py +41 -0
- sample_factory/utils/__init__.py +0 -0
- sample_factory/utils/algo_version.py +3 -0
- sample_factory/utils/attr_dict.py +8 -0
- sample_factory/utils/decay.py +47 -0
- sample_factory/utils/dicts.py +103 -0
- sample_factory/utils/get_available_gpus.py +42 -0
- sample_factory/utils/gifs.py +41 -0
- sample_factory/utils/gpu_utils.py +90 -0
- sample_factory/utils/network.py +15 -0
- sample_factory/utils/normalize.py +76 -0
- sample_factory/utils/tb.py +91 -0
- sample_factory/utils/timing.py +161 -0
- sample_factory/utils/typing.py +31 -0
- sample_factory/utils/utils.py +497 -0
- sample_factory/utils/wandb_utils.py +76 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from comrad.curriculum.base import Curriculum
|
|
2
|
+
from comrad.curriculum.uniform import UniformCurriculum
|
|
3
|
+
from comrad.curriculum.sequential import SequentialCurriculum
|
|
4
|
+
from comrad.curriculum.learning_progress import LearningProgress
|
|
5
|
+
from comrad.curriculum.omni import OMNICurriculum
|
|
6
|
+
from comrad.curriculum.plr import PrioritizedLevelReplay
|
|
7
|
+
|
|
8
|
+
def make_curriculum(n_tasks: int, strategy: str = "uniform", **kwargs) -> Curriculum:
|
|
9
|
+
"""Factory to spin up proper curriculum based on configuration."""
|
|
10
|
+
strategies = {
|
|
11
|
+
"uniform": UniformCurriculum,
|
|
12
|
+
"sequential": SequentialCurriculum,
|
|
13
|
+
"learning_progress": LearningProgress,
|
|
14
|
+
"omni": OMNICurriculum,
|
|
15
|
+
"plr": PrioritizedLevelReplay,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
assert strategy in strategies, f"Unknown strategy '{strategy}'. Available: {list(strategies.keys())}"
|
|
19
|
+
|
|
20
|
+
return strategies[strategy](n_tasks=n_tasks, **kwargs)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Curriculum",
|
|
24
|
+
"UniformCurriculum",
|
|
25
|
+
"SequentialCurriculum",
|
|
26
|
+
"LearningProgress",
|
|
27
|
+
"OMNICurriculum",
|
|
28
|
+
"PrioritizedLevelReplay",
|
|
29
|
+
"make_curriculum"
|
|
30
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
import os
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Curriculum:
|
|
8
|
+
"""
|
|
9
|
+
Abstract base curriculum class for a fixed discrete batch of tasks (WADs).
|
|
10
|
+
Requires multiprocessing arrays and locks for disjoint worker/learner access.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
EMA_ALPHA = 0.1
|
|
14
|
+
SCORE_MEAN_VALUE_L1 = "mean_value_l1"
|
|
15
|
+
SCORE_MAX_VALUE_L1 = "max_value_l1"
|
|
16
|
+
SCORE_MEAN_ADVANTAGE = "mean_advantage"
|
|
17
|
+
SCORE_MAX_ADVANTAGE = "max_advantage"
|
|
18
|
+
SCORE_MEAN_ENTROPY = "mean_entropy"
|
|
19
|
+
SCORE_MAX_ENTROPY = "max_entropy"
|
|
20
|
+
|
|
21
|
+
def __init__(self, n_tasks: int, seed: Optional[int] = None, tasks: Optional[list] = None, **kwargs):
|
|
22
|
+
self.ctx = multiprocessing.get_context("spawn")
|
|
23
|
+
self._n = n_tasks
|
|
24
|
+
self._base_seed = int(seed) if seed is not None else int.from_bytes(os.urandom(8), "little")
|
|
25
|
+
self._rng_counter = self.ctx.Value("Q", 0)
|
|
26
|
+
self._task_ids = tuple(tasks) if tasks is not None else None
|
|
27
|
+
|
|
28
|
+
self._weights = self.ctx.Array('d', [1.0 / n_tasks] * n_tasks)
|
|
29
|
+
self._counts = self.ctx.Array('l', [0] * n_tasks)
|
|
30
|
+
self._lock = self.ctx.Lock()
|
|
31
|
+
|
|
32
|
+
def update(self, task_idx: int, episode_return: float) -> None:
|
|
33
|
+
"""Called at end of each episode."""
|
|
34
|
+
with self._lock:
|
|
35
|
+
self._counts[task_idx] += 1
|
|
36
|
+
self._update_logic(task_idx, float(episode_return))
|
|
37
|
+
self._recompute_weights()
|
|
38
|
+
|
|
39
|
+
def update_task_score(self, task_idx: int, score: dict, num_steps: int = 1) -> None:
|
|
40
|
+
"""Used by sample factory learners (e.g. PLR)"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def sample(self) -> int:
|
|
44
|
+
with self._lock:
|
|
45
|
+
task_idx = self._sample_logic()
|
|
46
|
+
return task_idx
|
|
47
|
+
|
|
48
|
+
def _sample_logic(self) -> int:
|
|
49
|
+
"""Isolated locked core for actually rolling the random sample"""
|
|
50
|
+
weights = np.array(self._weights[:])
|
|
51
|
+
return self._choice(self._n, p=weights)
|
|
52
|
+
|
|
53
|
+
def _next_rng(self) -> np.random.Generator:
|
|
54
|
+
"""
|
|
55
|
+
Generate randomness from a single shared draw counter so spawned workers
|
|
56
|
+
do not each replay an identical local RNG stream.
|
|
57
|
+
"""
|
|
58
|
+
counter = self._rng_counter.value
|
|
59
|
+
self._rng_counter.value = counter + 1
|
|
60
|
+
seed = (self._base_seed + 0x9E3779B97F4A7C15 * counter) & ((1 << 64) - 1)
|
|
61
|
+
return np.random.default_rng(seed)
|
|
62
|
+
|
|
63
|
+
def _choice(self, n: int, p=None) -> int:
|
|
64
|
+
return int(self._next_rng().choice(n, p=p))
|
|
65
|
+
|
|
66
|
+
def _random(self) -> float:
|
|
67
|
+
return float(self._next_rng().random())
|
|
68
|
+
|
|
69
|
+
def _update_logic(self, task_idx: int, episode_return: float) -> None:
|
|
70
|
+
"""Sub-classes override this to implement update behavior."""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def _recompute_weights(self) -> None:
|
|
74
|
+
"""Sub-classes override this to update probabilities inside self._weights."""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def task_weights(self) -> np.ndarray:
|
|
78
|
+
with self._lock:
|
|
79
|
+
return np.array(self._weights[:])
|
|
80
|
+
|
|
81
|
+
def task_returns(self) -> np.ndarray:
|
|
82
|
+
"""Return fast moving average or appropriate returns proxy."""
|
|
83
|
+
# By default just returns zeroes, overridden in LP/OMNI classes
|
|
84
|
+
return np.zeros(self._n)
|
|
85
|
+
|
|
86
|
+
def task_scores(self) -> np.ndarray:
|
|
87
|
+
"""PLR scores or similar metric."""
|
|
88
|
+
return np.zeros(self._n)
|
|
89
|
+
|
|
90
|
+
def metrics(self) -> dict:
|
|
91
|
+
"""General metrics reporting."""
|
|
92
|
+
return {}
|
|
93
|
+
|
|
94
|
+
def state_dict(self) -> dict:
|
|
95
|
+
with self._lock:
|
|
96
|
+
state = {
|
|
97
|
+
"version": 1,
|
|
98
|
+
"curriculum_type": type(self).__name__,
|
|
99
|
+
"n_tasks": self._n,
|
|
100
|
+
"base_seed": int(self._base_seed),
|
|
101
|
+
"rng_counter": int(self._rng_counter.value),
|
|
102
|
+
"weights": list(self._weights[:]),
|
|
103
|
+
"counts": list(self._counts[:]),
|
|
104
|
+
}
|
|
105
|
+
if self._task_ids is not None:
|
|
106
|
+
state["task_ids"] = list(self._task_ids)
|
|
107
|
+
state.update(self._state_dict_locked())
|
|
108
|
+
return state
|
|
109
|
+
|
|
110
|
+
def load_state_dict(self, state: dict) -> None:
|
|
111
|
+
expected_type = type(self).__name__
|
|
112
|
+
actual_type = state.get("curriculum_type", expected_type)
|
|
113
|
+
if actual_type != expected_type:
|
|
114
|
+
raise ValueError(f"Expected curriculum state for {expected_type}, got {actual_type}")
|
|
115
|
+
if int(state.get("n_tasks", self._n)) != self._n:
|
|
116
|
+
raise ValueError(f"Expected curriculum state for {self._n} tasks, got {state.get('n_tasks')}")
|
|
117
|
+
expected_task_ids = list(self._task_ids) if self._task_ids is not None else None
|
|
118
|
+
actual_task_ids = state.get("task_ids", None)
|
|
119
|
+
if expected_task_ids is not None and actual_task_ids != expected_task_ids:
|
|
120
|
+
raise ValueError(f"Expected curriculum task ids {expected_task_ids}, got {actual_task_ids}")
|
|
121
|
+
|
|
122
|
+
with self._lock:
|
|
123
|
+
self._base_seed = int(state.get("base_seed", self._base_seed))
|
|
124
|
+
self._rng_counter.value = int(state.get("rng_counter", 0))
|
|
125
|
+
self._weights[:] = [float(weight) for weight in state.get("weights", self._weights[:])]
|
|
126
|
+
self._counts[:] = [int(count) for count in state.get("counts", self._counts[:])]
|
|
127
|
+
self._load_state_dict_locked(state)
|
|
128
|
+
|
|
129
|
+
def _state_dict_locked(self) -> dict:
|
|
130
|
+
return {}
|
|
131
|
+
|
|
132
|
+
def _load_state_dict_locked(self, state: dict) -> None:
|
|
133
|
+
pass
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from comrad.curriculum.base import Curriculum
|
|
3
|
+
|
|
4
|
+
class LearningProgress(Curriculum):
|
|
5
|
+
"""
|
|
6
|
+
Syllabus-inspired Learning Progress curriculum tracking fast and slow EMAs over
|
|
7
|
+
normalized per-task success metrics. The raw terminal `true_objective` signal is
|
|
8
|
+
tracked separately for diagnostics/checkpoint continuity.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
n_tasks: int,
|
|
14
|
+
p_theta: float = 0.1,
|
|
15
|
+
max_return: float = 100.0,
|
|
16
|
+
min_return: float = -100.0,
|
|
17
|
+
uniform_prob: float = 0.25,
|
|
18
|
+
**kwargs,
|
|
19
|
+
):
|
|
20
|
+
super().__init__(n_tasks, **kwargs)
|
|
21
|
+
self.p_theta = p_theta
|
|
22
|
+
self.max_return = max_return
|
|
23
|
+
self.min_return = min_return
|
|
24
|
+
self.uniform_prob = uniform_prob
|
|
25
|
+
self._stale_dist = self.ctx.Value('b', True)
|
|
26
|
+
self._p_fast = self.ctx.Array('d', [0.0] * n_tasks)
|
|
27
|
+
self._p_slow = self.ctx.Array('d', [0.0] * n_tasks)
|
|
28
|
+
self._p_true = self.ctx.Array('d', [0.0] * n_tasks)
|
|
29
|
+
self._p_seen = self.ctx.Array('d', [0.0] * n_tasks) # 0.0 = unseen, 1.0 = seen
|
|
30
|
+
|
|
31
|
+
def _sample_logic(self) -> int:
|
|
32
|
+
if self._stale_dist.value:
|
|
33
|
+
self._recompute_weights()
|
|
34
|
+
self._stale_dist.value = False
|
|
35
|
+
weights = np.array(self._weights[:])
|
|
36
|
+
return self._choice(self._n, p=weights)
|
|
37
|
+
|
|
38
|
+
def update(self, task_idx: int, task_score: float) -> None:
|
|
39
|
+
with self._lock:
|
|
40
|
+
self._counts[task_idx] += 1
|
|
41
|
+
self._update_logic(task_idx, float(task_score))
|
|
42
|
+
self._stale_dist.value = True
|
|
43
|
+
|
|
44
|
+
def _normalize(self, task_score: float) -> float:
|
|
45
|
+
"""Map raw `true_objective` success metrics to [0, 1]."""
|
|
46
|
+
r_min = self.min_return
|
|
47
|
+
r_max = self.max_return
|
|
48
|
+
if r_max <= r_min:
|
|
49
|
+
return 0.0
|
|
50
|
+
normalized = (task_score - r_min) / (r_max - r_min)
|
|
51
|
+
return float(np.clip(normalized, 0.0, 1.0))
|
|
52
|
+
|
|
53
|
+
def _update_logic(self, task_idx: int, task_score: float) -> None:
|
|
54
|
+
"""Update normalized LP EMAs plus a raw `true_objective` EMA."""
|
|
55
|
+
p = self._normalize(task_score)
|
|
56
|
+
alpha = self.EMA_ALPHA
|
|
57
|
+
|
|
58
|
+
if self._p_seen[task_idx] == 0.0:
|
|
59
|
+
self._p_fast[task_idx] = p
|
|
60
|
+
self._p_slow[task_idx] = p
|
|
61
|
+
self._p_true[task_idx] = task_score
|
|
62
|
+
self._p_seen[task_idx] = 1.0
|
|
63
|
+
else:
|
|
64
|
+
old_fast = self._p_fast[task_idx]
|
|
65
|
+
old_true = self._p_true[task_idx]
|
|
66
|
+
self._p_fast[task_idx] = p * alpha + old_fast * (1.0 - alpha)
|
|
67
|
+
self._p_slow[task_idx] = self._p_fast[task_idx] * alpha + self._p_slow[task_idx] * (1.0 - alpha)
|
|
68
|
+
self._p_true[task_idx] = task_score * alpha + old_true * (1.0 - alpha)
|
|
69
|
+
|
|
70
|
+
def _reweight(self, p: np.ndarray) -> np.ndarray:
|
|
71
|
+
numerator = p * (1.0 - self.p_theta)
|
|
72
|
+
denominator = p + self.p_theta * (1.0 - 2.0 * p)
|
|
73
|
+
return numerator / denominator
|
|
74
|
+
|
|
75
|
+
def _recompute_weights(self) -> None:
|
|
76
|
+
seen = np.array(self._p_seen[:]) > 0.0
|
|
77
|
+
unseen = ~seen
|
|
78
|
+
|
|
79
|
+
# LP is undefined until every task has at least some signal.
|
|
80
|
+
# Keep the curriculum uniform during this cold-start phase instead of
|
|
81
|
+
# forcing unseen-only sweeps or collapsing onto the first updated task.
|
|
82
|
+
if unseen.any():
|
|
83
|
+
self._weights[:] = (np.ones(self._n) / self._n).tolist()
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
fast = np.array(self._p_fast[:])
|
|
87
|
+
slow = np.array(self._p_slow[:])
|
|
88
|
+
# Compute learning progress as absolute difference of reweighted EMAs
|
|
89
|
+
learning_progress = np.abs(self._reweight(fast) - self._reweight(slow))
|
|
90
|
+
# Keep tasks with any normalized success signal eligible when LP is flat.
|
|
91
|
+
# `_p_true` stores raw objectives for OMNI ordering, so using its sign here
|
|
92
|
+
# would incorrectly bias sampling toward tasks whose reward scale is positive.
|
|
93
|
+
posidxs = [i for i, lp in enumerate(learning_progress) if lp > 0.0 or fast[i] > 0.0]
|
|
94
|
+
any_progress = len(posidxs) > 0
|
|
95
|
+
subprobs = learning_progress[posidxs] if any_progress else learning_progress
|
|
96
|
+
|
|
97
|
+
std = np.std(subprobs)
|
|
98
|
+
# z-score
|
|
99
|
+
subprobs = (subprobs - np.mean(subprobs)) / (std if std else 1.0)
|
|
100
|
+
# sigmoid
|
|
101
|
+
subprobs = 1.0 / (1.0 + np.exp(-subprobs))
|
|
102
|
+
# normalize
|
|
103
|
+
subprobs = subprobs / (np.sum(subprobs))
|
|
104
|
+
|
|
105
|
+
if any_progress:
|
|
106
|
+
task_dist = np.zeros(self._n)
|
|
107
|
+
task_dist[posidxs] = subprobs
|
|
108
|
+
else:
|
|
109
|
+
task_dist = subprobs
|
|
110
|
+
|
|
111
|
+
if self.uniform_prob > 0.0:
|
|
112
|
+
uniform = np.ones(self._n) / self._n
|
|
113
|
+
task_dist = (1.0 - self.uniform_prob) * task_dist + self.uniform_prob * uniform
|
|
114
|
+
|
|
115
|
+
task_dist = task_dist / np.sum(task_dist)
|
|
116
|
+
self._weights[:] = task_dist.tolist()
|
|
117
|
+
|
|
118
|
+
def task_returns(self) -> np.ndarray:
|
|
119
|
+
with self._lock:
|
|
120
|
+
return np.array(self._p_fast[:])
|
|
121
|
+
|
|
122
|
+
def _state_dict_locked(self) -> dict:
|
|
123
|
+
return {
|
|
124
|
+
"stale_dist": bool(self._stale_dist.value),
|
|
125
|
+
"p_fast": list(self._p_fast[:]),
|
|
126
|
+
"p_slow": list(self._p_slow[:]),
|
|
127
|
+
"p_true": list(self._p_true[:]),
|
|
128
|
+
"p_seen": list(self._p_seen[:]),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def _load_state_dict_locked(self, state: dict) -> None:
|
|
132
|
+
self._stale_dist.value = bool(state.get("stale_dist", True))
|
|
133
|
+
self._p_fast[:] = [float(value) for value in state.get("p_fast", self._p_fast[:])]
|
|
134
|
+
self._p_slow[:] = [float(value) for value in state.get("p_slow", self._p_slow[:])]
|
|
135
|
+
self._p_true[:] = [float(value) for value in state.get("p_true", self._p_true[:])]
|
|
136
|
+
self._p_seen[:] = [float(value) for value in state.get("p_seen", self._p_seen[:])]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from os.path import join
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from sample_factory.algo.runners.runner import AlgoObserver
|
|
9
|
+
from sample_factory.algo.learning.learner import Learner
|
|
10
|
+
from sample_factory.utils.utils import experiment_dir, log
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def curriculum_state_file(cfg) -> str:
|
|
14
|
+
return join(experiment_dir(cfg), "curriculum_state.json")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def save_curriculum_state(cfg, curriculum) -> None:
|
|
18
|
+
if curriculum is None:
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
state_path = curriculum_state_file(cfg)
|
|
22
|
+
tmp_path = f"{state_path}.tmp"
|
|
23
|
+
with open(tmp_path, "w") as state_file:
|
|
24
|
+
json.dump(curriculum.state_dict(), state_file, indent=2)
|
|
25
|
+
os.replace(tmp_path, state_path)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_curriculum_state(cfg, curriculum) -> bool:
|
|
29
|
+
if curriculum is None:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
state_path = curriculum_state_file(cfg)
|
|
33
|
+
if not os.path.isfile(state_path):
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
with open(state_path, "r") as state_file:
|
|
37
|
+
state = json.load(state_file)
|
|
38
|
+
|
|
39
|
+
curriculum.load_state_dict(state)
|
|
40
|
+
return True
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CurriculumObserver(AlgoObserver):
|
|
44
|
+
def __init__(self, curriculum):
|
|
45
|
+
self.curriculum = curriculum
|
|
46
|
+
|
|
47
|
+
def on_init(self, runner) -> None:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
def on_training_step(self, runner, training_iteration_since_resume: int) -> None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def on_stop(self, runner) -> None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def extra_summaries(self, runner, policy_id, writer, env_steps) -> None:
|
|
57
|
+
if policy_id != 0 or self.curriculum is None:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
weights = self.curriculum.task_weights()
|
|
61
|
+
if weights.size > 0:
|
|
62
|
+
nonzero_weights = weights[weights > 0]
|
|
63
|
+
if nonzero_weights.size > 0:
|
|
64
|
+
entropy = float(-(nonzero_weights * np.log(nonzero_weights)).sum())
|
|
65
|
+
writer.add_scalar("curriculum/entropy", entropy, env_steps)
|
|
66
|
+
writer.add_scalar("curriculum/nonzero_probability_tasks", int(np.count_nonzero(weights)), env_steps)
|
|
67
|
+
|
|
68
|
+
task_returns = self.curriculum.task_returns()
|
|
69
|
+
if task_returns.size > 0 and not np.allclose(task_returns, 0.0):
|
|
70
|
+
writer.add_scalar("curriculum/mean_task_return", float(np.mean(task_returns)), env_steps)
|
|
71
|
+
|
|
72
|
+
metrics = self.curriculum.metrics()
|
|
73
|
+
for key, value in metrics.items():
|
|
74
|
+
if np.isscalar(value):
|
|
75
|
+
writer.add_scalar(f"curriculum/{key}", float(value), env_steps)
|
|
76
|
+
elif isinstance(value, np.ndarray) and value.size > 0 and np.issubdtype(value.dtype, np.number):
|
|
77
|
+
writer.add_scalar(f"curriculum/mean_{key}", float(np.mean(value)), env_steps)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def restore_curriculum_state(cfg, curriculum) -> None:
|
|
81
|
+
if curriculum is None or cfg.restart_behavior != "resume":
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
checkpoint_dir = Learner.checkpoint_dir(cfg, 0)
|
|
85
|
+
load_kind = getattr(cfg, "load_checkpoint_kind", "latest")
|
|
86
|
+
pattern = f"{dict(latest='checkpoint', best='best')[load_kind]}_*"
|
|
87
|
+
checkpoints = Learner.get_checkpoints(checkpoint_dir, pattern=pattern)
|
|
88
|
+
if not checkpoints:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
checkpoint_dict = Learner.load_checkpoint(checkpoints, torch.device("cpu"))
|
|
92
|
+
if checkpoint_dict is None:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
state = checkpoint_dict.get("curriculum_state", None)
|
|
96
|
+
if state is None:
|
|
97
|
+
raise RuntimeError("Checkpoint is missing curriculum_state; cannot safely resume curriculum-enabled training")
|
|
98
|
+
|
|
99
|
+
curriculum.load_state_dict(state)
|
|
100
|
+
log.info("Restored curriculum state from checkpoint %s", checkpoints[-1])
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from comrad.curriculum.learning_progress import LearningProgress
|
|
4
|
+
|
|
5
|
+
class OMNICurriculum(LearningProgress):
|
|
6
|
+
"""
|
|
7
|
+
Syllabus-inspired OMNI curriculum tracking fast and slow EMAs masked by tasks dependencies
|
|
8
|
+
suppressing mathematically boring tasks once prerequisites are met.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, n_tasks: int, tasks: list, interestingness: Optional[dict] = None, **kwargs):
|
|
12
|
+
super().__init__(n_tasks, tasks=tasks, **kwargs)
|
|
13
|
+
self.tasks = tasks or list(range(n_tasks))
|
|
14
|
+
if interestingness is not None:
|
|
15
|
+
bad = set(interestingness.keys()) - set(self.tasks)
|
|
16
|
+
if bad:
|
|
17
|
+
raise ValueError(
|
|
18
|
+
f"interestingness keys not in tasks: {bad}. "
|
|
19
|
+
f"Use task identifiers only."
|
|
20
|
+
)
|
|
21
|
+
self._interestingness = {
|
|
22
|
+
task: {other: bool(is_interesting) for other, is_interesting in mapping.items()}
|
|
23
|
+
for task, mapping in interestingness.items()
|
|
24
|
+
}
|
|
25
|
+
else:
|
|
26
|
+
# Default: mastery of a task makes itself boring, but other tasks remain interesting.
|
|
27
|
+
self._interestingness = {
|
|
28
|
+
t: {other: (other != t) for other in self.tasks}
|
|
29
|
+
for t in self.tasks
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
def _recompute_weights(self) -> None:
|
|
33
|
+
"""Combine LP weight calculation with "boring" penalty mask."""
|
|
34
|
+
super()._recompute_weights() # Update weights purely by LP
|
|
35
|
+
|
|
36
|
+
lp_dist = np.array(self._weights[:])
|
|
37
|
+
mastery = np.array(self._p_fast[:])
|
|
38
|
+
|
|
39
|
+
# OMNI prerequisite masking must be ordered by a comparable mastery signal.
|
|
40
|
+
# Raw true_objective values can differ wildly across tasks, so use the
|
|
41
|
+
# normalized fast EMA from LearningProgress instead.
|
|
42
|
+
tasks_by_success = np.argsort(mastery)
|
|
43
|
+
interesting_tasks = set()
|
|
44
|
+
boring_tasks = set()
|
|
45
|
+
|
|
46
|
+
for task_idx in tasks_by_success[::-1]:
|
|
47
|
+
if task_idx in interesting_tasks or task_idx in boring_tasks:
|
|
48
|
+
continue
|
|
49
|
+
interesting_tasks.add(task_idx)
|
|
50
|
+
|
|
51
|
+
task = self.tasks[task_idx]
|
|
52
|
+
if task not in self._interestingness:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
for other_idx, other_task in enumerate(self.tasks):
|
|
56
|
+
if other_task not in self._interestingness[task]:
|
|
57
|
+
boring_tasks.add(other_idx)
|
|
58
|
+
continue
|
|
59
|
+
if (
|
|
60
|
+
other_idx not in interesting_tasks
|
|
61
|
+
and other_idx not in boring_tasks
|
|
62
|
+
and not self._interestingness[task][other_task]
|
|
63
|
+
):
|
|
64
|
+
boring_tasks.add(other_idx)
|
|
65
|
+
|
|
66
|
+
# Build weight mask where boring items are heavily penalized
|
|
67
|
+
moi_weight = np.ones(self._n)
|
|
68
|
+
for i in boring_tasks:
|
|
69
|
+
moi_weight[i] = 0.001
|
|
70
|
+
|
|
71
|
+
dist = lp_dist * moi_weight
|
|
72
|
+
norm_dist = dist / dist.sum() if dist.sum() > 0 else np.ones(self._n) / self._n
|
|
73
|
+
|
|
74
|
+
self._weights[:] = norm_dist.tolist()
|
comrad/curriculum/plr.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from comrad.curriculum.base import Curriculum
|
|
3
|
+
|
|
4
|
+
class PrioritizedLevelReplay(Curriculum):
|
|
5
|
+
"""
|
|
6
|
+
Syllabus-inspired Prioritized Level Replay (PLR) tracking PPO rollout metrics natively.
|
|
7
|
+
Replays tasks proportional to learning potential metrics and random unseen tasks.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
n_tasks: int,
|
|
13
|
+
staleness_coef: float = 0.1,
|
|
14
|
+
score_transform: str = "rank",
|
|
15
|
+
temperature: float = 0.1,
|
|
16
|
+
staleness_transform: str = "power",
|
|
17
|
+
staleness_temperature: float = 1.0,
|
|
18
|
+
plr_score_key: str = "mean_value_l1",
|
|
19
|
+
alpha: float = 1.0,
|
|
20
|
+
replay_schedule: str = "proportionate",
|
|
21
|
+
replay_prob: float = 0.5,
|
|
22
|
+
rho: float = 1.0,
|
|
23
|
+
max_score_coef: float = 0.0,
|
|
24
|
+
eps: float = 0.05,
|
|
25
|
+
**kwargs
|
|
26
|
+
):
|
|
27
|
+
super().__init__(n_tasks, **kwargs)
|
|
28
|
+
self._staleness_coef = staleness_coef
|
|
29
|
+
self._score_transform = score_transform
|
|
30
|
+
self._temperature = temperature
|
|
31
|
+
self._staleness_transform = staleness_transform
|
|
32
|
+
self._staleness_temperature = staleness_temperature
|
|
33
|
+
self._plr_score_key = plr_score_key
|
|
34
|
+
self._alpha = alpha
|
|
35
|
+
self._replay_schedule = replay_schedule
|
|
36
|
+
self._replay_prob = replay_prob
|
|
37
|
+
self._rho = rho
|
|
38
|
+
self._max_score_coef = max_score_coef
|
|
39
|
+
self._eps = eps
|
|
40
|
+
|
|
41
|
+
self._task_scores = self.ctx.Array('d', [0.0] * n_tasks)
|
|
42
|
+
self._task_score_steps = self.ctx.Array('l', [0] * n_tasks)
|
|
43
|
+
self._task_staleness = self.ctx.Array('d', [0.0] * n_tasks)
|
|
44
|
+
self._unseen_task_weights = self.ctx.Array('d', [1.0] * n_tasks)
|
|
45
|
+
|
|
46
|
+
def _update_logic(self, task_idx: int, episode_return: float) -> None:
|
|
47
|
+
"""Called conventionally by workers. PLR relies on central PPO learner metrics natively."""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def update_task_score(self, task_idx: int, score: dict, num_steps: int = 1) -> None:
|
|
51
|
+
"""Inject from sample factory using native PLR learner loops."""
|
|
52
|
+
raw = float(score.get(self._plr_score_key, 0.0))
|
|
53
|
+
raw_max = float(score.get(self._plr_score_key.replace("mean_", "max_"), raw))
|
|
54
|
+
total_score = self._max_score_coef * raw_max + (1.0 - self._max_score_coef) * raw
|
|
55
|
+
num_steps = max(int(num_steps), 1)
|
|
56
|
+
|
|
57
|
+
with self._lock:
|
|
58
|
+
old = self._task_scores[task_idx]
|
|
59
|
+
self._task_scores[task_idx] = (1.0 - self._alpha) * old + self._alpha * total_score
|
|
60
|
+
self._task_score_steps[task_idx] += num_steps
|
|
61
|
+
self._unseen_task_weights[task_idx] = 0.0
|
|
62
|
+
self._recompute_weights()
|
|
63
|
+
|
|
64
|
+
def _sample_logic(self) -> int:
|
|
65
|
+
"""Override to implement complex replay schedule logic directly inside lock."""
|
|
66
|
+
proportion_seen = self._proportion_seen()
|
|
67
|
+
|
|
68
|
+
if proportion_seen < self._rho:
|
|
69
|
+
unseen = np.array(self._unseen_task_weights[:])
|
|
70
|
+
if (unseen > 0).any():
|
|
71
|
+
return self._sample_unseen_level(unseen)
|
|
72
|
+
return self._sample_replay_level() # fallback to replay if all seen but still under rho
|
|
73
|
+
|
|
74
|
+
do_replay = self._sample_replay_decision(proportion_seen)
|
|
75
|
+
if do_replay:
|
|
76
|
+
return self._sample_replay_level()
|
|
77
|
+
else:
|
|
78
|
+
unseen = np.array(self._unseen_task_weights[:])
|
|
79
|
+
if (unseen > 0).any():
|
|
80
|
+
return self._sample_unseen_level(unseen)
|
|
81
|
+
return self._sample_replay_level()
|
|
82
|
+
|
|
83
|
+
def _proportion_seen(self) -> float:
|
|
84
|
+
unseen = np.array(self._unseen_task_weights[:])
|
|
85
|
+
return (self._n - (unseen > 0).sum()) / self._n
|
|
86
|
+
|
|
87
|
+
def _sample_replay_decision(self, proportion_seen: float) -> bool:
|
|
88
|
+
if self._replay_schedule == "fixed":
|
|
89
|
+
if proportion_seen >= self._rho:
|
|
90
|
+
if self._random() < self._replay_prob or proportion_seen >= 1.0:
|
|
91
|
+
return True
|
|
92
|
+
return False
|
|
93
|
+
else: # proportionate
|
|
94
|
+
if proportion_seen >= self._rho and self._random() < min(proportion_seen, self._replay_prob):
|
|
95
|
+
return True
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def _sample_replay_level(self) -> int:
|
|
99
|
+
weights = self._calculate_score_weights()
|
|
100
|
+
task_idx = self._choice(self._n, p=weights)
|
|
101
|
+
self._update_staleness(task_idx)
|
|
102
|
+
return task_idx
|
|
103
|
+
|
|
104
|
+
def _sample_unseen_level(self, unseen: np.ndarray) -> int:
|
|
105
|
+
unseen_w = unseen / unseen.sum()
|
|
106
|
+
task_idx = self._choice(self._n, p=unseen_w)
|
|
107
|
+
self._update_staleness(task_idx)
|
|
108
|
+
return task_idx
|
|
109
|
+
|
|
110
|
+
def _update_staleness(self, selected_idx: int) -> None:
|
|
111
|
+
if self._staleness_coef > 0:
|
|
112
|
+
staleness = np.array(self._task_staleness[:])
|
|
113
|
+
staleness += 1.0
|
|
114
|
+
staleness[selected_idx] = 0.0
|
|
115
|
+
self._task_staleness[:] = staleness.tolist()
|
|
116
|
+
self._recompute_weights()
|
|
117
|
+
|
|
118
|
+
def _calculate_score_weights(self) -> np.ndarray:
|
|
119
|
+
scores = np.array(self._task_scores[:])
|
|
120
|
+
unseen = np.array(self._unseen_task_weights[:])
|
|
121
|
+
staleness = np.array(self._task_staleness[:])
|
|
122
|
+
|
|
123
|
+
weights = self._apply_score_transform(self._score_transform, self._temperature, scores)
|
|
124
|
+
weights *= (1.0 - unseen)
|
|
125
|
+
z = weights.sum()
|
|
126
|
+
if z > 0:
|
|
127
|
+
weights /= z
|
|
128
|
+
else:
|
|
129
|
+
seen = (unseen == 0.0)
|
|
130
|
+
weights = seen.astype(float)
|
|
131
|
+
if seen.any():
|
|
132
|
+
weights /= weights.sum()
|
|
133
|
+
|
|
134
|
+
if self._staleness_coef > 0:
|
|
135
|
+
staleness_w = self._apply_score_transform(
|
|
136
|
+
self._staleness_transform, self._staleness_temperature, staleness
|
|
137
|
+
)
|
|
138
|
+
staleness_w *= (1.0 - unseen)
|
|
139
|
+
z_s = staleness_w.sum()
|
|
140
|
+
if z_s > 0:
|
|
141
|
+
staleness_w /= z_s
|
|
142
|
+
else:
|
|
143
|
+
staleness_w = (1.0 / self._n) * (1.0 - unseen)
|
|
144
|
+
weights = (1.0 - self._staleness_coef) * weights + self._staleness_coef * staleness_w
|
|
145
|
+
|
|
146
|
+
total = weights.sum()
|
|
147
|
+
return weights / total if total > 0 else weights
|
|
148
|
+
|
|
149
|
+
def _recompute_weights(self) -> None:
|
|
150
|
+
self._weights[:] = self._calculate_score_weights().tolist()
|
|
151
|
+
|
|
152
|
+
def _apply_score_transform(self, transform: str, temperature: float, scores: np.ndarray) -> np.ndarray:
|
|
153
|
+
if transform == "rank":
|
|
154
|
+
temp = np.flip(scores.argsort())
|
|
155
|
+
ranks = np.empty_like(temp, dtype=float)
|
|
156
|
+
ranks[temp] = np.arange(len(temp)) + 1
|
|
157
|
+
return 1.0 / (ranks ** (1.0 / temperature))
|
|
158
|
+
elif transform == "power":
|
|
159
|
+
eps = 0.0 if self._staleness_coef > 0 else 1e-3
|
|
160
|
+
return (np.clip(scores, 0, None) + eps) ** (1.0 / temperature)
|
|
161
|
+
elif transform == "softmax":
|
|
162
|
+
return np.exp(scores / temperature)
|
|
163
|
+
elif transform == "max":
|
|
164
|
+
w = np.zeros_like(scores)
|
|
165
|
+
scores_ = scores.copy()
|
|
166
|
+
scores_[np.array(self._unseen_task_weights[:]) > 0] = -np.inf
|
|
167
|
+
argmax_candidates = np.flatnonzero(np.isclose(scores_, scores_.max()))
|
|
168
|
+
argmax = argmax_candidates[self._choice(len(argmax_candidates))]
|
|
169
|
+
w[argmax] = 1.0
|
|
170
|
+
return w
|
|
171
|
+
elif transform == "eps_greedy":
|
|
172
|
+
w = np.zeros_like(scores)
|
|
173
|
+
w[scores.argmax()] = 1.0 - self._eps
|
|
174
|
+
w += self._eps / self._n
|
|
175
|
+
return w
|
|
176
|
+
return np.ones_like(scores)
|
|
177
|
+
|
|
178
|
+
def task_scores(self) -> np.ndarray:
|
|
179
|
+
with self._lock:
|
|
180
|
+
return np.array(self._task_scores[:])
|
|
181
|
+
|
|
182
|
+
def metrics(self) -> dict:
|
|
183
|
+
with self._lock:
|
|
184
|
+
unseen = np.array(self._unseen_task_weights[:])
|
|
185
|
+
return {
|
|
186
|
+
"task_scores": np.array(self._task_scores[:]),
|
|
187
|
+
"task_score_steps": np.array(self._task_score_steps[:]),
|
|
188
|
+
"unseen_task_weights": unseen,
|
|
189
|
+
"task_staleness": np.array(self._task_staleness[:]),
|
|
190
|
+
"proportion_seen": self._proportion_seen(),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
def _state_dict_locked(self) -> dict:
|
|
194
|
+
return {
|
|
195
|
+
"task_scores": list(self._task_scores[:]),
|
|
196
|
+
"task_score_steps": list(self._task_score_steps[:]),
|
|
197
|
+
"task_staleness": list(self._task_staleness[:]),
|
|
198
|
+
"unseen_task_weights": list(self._unseen_task_weights[:]),
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
def _load_state_dict_locked(self, state: dict) -> None:
|
|
202
|
+
self._task_scores[:] = [float(value) for value in state.get("task_scores", self._task_scores[:])]
|
|
203
|
+
self._task_score_steps[:] = [int(value) for value in state.get("task_score_steps", self._task_score_steps[:])]
|
|
204
|
+
self._task_staleness[:] = [float(value) for value in state.get("task_staleness", self._task_staleness[:])]
|
|
205
|
+
self._unseen_task_weights[:] = [
|
|
206
|
+
float(value) for value in state.get("unseen_task_weights", self._unseen_task_weights[:])
|
|
207
|
+
]
|
|
208
|
+
self._recompute_weights()
|