AlphaGenome / flax_model /alphagenome /_sdk /tensor_utils.py
OneScience's picture
Upload folder using huggingface_hub
4516781 verified
Raw
History Blame Contribute Delete
6.01 kB
# Copyright 2024 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for converting NumPy arrays to Tensor protocol buffers."""
from collections.abc import Iterable, Sequence
from flax_model.alphagenome._sdk.protos import tensor_pb2
import immutabledict
import ml_dtypes
import numpy as np
import zstandard
_TENSOR_DTYPE_TO_NUMPY_DTYPE = immutabledict.immutabledict({
tensor_pb2.DataType.DATA_TYPE_BFLOAT16: np.dtype(ml_dtypes.bfloat16),
tensor_pb2.DataType.DATA_TYPE_FLOAT16: np.dtype(np.float16),
tensor_pb2.DataType.DATA_TYPE_FLOAT32: np.dtype(np.float32),
tensor_pb2.DataType.DATA_TYPE_FLOAT64: np.dtype(np.float64),
tensor_pb2.DataType.DATA_TYPE_INT8: np.dtype(np.int8),
tensor_pb2.DataType.DATA_TYPE_INT32: np.dtype(np.int32),
tensor_pb2.DataType.DATA_TYPE_INT64: np.dtype(np.int64),
tensor_pb2.DataType.DATA_TYPE_UINT8: np.dtype(np.uint8),
tensor_pb2.DataType.DATA_TYPE_UINT32: np.dtype(np.uint32),
tensor_pb2.DataType.DATA_TYPE_UINT64: np.dtype(np.uint64),
tensor_pb2.DataType.DATA_TYPE_BOOL: np.dtype(bool),
})
_NUMPY_DTYPE_TO_TENSOR_DTYPE = immutabledict.immutabledict(
{value: key for key, value in _TENSOR_DTYPE_TO_NUMPY_DTYPE.items()}
)
def _compress_bytes(
array: np.ndarray, compression_type: tensor_pb2.CompressionType
):
"""Compresses a c-contiguous array to the specified compression type."""
assert array.flags.c_contiguous
array = array.view(np.uint8)
match compression_type:
case tensor_pb2.CompressionType.COMPRESSION_TYPE_ZSTD:
return zstandard.compress(array.data)
case tensor_pb2.CompressionType.COMPRESSION_TYPE_NONE:
return bytes(array.data)
def _decompress_bytes(
data: bytes, compression_type: tensor_pb2.CompressionType
):
"""Decompress bytes using the specified compression type."""
match compression_type:
case tensor_pb2.CompressionType.COMPRESSION_TYPE_ZSTD:
return zstandard.decompress(data)
case tensor_pb2.CompressionType.COMPRESSION_TYPE_NONE:
return data
def pack_tensor(
value: ...,
*,
bytes_per_chunk: int = 0,
compression_type: tensor_pb2.CompressionType = (
tensor_pb2.CompressionType.COMPRESSION_TYPE_NONE
),
) -> tuple[tensor_pb2.Tensor, Sequence[tensor_pb2.TensorChunk]]:
"""Encodes the value as a Tensor and optional sequence of chunks.
Args:
value: An array-like object to pack. For example, scalar (float, int, bool,
etc.), NumPy array, or nested lists of scalars.
bytes_per_chunk: The number of bytes to include in each chunk. If 0, the
entire value will be packed into the Tensor proto, otherwise the value
will be split into chunks of this size.
compression_type: The type of compression to apply to the data. This is
applied to each chunk separately.
Returns:
Tuple of Tensor protocol buffer and, if items_per_chunk is greater than 0, a
sequence of TensorChunk protos.
"""
packed = tensor_pb2.Tensor()
value = np.ascontiguousarray(value)
packed.shape[:] = value.shape
packed.data_type = _NUMPY_DTYPE_TO_TENSOR_DTYPE[value.dtype]
chunks = []
if bytes_per_chunk > 0:
items_per_chunk = bytes_per_chunk // value.itemsize
if bytes_per_chunk < value.itemsize:
raise ValueError(f'{bytes_per_chunk=} must be >= {value.itemsize=}.')
for chunk in np.split(
value.ravel(), range(items_per_chunk, value.size, items_per_chunk)
):
chunks.append(
tensor_pb2.TensorChunk(
data=_compress_bytes(chunk, compression_type),
compression_type=compression_type,
)
)
packed.chunk_count = len(chunks)
else:
packed.array.data = _compress_bytes(value, compression_type)
packed.array.compression_type = compression_type
return packed, chunks
def unpack_proto(
proto: tensor_pb2.Tensor,
chunks: Iterable[tensor_pb2.TensorChunk] = (),
) -> np.ndarray:
"""Converts a Tensor proto and any chunks into a NumPy array.
Args:
proto: Tensor proto to unpack.
chunks: Optional sequence of TensorChunk protos to unpack.
Returns:
NumPy array of the unpacked data.
"""
dtype = _TENSOR_DTYPE_TO_NUMPY_DTYPE[proto.data_type]
match proto.WhichOneof('payload'):
case 'array':
data = _decompress_bytes(proto.array.data, proto.array.compression_type)
array = np.frombuffer(data, dtype=dtype).reshape(proto.shape)
case 'chunk_count':
array = np.empty(np.prod(proto.shape) * dtype.itemsize, dtype=np.uint8)
bytes_received = 0
for chunk in chunks:
chunk_data = np.frombuffer(
_decompress_bytes(chunk.data, chunk.compression_type),
dtype=np.uint8,
)
array[bytes_received : bytes_received + chunk_data.nbytes] = chunk_data
bytes_received += chunk_data.nbytes
if bytes_received != array.nbytes:
raise ValueError(
f'Expected {array.nbytes} bytes but only received {bytes_received} '
'bytes.'
)
array = array.view(dtype).reshape(proto.shape)
case _:
raise ValueError(
f'Unsupported payload type: {proto.WhichOneof("payload")}'
)
return array
def upcast_floating(x: np.ndarray) -> np.ndarray:
"""Helper to upcast low-precision floating point arrays to float32."""
dtype = np.result_type(x)
if (
np.issubdtype(dtype, np.floating) or dtype == ml_dtypes.bfloat16
) and dtype.itemsize < 4:
return x.astype(np.float32)
else:
return x