Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Barracuda inference for hybrid actions #4611

Merged
merged 14 commits into from
Nov 5, 2020

Conversation

dongruoping
Copy link
Contributor

@dongruoping dongruoping commented Oct 29, 2020

Proposed change(s)

Barracuda inference for hybrid actions.

Major changes:

1. The input argument lastActions to TensorApplier.IApplier is now an ActionBuffers instead of float[].

2. Changes to model output nodes

To support hybrid actions, deprecated old action output nodes which only work for single-type actions and added new output nodes:

  • Deprecated action in favor of continuous_actions and discrete_actions
  • Similarly, deprecated action_output_shape in favor of continuous_action_output_shape and discrete_action_output_shape
  • is_continuous_control is also deprecated

Now the expectation of the model outputs is either set of:

  • (old case) action, action_output_shape, is_continuous_control
  • (new case) continuous_actions, discrete_actions, continuous_action_output_shape, discrete_action_output_shape

Running inference will use the new nodes by default, and fall back to old ones if they doesn't exist.
Deprecated nodes are still supported and the code is able to run old models.

Version compatibility:
old model, old C# - current behavior, use old output nodes
new model, old C# - use old output nodes just like current behavior, new nodes are ignored
old model, new C# - detect that new output nodes do not exist in the model and fall back to old ones
new model, new C# - use new output nodes

Useful links (Github issues, JIRA tickets, ML-Agents forum threads etc.)

Types of change(s)

  • Bug fix
  • New feature
  • Code refactor
  • Breaking change
  • Documentation update
  • Other (please describe)

Checklist

  • Added tests that prove my fix is effective or that my feature works
  • Updated the changelog (if applicable)
  • Updated the documentation (if applicable)
  • Updated the migration guide (if applicable)

Other comments

