'Evaluate MoE on given inputs.
This class is designed for the case where the rest of the model is using
data parallelism.   We receive an array of input `Tensor`s, one per
datashard, and we produce a list of output Tensors, one per datashard.
We provide a convenient debugging tool for determining the set of examples
that we passed to each expert.  The caller may provide a `Tensor` of
"identifiers", of any type whose first dimension matches the number of
input examples. The function will then return a list
"expert_to_identifiers", with one `Tensor` for each expert containing the
identifiers for all examples assigned to that expert.  A parallel list of
`Tensor`s, "expert_to_gates", is also returned, containing the
corresponding gate values.
Args:
datashard_devices: a `list` of device strings of length `num_datashards`.
Which devices to use for the output tensors.
xs: A `list` of `Tensor`s of length `num_datashards`.  Each has shape
`[batch_size[d], input_size].
train: a boolean `Scalar`.   When train=`True`, noise is added to the
gating function.
summaries: a boolean.  Whether to write summaries.
identifiers: an optional list of tensors.
Each tensor has shape [<batch_size[datashard]>, extra_dims]
shadow_xs: Optional `list` of `Tensor`s of length `num_datashards`.  Each
has shape `[batch_size[d], input_size]. Shadow_xs is useful if you want
to dispatch a transformed version of xs to the experts, but you want
untransformed xs for the gating network.
Returns:
ys: the output (a list of one tensor per datashard).  Each has shape
`[batch_size[d], output_size].
importance: a `Tensor` of shape `[n]` for a flat MoE or `[n1, n2]` for a
hierarchical MoE.  Batchwise sum of gates.
load:  a `Tensor` of shape `[n]` for a flat MoE or `[n1, n2]` for a
hierarchical MoE.  Smooth estimator of the number of
examples passed to each expert.  This is useful for load-balancing,
as any gradient on this `Tensor` will back-propagate to the gating
network.
expert_to_identifiers:  if `identifiers` was passed in, a list of
length `num_experts`.  Each element is a `Tensor` whose shape matches
that of `identifiers` in all but the first dimension.  Contains the
slices of `identifiers` corresponding to the batch elements that were
dispatched to that expert.
expert_to_gates: a list of one tensor per expert.
Each tensor has shape [<num_examples[expert]>]'

def Eval(self, datashard_devices, xs, train=True, summaries=False, identifiers=None, shadow_xs=None):

    n1 = self._primary_gating_hp.num_experts
    epsilon = 1e-10
    assert (len(datashard_devices) == len(xs))
    num_datashards = len(xs)
    expert_devices = self._expert_devices
    has_identifiers = (identifiers is not None)
    (primary_gates, primary_smooth_load) = Parallel(datashard_devices, self._primary_gating.Eval, xs, train, ([summaries] + ([False] * (num_datashards - 1))))
    primary_importance = tf.add_n(Parallel(datashard_devices, tf.reduce_sum, primary_gates, 0))
    primary_smooth_load = tf.add_n(primary_smooth_load)
    primary_true_load = tf.add_n(Parallel(datashard_devices, _GatesToLoad, primary_gates))
    primary_dispatcher = DistributedSparseDispatcher(datashard_devices, expert_devices, primary_gates)
    if (shadow_xs is None):
        secondary_input = primary_dispatcher.Dispatch(xs)
    else:
        secondary_input = primary_dispatcher.Dispatch(shadow_xs)
    primary_expert_to_identifiers = (primary_dispatcher.Dispatch(identifiers) if has_identifiers else None)
    primary_expert_to_gates = primary_dispatcher.ExpertToGates()
    if (not self._is_hierarchical):
        secondary_output = Parallel(expert_devices, (lambda a, b: a.Eval(b)), self._experts, secondary_input)
        ys = primary_dispatcher.Combine(secondary_output)
        return (ys, primary_importance, primary_smooth_load, primary_expert_to_identifiers, primary_expert_to_gates)
    (secondary_output, secondary_importance, secondary_load, secondary_expert_to_identifiers, secondary_expert_to_gates) = Parallel(expert_devices, [m.Eval for m in self._secondary_moe], secondary_input, train, primary_expert_to_gates, ([summaries] + ([False] * (n1 - 1))), primary_expert_to_identifiers)
    ys = primary_dispatcher.Combine(secondary_output, multiply_by_gates=False)
    importance = tf.stack(secondary_importance)
    load = (tf.stack(secondary_load) * tf.expand_dims((primary_smooth_load / (primary_true_load + epsilon)), 1))
    expert_to_identifiers = []
    if (identifiers is not None):
        for el in secondary_expert_to_identifiers:
            expert_to_identifiers.extend(el)
    expert_to_gates = []
    for el in secondary_expert_to_gates:
        expert_to_gates.extend(el)
    return (ys, importance, load, expert_to_identifiers, expert_to_gates)
