internal void Reduce(ModelBuilder net, ONNXNodeWrapper node, Layer.Type reduceType) { node.UnsupportedAttribute("keepdims", 1); // @TODO: extract into separate function and reuse for other Reduce ops var features = node.Input0Features; var rank = node.Input0Rank; object input = node.Input0; foreach (var onnxAxis in node.Axes) { var axis = ONNXLayout.ConvertAxisToBarracuda(onnxAxis, onnxRank: rank, onnxLayout: "NCHW"); input = net.Reduce(reduceType, $"{node.Name}__axis{axis}", input, axis); bool lastAxis = (axis == -1 || axis == node.Input0Rank - 1); // last axis in Barracuda is feature axis if (lastAxis) { features = 1; // if reducing over the last feature axis, then operation will collapse all features to 1 } rank--; // rank will be reduced after this operation Output(name, features: features, rank: rank); } net.Identity(node.Name, input); }
internal static void Resize2D(ModelBuilder net, ONNXNodeWrapper node, float[] scales) { if (!scales.All(x => x > 0.0f)) { Warn(net, node, $"Only positive scale values are supported."); } if (scales.All(x => x < 1.0f)) { if (!scales.All(x => Mathf.Approximately(1f / x, Mathf.Round(1f / x)))) { Warn(net, node, $"Only inverse of scale values which produce integer are currently supported. Inverse of scale value will be rounded to closest integer."); } var noStride = new[] { 1, 1 }; var noPad = new[] { 0, 0 }; var inverseScalesRoundedToInt = scales.Select(x => (int)Mathf.Round(1f / x)).ToArray(); // @TODO: nearest, actually this is bilinear downsampling net.AvgPool2D(node.Name, node.Input0, inverseScalesRoundedToInt, noStride, noPad); } else { if (!scales.All(x => Mathf.Approximately(x, Mathf.Round(x)))) { Warn(net, node, $"Only integer scale values are currently supported. Scale value will be rounded to closest integer value."); } var scalesRoundedToInt = scales.Select(x => (int)Mathf.Round(x)).ToArray(); net.Upsample2D(node.Name, node.Input0, scalesRoundedToInt); } }
// Logging helpers private static void Warn(ModelBuilder builder, ONNXNodeWrapper node, string message) { Warn(builder.model, node.Name, message); }
private void Output(ONNXNodeWrapper node, int features = -1, int rank = -1) { Output(node.Name, features, rank); }
// Helpers to keep track of model tensors private void Const(ONNXNodeWrapper node, ONNXTensor onnxTensor) { m_ModelTensors.AddConstant(node.Name, onnxTensor); }
private ONNXTensor BakeNodeIntoConstant(Action <ModelBuilder, ONNXNodeWrapper> opImportAction, ONNXNodeWrapper node) { var model = new Model(); var net = new ModelBuilder(model); // add all inputs as constants Debug.Assert(node.AreAllInputsConst); for (var i = 0; i < node.InputCount; ++i) { var assumeOnnxLayout = i == 0 ? "NCHW" : "CONST"; var input = node.Inputs[i]; net.Const(input, constantTensors[input].ToBarracuda(assumeOnnxLayout)); } // add node that we are going to bake into the constant opImportAction(net, node); // bake var noInputs = new Dictionary <string, Tensor>(); var useCPUforBaking = WorkerFactory.Device.CPU; var worker = WorkerFactory.CreateWorker(model, useCPUforBaking); var result = worker.ExecuteAndWaitForCompletion(noInputs); // convert from Barracuda back into ONNX layout var onnxData = ONNXTensor.Permute(result, new int[] { 0, 3, 1, 2 }); // NHWC -> NCHW var onnxShape = onnxData.shape.ToArray().Select(x => (long)x).ToArray(); return(new ONNXTensor(onnxData, onnxShape).SqueezeAll()); }
private Model ConvertOnnxModel(ModelProto onnxModel) { var model = new Model(); var modelBuilder = new ModelBuilder(model); // Convert graph inputs & outputs var initializersByName = onnxModel.Graph.Initializer.ToDictionary(i => i.Name, i => true); foreach (ValueInfoProto i in onnxModel.Graph.Input) { // skip input tensors that have initializer data, they are constant tensors not global inputs if (initializersByName.ContainsKey(i.Name)) { continue; } if (m_OverrideGlobalInputs.ContainsKey(i.Name)) { Const(i.Name, m_OverrideGlobalInputs[i.Name]); continue; } modelBuilder.Input(i.Name, ONNXLayout.ConvertSymbolicShapeToBarracuda(i.Type.TensorType.Shape, onnxLayout: "NCHW")); Output(i.Name, onnxShape: i.Type.TensorType.Shape.Dim.Select(d => d.DimValue).ToArray(), onnxLayout: "NCHW"); } foreach (ValueInfoProto o in onnxModel.Graph.Output) { modelBuilder.Output(o.Name); } // TODO: process model (recurrent nodes) memories // Read constants from initializer list foreach (TensorProto initializer in onnxModel.Graph.Initializer) { Const(initializer.Name, new ONNXTensor(initializer)); } // Convert graph nodes foreach (NodeProto onnxNode in onnxModel.Graph.Node) { var node = new ONNXNodeWrapper(onnxNode, m_ModelTensors, model.Warnings); var nodeId = node.Name; var opType = node.OperatorType; Output(node); bool injectDummy = false; if (m_NodeImporters.ContainsKey(opType)) { try { if (node.AreAllInputsConst && !m_ShouldNotBeBaked.Contains(opType)) { Profiler.BeginSample($"Bake {opType} {node.Name}"); var bakedTensor = BakeNodeIntoConstant(m_NodeImporters[opType], node); Const(node.Name, bakedTensor); var printTensor = bakedTensor.ToBarracuda("NCHW"); D.Log($"Baked node {nodeId} into constant of shape {printTensor.shape} and values: {printTensor.DataToString()}"); Profiler.EndSample(); } else { Profiler.BeginSample($"Import {opType} {node.Name}"); m_NodeImporters[opType](modelBuilder, node); Profiler.EndSample(); } } catch (Exception e) { // We support the layer but something went wrong while importing it // We log the problem and insert an identity layer string message = $"Unexpected error while parsing layer {nodeId} of type {opType}.\n{e.Message}\n\nJson: {onnxNode}\n{e.StackTrace}\n"; Warn(model, nodeId, message); injectDummy = true; } } else { //We don't support this type of layer //We log the problem and insert an identity layer string message = $"Unknown type encountered while parsing layer {nodeId} of type {opType}. We replace by an identity layer."; Warn(model, nodeId, message); injectDummy = true; } if (injectDummy) { var originalLayerHadInputs = (node.InputCount > 0); if (originalLayerHadInputs) { modelBuilder.Identity(nodeId, node.Input0); } else // if errorneous layer had no inputs, inject dummy constant which does not require any inputs { modelBuilder.Const(nodeId, new Tensor()); } } m_ModelTensors.CompleteUninitializedFields(node); } // Convert constant tensors int insertionIndex = 0; foreach (var entry in constantTensors) { modelBuilder.Const(entry.Key, entry.Value.ToBarracuda(onnxLayout: "CONST"), insertionIndex++); } // Model should not contain any broken links in the end var unconnectedInputs = ModelAnalyzer.FindBrokenLinks(model); Debug.Assert(unconnectedInputs.Length == 0); if (unconnectedInputs.Length > 0) { var message = $"Broken links: {string.Join(", ", unconnectedInputs)}"; Warn(model, "", message); } // Parse meta data var irVersion = onnxModel.IrVersion; // legacy if (onnxModel.OpsetImport?.Count > 0) { irVersion = onnxModel.OpsetImport[0].Version; } model.ProducerName = $"{onnxModel.ProducerName} v{onnxModel.ProducerVersion}"; model.IrSource = "ONNX"; model.IrVersion = $"{irVersion}"; // strip :0 at the end of string name for TF import if (patchRemoveTrailingTFExportCharacters) { model.inputs = model.inputs.Select(i => { i.name = i.name.EndsWith(":0") ? i.name.Remove(i.name.Length - 2) : i.name; return(i); }).ToList(); model.outputs = model.outputs.Select(o => { o = o.EndsWith(":0") ? o.Remove(o.Length - 2) : o; return(o); }).ToList(); model.memories = model.memories.Select(m => { m.input = m.input.EndsWith(":0") ? m.input.Remove(m.input.Length - 2) : m.input; m.output = m.output.EndsWith(":0") ? m.output.Remove(m.output.Length - 2) : m.output; return(m); }).ToList(); model.layers = model.layers.Select(l => { l.name = l.name.EndsWith(":0") ? l.name.Remove(l.name.Length - 2) : l.name; for (int i = 0; i < l.datasets.Length; i++) { l.datasets[i].name = l.datasets[i].name.EndsWith(":0") ? l.datasets[i].name.Remove(l.datasets[i].name.Length - 2) : l.datasets[i].name; } for (int i = 0; i < l.inputs.Length; i++) { l.inputs[i] = l.inputs[i].EndsWith(":0") ? l.inputs[i].Remove(l.inputs[i].Length - 2) : l.inputs[i]; } return(l); }).ToList(); } return(model); }