{
actionValue = new float[actionSize];
lastActions[agentId] = actionValue;
actionBuffer = new ActionBuffers(new float[m_ActionSpec.NumContinuousActions],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I don't like about this is ContinuousActionOutputApplier has to initialize new ActionBuffers based on discrete actions branchSizes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make another ActionBuffers constructor (or static method) that takes an ActionSpec?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can do that.
But even with that I'm still not able to initialize continuous/discrete field separately and still need both sizes to initialize the new ActionBuffers here, due to ActionBuffers's read-only properties. I wonder if it's reasonable to change that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think initializing them separately is a requirement; you have all the information you need in the ActionSpec. Encapsulating it in an ActionBuffers static method seems fine to me.

You could also move the logic to a ModelRunner method, and call it in ModelRunner.DecideBatch(), just before the call to m_TensorApplier.ApplyTensors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is also the case that the DiscreteActionOutputApplier initializes ActionBuffers. Maybe we could have a InitializeBuffers method that gets called by both ContinuousActionOutputApplier and DiscreteActionOutputApplier if the ActionBuffers does not yet exist,.

{
actionValue = new float[actionSize];
lastActions[agentId] = actionValue;
actionBuffer = new ActionBuffers(new float[m_ActionSpec.NumContinuousActions],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is also the case that the DiscreteActionOutputApplier initializes ActionBuffers. Maybe we could have a InitializeBuffers method that gets called by both ContinuousActionOutputApplier and DiscreteActionOutputApplier if the ActionBuffers does not yet exist,.

@@ -16,7 +16,7 @@ internal class BarracudaModelParamLoader
{
enum ModelActionType
{
Unknown,
Hybrid,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still leave Unknown to be 0, and make Hybrid the third option

names.Add(TensorNames.ActionOutput);
if (!model.outputs.Contains(TensorNames.ContinuousActionOutput) && !model.outputs.Contains(TensorNames.DiscreteActionOutput))
{
names.Add(TensorNames.ActionOutputDeprecated);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the change, I think this is a very complex part of the codebase that we do not modify often, so do not hesitate to add some code comments.

@@ -164,9 +178,6 @@ public static string[] GetOutputNames(Model model)

var modelApiVersion = (int)model.GetTensorByName(TensorNames.VersionNumber)[0];
var memorySize = (int)model.GetTensorByName(TensorNames.MemorySize)[0];
var isContinuousInt = (int)model.GetTensorByName(TensorNames.IsContinuousControl)[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are modifying the expectation on the constants and outputs of the model. I think you should summarize in the PR description the overall changes made and how older models will work with the new code (and vice versa)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in the description

}
else
{
var isContinuous = model.GetTensorByName(TensorNames.IsContinuousControl)[0] > 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is IsContinuousControl deprecated? If it is the case, rename to IsContinuousControlDeprecated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, changed it as suggested

modelActionType = GetActionType(modelContinuousActionSize > 0, modelDiscreteActionSize > 0);
}
else
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
{
// For backwards compatibility

@@ -23,12 +23,16 @@ internal static class TensorNames
public const string MemorySize = "memory_size";
public const string VersionNumber = "version_number";
public const string IsContinuousControl = "is_continuous_control";
public const string ActionOutputShape = "action_output_shape";
public const string ActionOutput = "action";
public const string ActionOutputShapeDeprecated = "action_output_shape";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate the Deprecated section from the rest with a blank line for clarity

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move ActionOutputShapeDeprecated at the bottom with ActionOutputDeprecated

@vincentpierre vincentpierre self-requested a review November 3, 2020 18:11
@@ -157,7 +157,7 @@ public void TestGetOutputTensors1()
{
var model = ModelLoader.Load(continuous2vis8vec2actionModel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you made a lot of improvements to the model loading logic. I think you should add more tests to check your logic (loading an old model and a new model and see if they behave properly)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More tests with new models added

if (actionSpec.NumContinuousActions > 0)
{
m_Dict[TensorNames.ActionOutput] = new ContinuousActionOutputApplier();
var tensorName = useDeprecated ? TensorNames.ActionOutputDeprecated : TensorNames.ContinuousActionOutput;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

model.ContinuousOutputName()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's possible that the model is null here

actionSpec.CheckNotHybrid();

bool useDeprecated = false;
if (barracudaModel != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just early out if the model is null? Might make some of the code cleaner. (same for TensorGenerator below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@dongruoping dongruoping marked this pull request as ready for review November 4, 2020 19:33
@@ -72,12 +65,9 @@ public static int GetNumVisualInputs(Model model)

foreach (var input in model.inputs)
{
if (input.shape.Length == 4)
if (input.name.StartsWith(TensorNames.VisualObservationPlaceholderPrefix))
Copy link
Contributor

@chriselion chriselion Nov 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't have to be in this PR, but you might want to consider moving GetNumVisualInputs(), GetOutputNames(), etc to the extension methods too.

@@ -152,21 +149,13 @@ public static string[] GetOutputNames(Model model)
return failedModelChecks;
}

foreach (var constantName in TensorNames.RequiredConstants)
var modelApiVersionTensor = model.GetTensorByName(TensorNames.VersionNumber);
if (modelApiVersionTensor == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, doesn't need to be in this PR, but I feel like we lost some of the checks that the tensors we need are in the Model. It's never an issue for model files that we create, but a frequent source of forum/github posts would be people trying to train their own models, then add them for inference and get null reference errors because the assumed the tensors were there without checking.

I need to look a little more closely, but I think things like HasContinuousOutputs() would throw if not of the expected tensors are there.

We could add something like

bool CheckExpectedTensors(List<string> errorMessagesOut) {}

to the extension methods, call it once here, and then assume everything is present after that.

We should also have a test that CheckModel() with a basically empty model doesn't throw and returns some error strings.

Feel free to just log a jira as a reminder to come back to this, since I think it's a small regression in usability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I took care of that by the order of the checks, but I agree we should check it explicitly for safety.
Made a JIRA ticket.

@dongruoping
Copy link
Contributor Author

Some tests are failing because since this PR is not self-contained, and it needs some changes on python. Leave those to python PR will check when merge.

@dongruoping dongruoping changed the title [WIP] Barracuda inference for hybrid actions Barracuda inference for hybrid actions Nov 5, 2020
@dongruoping dongruoping merged commit fbbff02 into develop-hybrid-actions-csharp Nov 5, 2020
@delete-merged-branch delete-merged-branch bot deleted the develop-hybrid-inference branch November 5, 2020 21:02
dongruoping added a commit that referenced this pull request Dec 4, 2020
* Add hybrid action capability flag (#4576)

* Change BrainParametersProto to support ActionSpec (#4579)

* Assign new BrainParametersProto fields based on capabilities (#4581)

* ActionBuffer with hybrid actions for RemotePolicy (#4592)

* Barracuda inference for hybrid actions (#4611)

* Refactor BarracudaModel loader checks (#4629)

* Export separate nodes for continuous/discrete actions (#4655)

* Separate continuous/discrete actions in AgentActionProto (#4698)

* Force different nodes for new and deprecated action output (#4705)
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 6, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants