From 9b32ca928f6c4a7248aba82bd6f0b2a8a3b72c1a Mon Sep 17 00:00:00 2001 From: Miguel de Icaza Date: Mon, 6 Nov 2017 22:34:53 -0500 Subject: [PATCH] TensorFlow 1.4 C API bindings + docs --- SampleTest/LowLevelTests.cs | 4 +- TensorFlowSharp/Buffer.cs | 3 + TensorFlowSharp/Queue.cs | 4 + TensorFlowSharp/Tensor.cs | 12 +- TensorFlowSharp/Tensorflow.cs | 375 +++++++++++++++++++++++++++++++++- 5 files changed, 382 insertions(+), 16 deletions(-) diff --git a/SampleTest/LowLevelTests.cs b/SampleTest/LowLevelTests.cs index 2db04c44..e93498c6 100644 --- a/SampleTest/LowLevelTests.cs +++ b/SampleTest/LowLevelTests.cs @@ -250,7 +250,7 @@ class AttributeTest : IDisposable static int counter; public TFStatus Status; TFGraph graph; - TFOperationDesc desc; + //TFOperationDesc desc; public AttributeTest () { @@ -331,7 +331,7 @@ public void AddControlInput () runner.AddTarget (noop); runner.Run (); throw new Exception ("This should have thrown an exception"); - } catch (Exception e) { + } catch (Exception) { Console.WriteLine ("Success, got the expected exception when using tensorflow control inputs to assert"); } } diff --git a/TensorFlowSharp/Buffer.cs b/TensorFlowSharp/Buffer.cs index 651952ce..9a799748 100644 --- a/TensorFlowSharp/Buffer.cs +++ b/TensorFlowSharp/Buffer.cs @@ -74,6 +74,9 @@ public class TFBuffer : TFDisposable internal TFBuffer (IntPtr handle) : base (handle) { } + /// + /// Initializes a new instance of the class. + /// unsafe public TFBuffer () : base ((IntPtr)TF_NewBuffer ()) { } diff --git a/TensorFlowSharp/Queue.cs b/TensorFlowSharp/Queue.cs index f7dcd4eb..19efbbdc 100644 --- a/TensorFlowSharp/Queue.cs +++ b/TensorFlowSharp/Queue.cs @@ -25,6 +25,10 @@ public QueueBase (TFSession session) Session = session ?? throw new ArgumentNullException (nameof (session)); } + /// + /// The session that this QueueBased was created for. + /// + /// The session. protected TFSession Session { get; private set; } /// diff --git a/TensorFlowSharp/Tensor.cs b/TensorFlowSharp/Tensor.cs index ea00cfad..20010e16 100644 --- a/TensorFlowSharp/Tensor.cs +++ b/TensorFlowSharp/Tensor.cs @@ -1118,6 +1118,10 @@ public object GetValue (bool jagged = false) } } + /// + /// Returns a that represents the current . + /// + /// A that represents the current . public override string ToString () { var n = NumDims; @@ -1134,14 +1138,6 @@ public override string ToString () return sb.ToString (); } - - - - - - - - private static int [] getLength (Array array, bool deep = true, bool max = false) { // This function gets the length of all dimensions in a multidimensional, jagged, or mixed array. diff --git a/TensorFlowSharp/Tensorflow.cs b/TensorFlowSharp/Tensorflow.cs index e5f71cab..0f361989 100644 --- a/TensorFlowSharp/Tensorflow.cs +++ b/TensorFlowSharp/Tensorflow.cs @@ -30,6 +30,7 @@ using TF_ImportGraphDefOptions = System.IntPtr; using TF_Library = System.IntPtr; using TF_BufferPtr = System.IntPtr; +using TF_Function = System.IntPtr; using size_t = System.UIntPtr; using System.Numerics; @@ -517,6 +518,12 @@ public void SetTensorShape (TFOutput output, long [] dims, TFStatus status = nul [DllImport (NativeBinding.TensorFlowLibrary)] static extern unsafe int TF_GraphGetTensorNumDims (TF_Graph graph, TFOutput output, TF_Status status); + /// + /// Returns the number of dimensions of the Tensor referenced by output + /// + /// The number of dimensions of the tensor. + /// The tensor to probe. + /// Status buffer, if specified a status code will be left here, if not specified, a exception is raised if there is an error. public int GetTensorNumDims (TFOutput output, TFStatus status = null) { if (handle == IntPtr.Zero) @@ -1017,6 +1024,134 @@ public TFOutput [] AddGradients (TFOutput [] y, TFOutput [] x, TFOutput [] dx = return null; return ret; } + + [DllImport (NativeBinding.TensorFlowLibrary)] + static extern unsafe void TF_GraphCopyFunction (TF_Graph graph, TF_Function func, TF_Function grad, TF_Status status); + + [DllImport (NativeBinding.TensorFlowLibrary)] + static extern unsafe IntPtr TF_GraphToFunction (TF_Graph body, string fn_name, byte append_hash_to_fn_name, + int num_opers, IntPtr opers, + int ninputs, TFOutput [] inputs, + int noutputs, TFOutput [] ouputs, + string [] output_names, + IntPtr options, + string description, + TF_Status status); + + /// + /// Creates a TFFunction from a TFGraph + /// + /// The function. + /// Name of the new function. Should match the operation name (OpDef.name) regexp [A-Z][A-Za-z0-9_.\\-/]*. If appendHashToFunctioName is false, the name must be unique (at least those registered in graphs where this function will be used). + /// Optional, human readable description of this function. + /// Array of operations to become the body of the function or null. + /// If no array is given , all the + /// operations in function body will become part of the function + /// except operations referenced in inputs. These operations + /// must have a single output (these operations are typically + /// placeholders created for the sole purpose of representing + /// an input). + /// + /// If an array is given, all operations + /// in it will become part of the function. In particular, no + /// automatic skipping of dummy input operations is performed. + /// + /// Array that specify the inputs to the function, or null. The names used for function inputs are normalized + /// names of the operations (usually placeholders) pointed to by + /// inputs. These operation names should start with a letter. + /// Normalization will convert all letters to lowercase and + /// non-alphanumeric characters to '_' to make resulting names match + /// the "[a-z][a-z0-9_]*" pattern for operation argument names. + /// `inputs` cannot contain the same tensor twice. + /// rray that specify the inputs to the function, or null. This can contain the same tensor twice. + /// The names of the function's outputs. The array either has the same elements of outputs, or be null. Names must match "[a-z][a-z0-9_]*" regexp, if null is passed, the names are generated automatically. + /// If set to true appends hash to functionName, otherwise it will use the specified name in functionName. + /// Status buffer, if specified a status code will be left here, if not specified, a exception is raised if there is an error. + /// + /// + /// This method converts the graph whose operations (or a subset of its operations) will be converted + /// into a TFFunction. + /// + /// + /// Note that when the same TF_Output is listed as both an input and an output, + /// the corresponding function's output will equal to this input, + /// instead of the original node's output. + /// + /// + /// Callers must also satisfy the following constraints: + /// + /// + /// cannot refer to TFOutputs within a control flow context. For + /// example, one cannot use the output of "switch" node as input. + /// + /// + /// and cannot have reference types. Reference types are + /// not exposed through C API and are being replaced with Resources. We support + /// reference types inside function's body to support legacy code. Do not + /// use them in new code. + /// + /// + /// Every node in the function's body must have all of its inputs (including + /// control inputs). In other words, for every node in the body, each input + /// must be either listed in or must come from another node in + /// the body. In particular, it is an error to have a control edge going from + /// a node outside of the body into a node in the body. This applies to control + /// edges going from nodes referenced in to nodes in the body when + /// the former nodes are not in the body (automatically skipped or not + /// included in explicitly specified body). + /// + /// + public TFFunction ToFunction (string functionName, + string description, + TFOperation [] operations, + TFOutput [] inputs, + TFOutput [] outputs, + string [] outputNames, + bool appendHashToFunctionName = false, + TFStatus status = null) + { + if (functionName == null) + throw new ArgumentNullException (nameof (functionName)); + if (outputs == null) { + if (outputNames != null) + throw new ArgumentException ("outputs is null, but outputNames is not", nameof (outputNames)); + } else { + if (outputNames != null && outputs.Length != outputNames.Length) + throw new ArgumentException ("the outputs and outputNames array are specified, but have different lenghts"); + } + var cstatus = TFStatus.Setup (status); + + unsafe { + IntPtr functionOptions = IntPtr.Zero; + IntPtr ops = IntPtr.Zero; + int nops; + if (operations == null) { + nops = 0; + ops = IntPtr.Zero; + } else { + nops = operations.Length; + ops = Marshal.AllocHGlobal (sizeof (IntPtr) * operations.Length); + for (int i = 0; i < nops; i++) + Marshal.WriteIntPtr (ops, i * sizeof (IntPtr), operations [i].handle); + } + + var fnHandle = TF_GraphToFunction (handle, functionName, (byte) (appendHashToFunctionName ? 1 : 0), + nops, ops, + inputs == null ? 0 : inputs.Length, inputs, + outputs == null ? 0 : outputs.Length, outputs, + outputNames, + functionOptions, + description, + cstatus.Handle); + if (ops != IntPtr.Zero) + Marshal.FreeHGlobal (ops); + + if (!cstatus.CheckMaybeRaise (status, last: false)) + return null; + return new TFFunction (fnHandle); + } + } + } // @@ -1036,6 +1171,70 @@ internal override void NativeDispose (TF_Status handle) } } + /// + /// A grouping of operations with defined inputs and outputs. + /// Once created and added to graphs, functions can be invoked by creating an + /// operation whose operation type matches the function name. + /// + public class TFFunction : TFDisposable { + internal TFFunction (IntPtr handle) : base (handle) + { + } + + [DllImport (NativeBinding.TensorFlowLibrary)] + static extern unsafe void TF_FunctionToFunctionDef (IntPtr func, IntPtr buffer, TF_Status status); + + /// + /// Write out a serialized representation of the function as a FunctionDef protocol message to the provided + /// + /// An allocated buffer where the function will be serialized. + /// Status buffer, if specified a status code will be left here, if not specified, a exception is raised if there is an error. + public void ToFunctionDef (TFBuffer outputFuncDef, TFStatus status = null) + { + if (outputFuncDef == null) + throw new ArgumentNullException (nameof (outputFuncDef)); + var cstatus = TFStatus.Setup (status); + TF_FunctionToFunctionDef (handle, outputFuncDef.Handle, cstatus.Handle); + cstatus.CheckMaybeRaise (status, last: false); + } + + [DllImport (NativeBinding.TensorFlowLibrary)] + static extern unsafe void TF_DeleteFunction (IntPtr handle); + + internal override void NativeDispose (TF_Status handle) + { + TF_DeleteFunction (handle); + } + + [DllImport (NativeBinding.TensorFlowLibrary)] + static extern unsafe IntPtr TF_FunctionImportFunctionDef (byte* proto, IntPtr len, TF_Status status); + + /// + /// Construct and return the function whose FunctionDef representation is + /// serialized in proto + /// + /// The function definition, or null on failure. + /// Array containing the serialized FunctionDef in a protocol buffer. + /// Status buffer, if specified a status code will be left here, if not specified, a exception is raised if there is an error. + public TFFunction ImportFunctionDef (byte [] proto, TFStatus status = null) + { + if (proto == null) + throw new ArgumentNullException (nameof (proto)); + + var cstatus = TFStatus.Setup (status); + unsafe { + IntPtr res; + fixed (byte* p = &proto [0]) { + res = TF_FunctionImportFunctionDef (p, (IntPtr) proto.Length, cstatus.Handle); + if (!cstatus.CheckMaybeRaise (status, last: false)) + return null; + } + return new TFFunction (handle); + } + } + } + + /// /// TFGraph name scope handle /// @@ -1115,6 +1314,11 @@ internal override void NativeDispose (IntPtr handle) [DllImport (NativeBinding.TensorFlowLibrary)] static extern unsafe void TF_SetDevice (TF_OperationDescription desc, string device); + /// + /// Specifies the device for the operation, if one is not provided, the operation is unconstrained. + /// + /// This instance, allows for chaining operation invocations. + /// The device to constraint to in this operation. public TFOperationDesc SetDevice (string device) { if (handle == IntPtr.Zero) @@ -1530,11 +1734,11 @@ public partial class TFOperation { internal IntPtr handle; - /// - /// Gets the handle to the unmanaged TF_Operation object. - /// - /// The handle. - public IntPtr Handle => handle; + /// + /// Gets the handle to the unmanaged TF_Operation object. + /// + /// The handle. + public IntPtr Handle => handle; // Pointer to the graph, to keep it from collecting if there are TFOperations alive. internal TFGraph graph; @@ -2632,6 +2836,9 @@ public enum TFDataType : uint /// /// Variant data type /// + Variant = 21, + + } /// @@ -2639,29 +2846,168 @@ public enum TFDataType : uint /// public enum TFCode : uint { + /// + /// Not an error; returned on success + /// Ok = 0, + /// + /// The operation was cancelled (typically by the caller). + /// Cancelled = 1, + /// + /// Unknown error. An example of where this error may be returned is + /// if a Status value received from another address space belongs to + /// an error-space that is not known in this address space. Also + /// errors raised by APIs that do not return enough error information + /// may be converted to this error. + /// Unknown = 2, + + /// + /// Client specified an invalid argument. Note that this differs + /// from FailedPrecondition. InvalidArgumentindicates arguments + /// that are problematic regardless of the state of the system + /// (e.g., a malformed file name). + /// InvalidArgument = 3, + + /// + /// Deadline expired before operation could complete. For operations + /// that change the state of the system, this error may be returned + /// even if the operation has completed successfully. For example, a + /// successful response from a server could have been delayed long + /// enough for the deadline to expire. + /// DeadlineExceeded = 4, + + /// + /// Some requested entity (e.g., file or directory) was not found. + /// For privacy reasons, this code may be returned when the client + /// does not have the access right to the entity. + /// NotFound = 5, + + /// + /// Some entity that we attempted to create (e.g., file or directory) already exists. + /// AlreadyExists = 6, + + /// + /// The caller does not have permission to execute the specified + // operation. PermissionDenied must not be used for rejections + // caused by exhausting some resource (use ResourceExhausted + // instead for those errors). PermissionDeniedmust not be + // used if the caller can not be identified (use Unauthenticated + // instead for those errors). + /// PermissionDenied = 7, + + /// + /// The request does not have valid authentication credentials for the + /// operation. + /// Unauthenticated = 16, + + /// + /// Some resource has been exhausted, perhaps a per-user quota, or + /// perhaps the entire file system is out of space. + /// ResourceExhausted = 8, + + /// + /// Operation was rejected because the system is not in a state + /// required for the operation's execution. For example, directory + /// to be deleted may be non-empty, an rmdir operation is applied to + /// a non-directory, etc. + /// + /// A litmus test that may help a service implementor in deciding + /// between FailedPrecondition, Aborted, and Unavailable: + /// + /// (a) Use Unavailableif the client can retry just the failing call. + /// (b) Use Aborted if the client should retry at a higher-level + /// (e.g., restarting a read-modify-write sequence). + /// (c) Use FailedPrecondition if the client should not retry until + /// the system state has been explicitly fixed. E.g., if an "rmdir" + /// fails because the directory is non-empty, FailedPrecondition + /// should be returned since the client should not retry unless + /// they have first fixed up the directory by deleting files from it. + /// (d) Use FailedPrecondition if the client performs conditional + /// REST Get/Update/Delete on a resource and the resource on the + /// server does not match the condition. E.g., conflicting + /// read-modify-write on the same resource. + /// FailedPrecondition = 9, + + /// + /// The operation was aborted, typically due to a concurrency issue + /// like sequencer check failures, transaction aborts, etc. + /// + /// See litmus test above for deciding between FailedPrecondition, + /// Aborted and Unavailable + /// Aborted = 10, + + /// + /// Operation tried to iterate past the valid input range. E.g., seeking or + // reading past end of file. + // + // Unlike InvalidArgument, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate InvalidArgument if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // OutOfRange if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between FailedPrecondition and + // OutOfRange. We recommend using OutOfRane (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an OutOfRange error to detect when + // they are done. + /// OutOfRange = 11, + + /// + /// Operation is not implemented or not supported/enabled in this service. + /// Unimplemented = 12, + + /// + /// Internal errors. Means some invariants expected by underlying + /// system has been broken. If you see one of these errors, + /// something is very broken. + /// Internal = 13, + + /// + /// The service is currently unavailable. This is a most likely a + /// transient condition and may be corrected by retrying with + /// a backoff. + /// + /// See litmus test above for deciding between FailedPrecondition, + /// Aborted, and Unavailable. + /// Unavailable = 14, + + /// + /// Unrecoverable data loss or corruption. + /// DataLoss = 15 } + /// + /// Represents a specific input of an operation. + /// [StructLayout (LayoutKind.Sequential)] public struct TFInput { + /// + /// The operation that this input is for + /// public unsafe TF_Operation Operation; + + /// + /// The index of the output within the Operation + /// public int Index; // extern TF_Output TF_OperationInput (TF_Input oper_in); @@ -2700,6 +3046,10 @@ public TFOutput GetOutput (TFInput operIn) public struct TFOutput { unsafe TF_Operation LLOperation; + + /// + /// The index of the output within the operation. + /// public int Index; // extern int TF_OperationOutputNumConsumers (TF_Output oper_out); @@ -2741,7 +3091,7 @@ public TFOutput (TFOperation operation, int index = 0) /// /// Initializes a new TFOutput instance from another TFOutput /// - /// The other TFOutput that is having its operation attached. + /// The other TFOutput that is having its operation attached. /// The index of the output within the operation, if not specified, it defaults to zero. public TFOutput (TFOutput output, int index = 0) { @@ -2781,6 +3131,11 @@ public TFInput [] OutputConsumers { /// /// The operation. public TFOperation Operation => new TFOperation (null, LLOperation); + + /// + /// Returns a that represents the current . + /// + /// A that represents the current . public override string ToString () { return string.Format ("[{3} Index={1} Operation={2} (0x{0:X})]", (long) LLOperation, Index, Operation, OutputType); @@ -2855,6 +3210,10 @@ public struct TFAttributeMetadata public TFAttributeType Type; public long TotalSize; + /// + /// Returns a that represents the current . + /// + /// A that represents the current . public override string ToString () { return string.Format ($"[TFAttributeMetadata IsList={IsList} ListSize={ListSize} Type={Type} TotalSize={TotalSize}]"); @@ -2997,6 +3356,10 @@ public bool IsLongArray { } } + /// + /// Returns a that represents the current . + /// + /// A that represents the current . public override string ToString () { if (dims == null)