fixed nested with statement dependency bug in graphtags/v0.9
@@ -30,15 +30,15 @@ namespace Tensorflow | |||
/// <returns>A list of control inputs for the op to be created.</returns> | |||
private ITensorOrOperation[] _control_dependencies_for_inputs(ITensorOrOperation[] input_ops) | |||
{ | |||
var ret = new ITensorOrOperation[0]; | |||
var ret = new List<ITensorOrOperation>(); | |||
foreach(var controller in _control_dependencies_stack) | |||
foreach (var controller in _control_dependencies_stack) | |||
{ | |||
bool dominated = false; | |||
// If any of the input_ops already depends on the inputs from controller, | |||
// we say that the new op is dominated (by that input), and we therefore | |||
// do not need to add control dependencies for this controller's inputs. | |||
foreach(var op in input_ops) | |||
foreach (var op in input_ops) | |||
{ | |||
if (controller.op_in_group(op)) | |||
{ | |||
@@ -48,12 +48,22 @@ namespace Tensorflow | |||
} | |||
if (!dominated) | |||
ret = controller.control_inputs.Where(x => !input_ops.Contains(x)).ToArray(); | |||
ret.AddRange(controller.control_inputs.Where(x => !input_ops.Contains(x))); | |||
} | |||
return ret; | |||
return ret.ToArray(); | |||
} | |||
/// <summary> | |||
/// Returns a context manager that specifies control dependencies. | |||
/// | |||
/// Use with the `with` keyword to specify that all operations constructed | |||
/// within the context should have control dependencies on | |||
/// `control_inputs`. | |||
/// </summary> | |||
public _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||
=> control_dependencies(control_inputs == null ? null : control_inputs.OfType<object>().ToArray()); | |||
/// <summary> | |||
/// Returns a context manager that specifies control dependencies. | |||
/// | |||
@@ -61,7 +71,7 @@ namespace Tensorflow | |||
/// within the context should have control dependencies on | |||
/// `control_inputs`. | |||
/// </summary> | |||
public _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||
public _ControlDependenciesController control_dependencies(object[] control_inputs) | |||
{ | |||
if (control_inputs == null) | |||
return new _ControlDependenciesController(this, null); | |||
@@ -69,9 +79,26 @@ namespace Tensorflow | |||
var control_ops = new List<ITensorOrOperation>(); | |||
foreach (var c in control_inputs) | |||
{ | |||
control_ops.Add(c); | |||
switch (c) | |||
{ | |||
// TODO: implement IndexedSlices | |||
//case IndexedSlices islice: | |||
// control_ops.Add(islice.op); | |||
// break; | |||
case Tensor t: | |||
control_ops.Add(t.op); | |||
break; | |||
case Operation op: | |||
control_ops.Add(op); | |||
break; | |||
default: | |||
var t1 = _as_graph_element(c); | |||
if (t1 == null) | |||
throw new TypeError($"Control input must be Operation or Tensor:{c}"); | |||
control_ops.Add(t1.op); | |||
break; | |||
} | |||
} | |||
return new _ControlDependenciesController(this, control_ops); | |||
} | |||
@@ -103,6 +130,9 @@ namespace Tensorflow | |||
_control_dependencies_stack.Dequeue(); | |||
} | |||
/// <summary> | |||
/// Record that the given op depends on all registered control dependencies. | |||
/// </summary> | |||
public void _record_op_seen_by_control_dependencies(Operation op) | |||
{ | |||
foreach (var controller in _control_dependencies_stack) | |||
@@ -21,8 +21,14 @@ namespace Tensorflow | |||
public OperationDescription NewOperation(string opType, string opName) | |||
{ | |||
return c_api.TF_NewOperation(_handle, opType, opName); | |||
} | |||
} | |||
/// <summary> | |||
/// Returns the `Operation` with the given `name`. | |||
/// | |||
/// This method may be called concurrently from multiple threads. | |||
/// </summary> | |||
/// <param name="name">The name of the `Operation` to return.</param> | |||
public Operation get_operation_by_name(string name) | |||
=> as_graph_element(name, allow_tensor: false, allow_operation: true) as Operation; | |||
@@ -17,8 +17,29 @@ namespace Tensorflow | |||
private bool _new_stack; | |||
private IControlFlowContext _old_control_flow_context; | |||
public ITensorOrOperation[] control_inputs => _control_inputs_val.ToArray(); | |||
public ITensorOrOperation[] control_inputs => _control_inputs_val.ToArray(); | |||
/// <summary> | |||
/// Create a new `_ControlDependenciesController`. | |||
/// | |||
/// A `_ControlDependenciesController` is the context manager for | |||
/// `with tf.control_dependencies()` blocks.These normally nest, | |||
/// as described in the documentation for `control_dependencies()`. | |||
/// | |||
/// The `control_inputs` argument list control dependencies that must be | |||
/// added to the current set of control dependencies.Because of | |||
/// uniquification the set can be empty even if the caller passed a list of | |||
/// ops.The special value `None` indicates that we want to start a new | |||
/// empty set of control dependencies instead of extending the current set. | |||
/// | |||
/// In that case we also clear the current control flow context, which is an | |||
/// additional mechanism to add control dependencies. | |||
/// </summary> | |||
/// <param name="graph">The graph that this controller is managing.</param> | |||
/// <param name="control_inputs">List of ops to use as control inputs in addition | |||
/// to the current control dependencies.None to indicate that | |||
/// the dependencies should be cleared. | |||
/// </param> | |||
public _ControlDependenciesController(Graph graph, List<ITensorOrOperation> control_inputs) | |||
{ | |||
_graph = graph; | |||
@@ -1,68 +1,79 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
namespace Tensorflow | |||
{ | |||
public partial class Operation | |||
{ | |||
public TF_Output Input(int index) => c_api.TF_OperationInput(new TF_Input(_handle, index)); | |||
public TF_DataType InputType(int index) => c_api.TF_OperationInputType(new TF_Input(_handle, index)); | |||
public int InputListLength(string name) => c_api.TF_OperationInputListLength(_handle, name, status); | |||
public int NumInputs => c_api.TF_OperationNumInputs(_handle); | |||
private TF_DataType[] _input_types => _inputs._inputs.Select(x => x.dtype).ToArray(); | |||
private InputList _inputs; | |||
public InputList inputs | |||
{ | |||
get | |||
{ | |||
if (_inputs == null) | |||
{ | |||
var retval = new Tensor[NumInputs]; | |||
for (int i = 0; i < NumInputs; i++) | |||
{ | |||
var tf_outpus = Input(i); | |||
var op = new Operation(tf_outpus.oper); | |||
retval[i] = op.outputs[tf_outpus.index]; | |||
} | |||
_inputs = new InputList(retval); | |||
} | |||
return _inputs; | |||
} | |||
} | |||
public int NumControlInputs => c_api.TF_OperationNumControlInputs(_handle); | |||
public Operation[] control_inputs | |||
{ | |||
get | |||
{ | |||
return GetControlInputs(); | |||
} | |||
} | |||
public unsafe Operation[] GetControlInputs() | |||
{ | |||
var control_inputs = new Operation[NumControlInputs]; | |||
if (NumControlInputs > 0) | |||
{ | |||
IntPtr control_input_handle = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * NumControlInputs); | |||
c_api.TF_OperationGetControlInputs(_handle, control_input_handle, NumControlInputs); | |||
for (int i = 0; i < NumControlInputs; i++) | |||
{ | |||
var handle = control_input_handle + Marshal.SizeOf<IntPtr>() * i; | |||
control_inputs[i] = new Operation(*(IntPtr*)handle); | |||
} | |||
} | |||
return control_inputs; | |||
} | |||
} | |||
} | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
namespace Tensorflow | |||
{ | |||
// from ops.py | |||
public partial class Operation | |||
{ | |||
public TF_Output Input(int index) => c_api.TF_OperationInput(new TF_Input(_handle, index)); | |||
public TF_DataType InputType(int index) => c_api.TF_OperationInputType(new TF_Input(_handle, index)); | |||
public int InputListLength(string name) => c_api.TF_OperationInputListLength(_handle, name, status); | |||
public int NumInputs => c_api.TF_OperationNumInputs(_handle); | |||
private TF_DataType[] _input_types => _inputs._inputs.Select(x => x.dtype).ToArray(); | |||
private InputList _inputs; | |||
public InputList inputs | |||
{ | |||
get | |||
{ | |||
if (_inputs == null) | |||
{ | |||
var retval = new Tensor[NumInputs]; | |||
for (int i = 0; i < NumInputs; i++) | |||
{ | |||
var tf_outpus = Input(i); | |||
var op = new Operation(tf_outpus.oper); | |||
retval[i] = op.outputs[tf_outpus.index]; | |||
} | |||
_inputs = new InputList(retval); | |||
} | |||
return _inputs; | |||
} | |||
} | |||
public int NumControlInputs => c_api.TF_OperationNumControlInputs(_handle); | |||
/// <summary> | |||
/// The `Operation` objects on which this op has a control dependency. | |||
/// | |||
/// Before this op is executed, TensorFlow will ensure that the | |||
/// operations in `self.control_inputs` have finished executing.This | |||
/// mechanism can be used to run ops sequentially for performance | |||
/// reasons, or to ensure that the side effects of an op are observed | |||
/// in the correct order. | |||
/// </summary> | |||
public Operation[] control_inputs | |||
{ | |||
get | |||
{ | |||
return GetControlInputs(); | |||
} | |||
} | |||
public unsafe Operation[] GetControlInputs() | |||
{ | |||
var control_inputs = new Operation[NumControlInputs]; | |||
if (NumControlInputs > 0) | |||
{ | |||
IntPtr control_input_handle = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * NumControlInputs); | |||
c_api.TF_OperationGetControlInputs(_handle, control_input_handle, NumControlInputs); | |||
for (int i = 0; i < NumControlInputs; i++) | |||
{ | |||
var handle = control_input_handle + Marshal.SizeOf<IntPtr>() * i; | |||
control_inputs[i] = new Operation(*(IntPtr*)handle); | |||
} | |||
} | |||
return control_inputs; | |||
} | |||
} | |||
} |
@@ -45,7 +45,6 @@ Bug memory leak issue when allocating Tensor.</PackageReleaseNotes> | |||
<ItemGroup> | |||
<PackageReference Include="Google.Protobuf" Version="3.7.0" /> | |||
<PackageReference Include="NumSharp" Version="0.8.3" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -119,11 +119,14 @@ namespace Tensorflow | |||
/// A context manager that specifies control dependencies for all | |||
/// operations constructed within the context. | |||
/// </returns> | |||
public static _ControlDependenciesController control_dependencies(Operation[] control_inputs) | |||
public static _ControlDependenciesController control_dependencies(object[] control_inputs) | |||
{ | |||
return get_default_graph().control_dependencies(control_inputs); | |||
} | |||
public static _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||
=> control_dependencies(control_inputs == null ? null : control_inputs.OfType<object>().ToArray()); | |||
/// <summary> | |||
/// Creates a TF_Operation. | |||
/// </summary> | |||
@@ -23,7 +23,7 @@ namespace TensorFlowNET.UnitTest | |||
{ | |||
a = constant_op.constant(1.0); | |||
b = constant_op.constant(1.0); | |||
with(g.control_dependencies(new ITensorOrOperation[] { a }), x => | |||
with(g.control_dependencies(new[] { a }), x => | |||
{ | |||
c = constant_op.constant(1.0); | |||
d = array_ops.identity(b); | |||
@@ -36,15 +36,15 @@ namespace TensorFlowNET.UnitTest | |||
Assert.AreEqual(0, e.op.control_inputs.Length); | |||
} | |||
[Ignore("Part of this test is not compiling")] | |||
[Ignore("Future is not supported yet")] | |||
[TestMethod] | |||
public void TestEager() | |||
{ | |||
Tensor a = null, b = null, c = null, d = null, e = null; | |||
Tensor a = null, c = null, d = null, e = null; | |||
object b = null; | |||
var calls = 0; | |||
Func<Tensor> future = () => | |||
{ | |||
calls += 1; | |||
return constant_op.constant(2.0); | |||
}; | |||
@@ -55,26 +55,26 @@ namespace TensorFlowNET.UnitTest | |||
if (context.executing_eagerly()) | |||
{ | |||
// TODO: make this compile (see original Python code below) | |||
//a = constant_op.constant(1.0); | |||
//b = future; // <--- {henon} obviously, this doesn't compile, looks like control_dependencies needs to be able to take callables as well. | |||
//with(ops.control_dependencies(new Operation[] {a, b}), ctrl => | |||
//{ | |||
// return c = constant_op.constant(3.0); | |||
//}); | |||
//Assert.AreEqual(calls, 1); | |||
a = constant_op.constant(1.0); | |||
b = future; // <--- {henon} obviously, this doesn't compile, looks like control_dependencies needs to be able to take callables as well. | |||
with(ops.control_dependencies(new object[] { a, b }), ctrl => | |||
{ | |||
return c = constant_op.constant(3.0); | |||
}); | |||
Assert.AreEqual(calls, 1); | |||
} | |||
else | |||
{ | |||
var graph = tf.Graph(); | |||
with<Graph>(graph.as_default(), g => | |||
var graph = tf.Graph().as_default(); | |||
with<Graph>(graph, g => | |||
{ | |||
a = constant_op.constant(1.0); | |||
b = future(); | |||
with(g.control_dependencies(new ITensorOrOperation[] { a, b }), ctrl => | |||
{ | |||
c = constant_op.constant(3.0); | |||
}); | |||
Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op, b.op })); | |||
var b1 = future(); | |||
with(g.control_dependencies(new [] { a, b}), ctrl => | |||
{ | |||
c = constant_op.constant(3.0); | |||
}); | |||
Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op, b1.op })); | |||
Assert.AreEqual(1, calls); | |||
}); | |||
@@ -106,100 +106,107 @@ namespace TensorFlowNET.UnitTest | |||
} | |||
// Note: {henon}, all tests below use the function _apply_op which is not really portable in C#, see original source below | |||
// but I think _apply_op(...) can just be replaced by g.create_op(...). | |||
/* | |||
def _apply_op(g, *args, **kwargs): | |||
op = g.create_op(*args, **kwargs) | |||
if len(op.outputs) == 1: | |||
return op.outputs[0] | |||
else: | |||
return op.outputs | |||
*/ | |||
[Ignore("")] | |||
[Ignore("How to port the ConvertibleObj?")] | |||
[TestMethod] | |||
public void TestBasicWithConversion() | |||
{ | |||
var g = ops.get_default_graph(); | |||
var g = tf.Graph().as_default(); | |||
// Note: _apply_op can be replaced by g.create_op | |||
var a = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
// TODO: ConvertibleObj, see original source below | |||
/* | |||
def testBasicWithConversion(self): | |||
g = ops.Graph() | |||
a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
def testBasicWithConversion(self): | |||
g = ops.Graph() | |||
a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
class ConvertibleObj(object): | |||
class ConvertibleObj(object): | |||
def _as_graph_element(self): | |||
return a | |||
def _as_graph_element(self): | |||
return a | |||
with g.control_dependencies([ConvertibleObj()]): | |||
c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
with g.control_dependencies([ConvertibleObj()]): | |||
c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
self.assertEqual(c.op.control_inputs, [a.op]) | |||
self.assertEqual(c.op.control_inputs, [a.op]) | |||
*/ | |||
} | |||
[Ignore("Fails with message: Op type not registered 'FloatOutput' in binary running on ...")] | |||
[TestMethod] | |||
public void TestNested() | |||
{ | |||
var g = ops.get_default_graph(); | |||
var a_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
var a_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
var a_3 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
var a_4 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
var g = tf.Graph().as_default(); | |||
var a_1 = constant_op.constant(1.0); | |||
var a_2 = constant_op.constant(3.0); | |||
var a_3 = constant_op.constant(4.0); | |||
var a_4 = constant_op.constant(5.0); | |||
Operation b_1 = null, b_2 = null; | |||
with(g.control_dependencies(new ITensorOrOperation[] { a_1, a_2, a_3, a_4 }), ctrl => | |||
{ | |||
b_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
}); | |||
with(g.control_dependencies(new ITensorOrOperation[] { a_1 }), ctrl1 => | |||
{ | |||
with(g.control_dependencies(new ITensorOrOperation[] { a_2 }), ctrl2 => | |||
{ | |||
with(g.control_dependencies(new ITensorOrOperation[] { a_3 }), ctrl3 => | |||
{ | |||
with(g.control_dependencies(new ITensorOrOperation[] { a_4 }), ctrl4 => | |||
{ | |||
b_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||
}); | |||
}); | |||
}); | |||
}); | |||
AssertItemsEqual(new[] {a_1.op, a_2.op, a_3.op, a_4.op}, b_1.op.control_inputs); | |||
with(g.control_dependencies(new[] { a_1, a_2, a_3, a_4 }), ctrl => | |||
{ | |||
b_1 = constant_op.constant(6.0); | |||
}); | |||
with(g.control_dependencies(new[] { a_1 }), ctrl1 => | |||
{ | |||
with(g.control_dependencies(new[] { a_2 }), ctrl2 => | |||
{ | |||
with(g.control_dependencies(new[] { a_3 }), ctrl3 => | |||
{ | |||
with(g.control_dependencies(new[] { a_4 }), ctrl4 => | |||
{ | |||
b_2 = constant_op.constant(7.0); | |||
}); | |||
}); | |||
}); | |||
}); | |||
AssertItemsEqual(new[] { a_1.op, a_2.op, a_3.op, a_4.op }, b_1.op.control_inputs); | |||
AssertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs); | |||
/* | |||
def testNested(self): | |||
g = ops.Graph() | |||
a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
with g.control_dependencies([a_1, a_2, a_3, a_4]): | |||
b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
with g.control_dependencies([a_1]): | |||
with g.control_dependencies([a_2]): | |||
with g.control_dependencies([a_3]): | |||
with g.control_dependencies([a_4]): | |||
b_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||
self.assertItemsEqual([a_1.op, a_2.op, a_3.op, a_4.op], | |||
b_1.op.control_inputs) | |||
self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) | |||
*/ | |||
} | |||
[Ignore("will fail due to unsupported op 'FloatOutput'")] | |||
[Ignore("Fails")] | |||
[TestMethod] | |||
public void TestClear() | |||
{ | |||
var g = tf.Graph().as_default(); | |||
var a_1 = constant_op.constant(1.0); | |||
var a_2 = constant_op.constant(3.0); | |||
var a_3 = constant_op.constant(4.0); | |||
var a_4 = constant_op.constant(5.0); | |||
Operation b_3_4 = null, b_3 = null, b_none = null, b_1 = null, b_1_2 = null, b_none2 = null; | |||
with(g.control_dependencies(new[] { a_1 }), ctrl1 => | |||
{ | |||
with(g.control_dependencies(new[] { a_2 }), ctrl2 => | |||
{ | |||
with(g.control_dependencies(null), ctrl3 => | |||
{ | |||
with(g.control_dependencies(new[] { a_3 }), ctrl4 => | |||
{ | |||
with(g.control_dependencies(new[] { a_4 }), ctrl5 => | |||
{ | |||
// deps [a_3, a_4] | |||
b_3_4 = constant_op.constant(7.0); | |||
}); | |||
// deps = [a_3] | |||
b_3 = constant_op.constant(8.0); | |||
}); | |||
// deps back to None | |||
b_none = constant_op.constant(9.0); | |||
}); | |||
// deps back to [a_1, a_2] | |||
b_1_2 = constant_op.constant(10.0); | |||
}); | |||
// deps back to [a_1] | |||
b_1 = constant_op.constant(11.0); | |||
with(g.control_dependencies(null), ctrl6 => | |||
{ | |||
// deps are None again | |||
b_none2 = constant_op.constant(12.0); | |||
}); | |||
}); | |||
AssertItemsEqual(new[] {a_3.op, a_4.op}, b_3_4.op.control_inputs); | |||
AssertItemsEqual(new[] {a_3.op}, b_3.op.control_inputs); | |||
AssertItemsEqual(new object[0], b_none.op.control_inputs); | |||
AssertItemsEqual(new[] {a_1.op, a_2.op}, b_1_2.op.control_inputs); | |||
AssertItemsEqual(new[] {a_1.op}, b_1.op.control_inputs); | |||
AssertItemsEqual(new object[0], b_none2.op.control_inputs); | |||
/* | |||
def testClear(self): | |||
g = ops.Graph() | |||