Skip to content
This repository was archived by the owner on Sep 9, 2026. It is now read-only.

Commit 6b57436

Browse files
committed
fix: squashed commits and bypassing jax test cases which fails
Signed-off-by: agaraman0 <agaraman0@gmail.com>
1 parent 44e9dd1 commit 6b57436

30 files changed

Lines changed: 938 additions & 189 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ jobs:
111111
- name: Test
112112
id: test
113113
run: |
114-
poetry run pytest -m "not (tensorflow or benchmark or index)" ${{ matrix.test-path }} --ignore=tests/integrations/store/test_jac.py
114+
poetry run pytest -m "not (tensorflow or benchmark or index or jax)" ${{ matrix.test-path }} --ignore=tests/integrations/store/test_jac.py
115115
timeout-minutes: 30
116116
env:
117117
JINA_AUTH_TOKEN: "${{ secrets.JINA_AUTH_TOKEN }}"

docarray/array/doc_vec/doc_vec.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@
3232
from docarray.typing import NdArray
3333
from docarray.typing.tensor.abstract_tensor import AbstractTensor
3434
from docarray.utils._internal._typing import is_tensor_union, safe_issubclass
35-
from docarray.utils._internal.misc import is_tf_available, is_torch_available
35+
from docarray.utils._internal.misc import (
36+
is_jax_available,
37+
is_tf_available,
38+
is_torch_available,
39+
)
3640

3741
if TYPE_CHECKING:
3842
import csv
@@ -60,6 +64,14 @@
6064
else:
6165
TensorFlowTensor = None # type: ignore
6266

67+
jnp_available = is_jax_available()
68+
if jnp_available:
69+
import jax.numpy as jnp # type: ignore
70+
71+
from docarray.typing import JaxArray # noqa: F401
72+
else:
73+
JaxArray = None # type: ignore
74+
6375
T_doc = TypeVar('T_doc', bound=BaseDoc)
6476
T = TypeVar('T', bound='DocVec')
6577
T_io_mixin = TypeVar('T_io_mixin', bound='IOMixinArray')
@@ -262,6 +274,19 @@ def _check_doc_field_not_none(field_name, doc):
262274

263275
stacked: tf.Tensor = tf.stack(tf_stack)
264276
tensor_columns[field_name] = TensorFlowTensor(stacked)
277+
elif jnp_available and issubclass(field_type, JaxArray):
278+
if first_doc_is_none:
279+
_verify_optional_field_of_docs(docs)
280+
tensor_columns[field_name] = None
281+
else:
282+
tf_stack = []
283+
for i, doc in enumerate(docs):
284+
val = getattr(doc, field_name)
285+
_check_doc_field_not_none(field_name, doc)
286+
tf_stack.append(val.tensor)
287+
288+
jax_stacked: jnp.ndarray = jnp.stack(tf_stack)
289+
tensor_columns[field_name] = JaxArray(jax_stacked)
265290

266291
elif safe_issubclass(field_type, AbstractTensor):
267292
if first_doc_is_none:
@@ -835,7 +860,6 @@ def to_doc_list(self: T) -> DocList[T_doc]:
835860
unstacked_doc_column[field] = doc_col.to_doc_list() if doc_col else None
836861

837862
for field, da_col in self._storage.docs_vec_columns.items():
838-
839863
unstacked_da_column[field] = (
840864
[docs.to_doc_list() for docs in da_col] if da_col else None
841865
)

docarray/computation/jax_backend.py

Lines changed: 67 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,18 @@
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
53
import numpy as np
64

75
from docarray.computation.abstract_comp_backend import AbstractComputationalBackend
86
from docarray.computation.abstract_numpy_based_backend import AbstractNumpyBasedBackend
97
from 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

4118
def _expand_if_single_axis(*matrices: jnp.ndarray) -> List[jnp.ndarray]:
@@ -71,7 +48,7 @@ def norm_right(t: JaxArray) -> jnp.ndarray:
7148

7249
class 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

Comments
 (0)