From c33a3a7444469348c520c25c5f5506544573939d Mon Sep 17 00:00:00 2001 From: Alexander Novikov Date: Wed, 13 Sep 2023 17:18:43 +0000 Subject: [PATCH 1/3] cached_session for graph tests --- .../ControlFlowTest/WhileContextTestCase.cs | 3 +- .../GradientTest/GradientTest.cs | 21 ++- .../PythonTest.cs | 148 +++++++++++++++++- 3 files changed, 156 insertions(+), 16 deletions(-) diff --git a/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs b/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs index c637cf858..4dee61337 100644 --- a/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs +++ b/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Linq; using Tensorflow; using static Tensorflow.Binding; @@ -29,7 +30,7 @@ private void _testWhileContextHelper(int maximum_iterations) var b = new Func(x => math_ops.add(x, 1, name: "c")); //control_flow_ops.while_loop( // c, b, i , maximum_iterations: tf.constant(maximum_iterations)); - foreach (Operation op in sess.graph.get_operations()) + foreach (Operation op in sess.Single().graph.get_operations()) { var control_flow_context = op._get_control_flow_context(); /*if (control_flow_context != null) diff --git a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs index f240817b4..37bc646dd 100644 --- a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs +++ b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs @@ -388,22 +388,19 @@ public void testBoundaryStop() } - [Ignore("TODO")] [TestMethod] public void testBoundaryContinue() { - //@test_util.run_v1_only("b/120545219") - //def testBoundaryContinue(self): - // # Test that we differentiate both 'x' and 'y' correctly when x is a - // # predecessor of y. - // with self.cached_session(): - // x = constant(1.0) - // y = x * 2.0 - // z = y * 3.0 - // grads = gradients.gradients(z, [x, y]) - // self.assertTrue(all(x is not None for x in grads)) - // self.assertEqual(6.0, grads[0].eval()) + // Test that we differentiate both 'x' and 'y' correctly when x is a + // predecessor of y. + self.cached_session(); + var x = tf.constant(1.0); + var y = x * 2.0; + var z = y * 3.0; + var grads = tf.gradients(z, new[] { x, y }); + self.assertTrue(all(grads.Select(x => x != null))); + self.assertEqual(6.0, grads[0].eval()); } [Ignore("TODO")] diff --git a/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs b/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs index 513791933..90abc0cc9 100644 --- a/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs +++ b/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs @@ -6,6 +6,8 @@ using System.Linq; using Tensorflow; using static Tensorflow.Binding; +using OneOf.Types; +using System.Collections.Generic; namespace TensorFlowNET.UnitTest { @@ -139,6 +141,21 @@ public void assertProtoEquals(object toProto, object o) #region tensor evaluation and test session + private Session _cached_session = null; + private Graph _cached_graph = null; + private object _cached_config = null; + private bool _cached_force_gpu = false; + + private void _ClearCachedSession() + { + if (self._cached_session != null) + { + self._cached_session.Dispose(); + self._cached_session = null; + } + } + + //protected object _eval_helper(Tensor[] tensors) //{ // if (tensors == null) @@ -203,10 +220,57 @@ public T evaluate(Tensor tensor) } } - - public Session cached_session() + ///Returns a TensorFlow Session for use in executing tests. + public IEnumerable cached_session( + Graph graph = null, object config = null, bool use_gpu = false, bool force_gpu = false) { - throw new NotImplementedException(); + // This method behaves differently than self.session(): for performance reasons + // `cached_session` will by default reuse the same session within the same + // test.The session returned by this function will only be closed at the end + // of the test(in the TearDown function). + + // Use the `use_gpu` and `force_gpu` options to control where ops are run.If + // `force_gpu` is True, all ops are pinned to `/ device:GPU:0`. Otherwise, if + // `use_gpu` is True, TensorFlow tries to run as many ops on the GPU as + // possible.If both `force_gpu and `use_gpu` are False, all ops are pinned to + // the CPU. + + // Example: + // python + // class MyOperatorTest(test_util.TensorFlowTestCase) : + // def testMyOperator(self): + // with self.cached_session() as sess: + // valid_input = [1.0, 2.0, 3.0, 4.0, 5.0] + // result = MyOperator(valid_input).eval() + // self.assertEqual(result, [1.0, 2.0, 3.0, 5.0, 8.0] + // invalid_input = [-1.0, 2.0, 7.0] + // with self.assertRaisesOpError("negative input not supported"): + // MyOperator(invalid_input).eval() + + + // Args: + // graph: Optional graph to use during the returned session. + // config: An optional config_pb2.ConfigProto to use to configure the + // session. + // use_gpu: If True, attempt to run as many ops as possible on GPU. + // force_gpu: If True, pin all ops to `/device:GPU:0`. + + // Yields: + // A Session object that should be used as a context manager to surround + // the graph building and execution code in a test case. + + + // TODO: + // if context.executing_eagerly(): + // return self._eval_helper(tensors) + // else: + { + var sess = self._get_cached_session( + graph, config, force_gpu, crash_if_inconsistent_args: true); + var cached = self._constrain_devices_and_set_default(sess, use_gpu, force_gpu); + return cached; + + } } //Returns a TensorFlow Session for use in executing tests. @@ -254,6 +318,40 @@ public Session session(Graph graph = null, object config = null, bool use_gpu = return s.as_default(); } + private IEnumerable _constrain_devices_and_set_default(Session sess, bool use_gpu, bool force_gpu) + { + // Set the session and its graph to global default and constrain devices.""" + // if context.executing_eagerly(): + // yield None + // else: + { + sess.graph.as_default(); + sess.as_default(); + { + if (force_gpu) + { + // TODO: + + // Use the name of an actual device if one is detected, or + // '/device:GPU:0' otherwise + /* var gpu_name = gpu_device_name(); + if (!gpu_name) + gpu_name = "/device:GPU:0" + using (sess.graph.device(gpu_name)) { + yield return sess; + }*/ + yield return sess; + } + else if (use_gpu) + yield return sess; + else + using (sess.graph.device("/device:CPU:0")) + yield return sess; + } + + } + } + // See session() for details. private Session _create_session(Graph graph, object cfg, bool forceGpu) { @@ -298,6 +396,50 @@ private Session _create_session(Graph graph, object cfg, bool forceGpu) return new Session(graph);//, config = prepare_config(config)) } + private Session _get_cached_session( + Graph graph = null, + object config = null, + bool force_gpu = false, + bool crash_if_inconsistent_args = true) + { + // See cached_session() for documentation. + if (self._cached_session == null) + { + var sess = self._create_session(graph, config, force_gpu); + self._cached_session = sess; + self._cached_graph = graph; + self._cached_config = config; + self._cached_force_gpu = force_gpu; + return sess; + } else { + + if (crash_if_inconsistent_args && !self._cached_graph.Equals(graph)) + throw new ValueError(@"The graph used to get the cached session is + different than the one that was used to create the + session. Maybe create a new session with + self.session()"); + if (crash_if_inconsistent_args && !self._cached_config.Equals(config)) { + throw new ValueError(@"The config used to get the cached session is + different than the one that was used to create the + session. Maybe create a new session with + self.session()"); + } + if (crash_if_inconsistent_args && !self._cached_force_gpu.Equals(force_gpu)) { + throw new ValueError(@"The force_gpu value used to get the cached session is + different than the one that was used to create the + session. Maybe create a new session with + self.session()"); + } + return _cached_session; + } + } + + [TestCleanup] + public void Cleanup() + { + _ClearCachedSession(); + } + #endregion public void AssetSequenceEqual(T[] a, T[] b) From ae50fa93bac27f9c7c77b7a38289f20d78480b3a Mon Sep 17 00:00:00 2001 From: Alexander Novikov Date: Thu, 14 Sep 2023 03:58:15 +0000 Subject: [PATCH 2/3] fix fleaky test boundary continue --- test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs index 37bc646dd..0b4d79bb7 100644 --- a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs +++ b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs @@ -394,7 +394,7 @@ public void testBoundaryContinue() // Test that we differentiate both 'x' and 'y' correctly when x is a // predecessor of y. - self.cached_session(); + var sess = self.cached_session().Single(); var x = tf.constant(1.0); var y = x * 2.0; var z = y * 3.0; From 9d71cad96ecb69cd83c2b113fc808b608fbd7875 Mon Sep 17 00:00:00 2001 From: Alexander Novikov Date: Thu, 14 Sep 2023 11:21:18 +0000 Subject: [PATCH 3/3] using and no IEnumerable --- .../ControlFlowTest/WhileContextTestCase.cs | 4 ++-- .../GradientTest/GradientTest.cs | 16 ++++++++------ .../PythonTest.cs | 22 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs b/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs index 4dee61337..e93324f3e 100644 --- a/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs +++ b/test/TensorFlowNET.Graph.UnitTest/ControlFlowTest/WhileContextTestCase.cs @@ -24,13 +24,13 @@ public void SimpleWhileLoop() private void _testWhileContextHelper(int maximum_iterations) { // TODO: implement missing code dependencies - var sess = this.cached_session(); + using var sess = this.cached_session(); var i = constant_op.constant(0, name: "i"); var c = new Func(x => gen_math_ops.less(x, ops.convert_to_tensor(10), name: "c")); var b = new Func(x => math_ops.add(x, 1, name: "c")); //control_flow_ops.while_loop( // c, b, i , maximum_iterations: tf.constant(maximum_iterations)); - foreach (Operation op in sess.Single().graph.get_operations()) + foreach (Operation op in sess.graph.get_operations()) { var control_flow_context = op._get_control_flow_context(); /*if (control_flow_context != null) diff --git a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs index 0b4d79bb7..099c11627 100644 --- a/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs +++ b/test/TensorFlowNET.Graph.UnitTest/GradientTest/GradientTest.cs @@ -394,13 +394,15 @@ public void testBoundaryContinue() // Test that we differentiate both 'x' and 'y' correctly when x is a // predecessor of y. - var sess = self.cached_session().Single(); - var x = tf.constant(1.0); - var y = x * 2.0; - var z = y * 3.0; - var grads = tf.gradients(z, new[] { x, y }); - self.assertTrue(all(grads.Select(x => x != null))); - self.assertEqual(6.0, grads[0].eval()); + using (self.cached_session()) + { + var x = tf.constant(1.0); + var y = x * 2.0; + var z = y * 3.0; + var grads = tf.gradients(z, new[] { x, y }); + self.assertTrue(all(grads.Select(x => x != null))); + self.assertEqual(6.0, grads[0].eval()); + } } [Ignore("TODO")] diff --git a/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs b/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs index 90abc0cc9..ccf59f5ae 100644 --- a/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs +++ b/test/TensorFlowNET.Graph.UnitTest/PythonTest.cs @@ -221,7 +221,7 @@ public T evaluate(Tensor tensor) } ///Returns a TensorFlow Session for use in executing tests. - public IEnumerable cached_session( + public Session cached_session( Graph graph = null, object config = null, bool use_gpu = false, bool force_gpu = false) { // This method behaves differently than self.session(): for performance reasons @@ -267,9 +267,8 @@ public IEnumerable cached_session( { var sess = self._get_cached_session( graph, config, force_gpu, crash_if_inconsistent_args: true); - var cached = self._constrain_devices_and_set_default(sess, use_gpu, force_gpu); - return cached; - + using var cached = self._constrain_devices_and_set_default(sess, use_gpu, force_gpu); + return cached; } } @@ -318,13 +317,12 @@ public Session session(Graph graph = null, object config = null, bool use_gpu = return s.as_default(); } - private IEnumerable _constrain_devices_and_set_default(Session sess, bool use_gpu, bool force_gpu) + private Session _constrain_devices_and_set_default(Session sess, bool use_gpu, bool force_gpu) { // Set the session and its graph to global default and constrain devices.""" - // if context.executing_eagerly(): - // yield None - // else: - { + if (tf.executing_eagerly()) + return null; + else { sess.graph.as_default(); sess.as_default(); { @@ -340,13 +338,13 @@ private IEnumerable _constrain_devices_and_set_default(Session sess, bo using (sess.graph.device(gpu_name)) { yield return sess; }*/ - yield return sess; + return sess; } else if (use_gpu) - yield return sess; + return sess; else using (sess.graph.device("/device:CPU:0")) - yield return sess; + return sess; } }