1- from typing import Any , Callable , List , Optional , Tuple
1+ from typing import TYPE_CHECKING , Any , Callable , List , Optional , Tuple
22
3- import jax
4- import jax .numpy as jnp
53import numpy as np
64
75from docarray .computation .abstract_comp_backend import AbstractComputationalBackend
86from docarray .computation .abstract_numpy_based_backend import AbstractNumpyBasedBackend
97from docarray .typing import JaxArray
8+ from docarray .utils ._internal .misc import import_library
109
11-
12- def _unsqueeze_if_single_axis (* matrices ) -> List [jnp .ndarray ]:
13- """Unsqueezes tensors that only have one axis, at dim 0.
14- This ensures that all outputs can be treated as matrices, not vectors.
15-
16- :param matrices: Matrices to be unsqueezed
17- :return: List of the input matrices,
18- where single axis matrices are unsqueezed at dim 0.
19- """
20- unsqueezed = []
21- for m in matrices :
22- if len (m .shape ) == 1 :
23- unsqueezed .append (jnp .expand_dims (m , axis = 0 ))
24- else :
25- unsqueezed .append (m )
26- return unsqueezed
27-
28-
29- def _unsqueeze_if_scalar (t ):
30- """
31- Unsqueezes tensor of a scalar, from shape () to shape (1,).
32-
33- :param t: tensor to unsqueeze.
34- :return: unsqueezed tf.Tensor
35- """
36- if len (t .shape ) == 0 : # avoid scalar output
37- t = jnp .expand_dims (t , 0 )
38- return t
10+ if TYPE_CHECKING :
11+ import jax
12+ import jax .numpy as jnp
13+ else :
14+ jax = import_library ('jax' , raise_error = True )
15+ jnp = jax .numpy
3916
4017
4118def _expand_if_single_axis (* matrices : jnp .ndarray ) -> List [jnp .ndarray ]:
@@ -71,7 +48,7 @@ def norm_right(t: JaxArray) -> jnp.ndarray:
7148
7249class JaxCompBackend (AbstractNumpyBasedBackend ):
7350 """
74- Computational backend for Numpy .
51+ Computational backend for Jax .
7552 """
7653
7754 _module = jnp
@@ -95,16 +72,16 @@ def device(cls, tensor: 'JaxArray') -> Optional[str]:
9572 return cls ._get_tensor (tensor ).device ().platform
9673
9774 @classmethod
98- def to_numpy (cls , array : 'jax.numpy.array ' ) -> 'np.ndarray' :
99- return np . array ( cls ._get_tensor (array ))
75+ def to_numpy (cls , array : 'JaxArray ' ) -> 'np.ndarray' :
76+ return cls ._get_tensor (array ). __array__ ( )
10077
10178 @classmethod
10279 def none_value (cls ) -> Any :
103- """Provide a compatible value that represents None in numpy ."""
80+ """Provide a compatible value that represents None in jax ."""
10481 return jnp .nan
10582
10683 @classmethod
107- def detach (cls , tensor : 'jax.numpy.array ' ) -> 'jax.numpy.array ' :
84+ def detach (cls , tensor : 'JaxArray ' ) -> 'JaxArray ' :
10885 """
10986 Returns the tensor detached from its current graph.
11087
@@ -114,7 +91,7 @@ def detach(cls, tensor: 'jax.numpy.array') -> 'jax.numpy.array':
11491 return cls ._cast_output (jax .lax .stop_gradient (cls ._get_tensor (tensor )))
11592
11693 @classmethod
117- def dtype (cls , tensor : 'JaxArray' ) -> np .dtype :
94+ def dtype (cls , tensor : 'JaxArray' ) -> jnp .dtype :
11895 """Get the data type of the tensor."""
11996 d_type = cls ._get_tensor (tensor ).dtype
12097 return d_type .name
@@ -126,7 +103,7 @@ def minmax_normalize(
126103 t_range : Tuple = (0 , 1 ),
127104 x_range : Optional [Tuple ] = None ,
128105 eps : float = 1e-7 ,
129- ) -> 'jax.numpy.array ' :
106+ ) -> 'JaxArray ' :
130107 """
131108 Normalize values in `tensor` into `t_range`.
132109
@@ -156,7 +133,23 @@ def minmax_normalize(
156133 normalized = jnp .clip (r , * ((a , b ) if a < b else (b , a )))
157134 return cls ._cast_output (jnp .asarray (normalized , cls ._get_tensor (tensor ).dtype ))
158135
159- class Retrieval (AbstractComputationalBackend .Retrieval [jax .numpy .array ]):
136+ @classmethod
137+ def equal (cls , tensor1 : 'JaxArray' , tensor2 : 'JaxArray' ) -> bool :
138+ """
139+ Check if two tensors are equal.
140+
141+ :param tensor1: the first tensor
142+ :param tensor2: the second tensor
143+ :return: True if two tensors are equal, False otherwise.
144+ If one or more of the inputs is not a TensorFlowTensor, return False.
145+ """
146+ t1 , t2 = getattr (tensor1 , 'tensor' , None ), getattr (tensor2 , 'tensor' , None )
147+ if isinstance (t1 , jnp .ndarray ) and isinstance (t2 , jnp .ndarray ):
148+ # mypy doesn't know that tf.is_tensor implies that t1, t2 are not None
149+ return t1 .shape == t2 .shape and jnp .all (jnp .equal (t1 , t1 )) # type: ignore
150+ return False
151+
152+ class Retrieval (AbstractComputationalBackend .Retrieval [JaxArray ]):
160153 """
161154 Abstract class for retrieval and ranking functionalities
162155 """
@@ -174,7 +167,7 @@ def top_k(
174167 Can also be used to retrieve the top k largest values,
175168 by setting the `descending` flag.
176169
177- :param values: Torch tensor of values to rank.
170+ :param values: Jax tensor of values to rank.
178171 Should be of shape (n_queries, n_values_per_query).
179172 Inputs of shape (n_values_per_query,) will be expanded
180173 to (1, n_values_per_query).
@@ -188,30 +181,30 @@ def top_k(
188181 if device is not None :
189182 values = comp_be .to_device (values , device )
190183
191- values : jnp .ndarray = comp_be ._get_tensor (values )
184+ jax_values : jnp .ndarray = comp_be ._get_tensor (values )
192185
193- if len (values .shape ) == 1 :
194- values = jnp .expand_dims (values , axis = 0 )
186+ if len (jax_values .shape ) == 1 :
187+ jax_values = jnp .expand_dims (jax_values , axis = 0 )
195188
196189 if descending :
197- values = - values
190+ jax_values = - jax_values
198191
199- if k >= values .shape [1 ]:
200- idx = values .argsort (axis = 1 )[:, :k ]
201- values = jnp .take_along_axis (values , idx , axis = 1 )
192+ if k >= jax_values .shape [1 ]:
193+ idx = jax_values .argsort (axis = 1 )[:, :k ]
194+ jax_values = jnp .take_along_axis (jax_values , idx , axis = 1 )
202195 else :
203- idx_ps = values .argpartition (kth = k , axis = 1 )[:, :k ]
204- values = jnp .take_along_axis (values , idx_ps , axis = 1 )
205- idx_fs = values .argsort (axis = 1 )
196+ idx_ps = jax_values .argpartition (kth = k , axis = 1 )[:, :k ]
197+ jax_values = jnp .take_along_axis (jax_values , idx_ps , axis = 1 )
198+ idx_fs = jax_values .argsort (axis = 1 )
206199 idx = jnp .take_along_axis (idx_ps , idx_fs , axis = 1 )
207- values = jnp .take_along_axis (values , idx_fs , axis = 1 )
200+ jax_values = jnp .take_along_axis (jax_values , idx_fs , axis = 1 )
208201
209202 if descending :
210- values = - values
203+ jax_values = - jax_values
211204
212- return comp_be ._cast_output (values ), comp_be ._cast_output (idx )
205+ return comp_be ._cast_output (jax_values ), comp_be ._cast_output (idx )
213206
214- class Metrics (AbstractComputationalBackend .Metrics [jnp . ndarray ]):
207+ class Metrics (AbstractComputationalBackend .Metrics [JaxArray ]):
215208 """
216209 Abstract base class for metrics (distances and similarities).
217210 """
@@ -232,7 +225,7 @@ def cosine_sim(
232225 :param eps: a small jitter to avoid divide by zero
233226 :param device: the device to use for computations.
234227 If not provided, the devices of x_mat and y_mat are used.
235- :return: Tensor of shape (n_vectors, n_vectors) containing all pairwise
228+ :return: JaxArray of shape (n_vectors, n_vectors) containing all pairwise
236229 cosine distances.
237230 The index [i_x, i_y] contains the cosine distance between
238231 x_mat[i_x] and y_mat[i_y].
@@ -241,7 +234,7 @@ def cosine_sim(
241234 x_mat_jax : jnp .ndarray = comp_be ._get_tensor (x_mat )
242235 y_mat_jax : jnp .ndarray = comp_be ._get_tensor (y_mat )
243236
244- x_mat_jax , y_mat_jax = _unsqueeze_if_single_axis (x_mat_jax , y_mat_jax )
237+ x_mat_jax , y_mat_jax = _expand_if_single_axis (x_mat_jax , y_mat_jax )
245238
246239 sims = jnp .clip (
247240 (jnp .dot (x_mat_jax , y_mat_jax .T ) + eps )
@@ -255,66 +248,68 @@ def cosine_sim(
255248 - 1 ,
256249 1 ,
257250 ).squeeze ()
258- sims = _unsqueeze_if_scalar (sims )
251+ sims = _expand_if_scalar (sims )
259252
260253 return comp_be ._cast_output (sims )
261254
262255 @classmethod
263256 def euclidean_dist (
264- cls , x_mat : jnp . ndarray , y_mat : jnp . ndarray , device : Optional [str ] = None
257+ cls , x_mat : JaxArray , y_mat : JaxArray , device : Optional [str ] = None
265258 ) -> JaxArray :
266259 """Pairwise Euclidian distances between all vectors in x_mat and y_mat.
267260
268- :param x_mat: np .ndarray of shape (n_vectors, n_dim), where n_vectors is
261+ :param x_mat: jnp .ndarray of shape (n_vectors, n_dim), where n_vectors is
269262 the number of vectors and n_dim is the number of dimensions of each
270263 example.
271- :param y_mat: np .ndarray of shape (n_vectors, n_dim), where n_vectors is
264+ :param y_mat: jnp .ndarray of shape (n_vectors, n_dim), where n_vectors is
272265 the number of vectors and n_dim is the number of dimensions of each
273266 example.
274267 :param eps: a small jitter to avoid divde by zero
275268 :param device: Not supported for this backend
276- :return: np.ndarray of shape (n_vectors, n_vectors) containing all
269+ :return: JaxArray of shape (n_vectors, n_vectors) containing all
277270 pairwise euclidian distances.
278271 The index [i_x, i_y] contains the euclidian distance between
279272 x_mat[i_x] and y_mat[i_y].
280273 """
281274 comp_be = JaxCompBackend
282- x_mat : jnp .ndarray = comp_be ._get_tensor (x_mat )
283- y_mat : jnp .ndarray = comp_be ._get_tensor (y_mat )
275+ x_mat_jax : jnp .ndarray = comp_be ._get_tensor (x_mat )
276+ y_mat_jax : jnp .ndarray = comp_be ._get_tensor (y_mat )
284277 if device is not None :
285278 # warnings.warn('`device` is not supported for numpy operations')
286279 pass
287280
288- x_mat , y_mat = _expand_if_single_axis (x_mat , y_mat )
281+ x_mat_jax , y_mat_jax = _expand_if_single_axis (x_mat_jax , y_mat_jax )
289282
290- x_mat = comp_be ._cast_output (x_mat )
291- y_mat = comp_be ._cast_output (y_mat )
283+ x_mat_jax_arr : JaxArray = comp_be ._cast_output (x_mat_jax )
284+ y_mat_jax_arr : JaxArray = comp_be ._cast_output (y_mat_jax )
292285
293286 dists = _expand_if_scalar (
294287 jnp .sqrt (
295- comp_be ._get_tensor (cls .sqeuclidean_dist (x_mat , y_mat ))
288+ comp_be ._get_tensor (
289+ cls .sqeuclidean_dist (x_mat_jax_arr , y_mat_jax_arr )
290+ )
296291 ).squeeze ()
297292 )
298293
299294 return comp_be ._cast_output (dists )
300295
301296 @staticmethod
302297 def sqeuclidean_dist (
303- x_mat : jnp . ndarray ,
304- y_mat : jnp . ndarray ,
298+ x_mat : JaxArray ,
299+ y_mat : JaxArray ,
305300 device : Optional [str ] = None ,
306301 ) -> JaxArray :
307302 """Pairwise Squared Euclidian distances between all vectors in
308303 x_mat and y_mat.
309304
310- :param x_mat: np .ndarray of shape (n_vectors, n_dim), where n_vectors is
305+ :param x_mat: jnp .ndarray of shape (n_vectors, n_dim), where n_vectors is
311306 the number of vectors and n_dim is the number of dimensions of each
312307 example.
313- :param y_mat: np .ndarray of shape (n_vectors, n_dim), where n_vectors is
308+ :param y_mat: jnp .ndarray of shape (n_vectors, n_dim), where n_vectors is
314309 the number of vectors and n_dim is the number of dimensions of each
315310 example.
316311 :param device: Not supported for this backend
317- :return: np.ndarray of shape (n_vectors, n_vectors) containing all
312+ :return: JaxArray of shape (n_vectors, n_vectors) containing all
318313 pairwise Squared Euclidian distances.
319314 The index [i_x, i_y] contains the cosine Squared Euclidian between
320315 x_mat[i_x] and y_mat[i_y].
0 commit comments