using System; using System.Collections.Generic; using UnityEngine; using System.Linq; using Barracuda; using MLAgents.InferenceBrain; using UnityEngine.Profiling; namespace MLAgents { public enum InferenceDevice { CPU = 0, GPU = 1 } /// /// The Learning Brain works differently if you are training it or not. /// When training your Agents, drag the Learning Brain to the Academy's BroadcastHub and check /// the checkbox Control. When using a pretrained model, just drag the Model file into the /// Model property of the Learning Brain. /// The property model corresponds to the Model currently attached to the Brain. Before /// being used, a call to ReloadModel is required. /// When the Learning Brain is not training, it uses a TensorFlow model to make decisions. /// The Proximal Policy Optimization (PPO) and Behavioral Cloning algorithms included with /// the ML-Agents SDK produce trained TensorFlow models that you can use with the /// Learning Brain. /// [CreateAssetMenu(fileName = "NewLearningBrain", menuName = "ML-Agents/Learning Brain")] public class LearningBrain : Brain { private ITensorAllocator m_TensorAllocator; private TensorGenerator m_TensorGenerator; private TensorApplier m_TensorApplier; public NNModel model; private Model m_BarracudaModel; private IWorker m_Engine; private bool m_Verbose = false; private BarracudaModelParamLoader m_ModelParamLoader; private string[] m_OutputNames; [Tooltip("Inference execution device. CPU is the fastest option for most of ML Agents models. " + "(This field is not applicable for training).")] public InferenceDevice inferenceDevice = InferenceDevice.CPU; private IReadOnlyList m_InferenceInputs; private IReadOnlyList m_InferenceOutputs; [NonSerialized] private bool m_IsControlled; /// /// When Called, the brain will be controlled externally. It will not use the /// model to decide on actions. /// public void SetToControlledExternally() { m_IsControlled = true; } /// protected override void Initialize() { ReloadModel(); } /// /// Initializes the Brain with the Model that it will use when selecting actions for /// the agents /// /// The seed that will be used to initialize the RandomNormal /// and Multinomial obsjects used when running inference. /// Throws an error when the model is null /// public void ReloadModel(int seed = 0) { if (m_TensorAllocator == null) m_TensorAllocator = new TensorCachingAllocator(); if (model != null) { #if BARRACUDA_VERBOSE _verbose = true; #endif D.logEnabled = m_Verbose; // Cleanup previous instance if (m_Engine != null) m_Engine.Dispose(); m_BarracudaModel = ModelLoader.Load(model.Value); var executionDevice = inferenceDevice == InferenceDevice.GPU ? BarracudaWorkerFactory.Type.ComputePrecompiled : BarracudaWorkerFactory.Type.CSharp; m_Engine = BarracudaWorkerFactory.CreateWorker(executionDevice, m_BarracudaModel, m_Verbose); } else { m_BarracudaModel = null; m_Engine = null; } m_ModelParamLoader = BarracudaModelParamLoader.GetLoaderAndCheck(m_Engine, m_BarracudaModel, brainParameters); m_InferenceInputs = m_ModelParamLoader.GetInputTensors(); m_OutputNames = m_ModelParamLoader.GetOutputNames(); m_TensorGenerator = new TensorGenerator(brainParameters, seed, m_TensorAllocator, m_BarracudaModel); m_TensorApplier = new TensorApplier(brainParameters, seed, m_TensorAllocator, m_BarracudaModel); } /// /// Return a list of failed checks corresponding to the failed compatibility checks /// between the Model and the BrainParameters. Note : This does not reload the model. /// If changes have been made to the BrainParameters or the Model, the model must be /// reloaded using GiveModel before trying to get the compatibility checks. /// /// The list of the failed compatibility checks between the Model and the /// Brain Parameters public IEnumerable GetModelFailedChecks() { return (m_ModelParamLoader != null) ? m_ModelParamLoader.GetChecks() : new List(); } /// protected override void DecideAction() { if (m_IsControlled) { m_AgentInfos.Clear(); return; } var currentBatchSize = m_AgentInfos.Count(); if (currentBatchSize == 0) { return; } Profiler.BeginSample("LearningBrain.DecideAction"); if (m_Engine == null) { Debug.LogError($"No model was present for the Brain {name}."); return; } Profiler.BeginSample($"MLAgents.{name}.GenerateTensors"); // Prepare the input tensors to be feed into the engine m_TensorGenerator.GenerateTensors(m_InferenceInputs, currentBatchSize, m_AgentInfos); Profiler.EndSample(); Profiler.BeginSample($"MLAgents.{name}.PrepareBarracudaInputs"); var inputs = PrepareBarracudaInputs(m_InferenceInputs); Profiler.EndSample(); // Execute the Model Profiler.BeginSample($"MLAgents.{name}.ExecuteGraph"); m_Engine.Execute(inputs); Profiler.EndSample(); Profiler.BeginSample($"MLAgents.{name}.FetchBarracudaOutputs"); m_InferenceOutputs = FetchBarracudaOutputs(m_OutputNames); Profiler.EndSample(); Profiler.BeginSample($"MLAgents.{name}.ApplyTensors"); // Update the outputs m_TensorApplier.ApplyTensors(m_InferenceOutputs, m_AgentInfos); Profiler.EndSample(); m_AgentInfos.Clear(); Profiler.EndSample(); } protected Dictionary PrepareBarracudaInputs(IEnumerable infInputs) { var inputs = new Dictionary(); foreach (var inp in m_InferenceInputs) { inputs[inp.name] = inp.data; } return inputs; } protected List FetchBarracudaOutputs(string[] names) { var outputs = new List(); foreach (var n in names) { var output = m_Engine.Peek(n); outputs.Add(TensorUtils.TensorProxyFromBarracuda(output, n)); } return outputs; } public void OnDisable() { m_Engine?.Dispose(); m_TensorAllocator?.Reset(false); } } }