From 263fbcf2444e82706a0a9ff358806d94a0bc7f08 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Mon, 6 Jul 2020 07:50:37 -0700 Subject: [PATCH] Implement SafeTensorHandleHandle as a wrapper for TFE_TensorHandle --- src/TensorFlowNET.Core/Device/c_api.device.cs | 2 +- .../Eager/EagerRunner.TFE_Execute.cs | 6 +- .../Eager/EagerRunner.TFE_FastPathExecute.cs | 6 +- .../Eager/EagerTensor.Creation.cs | 14 +- .../Eager/EagerTensor.Implicit.cs | 3 +- .../Eager/SafeTensorHandleHandle.cs | 40 ++++++ .../Eager/TFE_TensorHandle.cs | 19 --- src/TensorFlowNET.Core/Eager/c_api.eager.cs | 37 +++-- .../Tensors/EagerTensorV2.cs | 8 +- src/TensorFlowNET.Core/Tensors/Tensor.cs | 3 +- .../Util/SafeHandleArrayMarshaler.cs | 132 ++++++++++++++++++ .../NativeAPI/CApiTest.cs | 22 +-- .../Eager/CApi.Eager.Execute_MatMul_CPU.cs | 30 ++-- .../CApi.Eager.OpGetInputAndOutputLengths.cs | 36 ++--- ...pi.Eager.OpInferMixedTypeInputListAttrs.cs | 20 ++- .../Eager/CApi.Eager.TensorHandle.cs | 3 +- .../Eager/CApi.Eager.TensorHandleDevices.cs | 69 ++++----- .../NativeAPI/Eager/CApi.Eager.Variables.cs | 57 ++++---- .../NativeAPI/Eager/CApi.Eager.cs | 36 ++--- 19 files changed, 369 insertions(+), 174 deletions(-) create mode 100644 src/TensorFlowNET.Core/Eager/SafeTensorHandleHandle.cs delete mode 100644 src/TensorFlowNET.Core/Eager/TFE_TensorHandle.cs create mode 100644 src/TensorFlowNET.Core/Util/SafeHandleArrayMarshaler.cs diff --git a/src/TensorFlowNET.Core/Device/c_api.device.cs b/src/TensorFlowNET.Core/Device/c_api.device.cs index 7ba6fed7..3b7cca5a 100644 --- a/src/TensorFlowNET.Core/Device/c_api.device.cs +++ b/src/TensorFlowNET.Core/Device/c_api.device.cs @@ -67,7 +67,7 @@ namespace Tensorflow /// TF_Status* /// TFE_TensorHandle* [DllImport(TensorFlowLibName)] - public static extern IntPtr TFE_TensorHandleCopyToDevice(IntPtr h, SafeContextHandle ctx, string device_name, SafeStatusHandle status); + public static extern SafeTensorHandleHandle TFE_TensorHandleCopyToDevice(SafeTensorHandleHandle h, SafeContextHandle ctx, string device_name, SafeStatusHandle status); /// /// Retrieves the full name of the device (e.g. /job:worker/replica:0/...) diff --git a/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_Execute.cs b/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_Execute.cs index 78a63b77..c2764cae 100644 --- a/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_Execute.cs +++ b/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_Execute.cs @@ -33,7 +33,7 @@ namespace Tensorflow.Eager { for (int i = 0; i < inputs.Length; ++i) { - IntPtr tensor_handle; + SafeTensorHandleHandle tensor_handle; switch (inputs[i]) { case EagerTensor et: @@ -50,10 +50,10 @@ namespace Tensorflow.Eager if (status.ok() && attrs != null) SetOpAttrs(op, attrs); - var outputs = new IntPtr[num_outputs]; + var outputs = new SafeTensorHandleHandle[num_outputs]; if (status.ok()) { - c_api.TFE_Execute(op, outputs, ref num_outputs, status.Handle); + c_api.TFE_Execute(op, outputs, out num_outputs, status.Handle); status.Check(true); } return outputs.Select(x => new EagerTensor(x)).ToArray(); diff --git a/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_FastPathExecute.cs b/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_FastPathExecute.cs index b2392d52..13189b1d 100644 --- a/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_FastPathExecute.cs +++ b/src/TensorFlowNET.Core/Eager/EagerRunner.TFE_FastPathExecute.cs @@ -154,8 +154,8 @@ namespace Tensorflow.Eager num_retvals += (int)delta; } - var retVals = new IntPtr[num_retvals]; - c_api.TFE_Execute(op, retVals, ref num_retvals, status.Handle); + var retVals = new SafeTensorHandleHandle[num_retvals]; + c_api.TFE_Execute(op, retVals, out num_retvals, status.Handle); status.Check(true); var flat_result = retVals.Select(x => new EagerTensor(x)).ToArray(); @@ -220,7 +220,7 @@ namespace Tensorflow.Eager SafeOpHandle op, Status status) { - IntPtr input_handle; + SafeTensorHandleHandle input_handle; // ConvertToTensor(); switch (inputs) diff --git a/src/TensorFlowNET.Core/Eager/EagerTensor.Creation.cs b/src/TensorFlowNET.Core/Eager/EagerTensor.Creation.cs index ab558f5d..543f671a 100644 --- a/src/TensorFlowNET.Core/Eager/EagerTensor.Creation.cs +++ b/src/TensorFlowNET.Core/Eager/EagerTensor.Creation.cs @@ -14,7 +14,7 @@ namespace Tensorflow.Eager } - public EagerTensor(IntPtr handle) : base(IntPtr.Zero) + public EagerTensor(SafeTensorHandleHandle handle) : base(IntPtr.Zero) { EagerTensorHandle = handle; Resolve(); @@ -58,14 +58,20 @@ namespace Tensorflow.Eager } public override IntPtr ToPointer() - => EagerTensorHandle; + => EagerTensorHandle?.DangerousGetHandle() ?? IntPtr.Zero; + + protected override void DisposeManagedResources() + { + base.DisposeManagedResources(); + + //print($"deleting DeleteTensorHandle {Id} {EagerTensorHandle.ToString("x16")}"); + EagerTensorHandle.Dispose(); + } protected override void DisposeUnmanagedResources(IntPtr handle) { //print($"deleting DeleteTensorHandle {Id} {_handle.ToString("x16")}"); c_api.TF_DeleteTensor(_handle); - //print($"deleting DeleteTensorHandle {Id} {EagerTensorHandle.ToString("x16")}"); - c_api.TFE_DeleteTensorHandle(EagerTensorHandle); } } } diff --git a/src/TensorFlowNET.Core/Eager/EagerTensor.Implicit.cs b/src/TensorFlowNET.Core/Eager/EagerTensor.Implicit.cs index 63112b0a..3635808d 100644 --- a/src/TensorFlowNET.Core/Eager/EagerTensor.Implicit.cs +++ b/src/TensorFlowNET.Core/Eager/EagerTensor.Implicit.cs @@ -8,7 +8,8 @@ namespace Tensorflow.Eager { public partial class EagerTensor { + [Obsolete("Implicit conversion of EagerTensor to IntPtr is not supported.", error: true)] public static implicit operator IntPtr(EagerTensor tensor) - => tensor.EagerTensorHandle; + => throw new NotSupportedException(); } } diff --git a/src/TensorFlowNET.Core/Eager/SafeTensorHandleHandle.cs b/src/TensorFlowNET.Core/Eager/SafeTensorHandleHandle.cs new file mode 100644 index 00000000..f3a836db --- /dev/null +++ b/src/TensorFlowNET.Core/Eager/SafeTensorHandleHandle.cs @@ -0,0 +1,40 @@ +/***************************************************************************** + Copyright 2018 The TensorFlow.NET Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using System; +using Tensorflow.Util; + +namespace Tensorflow.Eager +{ + public sealed class SafeTensorHandleHandle : SafeTensorflowHandle + { + private SafeTensorHandleHandle() + { + } + + public SafeTensorHandleHandle(IntPtr handle) + : base(handle) + { + } + + protected override bool ReleaseHandle() + { + c_api.TFE_DeleteTensorHandle(handle); + SetHandle(IntPtr.Zero); + return true; + } + } +} diff --git a/src/TensorFlowNET.Core/Eager/TFE_TensorHandle.cs b/src/TensorFlowNET.Core/Eager/TFE_TensorHandle.cs deleted file mode 100644 index aad81637..00000000 --- a/src/TensorFlowNET.Core/Eager/TFE_TensorHandle.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; - -namespace Tensorflow.Eager -{ - [StructLayout(LayoutKind.Sequential)] - public struct TFE_TensorHandle - { - IntPtr _handle; - - public static implicit operator IntPtr(TFE_TensorHandle tensor) - => tensor._handle; - - public override string ToString() - => $"TFE_TensorHandle 0x{_handle.ToString("x16")}"; - } -} diff --git a/src/TensorFlowNET.Core/Eager/c_api.eager.cs b/src/TensorFlowNET.Core/Eager/c_api.eager.cs index bc215554..4c44628e 100644 --- a/src/TensorFlowNET.Core/Eager/c_api.eager.cs +++ b/src/TensorFlowNET.Core/Eager/c_api.eager.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; using Tensorflow.Device; using Tensorflow.Eager; +using Tensorflow.Util; namespace Tensorflow { @@ -66,7 +67,7 @@ namespace Tensorflow /// TF_Status* /// [DllImport(TensorFlowLibName)] - public static extern int TFE_OpAddInputList(SafeOpHandle op, IntPtr[] inputs, int num_inputs, SafeStatusHandle status); + public static extern int TFE_OpAddInputList(SafeOpHandle op, [In, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(SafeHandleArrayMarshaler))] SafeTensorHandleHandle[] inputs, int num_inputs, SafeStatusHandle status); /// /// @@ -90,6 +91,20 @@ namespace Tensorflow [DllImport(TensorFlowLibName)] public static extern void TFE_DeleteContext(IntPtr ctx); + public static void TFE_Execute(SafeOpHandle op, SafeTensorHandleHandle[] retvals, out int num_retvals, SafeStatusHandle status) + { + unsafe + { + num_retvals = retvals?.Length ?? 0; + var rawReturns = stackalloc IntPtr[num_retvals]; + TFE_Execute(op, rawReturns, ref num_retvals, status); + for (var i = 0; i < num_retvals; i++) + { + retvals[i] = new SafeTensorHandleHandle(rawReturns[i]); + } + } + } + /// /// Execute the operation defined by 'op' and return handles to computed /// tensors in `retvals`. @@ -99,7 +114,7 @@ namespace Tensorflow /// int* /// TF_Status* [DllImport(TensorFlowLibName)] - public static extern void TFE_Execute(SafeOpHandle op, IntPtr[] retvals, ref int num_retvals, SafeStatusHandle status); + private static unsafe extern void TFE_Execute(SafeOpHandle op, IntPtr* retvals, ref int num_retvals, SafeStatusHandle status); /// /// @@ -198,7 +213,7 @@ namespace Tensorflow /// TFE_TensorHandle* /// TF_Status* [DllImport(TensorFlowLibName)] - public static extern void TFE_OpAddInput(SafeOpHandle op, IntPtr h, SafeStatusHandle status); + public static extern void TFE_OpAddInput(SafeOpHandle op, SafeTensorHandleHandle h, SafeStatusHandle status); /// /// @@ -206,10 +221,10 @@ namespace Tensorflow /// const tensorflow::Tensor& /// TFE_TensorHandle* [DllImport(TensorFlowLibName)] - public static extern TFE_TensorHandle TFE_NewTensorHandle(IntPtr t, SafeStatusHandle status); + public static extern SafeTensorHandleHandle TFE_NewTensorHandle(IntPtr t, SafeStatusHandle status); [DllImport(TensorFlowLibName)] - public static extern IntPtr TFE_EagerTensorHandle(IntPtr t); + public static extern SafeTensorHandleHandle TFE_EagerTensorHandle(IntPtr t); /// /// Sets the default execution mode (sync/async). Note that this can be @@ -226,7 +241,7 @@ namespace Tensorflow /// TFE_TensorHandle* /// [DllImport(TensorFlowLibName)] - public static extern TF_DataType TFE_TensorHandleDataType(IntPtr h); + public static extern TF_DataType TFE_TensorHandleDataType(SafeTensorHandleHandle h); /// /// This function will block till the operation that produces `h` has @@ -237,7 +252,7 @@ namespace Tensorflow /// TF_Status* /// [DllImport(TensorFlowLibName)] - public static extern IntPtr TFE_TensorHandleResolve(IntPtr h, SafeStatusHandle status); + public static extern IntPtr TFE_TensorHandleResolve(SafeTensorHandleHandle h, SafeStatusHandle status); /// @@ -247,10 +262,10 @@ namespace Tensorflow /// TF_Status* /// [DllImport(TensorFlowLibName)] - public static extern int TFE_TensorHandleNumDims(IntPtr h, SafeStatusHandle status); + public static extern int TFE_TensorHandleNumDims(SafeTensorHandleHandle h, SafeStatusHandle status); [DllImport(TensorFlowLibName)] - public static extern int TFE_TensorHandleDim(IntPtr h, int dim, SafeStatusHandle status); + public static extern int TFE_TensorHandleDim(SafeTensorHandleHandle h, int dim, SafeStatusHandle status); /// /// Returns the device of the operation that produced `h`. If `h` was produced by @@ -263,7 +278,7 @@ namespace Tensorflow /// TF_Status* /// [DllImport(TensorFlowLibName)] - public static extern IntPtr TFE_TensorHandleDeviceName(IntPtr h, SafeStatusHandle status); + public static extern IntPtr TFE_TensorHandleDeviceName(SafeTensorHandleHandle h, SafeStatusHandle status); /// /// Returns the name of the device in whose memory `h` resides. @@ -272,7 +287,7 @@ namespace Tensorflow /// TF_Status* /// [DllImport(TensorFlowLibName)] - public static extern IntPtr TFE_TensorHandleBackingDeviceName(IntPtr h, SafeStatusHandle status); + public static extern IntPtr TFE_TensorHandleBackingDeviceName(SafeTensorHandleHandle h, SafeStatusHandle status); /// /// diff --git a/src/TensorFlowNET.Core/Tensors/EagerTensorV2.cs b/src/TensorFlowNET.Core/Tensors/EagerTensorV2.cs index 43e2312d..b2ba7f28 100644 --- a/src/TensorFlowNET.Core/Tensors/EagerTensorV2.cs +++ b/src/TensorFlowNET.Core/Tensors/EagerTensorV2.cs @@ -12,7 +12,7 @@ namespace Tensorflow { public class EagerTensorV2 : DisposableObject, ITensor { - IntPtr EagerTensorHandle; + SafeTensorHandleHandle EagerTensorHandle; public string Device => c_api.StringPiece(c_api.TFE_TensorHandleDeviceName(EagerTensorHandle, tf.status.Handle)); public EagerTensorV2(IntPtr handle) @@ -64,10 +64,14 @@ namespace Tensorflow } }*/ + protected override void DisposeManagedResources() + { + EagerTensorHandle.Dispose(); + } + protected override void DisposeUnmanagedResources(IntPtr handle) { c_api.TF_DeleteTensor(_handle); - c_api.TFE_DeleteTensorHandle(EagerTensorHandle); } } } diff --git a/src/TensorFlowNET.Core/Tensors/Tensor.cs b/src/TensorFlowNET.Core/Tensors/Tensor.cs index 7293f9e7..87e6f7f2 100644 --- a/src/TensorFlowNET.Core/Tensors/Tensor.cs +++ b/src/TensorFlowNET.Core/Tensors/Tensor.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using static Tensorflow.Binding; +using Tensorflow.Eager; using Tensorflow.Framework; namespace Tensorflow @@ -94,7 +95,7 @@ namespace Tensorflow /// /// TFE_TensorHandle /// - public IntPtr EagerTensorHandle { get; set; } + public SafeTensorHandleHandle EagerTensorHandle { get; set; } /// /// Returns the shape of a tensor. diff --git a/src/TensorFlowNET.Core/Util/SafeHandleArrayMarshaler.cs b/src/TensorFlowNET.Core/Util/SafeHandleArrayMarshaler.cs new file mode 100644 index 00000000..74846d4f --- /dev/null +++ b/src/TensorFlowNET.Core/Util/SafeHandleArrayMarshaler.cs @@ -0,0 +1,132 @@ +/***************************************************************************** + Copyright 2018 The TensorFlow.NET Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using System; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; + +namespace Tensorflow.Util +{ + internal sealed class SafeHandleArrayMarshaler : ICustomMarshaler + { + private static readonly SafeHandleArrayMarshaler Instance = new SafeHandleArrayMarshaler(); + + private SafeHandleArrayMarshaler() + { + } + +#pragma warning disable IDE0060 // Remove unused parameter (method is used implicitly) + public static ICustomMarshaler GetInstance(string cookie) +#pragma warning restore IDE0060 // Remove unused parameter + { + return Instance; + } + + public int GetNativeDataSize() + { + return IntPtr.Size; + } + + [HandleProcessCorruptedStateExceptions] + public IntPtr MarshalManagedToNative(object ManagedObj) + { + if (ManagedObj is null) + return IntPtr.Zero; + + var array = (SafeHandle[])ManagedObj; + var native = IntPtr.Zero; + var marshaledArrayHandle = false; + try + { + native = Marshal.AllocHGlobal((array.Length + 1) * IntPtr.Size); + Marshal.WriteIntPtr(native, GCHandle.ToIntPtr(GCHandle.Alloc(array))); + marshaledArrayHandle = true; + + var i = 0; + var success = false; + try + { + for (i = 0; i < array.Length; i++) + { + success = false; + var current = array[i]; + var currentHandle = IntPtr.Zero; + if (current is object) + { + current.DangerousAddRef(ref success); + currentHandle = current.DangerousGetHandle(); + } + + Marshal.WriteIntPtr(native, ofs: (i + 1) * IntPtr.Size, currentHandle); + } + + return IntPtr.Add(native, IntPtr.Size); + } + catch + { + // Clean up any handles which were leased prior to the exception + var total = success ? i + 1 : i; + for (var j = 0; j < total; j++) + { + var current = array[i]; + if (current is object) + current.DangerousRelease(); + } + + throw; + } + } + catch + { + if (native != IntPtr.Zero) + { + if (marshaledArrayHandle) + GCHandle.FromIntPtr(Marshal.ReadIntPtr(native)).Free(); + + Marshal.FreeHGlobal(native); + } + + throw; + } + } + + public void CleanUpNativeData(IntPtr pNativeData) + { + if (pNativeData == IntPtr.Zero) + return; + + var managedHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(pNativeData, -IntPtr.Size)); + var array = (SafeHandle[])managedHandle.Target; + managedHandle.Free(); + + for (var i = 0; i < array.Length; i++) + { + if (array[i] is object && !array[i].IsClosed) + array[i].DangerousRelease(); + } + } + + public object MarshalNativeToManaged(IntPtr pNativeData) + { + throw new NotSupportedException(); + } + + public void CleanUpManagedData(object ManagedObj) + { + throw new NotSupportedException(); + } + } +} diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/CApiTest.cs b/test/TensorFlowNET.UnitTest/NativeAPI/CApiTest.cs index 0a5a0545..0dd2779b 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/CApiTest.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/CApiTest.cs @@ -56,10 +56,10 @@ namespace TensorFlowNET.UnitTest protected void TF_SetAttrBool(OperationDescription desc, string attrName, bool value) => c_api.TF_SetAttrBool(desc, attrName, value); - protected TF_DataType TFE_TensorHandleDataType(IntPtr h) + protected TF_DataType TFE_TensorHandleDataType(SafeTensorHandleHandle h) => c_api.TFE_TensorHandleDataType(h); - protected int TFE_TensorHandleNumDims(IntPtr h, SafeStatusHandle status) + protected int TFE_TensorHandleNumDims(SafeTensorHandleHandle h, SafeStatusHandle status) => c_api.TFE_TensorHandleNumDims(h, status); protected TF_Code TF_GetCode(Status s) @@ -83,7 +83,7 @@ namespace TensorFlowNET.UnitTest protected ulong TF_TensorByteSize(IntPtr t) => c_api.TF_TensorByteSize(t); - protected void TFE_OpAddInput(SafeOpHandle op, IntPtr h, SafeStatusHandle status) + protected void TFE_OpAddInput(SafeOpHandle op, SafeTensorHandleHandle h, SafeStatusHandle status) => c_api.TFE_OpAddInput(op, h, status); protected void TFE_OpSetAttrType(SafeOpHandle op, string attr_name, TF_DataType value) @@ -98,11 +98,11 @@ namespace TensorFlowNET.UnitTest protected SafeOpHandle TFE_NewOp(SafeContextHandle ctx, string op_or_function_name, SafeStatusHandle status) => c_api.TFE_NewOp(ctx, op_or_function_name, status); - protected IntPtr TFE_NewTensorHandle(IntPtr t, SafeStatusHandle status) + protected SafeTensorHandleHandle TFE_NewTensorHandle(IntPtr t, SafeStatusHandle status) => c_api.TFE_NewTensorHandle(t, status); - protected void TFE_Execute(SafeOpHandle op, IntPtr[] retvals, ref int num_retvals, SafeStatusHandle status) - => c_api.TFE_Execute(op, retvals, ref num_retvals, status); + protected void TFE_Execute(SafeOpHandle op, SafeTensorHandleHandle[] retvals, out int num_retvals, SafeStatusHandle status) + => c_api.TFE_Execute(op, retvals, out num_retvals, status); protected SafeContextOptionsHandle TFE_NewContextOptions() => c_api.TFE_NewContextOptions(); @@ -113,7 +113,7 @@ namespace TensorFlowNET.UnitTest protected int TFE_OpGetInputLength(SafeOpHandle op, string input_name, SafeStatusHandle status) => c_api.TFE_OpGetInputLength(op, input_name, status); - protected int TFE_OpAddInputList(SafeOpHandle op, IntPtr[] inputs, int num_inputs, SafeStatusHandle status) + protected int TFE_OpAddInputList(SafeOpHandle op, SafeTensorHandleHandle[] inputs, int num_inputs, SafeStatusHandle status) => c_api.TFE_OpAddInputList(op, inputs, num_inputs, status); protected int TFE_OpGetOutputLength(SafeOpHandle op, string input_name, SafeStatusHandle status) @@ -128,13 +128,13 @@ namespace TensorFlowNET.UnitTest protected void TFE_ExecutorWaitForAllPendingNodes(SafeExecutorHandle executor, SafeStatusHandle status) => c_api.TFE_ExecutorWaitForAllPendingNodes(executor, status); - protected IntPtr TFE_TensorHandleResolve(IntPtr h, SafeStatusHandle status) + protected IntPtr TFE_TensorHandleResolve(SafeTensorHandleHandle h, SafeStatusHandle status) => c_api.TFE_TensorHandleResolve(h, status); - protected string TFE_TensorHandleDeviceName(IntPtr h, SafeStatusHandle status) + protected string TFE_TensorHandleDeviceName(SafeTensorHandleHandle h, SafeStatusHandle status) => c_api.StringPiece(c_api.TFE_TensorHandleDeviceName(h, status)); - protected string TFE_TensorHandleBackingDeviceName(IntPtr h, SafeStatusHandle status) + protected string TFE_TensorHandleBackingDeviceName(SafeTensorHandleHandle h, SafeStatusHandle status) => c_api.StringPiece(c_api.TFE_TensorHandleBackingDeviceName(h, status)); protected SafeDeviceListHandle TFE_ContextListDevices(SafeContextHandle ctx, SafeStatusHandle status) @@ -149,7 +149,7 @@ namespace TensorFlowNET.UnitTest protected string TF_DeviceListName(SafeDeviceListHandle list, int index, SafeStatusHandle status) => c_api.TF_DeviceListName(list, index, status); - protected IntPtr TFE_TensorHandleCopyToDevice(IntPtr h, SafeContextHandle ctx, string device_name, SafeStatusHandle status) + protected SafeTensorHandleHandle TFE_TensorHandleCopyToDevice(SafeTensorHandleHandle h, SafeContextHandle ctx, string device_name, SafeStatusHandle status) => c_api.TFE_TensorHandleCopyToDevice(h, ctx, device_name, status); protected void TFE_OpSetDevice(SafeOpHandle op, string device_name, SafeStatusHandle status) diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Execute_MatMul_CPU.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Execute_MatMul_CPU.cs index 3a90c2f2..2e77fe9d 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Execute_MatMul_CPU.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Execute_MatMul_CPU.cs @@ -33,21 +33,25 @@ namespace TensorFlowNET.UnitTest.NativeAPI { CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var m = TestMatrixTensorHandle(); - var retvals = new IntPtr[] { IntPtr.Zero, IntPtr.Zero }; - using (var matmul = MatMulOp(ctx, m, m)) + var retvals = new SafeTensorHandleHandle[2]; + try { - int num_retvals = 2; - c_api.TFE_Execute(matmul, retvals, ref num_retvals, status); - EXPECT_EQ(1, num_retvals); - EXPECT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - } - - TFE_DeleteTensorHandle(m); + using (var m = TestMatrixTensorHandle()) + using (var matmul = MatMulOp(ctx, m, m)) + { + int num_retvals; + c_api.TFE_Execute(matmul, retvals, out num_retvals, status); + EXPECT_EQ(1, num_retvals); + EXPECT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + } - t = TFE_TensorHandleResolve(retvals[0], status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - TFE_DeleteTensorHandle(retvals[0]); + t = TFE_TensorHandleResolve(retvals[0], status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + } + finally + { + retvals[0]?.Dispose(); + } } ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpGetInputAndOutputLengths.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpGetInputAndOutputLengths.cs index bae67a34..187f71e5 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpGetInputAndOutputLengths.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpGetInputAndOutputLengths.cs @@ -24,9 +24,10 @@ namespace TensorFlowNET.UnitTest.NativeAPI using var ctx = NewContext(status); CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var input1 = TestMatrixTensorHandle(); - var input2 = TestMatrixTensorHandle(); - var retvals = new IntPtr[2]; + using var input1 = TestMatrixTensorHandle(); + using var input2 = TestMatrixTensorHandle(); + + var retvals = new SafeTensorHandleHandle[2]; using (var identityOp = TFE_NewOp(ctx, "IdentityN", status)) { CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); @@ -37,7 +38,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI EXPECT_EQ(-1, TFE_OpGetOutputLength(identityOp, "output", status)); CHECK_NE(TF_OK, TF_GetCode(status), TF_Message(status)); - var inputs = new IntPtr[] { input1, input2 }; + var inputs = new SafeTensorHandleHandle[] { input1, input2 }; TFE_OpAddInputList(identityOp, inputs, 2, status); CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); @@ -47,21 +48,24 @@ namespace TensorFlowNET.UnitTest.NativeAPI EXPECT_EQ(2, TFE_OpGetOutputLength(identityOp, "output", status)); CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - int num_retvals = 2; - TFE_Execute(identityOp, retvals, ref num_retvals, status); + int num_retvals; + TFE_Execute(identityOp, retvals, out num_retvals, status); EXPECT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - // Try to retrieve lengths after executing the op (should work) - EXPECT_EQ(2, TFE_OpGetInputLength(identityOp, "input", status)); - CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - EXPECT_EQ(2, TFE_OpGetOutputLength(identityOp, "output", status)); - CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + try + { + // Try to retrieve lengths after executing the op (should work) + EXPECT_EQ(2, TFE_OpGetInputLength(identityOp, "input", status)); + CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + EXPECT_EQ(2, TFE_OpGetOutputLength(identityOp, "output", status)); + CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + } + finally + { + retvals[0]?.Dispose(); + retvals[1]?.Dispose(); + } } - - TFE_DeleteTensorHandle(input1); - TFE_DeleteTensorHandle(input2); - TFE_DeleteTensorHandle(retvals[0]); - TFE_DeleteTensorHandle(retvals[1]); } } } diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpInferMixedTypeInputListAttrs.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpInferMixedTypeInputListAttrs.cs index 83d5902a..6e79c3e5 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpInferMixedTypeInputListAttrs.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.OpInferMixedTypeInputListAttrs.cs @@ -1,5 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; using Tensorflow; using Tensorflow.Eager; @@ -24,10 +23,9 @@ namespace TensorFlowNET.UnitTest.NativeAPI using var ctx = NewContext(status); CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var condition = TestScalarTensorHandle(true); - var t1 = TestMatrixTensorHandle(); - var t2 = TestAxisTensorHandle(); - var retvals = new IntPtr[1]; + using var condition = TestScalarTensorHandle(true); + using var t1 = TestMatrixTensorHandle(); + using var t2 = TestAxisTensorHandle(); using (var assertOp = TFE_NewOp(ctx, "Assert", status)) { CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); @@ -44,15 +42,13 @@ namespace TensorFlowNET.UnitTest.NativeAPI //EXPECT_EQ(attr_found->second.list().type(1), tensorflow::DataType::DT_FLOAT); //EXPECT_EQ(attr_found->second.list().type(2), tensorflow::DataType::DT_INT32); - int num_retvals = 1; - TFE_Execute(assertOp, retvals, ref num_retvals, status); + var retvals = new SafeTensorHandleHandle[1]; + int num_retvals; + TFE_Execute(assertOp, retvals, out num_retvals, status); EXPECT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - } - TFE_DeleteTensorHandle(condition); - TFE_DeleteTensorHandle(t1); - TFE_DeleteTensorHandle(t2); - TFE_DeleteTensorHandle(retvals[0]); + retvals[0]?.Dispose(); + } } } } diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandle.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandle.cs index c898f6d5..9eff35f1 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandle.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandle.cs @@ -13,7 +13,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI [TestMethod] public unsafe void TensorHandle() { - var h = TestMatrixTensorHandle(); + using var h = TestMatrixTensorHandle(); EXPECT_EQ(TF_FLOAT, c_api.TFE_TensorHandleDataType(h)); var status = c_api.TF_NewStatus(); @@ -28,7 +28,6 @@ namespace TensorFlowNET.UnitTest.NativeAPI EXPECT_EQ(3.0f, data[2]); EXPECT_EQ(4.0f, data[3]); c_api.TF_DeleteTensor(t); - c_api.TFE_DeleteTensorHandle(h); } } } diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandleDevices.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandleDevices.cs index d4dda7cc..ae3e07f2 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandleDevices.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.TensorHandleDevices.cs @@ -24,47 +24,52 @@ namespace TensorFlowNET.UnitTest.NativeAPI using var ctx = NewContext(status); ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var hcpu = TestMatrixTensorHandle(); - var device_name = TFE_TensorHandleDeviceName(hcpu, status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_TRUE(device_name.Contains("CPU:0")); - - var backing_device_name = TFE_TensorHandleBackingDeviceName(hcpu, status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_TRUE(backing_device_name.Contains("CPU:0")); - - // Disable the test if no GPU is present. - string gpu_device_name = ""; - if(GetDeviceName(ctx, ref gpu_device_name, "GPU")) + using (var hcpu = TestMatrixTensorHandle()) { - var hgpu = TFE_TensorHandleCopyToDevice(hcpu, ctx, gpu_device_name, status); - ASSERT_TRUE(TF_GetCode(status) == TF_OK, TF_Message(status)); + var device_name = TFE_TensorHandleDeviceName(hcpu, status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_TRUE(device_name.Contains("CPU:0")); - var retvals = new IntPtr[1]; - using (var shape_op = ShapeOp(ctx, hgpu)) + var backing_device_name = TFE_TensorHandleBackingDeviceName(hcpu, status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_TRUE(backing_device_name.Contains("CPU:0")); + + // Disable the test if no GPU is present. + string gpu_device_name = ""; + if (GetDeviceName(ctx, ref gpu_device_name, "GPU")) { - TFE_OpSetDevice(shape_op, gpu_device_name, status); - ASSERT_TRUE(TF_GetCode(status) == TF_OK, TF_Message(status)); - int num_retvals = 1; - c_api.TFE_Execute(shape_op, retvals, ref num_retvals, status); + using var hgpu = TFE_TensorHandleCopyToDevice(hcpu, ctx, gpu_device_name, status); ASSERT_TRUE(TF_GetCode(status) == TF_OK, TF_Message(status)); - // .device of shape is GPU since the op is executed on GPU - device_name = TFE_TensorHandleDeviceName(retvals[0], status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_TRUE(device_name.Contains("GPU:0")); + var retvals = new SafeTensorHandleHandle[1]; + using (var shape_op = ShapeOp(ctx, hgpu)) + { + TFE_OpSetDevice(shape_op, gpu_device_name, status); + ASSERT_TRUE(TF_GetCode(status) == TF_OK, TF_Message(status)); + int num_retvals; + c_api.TFE_Execute(shape_op, retvals, out num_retvals, status); + ASSERT_TRUE(TF_GetCode(status) == TF_OK, TF_Message(status)); - // .backing_device of shape is CPU since the tensor is backed by CPU - backing_device_name = TFE_TensorHandleBackingDeviceName(retvals[0], status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_TRUE(backing_device_name.Contains("CPU:0")); - } + try + { + // .device of shape is GPU since the op is executed on GPU + device_name = TFE_TensorHandleDeviceName(retvals[0], status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_TRUE(device_name.Contains("GPU:0")); - TFE_DeleteTensorHandle(retvals[0]); - TFE_DeleteTensorHandle(hgpu); + // .backing_device of shape is CPU since the tensor is backed by CPU + backing_device_name = TFE_TensorHandleBackingDeviceName(retvals[0], status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_TRUE(backing_device_name.Contains("CPU:0")); + } + finally + { + retvals[0]?.Dispose(); + } + } + } } - TFE_DeleteTensorHandle(hcpu); // not export api using var executor = TFE_ContextGetExecutorForThread(ctx); TFE_ExecutorWaitForAllPendingNodes(executor, status); diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Variables.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Variables.cs index a4d418ad..7d3c4195 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Variables.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.Variables.cs @@ -25,35 +25,42 @@ namespace TensorFlowNET.UnitTest.NativeAPI using var ctx = NewContext(status); ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var var_handle = CreateVariable(ctx, 12.0f, status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - - int num_retvals = 1; - var value_handle = new[] { IntPtr.Zero }; - using (var op = TFE_NewOp(ctx, "ReadVariableOp", status)) + using (var var_handle = CreateVariable(ctx, 12.0f, status)) { ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - TFE_OpSetAttrType(op, "dtype", TF_FLOAT); - TFE_OpAddInput(op, var_handle, status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - TFE_Execute(op, value_handle, ref num_retvals, status); - } - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_EQ(1, num_retvals); - EXPECT_EQ(TF_FLOAT, TFE_TensorHandleDataType(value_handle[0])); - EXPECT_EQ(0, TFE_TensorHandleNumDims(value_handle[0], status)); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - var value = 0f; // new float[1]; - var t = TFE_TensorHandleResolve(value_handle[0], status); - ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); - ASSERT_EQ(sizeof(float), (int)TF_TensorByteSize(t)); - tf.memcpy(&value, TF_TensorData(t).ToPointer(), sizeof(float)); - c_api.TF_DeleteTensor(t); - EXPECT_EQ(12.0f, value); + int num_retvals = 1; + var value_handle = new SafeTensorHandleHandle[1]; + using (var op = TFE_NewOp(ctx, "ReadVariableOp", status)) + { + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + TFE_OpSetAttrType(op, "dtype", TF_FLOAT); + TFE_OpAddInput(op, var_handle, status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + TFE_Execute(op, value_handle, out num_retvals, status); + } + + try + { + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_EQ(1, num_retvals); + EXPECT_EQ(TF_FLOAT, TFE_TensorHandleDataType(value_handle[0])); + EXPECT_EQ(0, TFE_TensorHandleNumDims(value_handle[0], status)); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + var value = 0f; // new float[1]; + var t = TFE_TensorHandleResolve(value_handle[0], status); + ASSERT_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); + ASSERT_EQ(sizeof(float), (int)TF_TensorByteSize(t)); + tf.memcpy(&value, TF_TensorData(t).ToPointer(), sizeof(float)); + c_api.TF_DeleteTensor(t); + EXPECT_EQ(12.0f, value); + } + finally + { + value_handle[0]?.Dispose(); + } + } - TFE_DeleteTensorHandle(var_handle); - TFE_DeleteTensorHandle(value_handle[0]); CHECK_EQ(TF_OK, TF_GetCode(status), TF_Message(status)); } } diff --git a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.cs b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.cs index f11bab79..82a1912a 100644 --- a/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.cs +++ b/test/TensorFlowNET.UnitTest/NativeAPI/Eager/CApi.Eager.cs @@ -12,7 +12,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI [TestClass] public partial class CApiEagerTest : CApiTest { - IntPtr TestMatrixTensorHandle() + SafeTensorHandleHandle TestMatrixTensorHandle() { var dims = new long[] { 2, 2 }; var data = new float[] { 1.0f, 2.0f, 3.0f, 4.0f }; @@ -26,7 +26,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI return th; } - SafeOpHandle MatMulOp(SafeContextHandle ctx, IntPtr a, IntPtr b) + SafeOpHandle MatMulOp(SafeContextHandle ctx, SafeTensorHandleHandle a, SafeTensorHandleHandle b) { using var status = TF_NewStatus(); @@ -64,7 +64,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI return false; } - SafeOpHandle ShapeOp(SafeContextHandle ctx, IntPtr a) + SafeOpHandle ShapeOp(SafeContextHandle ctx, SafeTensorHandleHandle a) { using var status = TF_NewStatus(); @@ -77,28 +77,28 @@ namespace TensorFlowNET.UnitTest.NativeAPI return op; } - unsafe IntPtr CreateVariable(SafeContextHandle ctx, float value, SafeStatusHandle status) + unsafe SafeTensorHandleHandle CreateVariable(SafeContextHandle ctx, float value, SafeStatusHandle status) { - var var_handle = new IntPtr[1]; - int num_retvals = 1; + var var_handle = new SafeTensorHandleHandle[1]; + int num_retvals; using (var op = TFE_NewOp(ctx, "VarHandleOp", status)) { - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); TFE_OpSetAttrType(op, "dtype", TF_FLOAT); TFE_OpSetAttrShape(op, "shape", new long[0], 0, status); TFE_OpSetAttrString(op, "container", "", 0); TFE_OpSetAttrString(op, "shared_name", "", 0); - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; - TFE_Execute(op, var_handle, ref num_retvals, status); + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); + TFE_Execute(op, var_handle, out num_retvals, status); } - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); CHECK_EQ(1, num_retvals); // Assign 'value' to it. using (var op = TFE_NewOp(ctx, "AssignVariableOp", status)) { - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); TFE_OpSetAttrType(op, "dtype", TF_FLOAT); TFE_OpAddInput(op, var_handle[0], status); @@ -107,22 +107,22 @@ namespace TensorFlowNET.UnitTest.NativeAPI tf.memcpy(TF_TensorData(t).ToPointer(), &value, TF_TensorByteSize(t)); var value_handle = c_api.TFE_NewTensorHandle(t, status); - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); TFE_OpAddInput(op, value_handle, status); - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); num_retvals = 0; - c_api.TFE_Execute(op, null, ref num_retvals, status); + c_api.TFE_Execute(op, null, out num_retvals, status); } - if (TF_GetCode(status) != TF_OK) return IntPtr.Zero; + if (TF_GetCode(status) != TF_OK) return new SafeTensorHandleHandle(IntPtr.Zero); CHECK_EQ(0, num_retvals); return var_handle[0]; } - IntPtr TestAxisTensorHandle() + SafeTensorHandleHandle TestAxisTensorHandle() { var dims = new long[] { 1 }; var data = new int[] { 1 }; @@ -135,7 +135,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI return th; } - IntPtr TestScalarTensorHandle(bool value) + SafeTensorHandleHandle TestScalarTensorHandle(bool value) { var data = new[] { value }; var t = c_api.TF_AllocateTensor(TF_BOOL, null, 0, sizeof(bool)); @@ -147,7 +147,7 @@ namespace TensorFlowNET.UnitTest.NativeAPI return th; } - IntPtr TestScalarTensorHandle(float value) + SafeTensorHandleHandle TestScalarTensorHandle(float value) { var data = new [] { value }; var t = c_api.TF_AllocateTensor(TF_FLOAT, null, 0, sizeof(float));