def _follow_named_rel(self, rel_name,
                        inverted):
    """Follow a specific relation in the KG.

    A relation-set following on a reified KB consists of two executions of
    _follow_named_rel(), one in the forward direction (inverse=1) and
    one in the backward direction (inverse=-1), described in Eq. 5 and 6
    respectively. Each execution of the function _follow_named_rel()
    requires to distribute the shards of the reified KB to different GPU
    devices and then reduce the results.

    Args:
      rel_name: a string naming a declared KG relation
      inverted: +1 to follow the relation normally, -1 to use inverses

    Returns:
      A NeuralQueryExpression
    """
    if inverted == +1:
      input_type_name = self.context.get_domain(rel_name)
      output_type_name = self.context.get_range(rel_name)
    else:
      if inverted != -1:
        raise ValueError('Inverted (%d) is neither +1 nor -1' % inverted)
      input_type_name = self.context.get_range(rel_name)
      output_type_name = self.context.get_domain(rel_name)

    m_is_sparse = not self.context.is_dense(rel_name)
    transpose_m = (inverted == -1)
    self._check_type_compatibility(input_type_name, self._type_name, 'follow')

    # we reserve GPU:0 to handle other computations, e.g. embedding lookup,
    # softmax, losses, etc.
    num_shards = self.context.gpus - 1
    sharded_outputs = [None] * num_shards
    # shard_offset is used for tracking where a shard starts, since each shard
    # may be of different sizes.
    shard_offset = 0
    for shard_id in range(num_shards):
      with tf.device('/gpu:%s' % (shard_id + 1)):
        # sharded reified KB are processed on different GPU devices, and
        # stored as a list of tensors.
        m = self.context.get_tf_tensor(rel_name, shard_id)
        x = self.tf if inverted < 0 \
            else self.tf[:, shard_offset: shard_offset + m.shape[1]]
        shard_offset += m.shape[1]
        output_expr = nql.matmul_any_tensor_dense_tensor(
            m, x, a_is_sparse=m_is_sparse, transpose_a=transpose_m)
        sharded_outputs[shard_id] = output_expr

    with tf.device('/gpu:0'):
      # sharded results are then combined when all shards are ready.
      output_expr = tf.concat(sharded_outputs, axis=1) if inverted < 0 \
          else tf.add_n(sharded_outputs)
    provenance = nql.NQExprProvenance(
        operation='follow', args=(rel_name, inverted), inner=self.provenance)
    return self.context.as_nql(output_expr, output_type_name, provenance)
Follow a specific relation in the KG.

    A relation-set following on a reified KB consists of two executions of
    _follow_named_rel(), one in the forward direction (inverse=1) and
    one in the backward direction (inverse=-1), described in Eq. 5 and 6
    respectively. Each execution of the function _follow_named_rel()
    requires to distribute the shards of the reified KB to different GPU
    devices and then reduce the results.

    Args:
      rel_name: a string naming a declared KG relation
      inverted: +1 to follow the relation normally, -1 to use inverses

    Returns:
      A NeuralQueryExpression
    
language/nql/nql/dist.py