Files
unity-application/Packages/com.unity.barracuda/Runtime/Core/Backends/ModelAnalyzer.cs
2023-03-18 19:53:17 +00:00

923 lines
33 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Profiling;
[assembly: InternalsVisibleTo("Unity.Barracuda.ONNX")]
[assembly: InternalsVisibleTo("Unity.Barracuda.Editor")]
namespace Unity.Barracuda {
internal class ModelAnalyzer
{
public static string GetDefaultInputName(Model model)
{
bool modelHasOnlyOneInput = model.inputs.Count == 1;
if (modelHasOnlyOneInput)
return model.inputs[0].name;
var memories = new HashSet<string>();
foreach (var m in model.memories)
memories.Add(m.input);
// find the first unconnected input as a default model input
var previousLayerNames = new HashSet<string>();
foreach (var l in model.layers)
{
previousLayerNames.Add(l.name);
bool layerDoesNotNeedInput = (l.type == Layer.Type.Load);
if (layerDoesNotNeedInput)
continue;
foreach (var inputName in l.inputs)
{
bool inputIsUnconnected = !previousLayerNames.Contains(inputName);
bool inputIsNotPartOfMemory = !memories.Contains(inputName);
if (inputIsUnconnected && inputIsNotPartOfMemory)
return inputName;
}
}
return "";
}
static public string GetDefaultOutputName(Model model)
{
if (model.outputs.Count == 1)
return model.outputs[0];
if (model.layers.Count > 0)
{
var lastLayer = model.layers[model.layers.Count - 1];
return lastLayer.name;
}
return "";
}
public static TensorShape?[] ListTemporaryTensorShapes(Model model, IDictionary<string, TensorShape> inputShapes)
{
IDictionary<string, TensorShape?> shapesByName;
return ListTemporaryTensorShapes(model, inputShapes, out shapesByName);
}
public static TensorShape?[] ListTemporaryTensorShapes(Model model, IDictionary<string, TensorShape> inputShapes,
out IDictionary<string, TensorShape?> shapesByName)
{
Profiler.BeginSample ("Barracuda.ListTemporaryTensorShapes");
var shapes = new List<TensorShape?>();
shapesByName = new Dictionary<string, TensorShape?>();
foreach (var entry in inputShapes)
shapesByName.Add(entry.Key, entry.Value);
TensorShape? Xn;
shapesByName.TryGetValue(GetDefaultInputName(model), out Xn); // default input
TensorShape? O = Xn;
foreach (var l in model.layers)
{
if (l.inputs.Length > 0 && shapesByName.TryGetValue(l.inputs[0], out TensorShape? xShape))
Xn = xShape;
else
Xn = O; // previous output is used, if-and-only-if layer has no explicit inputs
if (Xn == null)
{
shapes.Add(Xn);
shapesByName.Add(l.name, Xn);
continue;
}
TensorShape X = Xn.Value;
if (l.type == Layer.Type.Dense)
{
Assert.IsNotNull(l.datasets);
var W = l.datasets[0].shape;
O = new TensorShape(X.flatHeight, W.flatWidth);
}
else if (l.type == Layer.Type.Dense3)
{
Assert.IsNotNull(l.datasets);
var W = l.datasets[0].shape;
O = new TensorShape(X.batch, 1, W.channels, X.channels);
}
else if (l.type == Layer.Type.MatMul)
{
if (!shapesByName.ContainsKey(l.inputs[1]) || shapesByName[l.inputs[1]] == null)
{
O = null;
break;
}
var Y = shapesByName[l.inputs[1]].Value;
int rankX;
int rankY;
List<int> onnxXshape;
List<int> onnxYshape;
if (l.pool == null || l.pool.Length == 0)
{
LegacyGetXYRanks(X, Y, out rankX, out rankY);
}
else
{
rankX = l.pool[0];
rankY = l.pool[1];
}
onnxXshape = Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaShapeToOnnxLayout(X, rankX);
onnxYshape = Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaShapeToOnnxLayout(Y, rankY);
int rankO = Math.Max(rankX, rankY);
// pad 1 on front of shape to both be rankO shape
for (int i = 0; i < (rankX - rankY); i++)
onnxYshape.Insert(0, 1);
for (int i = 0; i < (rankY - rankX); i++)
onnxXshape.Insert(0, 1);
if (rankO == 2)
O = new TensorShape(onnxXshape[0], 1, 1, onnxYshape[1]);
else if (rankO == 3)
O = new TensorShape(Math.Max(onnxXshape[0], onnxYshape[0]), 1, onnxYshape[2], onnxXshape[1]);
else
O = new TensorShape(Math.Max(onnxXshape[0], onnxYshape[0]), onnxXshape[2], onnxYshape[3], Math.Max(onnxXshape[1], onnxYshape[1]));
}
else if (
l.type == Layer.Type.Conv2D ||
l.type == Layer.Type.Conv3D ||
l.type == Layer.Type.DepthwiseConv2D)
{
var K = l.datasets[0].shape;
Assert.IsNotNull(l.stride);
Assert.IsNotNull(l.pad);
var pad = X.AdjustPadToKernel(K, l.stride, l.pad);
O = X.ApplyKernel(K, l.stride, pad);
}
else if (
l.type == Layer.Type.Conv2DTrans)
{
var K = l.datasets[0].shape;
Assert.IsNotNull(l.stride);
Assert.IsNotNull(l.pad);
// pool size is treated as output_adjustment aka output_padding here
var outputAdjustment = l.pool;
var pad = X.AdjustPadToKernel(K, l.stride, l.pad);
O = X.ApplyKernelInverse(K, l.stride, pad, outputAdjustment);
}
else if (
l.type == Layer.Type.Upsample2D)
{
if(l.pool.Length != 2)
{
O = null;
}
else
{
// pool size is treated as upsample coefficient here
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 2);
O = new TensorShape(X.batch, X.height * l.pool[1], X.width * l.pool[0], X.channels);
}
}
else if (
l.type == Layer.Type.Upsample3D)
{
if(l.pool.Length != 2)
{
O = null;
}
else
{
// pool size is treated as upsample coefficient here
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 3);
O = new TensorShape(1,1,X.batch, 1, X.depth * l.pool[2], X.height * l.pool[1], X.width * l.pool[0], X.channels);
}
}
else if (
l.type == Layer.Type.Resample2D)
{
if(l.pool.Length != 2)
{
O = null;
}
else
{
// pool is treated as resample size here
var size = l.pool;
Assert.IsNotNull(size);
Assert.AreEqual(size.Length, 2);
O = new TensorShape(X.batch, size[1], size[0], X.channels);
}
}
else if (
l.type == Layer.Type.DepthToSpace)
{
// pool size is treated as blocksize here
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 2);
Assert.AreEqual(X.channels % (l.pool[0] * l.pool[1]), 0);
O = new TensorShape(X.batch, X.height * l.pool[1], X.width * l.pool[0], X.channels / (l.pool[0] * l.pool[1]));
}
else if (
l.type == Layer.Type.SpaceToDepth)
{
// pool size is treated as blocksize here
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 2);
O = new TensorShape(X.batch, X.height / l.pool[1], X.width / l.pool[0], X.channels * (l.pool[0] * l.pool[1]));
}
else if (
l.type == Layer.Type.MaxPool2D ||
l.type == Layer.Type.AvgPool2D)
{
Assert.IsNotNull(l.pool);
Assert.IsNotNull(l.stride);
Assert.IsNotNull(l.pad);
var pad = X.AdjustPadToPool(l.pool, l.stride, l.pad);
O = X.ApplyPool(l.pool, l.stride, pad);
}
else if (
l.type == Layer.Type.GlobalMaxPool2D ||
l.type == Layer.Type.GlobalAvgPool2D)
{
O = new TensorShape(X.batch, 1, 1, X.channels);
}
else if (l.type == Layer.Type.Border3D)
{
Assert.IsNotNull(l.pad);
// legacy support
if (l.pad.Length == 6)
X = X.ApplyBorder(new[] { l.pad[0], l.pad[1], l.pad[2], 0, l.pad[3], l.pad[4], l.pad[5], 0 });
else
O = X.ApplyBorder(l.pad);
}
else if (
l.type == Layer.Type.Border2D ||
l.type == Layer.Type.Pad2DReflect ||
l.type == Layer.Type.Pad2DSymmetric ||
l.type == Layer.Type.Pad2DEdge)
{
Assert.IsNotNull(l.pad);
// legacy support
if (l.pad.Length == 4)
X = X.ApplyBorder(new[] { l.pad[0], l.pad[1], 0, l.pad[2], l.pad[3], 0 });
else
O = X.ApplyBorder(l.pad);
}
else if (
l.type == Layer.Type.Conv3D ||
l.type == Layer.Type.Conv3DTrans ||
l.type == Layer.Type.Upsample3D ||
l.type == Layer.Type.MaxPool3D ||
l.type == Layer.Type.AvgPool3D ||
l.type == Layer.Type.GlobalMaxPool3D ||
l.type == Layer.Type.GlobalAvgPool3D ||
l.type == Layer.Type.Border3D)
{
throw new NotImplementedException();
}
else if (
l.type == Layer.Type.RandomNormal ||
l.type == Layer.Type.RandomUniform)
{
Assert.IsNotNull(l.pool);
// pool size is treated as shape constant, if not empty
// otherwise shape of the previous tensor is used
if (l.pool.Length > 0)
O = new TensorShape(l.pool);
else
O = X;
}
else if (l.type == Layer.Type.ConstantOfShape)
{
if(l.axis != 1)
O = null;
else
O = X;
}
else if (
l.type == Layer.Type.Multinomial)
{
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 1);
O = new TensorShape(X.batch, l.pool[0]);
}
else if (
l.type == Layer.Type.OneHot)
{
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 1);
int depth = l.pool[0];
int inputRank = l.axis;
inputRank = inputRank < 0 ? X.dimensions : inputRank;
if (inputRank == 1)
O = new TensorShape(X.flatHeight, depth);
else if (inputRank == 2)
O = new TensorShape(X.flatHeight, 1, depth, X.flatWidth);
else
O = new TensorShape(X.batch, X.height, depth, X.channels);
}
else if (l.type == Layer.Type.RoiAlign)
{
Assert.IsNotNull(l.pool);
Assert.AreEqual(l.pool.Length, 2);
if (shapesByName.TryGetValue(l.inputs[1], out TensorShape? shape) && shape != null)
{
int batches = shape.Value.flatHeight;
O = new TensorShape(batches, l.pool[0], l.pool[1], X.channels);
}
else
O = null;
}
else if (
l.type == Layer.Type.Add ||
l.type == Layer.Type.Sub ||
l.type == Layer.Type.Mul ||
l.type == Layer.Type.Div ||
l.type == Layer.Type.Pow ||
l.type == Layer.Type.Min ||
l.type == Layer.Type.Max ||
l.type == Layer.Type.Mean||
l.type == Layer.Type.Greater ||
l.type == Layer.Type.GreaterEqual ||
l.type == Layer.Type.Less ||
l.type == Layer.Type.LessEqual ||
l.type == Layer.Type.Equal ||
l.type == Layer.Type.LogicalOr ||
l.type == Layer.Type.LogicalAnd ||
l.type == Layer.Type.LogicalXor ||
l.type == Layer.Type.Where)
{
// gather shapes by names
var list = new List<TensorShape>(l.inputs.Length);
bool allShapesKnown = true;
foreach (var i in l.inputs)
{
if (shapesByName.TryGetValue(i, out TensorShape? shape) && shape != null)
list.Add(shape.Value);
else
allShapesKnown = false;
}
O = allShapesKnown ? TensorExtensions.Max(list.ToArray()) : default(TensorShape?);
}
else if (
l.type == Layer.Type.ReduceL1 ||
l.type == Layer.Type.ReduceL2 ||
l.type == Layer.Type.ReduceLogSum ||
l.type == Layer.Type.ReduceLogSumExp ||
l.type == Layer.Type.ReduceMax ||
l.type == Layer.Type.ReduceMean ||
l.type == Layer.Type.ReduceMin ||
l.type == Layer.Type.ReduceProd ||
l.type == Layer.Type.ReduceSum ||
l.type == Layer.Type.ReduceSumSquare ||
l.type == Layer.Type.ArgMax ||
l.type == Layer.Type.ArgMin)
{
O = X.Reduce(l.axis);
}
else if (
l.type == Layer.Type.Flatten)
{
O = X.Flatten();
}
else if (
l.type == Layer.Type.Reshape)
{
// pool size is treated as the shape, if not empty
var size = l.pool;
Assert.IsNotNull(size);
if (size.Length == 0 && l.inputs.Length > 1)
{
switch (l.axis)
{
// Legacy - use the shape of the input tensor as the shape
case -1:
if (shapesByName.TryGetValue(l.inputs[1], out TensorShape? shape))
size = shape.Value.ToArray();
break;
// Use the tensor values as the shape; Calculated at runtime
case 1:
O = null;
break;
}
if (O == null)
break;
}
Assert.IsTrue( (size.Length == 4) || (size.Length == 8));
O = X.Reshape(size);
}
else if (
l.type == Layer.Type.Expand)
{
// pool size is treated as new shape
var newShape = l.pool;
Assert.IsNotNull(newShape);
Assert.IsTrue(newShape.Length == 8 || newShape.Length == 4);
O = new TensorShape(newShape);
}
else if (
l.type == Layer.Type.Transpose)
{
var permutations = l.pool;
if (permutations == null)
O = new TensorShape(X.flatWidth, X.flatHeight);
else
{
Assert.IsTrue(permutations.Length == 8 || permutations.Length == 4);
O = X.Permute(permutations);
}
}
else if (
l.type == Layer.Type.Gather)
{
if (!shapesByName.TryGetValue(l.inputs[0], out TensorShape? input0Shape) || input0Shape == null
|| !shapesByName.TryGetValue(l.inputs[1], out TensorShape? input1Shape) || input1Shape == null)
{
O = null;
break;
}
int[] shape = input0Shape.Value.ToArray();
shape[l.axis] = input1Shape.Value.length;
O = new TensorShape(shape);
if (l.pool != null && l.pool.Length == 2 && l.pool[1] > 1)
{
int xRank = l.pool[0];
int indicesRank = l.pool[1];
var oShape = Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaShapeToList(O.Value, xRank);
var indicesShape = Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaShapeToList(input1Shape.Value, indicesRank);
int axis = Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaAxisToTensor(l.axis, xRank);
oShape.InsertRange(axis, indicesShape);
oShape.RemoveAt(axis + indicesShape.Count);
O = (O.Value).Reshape(Compiler.IRShapeInferenceHelper.ShapeInference.BarracudaLayoutToTensorShapeLayout(oShape.ToArray()));
// rank 2 -> 3
if (xRank == 2 && oShape.Count == 3)
O = (O.Value).Permute(new int[] { 0, 1, 3, 2 });
}
}
else if (l.type == Layer.Type.ScatterND)
{
O = X;
}
else if (
l.type == Layer.Type.Squeeze ||
l.type == Layer.Type.Unsqueeze)
{
O = X;
}
else if (
l.type == Layer.Type.Concat)
{
// gather shapes by names
var list = new List<TensorShape>(l.inputs.Length);
bool allShapesKnown = true;
foreach (var i in l.inputs)
{
if (!shapesByName.TryGetValue(i, out var shape) || shape == null)
{
allShapesKnown = false;
continue;
}
list.Add(shape.Value);
}
O = allShapesKnown ? TensorExtensions.Concat(list.ToArray(), l.axis) : default(TensorShape?);
}
else if (
l.type == Layer.Type.StridedSlice)
{
Assert.IsNotNull(l.pad);
Assert.IsNotNull(l.pool);
Assert.IsNotNull(l.stride);
O = X.ApplyStridedSlice(l.pad, l.pool, l.stride);
}
else if (
l.type == Layer.Type.Tile)
{
// pool size is treated as tiling coefficient here
Assert.IsNotNull(l.pool);
var scale = l.pool;
O = X.Scale(scale);
}
else if (
l.type == Layer.Type.Load)
{
O = l.datasets[0].shape;
}
else if (// elementwise operations
l.type == Layer.Type.Nop ||
l.type == Layer.Type.Activation ||
l.type == Layer.Type.ScaleBias ||
l.type == Layer.Type.Normalization ||
l.type == Layer.Type.LRN ||
l.type == Layer.Type.Dropout ||
l.type == Layer.Type.LogicalNot ||
l.type == Layer.Type.Sign)
{
// works in place, keeps the same shape size
O = X;
}
else if (
l.type == Layer.Type.TopKIndices ||
l.type == Layer.Type.TopKValues ||
l.type == Layer.Type.NonMaxSuppression ||
l.type == Layer.Type.LSTM ||
l.type == Layer.Type.NonZero)
{
// Calculated at runtime
O = null;
}
else if (l.type == Layer.Type.Shape)
{
int shapeRank = l.axis > 0 ? 1 : X.length;
O = new TensorShape(shapeRank, 1, 1, 1);
}
else if (
l.type == Layer.Type.Conv3D ||
l.type == Layer.Type.Conv3DTrans ||
l.type == Layer.Type.Upsample3D ||
l.type == Layer.Type.MaxPool3D ||
l.type == Layer.Type.AvgPool3D ||
l.type == Layer.Type.GlobalMaxPool3D ||
l.type == Layer.Type.GlobalAvgPool3D ||
l.type == Layer.Type.Border3D)
{
throw new NotImplementedException("3D operations are not implemented yet!");
}
else
{
throw new NotImplementedException($"Layer type {l.type} needs to be explicitly handled");
}
shapes.Add(O);
shapesByName.Add(l.name, O);
}
Profiler.EndSample();
return shapes.ToArray();
}
// TODO: Remove when the legacy importer / code path is no longer needed (i.e. when pool is always set)
public static void LegacyGetXYRanks(TensorShape X, TensorShape Y, out int rankX, out int rankY)
{
// ONNX rank 2 : N,C => N,1,1,C
// rank 3 : one must be N C W, (batches = N) => N, 1, W, C
// rank 4 : one must be N C H W, (batches = N * C) => N H W C
// X and Y can be different ranks
var onnxXshape = new List<int> { X.batch, X.channels, X.height, X.width };
if (X.height == 1) onnxXshape = new List<int> { X.batch, X.channels, X.width, 1 };
var onnxYshape = new List<int> { Y.batch, Y.channels, Y.height, Y.width };
if (Y.height == 1) onnxYshape = new List<int> { Y.batch, Y.channels, Y.width, 1 };
rankX = 0;
for (int i = 3; i >= 0; i--)
{
if (onnxXshape[i] != 1)
{
rankX = i + 1;
break;
}
}
rankY = 0;
for (int i = 3; i >= 0; i--)
{
if (onnxYshape[i] != 1)
{
rankY = i + 1;
break;
}
}
}
public static bool TryGetOutputTensorShape(Model model, IDictionary<string, TensorShape> inputShapes, string output, out TensorShape shape)
{
shape = new TensorShape();
IDictionary<string, TensorShape?> shapesByName;
ListTemporaryTensorShapes(model, inputShapes, out shapesByName);
TensorShape? dynamicShape;
bool found = shapesByName.TryGetValue(output, out dynamicShape) && dynamicShape != null;
if (found)
shape = dynamicShape.Value;
return found;
}
public static bool TryGetOutputTensorShape(Model model, string output, out TensorShape shape)
{
var inputShapes = new Dictionary<string, TensorShape>();
foreach (var i in model.inputs)
inputShapes.Add(i.name, new TensorShape(i.shape));
return TryGetOutputTensorShape(model, inputShapes, output, out shape);
}
public static bool FindLayerByName(Model model, string name, out Layer layer)
{
layer = new Layer("",Layer.Type.Nop);
foreach (var l in model.layers)
{
if (l.name == name)
{
layer = l;
return true;
}
}
return false;
}
public static HashSet<Layer> FindLayersThatRequireStorage(Model model)
{
var allInputsExceptFromPreviousLayer = new HashSet<string>();
Layer prevLayer = null;
foreach (var layer in model.layers)
{
foreach (var input in layer.inputs)
if (prevLayer != null && input != prevLayer.name)
allInputsExceptFromPreviousLayer.Add(input);
prevLayer = layer;
}
var allOutputs = new HashSet<string>();
foreach (var output in model.outputs)
allOutputs.Add(output);
foreach (var memory in model.memories)
allOutputs.Add(memory.output);
allOutputs.Add(GetDefaultOutputName(model));
var requireStorage = new HashSet<Layer>();
foreach (var layer in model.layers)
{
// loading constant tensor requires storage
if (layer.type == Layer.Type.Load)
requireStorage.Add(layer);
// @TBD: implement safety check that ensures Nop never has input
// otherwise it has to be treated as Load operation
if (layer.type == Layer.Type.Nop)
requireStorage.Add(layer);
if (allInputsExceptFromPreviousLayer.Contains(layer.name) ||
allOutputs.Contains(layer.name))
requireStorage.Add(layer);
}
return requireStorage;
}
public static HashSet<Layer> FindUpstreamLayers(Model model, string[] outputs)
{
// TODO: replace with var layersByName = model.layers.ToDictionary(i => i.name, i => i);
var layersByName = new Dictionary<string, Layer>();
foreach (var l in model.layers)
layersByName.Add(l.name, l);
var connected = new HashSet<Layer>();
var layersToVisit = new HashSet<Layer>();
foreach (var o in outputs)
if (layersByName.ContainsKey(o))
{
layersToVisit.Add(layersByName[o]);
connected.Add(layersByName[o]);
}
while (layersToVisit.Count > 0)
{
var visitNext = new HashSet<Layer>();
foreach (var l in layersToVisit)
foreach (var i in l.inputs)
if (layersByName.ContainsKey(i))
{
visitNext.Add(layersByName[i]);
connected.Add(layersByName[i]);
}
layersToVisit = visitNext;
}
return connected;
}
public static TensorShape FindLargestNecessaryTensorShape(Model model, IDictionary<string, TensorShape> inputShapes)
{
Profiler.BeginSample ("Barracuda.FindLargestNecessaryTensorShape");
var shapes = ListTemporaryTensorShapes(model, inputShapes);
var maxTensorShape = new TensorShape(1,1,1,1);
foreach (var X in shapes)
if (X?.length > maxTensorShape.length)
maxTensorShape = X.Value;
Profiler.EndSample ();
return maxTensorShape;
}
public static TensorShape FindLargestArgumentTensorShape(Model model)
{
TensorShape maxTensorShape = new TensorShape(1,1,1,1);
foreach (var layer in model.layers)
foreach (var arg in layer.datasets)
if (arg.shape.length > maxTensorShape.length)
maxTensorShape = arg.shape;
return maxTensorShape;
}
public static string[] FindUnusedLayers(Model model)
{
var layerUsageByName = model.layers.ToDictionary(i => i.name, i => false);
foreach (var layer in model.layers)
{
if (layer.flags.HasFlag(Layer.Flags.Preserve))
layerUsageByName[layer.name] = true;
foreach (var i in layer.inputs)
{
layerUsageByName[i] = true;
}
}
foreach (var o in model.outputs)
{
layerUsageByName[o] = true;
}
foreach (var mem in model.memories)
{
layerUsageByName[mem.output] = true;
}
return layerUsageByName.Where(keyValue => !keyValue.Value).Select(keyValue => keyValue.Key).ToArray();
}
private static string[] FindBrokenLinks(Model model, HashSet<string> links)
{
var allVariables = new HashSet<string>(model.layers.Select(i => i.name));
var globalInputs = new HashSet<string>(model.inputs.Select(i => i.name));
var memoryInputs = new HashSet<string>(model.memories.Select(i => i.input));
allVariables.UnionWith(globalInputs);
allVariables.UnionWith(memoryInputs);
var brokenLinks = links;
brokenLinks.ExceptWith(allVariables);
return brokenLinks.ToArray();
}
private static string[] FindBrokenLinks(Model model, string[] links)
{
return FindBrokenLinks(model, new HashSet<string>(links));
}
public static string[] FindBrokenLinks(Model model)
{
// check global outputs
var linksToInspect = new HashSet<string>(model.outputs);
// and all layers
foreach (var layer in model.layers)
foreach (var i in layer.inputs)
linksToInspect.Add(i);
return FindBrokenLinks(model, linksToInspect);
}
public static string[] FindUnconnectedInputs(Model model)
{
var unconnected = model.inputs.ToDictionary(i => i.name, i => true);
// check global outputs
foreach (var o in model.outputs)
unconnected.Remove(o);
// and all layers
foreach (var layer in model.layers)
foreach (var i in layer.inputs)
unconnected.Remove(i);
return unconnected.Keys.ToArray();
}
public static string[] FindLayerOutputs(Model model, string layerName)
{
var allVariables = model.layers.Where(x => x.inputs.Contains(layerName)).Select(x => x.name);
var globalOutputs = model.outputs.Where(x => x == layerName); ;
allVariables.Union(globalOutputs);
return allVariables.ToArray();
}
static public string[] FindUnconnectedOutputs(Model model)
{
return FindBrokenLinks(model, model.outputs.ToArray());
}
public static bool IsLayerBroacastable(Layer layer)
{
return layer.type == Layer.Type.Add ||
layer.type == Layer.Type.Sub ||
layer.type == Layer.Type.Mul ||
layer.type == Layer.Type.Div ||
layer.type == Layer.Type.Pow ||
layer.type == Layer.Type.Min ||
layer.type == Layer.Type.Max ||
layer.type == Layer.Type.Mean ||
layer.type == Layer.Type.Greater ||
layer.type == Layer.Type.GreaterEqual ||
layer.type == Layer.Type.Less ||
layer.type == Layer.Type.LessEqual ||
layer.type == Layer.Type.Equal ||
layer.type == Layer.Type.LogicalOr ||
layer.type == Layer.Type.LogicalAnd ||
layer.type == Layer.Type.LogicalXor ||
layer.type == Layer.Type.Where ||
layer.type == Layer.Type.Concat;
}
public static bool IsLayerBroadcastSkippable(Layer layer)
{
if(layer.type == Layer.Type.ConstantOfShape)
{
// dynamic shape support
if (layer.axis != 1)
return true;
else
return false;
}
return false;
}
// Allow some unknown input dimension for shape inference pass
// for now batch does not yield problematic shape inference, so allow for unkown batch
public static bool IsInputShapeAcceptablyKnowForShapeInference(Model.Input input) // acceptable unknown shape : N
{
for (int i = 0; i < input.shape.Length; i++)
{
var x = input.shape[i];
if (x <= 0 && i != TensorShape.DataBatch)
return false;
}
return true;
}
public static bool DoesTransposeChangeTensorLayout(TensorShape shape, int[] permutations)
{
var activeDimLayout = new List<int>();
for (int i = 0; i < 8; i++)
{
if (shape[i] != 1)
activeDimLayout.Add(i);
}
if (permutations.Length == 4)
permutations = TensorExtensions.Get8DPermutationsForNHWCPermutationsAndShape(shape, permutations);
var transposedLayout = TensorExtensions.Permute(new[] { 0, 1, 2, 3, 4, 5, 6, 7 }, permutations);
var permutedShape = shape.Permute(permutations);
var premutedActiveDimLayout = new List<int>();
for (int i = 0; i < 8; i++)
{
if (permutedShape[i] != 1)
premutedActiveDimLayout.Add(transposedLayout[i]);
}
return activeDimLayout.SequenceEqual(premutedActiveDimLayout);
}
}
} // namespace Unity.Barracuda