//  [python]
def test_horovod_grouped_allreduce_mixed_indexed_slices_average_gpu(self):
        """Test on GPU that the grouped allreduce correctly averages a mix of tensors and tf.IndexedSlices."""
        # Only do this test if there are GPUs available.
        if not tf.test.is_gpu_available(cuda_only=True):
            self.skipTest(("No GPUs available"))

        if int(os.environ.get('HOROVOD_MIXED_INSTALL', 0)):
            # Skip if compiled with CUDA but without HOROVOD_GPU_OPERATIONS.
            self.skipTest("Not compiled with HOROVOD_GPU_OPERATIONS")

        hvd.init()
        size = hvd.size()
        dtypes = self.filter_supported_types([tf.uint8, tf.int8, tf.int32, tf.int64, tf.float16, tf.float32, tf.float64])
        dims = [1, 2, 3]

        for dtype, dim in itertools.product(dtypes, dims):
            with tf.device("/gpu:%d" % hvd.local_rank()):
                maxval = 100 if dtype not in [tf.uint8, tf.int8] else 1
                minval = -maxval if dtype not in [tf.uint8] else 0
                # Create indexed slices
                slice_values = [tf.cast(self.random_uniform([17] * dim, minval, maxval), dtype=dtype) for _ in range(5)]
                tensors = [tf.IndexedSlices(tf.stack([x]*2),
                                            tf.convert_to_tensor([hvd.rank(), hvd.rank()+1])) for x in slice_values]

                # Append additional set of regular tensors
                for x in slice_values:
                    tensors.append(x)
                results = hvd.grouped_allreduce(tensors, op=hvd.Average)

            # Convert indexed slice to tensor (summing entries for duplicate indices) or append standard tensor results
            processed_results = []
            for x in results:
                if isinstance(x, tf.IndexedSlices):
                    keys = x.indices
                    processed_results.append(tf.cast(tf.math.unsorted_segment_sum(x.values, keys, tf.size(tf.unique(keys)[0])),
                                                     dtype=dtype))
                else:
                    processed_results.append(x)

            references = []
            # First append references for indexed slices
            for x in slice_values:
                slices = [x] * (hvd.size() + 1)
                for i in range(0, hvd.size() + 1):
                    if i == 0 or i == hvd.size():
                        slices[i] /= hvd.size()
                    else:
                        slices[i] *= 2
                        slices[i] /= hvd.size()
                references.append(tf.cast(tf.stack(slices), dtype=dtype))

            # Next, append references for standard tensors
            for x in slice_values:
                references.append(x)

            differences = [t1 - t2 for t1, t2 in zip(processed_results, references)]
            differences = [tf.cast(diff, tf.int32) if dtype in [tf.int8, tf.uint8] else diff for diff in differences]
            max_difference = tf.reduce_max([tf.reduce_max(tf.abs(diff)) for diff in differences])

            # Threshold for floating point equality depends on number of
            # ranks, since we're comparing against precise multiplication.
            if size <= 3 or dtype in [tf.uint8, tf.int8, tf.int32, tf.int64]:
                threshold = 0
            elif size < 10:
                threshold = 1e-4
            elif size < 15:
                threshold = 5e-4
            else:
                self.skipTest("Horovod cluster too large for precise multiplication comparison")

            diff = self.evaluate(max_difference)
            self.assertTrue(diff <= threshold, "hvd.grouped_allreduce produces incorrect results")