def _embed_hash_buckets(self, ids: tf.Tensor, embedding_size: int,
                          num_hashes: int, num_buckets: int,
                          initializer_range: int) -> tf.Tensor:
    """Converts IDs (e.g. codepoints) into embeddings via multiple hashing.

    Args:
      ids: The codepoints or other IDs to be hashed.
      embedding_size: The dimensionality of the returned embeddings.
      num_hashes: The number of hash functions to use.
      num_buckets: The number of hash buckets (i.e. embeddings in each table).
      initializer_range: Maximum absolute value for initial weights.

    Returns:
      The codepoint emeddings.
    """

    if embedding_size % num_hashes != 0:
      raise ValueError(f"Expected `embedding_size` ({embedding_size}) % "
                       f"`num_hashes` ({num_hashes}) == 0")

    shard_embedding_size = embedding_size // num_hashes

    hash_bucket_tensors = self._hash_bucket_tensors(
        ids, num_hashes=num_hashes, num_buckets=num_buckets)
    embedding_shards = []
    for i, hash_bucket_ids in enumerate(hash_bucket_tensors):
      embedding_table = tf.get_variable(
          name=f"embeddings/HashBucketCodepointEmbedder_{i}",
          shape=[num_buckets, shard_embedding_size],
          initializer=bert_modeling.create_initializer(initializer_range))
      shard_embeddings = tf.nn.embedding_lookup(embedding_table,
                                                hash_bucket_ids)
      embedding_shards.append(shard_embeddings)
    return tf.concat(embedding_shards, axis=-1)
Converts IDs (e.g. codepoints) into embeddings via multiple hashing.

    Args:
      ids: The codepoints or other IDs to be hashed.
      embedding_size: The dimensionality of the returned embeddings.
      num_hashes: The number of hash functions to use.
      num_buckets: The number of hash buckets (i.e. embeddings in each table).
      initializer_range: Maximum absolute value for initial weights.

    Returns:
      The codepoint emeddings.
    
language/canine/modeling.py