Add agent group names and TCP port configuration to enable multi-server training - #68
Add agent group names and TCP port configuration to enable multi-server training#68stefanfausser wants to merge 3 commits into
Conversation
|
This comment is a discussion on multiagent training including this approach/workflow. I have used this approach for testing previously, and it works (it can also work with the same port so multi-port is not a strict requirement). For now, for multiagent training we have: SB3Multi-agent training with shared policy and critic:This works for simple multiagent training with same obs/action space (some examples envs use it already). For special cases like training agents with different obs/action spaces, there are some non-optimal workarounds (results not guaranteed):
Obviously, both are non optimal, but are potential workarounds for using SB3 (or e.g. CleanRL single policy scripts) directly. Multi-agent training with multiple servers (this approach):It allows using multiple policies. There are those unique advantages as mentioned such as complete flexibility of what script is used on the server side (as you mentioned), For disadvantages: Rllib:It supports multiple policies, shared critic (not implemented in the example, but supported), This approach should also support extending to some other RL libs that support multi-agent training natively. For disadvantages: It hasn't been updated very recently (however version should be limited to the last version that was tested to work fine). This PR and method specifically:The main question is:
The code in this PR itself is written well and a small change either way, it shouldn't create compatibility issues. The only potential, yet small, UX cost I see is having multiple paths to multi-agent training,
These are thoughts for brainstorming only, not anything that prevents merging itself, Your description helps with these already, at best it could be slightly more clear that setting the TCP port is entirely optional, and that setting a port here could break the standard workflow with SB3 script with n_parallel > 1 (which sets the ports automatically and that would be ignored when a custom port is set in Godot), this could be partially solved with an implementation warning not to use it unless you mean to do n_parallel = 1. Also, I made some assumptions here based on previous experience with my tests without a detailed test of your PR implementation. Feel free to add info/correct anything. Not directly related to PR:Another interesting workaround I consider similar in spirit (have tried and thought about, but haven't yet integrated into GDRL main due to lack of Stepping can be done manually instead of at every action repeat. It's a small tweak/easy to test per env when needed, One limitation are potential connection timeouts (can be adjusted), Still, it's an interesting potential tweak I mention to users and thought I'd share it here as well. |
|
Thank you, for your very good thoughts @Ivan-267. Let me elaborate a bit more about this PR. The main idea is to allow multi-server training by running multiple Python servers independently where each Python server corresponds to a neural network (actor-critic architecture) that could be exported to an ONNX model (when ONNX export is supported) after the training. This way, even with basic/unchanged SB3-Python-server, it is possible to have multiple agents without sharing the same obs/action space or workarounds (as you said). Later, one (or all) of the multiple agents could be reused in another environment / game via "Onnx inference" training mode. The Rllib multi-policy mode would still work but I don't think that it is possible this way to have multiple ONNX models (one per policy) as an outcome.
This PR should not break compatibility (as you said) but introduces two issues:
As can be seen in above code ( Understanding this pattern, multiple-servers + Example with two sync nodes and Sync node a: 11008, 11009, 11010
This includes the following arguments: I think that this is not a large issue as all of this could be pre-set in the nodes and does not require it to be set via arguments. However, this behaviour should be documented.
I fully agree on this. The configurable group names and TCP ports would be needed to be documented carefully should this PR be accepted. However, with the defaults I set, everything works as it did before. |
I have to correct myself. While theoretically the TCP ports could be distributed this way, currently this does not work. In fact, an exported executable of a Godot environment with two or more sync nodes, cannot be executed error-free via a Python server with the So when this PR would be accepted then multi-server would only work along with Godot in-editor environments (at least this works fine for me). And exported executables with two or more sync nodes could not be started via a Python server. Maybe there could be a workaround but this would involve to start multiple Python servers manually where only the first is starting the exported executable of a Godot env and the others are connecting to it (this would involve setting the right TCP ports and |
|
Thanks for the detailed response. This comment is only about Rllib, the other part on SB3 I'm still considering (it takes a bit more time).
If you mean the original implementation, Rllib multiagent training can produce an .onnx model for each trained policy. You'll find two .onnx models in the example, each exported via Rllib multiagent training: With script changes, as mentioned previously, you could also utilize shared critics or other more advanced methods for multiagent training. Another nice feature is that the Rllib PPO implementation allows using hybrid actions (discrete + continuous), Note that this was some years ago already. I did set the maximum Rllib version to a tested one, so hopefully it's still functional. I'm aware some restructure of original code is needed for a full update, but if the set version works fine, it should be good enough for now. I may revisit it at some point depending on time and how frequently multi-policy (not just multiagent) training gets used. |
|
Note that we already reached some of the same conclusions, but I was already writing this reply at the same time, I'll keep some parts to keep things chronological.
I understand, I briefly tried using this method before as well (like you, within in-editor, I didn't deeply enough analyze the n_parallel case workarounds as we have the Rllib feature as the main option).
You are correct it can work, it does need a small modification. Let's take an example of 2 sync nodes (an env with 2 different agent types, like that platform example env). Scenario 1: Step 1: Step 2: Step 3: It should work, you can train, export to onnx, etc. Scenario 2: Step 1: Step 2: What do we want to happen? Solution:
Scenario 3: Step 1: Step 2: The previous solution won't work as It's this check in sb3 wrapper: if env_path is None and n_parallel > 1:
raise ValueError("You must provide the path to a exported game executable if n_parallel > 1")So, it should work if we modify this check and then launch 2 executables manually. UX things left to consider/address
Potential solutions:
Note: As this option is flexible (SB3, CleanRL, or anything), users might also want to write their own scripts per use case.
Potential solutions:
Also, we have a It would be simpler if one name is used only for both features, but they work quite differently (Godot group vs policy name that is sent to rllib), so we need to also avoid
Yes, I think the most common use case would be to use the same arguments anyway for most of those so it would not be a huge downside. Update: Test implementation: I tried this on the multiagent example with two sync nodes at different ports, your PR, this change above, and from Godot Editor. New Godot has Note that we're using the same port twice. But, as the listening socket isn't open the entire time (it closes after each Godot process connects), it doesn't result in an error. We could also consider whether this can cause any other issues more. Not commenting out the port increment makes the TCP override not work with It would be good to test more to confirm. We can potentially collaborate on the implementation too if time allows. Let me know your thoughts on these details.
Update 2: |
I think I addressed this part by my very recent commit. Please check. |
Yes, this would not work as you said. However, how about this scenario (I have not yet tested it)? Scenario 4: Step 1: Start 1 SB3 example training script with Port 11108 + 11109 could be for the platform sync node. Step 2: Start 1 SB3 example training script with This should start two executables From Godot plugin perspective, this could work when instead of the From the Python server SB3 example |
I checked your responses and can confirm. I am happy to collaborate with you. Please have a look at my previous comments. Maybe you could work on the SB3 Python example? (Just a suggestion) |
There was a scenario that did work in testing, under
Will think about your scenario to compare when I have more time, just wanted to remind it can/did work under at least one setting so far.
Yes, I think that makes sense, I can work on some of the changes on the main repo (we'll see which are needed exactly after working out the details) + add suggestions here as before, as time allows. |
I understood. I brought Scenario 4 up because I currently have no idea what problem could occur when TCP ports are reused. With Scenario 4, there would be no port reuse. |
I'd have to check the details more, but as is, the issue is with having multiple listening sockets on the same port. What is done with our n_parallel approach with SB3 is sequential. Start listening, connect one env, stop listening, start listening (from another Python Godot env instance), connect another... So two scripts using the same port started in parallel would cause the error. To be more robust, we'll prefer a multi-port solution. |
Agreed. So Scenario 4 (see above) could help. I'll try to test this hypothetical example as soon as possible (maybe this evening, maybe tomorrow) |
Yes, I see you've pushed some commits. P.S. After considering this further, I am going to try locally a slight modification to the plugin approach that builds on the PR. I am thinking of trying a This shouldn't affect your testing, it's just an alternate approach at this point which may or may not be used, we'll see. Edit: Already I see some things to work out with this approach (e.g. when it comes to inference/human mode settings) and whether it adds more complexity than it removes. For start I'll check only the training part to see if it's useful there. |
|
With the last commit I now added the Now with this PR, the Godot plugin side is prepared for Scenario 4. From my current understanding, on the Python side the following modifications would need to be done:
|
|
Scenario 4 works. In addition to this PR, the only necessary changes must be done in Here: and here (as you @Ivan-267 pointed out): In particular the last change is not "nice" but enough for testing Scenario 4. There is no need to change However, the port argument needs to be added to the Python SB3 example. Steps for Scenario 4 with the MultiAgentSimple example:
Please note that this will use two TCP ports (because of `n_parallel = 2'): 11008 and 11009
Please note that this will use two TCP ports (because of `n_parallel = 2'): 11108 and 11109 And two exported executables will run. Edit: The agents learned nicely and the model zips and onnx were saved. No errors |
|
Thanks, it's really good to have I was able to briefly test it and it seems to work for me as well. Some onnx errors, but it's likely due to versions of things, I'll do a fresh latest version install for a final onnx check later. In the following days, I'd like to try it a bit more. |
No error visible on my setup. Btw. I use ONNX version 1.20.1 for C# / Godot side and a newer version for Python. |
|
Just as a brief update, now that we have a baseline that worked in our tests, my plan is to try a few small modifications in the next few days or so as mentioned before. Python side implementation also depends on that. I will focus on that first, and if the results are interesting - then I will propose the modifications and we can see whether to merge them or keep as is. The ONNX issue is something I'll check after the implementation is fully done, I don't think it's related to this PR specifically. |



What this PR does:
Now multi-server + multi-agent can be done by having multiple AI controllers (e.g. one for the player and another for a movable platform as done in the MultiAgentSimple example) and one sync node per AI controller. The sync nodes find their corresponding AI-controllers by the agent group names. In addition to this, every sync node must have a distinct TCP port, which can now be overridden.
Then multiple of the Python server examples, e.g.
stable_baselines3_example.py, can be started in parallel, each with a different TCP port that match the TCP port of one of the sync nodes (it even could be two different Python server examples or two times the same server example but with different learning rates, n_steps etc.).How I tested it:
The current version of the MultiAgentSimple example with the current version of godot_rl_agents_plugin + this PR merged, and then a second sync node added, works out-of-the-box for training, Python inference and Onnx inference (with the two Onnx files from the two
stable_baselines3_example.pyservers).So for the MultiAgentSimple example, I started
stable_baselines3_example.pytwo times, one with TCP port11008(for the player, agent group nameAGENT_PLAYER) and the other with TCP port11009(for the platform, agent group nameAGENT_PLATFORM). For this I had to slightly modifystable_baselines3_example.pyby handing over the port argument toStableBaselinesGodotEnv()call.If this PR is considered to be merged, then I can add a port argument to SB3 Python example to finish this line of feature.