keras-nightly 3.14.0.dev2025122704__py3-none-any.whl → 3.14.0.dev2026012204__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. keras/_tf_keras/keras/dtype_policies/__init__.py +3 -0
  2. keras/_tf_keras/keras/ops/__init__.py +3 -0
  3. keras/_tf_keras/keras/ops/numpy/__init__.py +3 -0
  4. keras/_tf_keras/keras/quantizers/__init__.py +1 -0
  5. keras/dtype_policies/__init__.py +3 -0
  6. keras/ops/__init__.py +3 -0
  7. keras/ops/numpy/__init__.py +3 -0
  8. keras/quantizers/__init__.py +1 -0
  9. keras/src/backend/jax/nn.py +26 -9
  10. keras/src/backend/jax/numpy.py +16 -0
  11. keras/src/backend/numpy/numpy.py +23 -0
  12. keras/src/backend/openvino/numpy.py +369 -16
  13. keras/src/backend/tensorflow/numpy.py +34 -1
  14. keras/src/backend/tensorflow/rnn.py +17 -7
  15. keras/src/backend/torch/numpy.py +36 -0
  16. keras/src/backend/torch/rnn.py +28 -11
  17. keras/src/callbacks/orbax_checkpoint.py +75 -42
  18. keras/src/dtype_policies/__init__.py +2 -0
  19. keras/src/dtype_policies/dtype_policy.py +90 -1
  20. keras/src/layers/core/dense.py +122 -6
  21. keras/src/layers/core/einsum_dense.py +151 -7
  22. keras/src/layers/core/embedding.py +1 -1
  23. keras/src/layers/core/reversible_embedding.py +10 -1
  24. keras/src/layers/layer.py +5 -0
  25. keras/src/layers/preprocessing/feature_space.py +8 -4
  26. keras/src/layers/preprocessing/image_preprocessing/aug_mix.py +2 -2
  27. keras/src/layers/preprocessing/image_preprocessing/center_crop.py +13 -15
  28. keras/src/layers/preprocessing/image_preprocessing/random_contrast.py +3 -3
  29. keras/src/layers/preprocessing/image_preprocessing/resizing.py +10 -0
  30. keras/src/losses/losses.py +24 -0
  31. keras/src/models/model.py +18 -9
  32. keras/src/ops/image.py +109 -96
  33. keras/src/ops/numpy.py +181 -0
  34. keras/src/quantizers/__init__.py +2 -0
  35. keras/src/quantizers/awq.py +361 -0
  36. keras/src/quantizers/awq_config.py +140 -0
  37. keras/src/quantizers/awq_core.py +217 -0
  38. keras/src/quantizers/gptq.py +1 -2
  39. keras/src/quantizers/gptq_core.py +1 -1
  40. keras/src/quantizers/quantization_config.py +14 -0
  41. keras/src/quantizers/quantizers.py +61 -52
  42. keras/src/random/seed_generator.py +2 -2
  43. keras/src/saving/file_editor.py +81 -6
  44. keras/src/saving/orbax_util.py +50 -0
  45. keras/src/saving/saving_api.py +37 -14
  46. keras/src/utils/jax_layer.py +69 -31
  47. keras/src/utils/module_utils.py +11 -0
  48. keras/src/utils/tracking.py +5 -5
  49. keras/src/version.py +1 -1
  50. {keras_nightly-3.14.0.dev2025122704.dist-info → keras_nightly-3.14.0.dev2026012204.dist-info}/METADATA +1 -1
  51. {keras_nightly-3.14.0.dev2025122704.dist-info → keras_nightly-3.14.0.dev2026012204.dist-info}/RECORD +53 -49
  52. {keras_nightly-3.14.0.dev2025122704.dist-info → keras_nightly-3.14.0.dev2026012204.dist-info}/WHEEL +1 -1
  53. {keras_nightly-3.14.0.dev2025122704.dist-info → keras_nightly-3.14.0.dev2026012204.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ from openvino import Type
4
4
 
5
5
  from keras.src.backend import config
6
6
  from keras.src.backend.common import dtypes
7
+ from keras.src.backend.common.backend_utils import canonicalize_axis
7
8
  from keras.src.backend.common.variables import standardize_dtype
8
9
  from keras.src.backend.openvino.core import DTYPES_MAX
9
10
  from keras.src.backend.openvino.core import DTYPES_MIN
@@ -705,7 +706,16 @@ def broadcast_to(x, shape):
705
706
 
706
707
 
707
708
  def cbrt(x):
708
- raise NotImplementedError("`cbrt` is not supported with openvino backend")
709
+ x = get_ov_output(x)
710
+ x_type = x.get_element_type()
711
+ if x_type.is_integral() or x_type == Type.boolean:
712
+ x = ov_opset.convert(x, OPENVINO_DTYPES[config.floatx()]).output(0)
713
+ sign_x = ov_opset.sign(x)
714
+ abs_x = ov_opset.absolute(x)
715
+ one_third = ov_opset.constant(1.0 / 3.0, x.get_element_type())
716
+ root_abs = ov_opset.power(abs_x, one_third)
717
+ res = ov_opset.multiply(sign_x, root_abs)
718
+ return OpenVINOKerasTensor(res.output(0))
709
719
 
710
720
 
711
721
  def ceil(x):
@@ -893,9 +903,53 @@ def diag(x, k=0):
893
903
 
894
904
 
895
905
  def diagonal(x, offset=0, axis1=0, axis2=1):
896
- raise NotImplementedError(
897
- "`diagonal` is not supported with openvino backend"
898
- )
906
+ x = get_ov_output(x)
907
+ shape = x.get_partial_shape()
908
+ rank = x.get_partial_shape().rank.get_length()
909
+ if rank is None:
910
+ raise ValueError("`diagonal` requires input tensor with static rank.")
911
+ if rank < 2:
912
+ raise ValueError(
913
+ f"diagonal requires input tensor with rank >= 2.Given rank: {rank}"
914
+ )
915
+ axis1 = canonicalize_axis(axis1, rank)
916
+ axis2 = canonicalize_axis(axis2, rank)
917
+ if axis1 == axis2:
918
+ raise ValueError("`axis1` and `axis2` cannot be the same.")
919
+
920
+ perm_order = [axis1, axis2] + [
921
+ i for i in range(rank) if i != axis1 and i != axis2
922
+ ]
923
+ perm_const = ov_opset.constant(perm_order, dtype=Type.i32).output(0)
924
+ x_transposed = ov_opset.transpose(x, perm_const)
925
+
926
+ N_dim = shape[axis1]
927
+ M_dim = shape[axis2]
928
+ if not N_dim.is_static or not M_dim.is_static:
929
+ raise ValueError(
930
+ "`diagonal` requires input tensor with static shape for axes "
931
+ f"`axis1` ({axis1}) and `axis2` ({axis2})."
932
+ )
933
+ N = N_dim.get_length()
934
+ M = M_dim.get_length()
935
+ if offset >= 0:
936
+ L = np.minimum(N, M - offset) if (M - offset) > 0 else 0
937
+ indices = [[i, i + offset] for i in range(L)]
938
+ else:
939
+ L = np.minimum(N + offset, M) if (N + offset) > 0 else 0
940
+ indices = [[i - offset, i] for i in range(L)]
941
+
942
+ indices = np.array(indices, dtype=np.int32).reshape(L, 2)
943
+ indices_const = ov_opset.constant(indices, dtype=Type.i32).output(0)
944
+
945
+ diag_gathered = ov_opset.gather_nd(x_transposed, indices_const)
946
+
947
+ out_rank = rank - 1
948
+ out_perm_order = list(range(1, out_rank)) + [0]
949
+ out_perm_const = ov_opset.constant(out_perm_order, dtype=Type.i32).output(0)
950
+
951
+ final_output = ov_opset.transpose(diag_gathered, out_perm_const)
952
+ return OpenVINOKerasTensor(final_output.output(0))
899
953
 
900
954
 
901
955
  def diff(a, n=1, axis=-1):
@@ -1071,7 +1125,94 @@ def expm1(x):
1071
1125
 
1072
1126
 
1073
1127
  def flip(x, axis=None):
1074
- raise NotImplementedError("`flip` is not supported with openvino backend")
1128
+ x_node = get_ov_output(x)
1129
+
1130
+ # Using OpenVINO tensor shape
1131
+ ndim = len(x_node.get_partial_shape())
1132
+ if ndim is None:
1133
+ raise ValueError(
1134
+ "The `flip` operation does not support tensors with dynamic rank "
1135
+ "for the OpenVINO backend."
1136
+ )
1137
+
1138
+ if axis is None:
1139
+ axis = list(range(ndim))
1140
+ elif isinstance(axis, int):
1141
+ axis = [axis]
1142
+
1143
+ axis = [a + ndim if a < 0 else a for a in axis]
1144
+
1145
+ begin = [0] * ndim
1146
+ end = [0] * ndim
1147
+ strides = [1] * ndim
1148
+ for a in axis:
1149
+ strides[a] = -1
1150
+
1151
+ all_ones_mask = [1] * ndim
1152
+ result = ov_opset.strided_slice(
1153
+ data=x_node,
1154
+ begin=begin,
1155
+ end=end,
1156
+ strides=strides,
1157
+ begin_mask=all_ones_mask,
1158
+ end_mask=all_ones_mask,
1159
+ )
1160
+ return OpenVINOKerasTensor(result.output(0))
1161
+
1162
+
1163
+ def rot90(array, k=1, axes=(0, 1)):
1164
+ """Rotate an array by 90 degrees in the plane specified by axes."""
1165
+ array = get_ov_output(array)
1166
+
1167
+ if not isinstance(axes, (tuple, list)) or len(axes) != 2:
1168
+ raise ValueError("axes must be a tuple of length 2")
1169
+
1170
+ shape = array.get_partial_shape()
1171
+ ndim = shape.rank.get_length()
1172
+ if ndim is None:
1173
+ raise ValueError(
1174
+ "`rot90` does not support tensors with dynamic rank "
1175
+ "for the OpenVINO backend."
1176
+ )
1177
+
1178
+ axis1 = canonicalize_axis(axes[0], ndim)
1179
+ axis2 = canonicalize_axis(axes[1], ndim)
1180
+
1181
+ if axis1 == axis2:
1182
+ raise ValueError("axes must be different")
1183
+
1184
+ k = k % 4
1185
+ if k == 0:
1186
+ return OpenVINOKerasTensor(array)
1187
+
1188
+ result = array
1189
+
1190
+ for _ in range(k):
1191
+ # 1️ Transpose axis1 <-> axis2
1192
+ perm = list(range(ndim))
1193
+ perm[axis1], perm[axis2] = perm[axis2], perm[axis1]
1194
+ perm_const = ov_opset.constant(perm, Type.i32).output(0)
1195
+ result = ov_opset.transpose(result, perm_const).output(0)
1196
+
1197
+ # 2️ Reverse along axis1 using StridedSlice
1198
+ begin = [0] * ndim
1199
+ end = [0] * ndim
1200
+ strides = [1] * ndim
1201
+ strides[axis1] = -1
1202
+
1203
+ begin_mask = [1] * ndim
1204
+ end_mask = [1] * ndim
1205
+
1206
+ result = ov_opset.strided_slice(
1207
+ data=result,
1208
+ begin=begin,
1209
+ end=end,
1210
+ strides=strides,
1211
+ begin_mask=begin_mask,
1212
+ end_mask=end_mask,
1213
+ ).output(0)
1214
+
1215
+ return OpenVINOKerasTensor(result)
1075
1216
 
1076
1217
 
1077
1218
  def floor(x):
@@ -1150,7 +1291,34 @@ def hstack(xs):
1150
1291
 
1151
1292
 
1152
1293
  def hypot(x1, x2):
1153
- raise NotImplementedError("`hypot` is not supported with openvino backend")
1294
+ element_type = None
1295
+ if isinstance(x1, OpenVINOKerasTensor):
1296
+ element_type = x1.output.get_element_type()
1297
+ if isinstance(x2, OpenVINOKerasTensor):
1298
+ element_type = x2.output.get_element_type()
1299
+ x1 = get_ov_output(x1, element_type)
1300
+ x2 = get_ov_output(x2, element_type)
1301
+ x1, x2 = _align_operand_types(x1, x2, "hypot()")
1302
+ x_type = x1.get_element_type()
1303
+ if x_type.is_integral() or x_type == Type.boolean:
1304
+ ov_type = OPENVINO_DTYPES[config.floatx()]
1305
+ x1 = ov_opset.convert(x1, ov_type)
1306
+ x2 = ov_opset.convert(x2, ov_type)
1307
+ x1_abs = ov_opset.absolute(x1)
1308
+ x2_abs = ov_opset.absolute(x2)
1309
+ max_val = ov_opset.maximum(x1_abs, x2_abs)
1310
+ min_val = ov_opset.minimum(x1_abs, x2_abs)
1311
+ one = ov_opset.constant(1, max_val.get_element_type())
1312
+ is_zero_mask = ov_opset.equal(
1313
+ max_val, ov_opset.constant(0, max_val.get_element_type())
1314
+ )
1315
+ safe_divisor = ov_opset.select(is_zero_mask, one, max_val)
1316
+ ratio = ov_opset.divide(min_val, safe_divisor)
1317
+ result = ov_opset.multiply(
1318
+ max_val,
1319
+ ov_opset.sqrt(ov_opset.add(one, ov_opset.multiply(ratio, ratio))),
1320
+ )
1321
+ return OpenVINOKerasTensor(result.output(0))
1154
1322
 
1155
1323
 
1156
1324
  def identity(n, dtype=None):
@@ -1287,7 +1455,66 @@ def isreal(x):
1287
1455
 
1288
1456
 
1289
1457
  def kron(x1, x2):
1290
- raise NotImplementedError("`kron` is not supported with openvino backend")
1458
+ x1 = get_ov_output(x1)
1459
+ x2 = get_ov_output(x2)
1460
+ x1, x2 = _align_operand_types(x1, x2, "kron()")
1461
+ x1_shape = x1.get_partial_shape()
1462
+ x2_shape = x2.get_partial_shape()
1463
+ if x1_shape.rank.is_dynamic or x2_shape.rank.is_dynamic:
1464
+ raise ValueError(
1465
+ "`kron` does not support tensors with dynamic rank for "
1466
+ "the OpenVINO backend."
1467
+ )
1468
+ ndim1 = x1_shape.rank.get_length()
1469
+ ndim2 = x2_shape.rank.get_length()
1470
+ if ndim1 < ndim2:
1471
+ axes = ov_opset.range(
1472
+ ov_opset.constant(0, Type.i32),
1473
+ ov_opset.constant(ndim2 - ndim1, Type.i32),
1474
+ ov_opset.constant(1, Type.i32),
1475
+ )
1476
+ x1 = ov_opset.unsqueeze(x1, axes)
1477
+ ndim1 = ndim2
1478
+ elif ndim2 < ndim1:
1479
+ axes = ov_opset.range(
1480
+ ov_opset.constant(0, Type.i32),
1481
+ ov_opset.constant(ndim1 - ndim2, Type.i32),
1482
+ ov_opset.constant(1, Type.i32),
1483
+ )
1484
+ x2 = ov_opset.unsqueeze(x2, axes)
1485
+ ndim2 = ndim1
1486
+ shape1 = ov_opset.shape_of(x1, Type.i32)
1487
+ shape2 = ov_opset.shape_of(x2, Type.i32)
1488
+ ones = ov_opset.broadcast(
1489
+ ov_opset.constant(1, Type.i32), ov_opset.constant([ndim1], Type.i32)
1490
+ )
1491
+ axis = ov_opset.constant(1, Type.i32)
1492
+ flatten = ov_opset.constant([-1], Type.i32)
1493
+ unsqueezed_ones = ov_opset.unsqueeze(ones, axis)
1494
+ x1_new_shape = ov_opset.reshape(
1495
+ ov_opset.concat(
1496
+ [ov_opset.unsqueeze(shape1, axis), unsqueezed_ones],
1497
+ axis=1,
1498
+ ),
1499
+ flatten,
1500
+ False,
1501
+ )
1502
+ x2_new_shape = ov_opset.reshape(
1503
+ ov_opset.concat(
1504
+ [unsqueezed_ones, ov_opset.unsqueeze(shape2, axis)],
1505
+ axis=1,
1506
+ ),
1507
+ flatten,
1508
+ False,
1509
+ )
1510
+ result = ov_opset.multiply(
1511
+ ov_opset.reshape(x1, x1_new_shape, False),
1512
+ ov_opset.reshape(x2, x2_new_shape, False),
1513
+ )
1514
+ result = ov_opset.reshape(
1515
+ result, ov_opset.multiply(shape1, shape2), False
1516
+ ).output(0)
1517
+ return OpenVINOKerasTensor(result)
1291
1518
 
1292
1519
 
1293
1520
  def lcm(x1, x2):
@@ -1552,9 +1779,42 @@ def logaddexp(x1, x2):
1552
1779
 
1553
1780
 
1554
1781
  def logaddexp2(x1, x2):
1555
- raise NotImplementedError(
1556
- "`logaddexp2` is not supported with openvino backend"
1782
+ element_type = None
1783
+ if isinstance(x1, OpenVINOKerasTensor):
1784
+ element_type = x1.output.get_element_type()
1785
+ if isinstance(x2, OpenVINOKerasTensor):
1786
+ element_type = x2.output.get_element_type()
1787
+ x1 = get_ov_output(x1, element_type)
1788
+ x2 = get_ov_output(x2, element_type)
1789
+ x1, x2 = _align_operand_types(x1, x2, "logaddexp2()")
1790
+
1791
+ if x1.element_type.is_integral() or x2.element_type.is_integral():
1792
+ float_dtype = OPENVINO_DTYPES[config.floatx()]
1793
+ if x1.get_element_type().is_integral():
1794
+ x1 = ov_opset.convert(x1, float_dtype)
1795
+ if x2.get_element_type().is_integral():
1796
+ x2 = ov_opset.convert(x2, float_dtype)
1797
+
1798
+ max_val = ov_opset.maximum(x1, x2)
1799
+
1800
+ sub = ov_opset.subtract(x1, x2)
1801
+ abs_diff = ov_opset.abs(sub)
1802
+
1803
+ neg_abs_diff = ov_opset.negative(abs_diff)
1804
+
1805
+ element_type = neg_abs_diff.get_element_type()
1806
+
1807
+ two = ov_opset.constant(2, dtype=element_type)
1808
+
1809
+ power_of_2 = ov_opset.power(two, neg_abs_diff)
1810
+
1811
+ one_plus_power = ov_opset.add(
1812
+ ov_opset.constant(1, dtype=element_type), power_of_2
1557
1813
  )
1814
+ log2_term = ov_opset.divide(ov_opset.log(one_plus_power), ov_opset.log(two))
1815
+ result = ov_opset.add(max_val, log2_term).output(0)
1816
+
1817
+ return OpenVINOKerasTensor(result)
1558
1818
 
1559
1819
 
1560
1820
  def logical_and(x1, x2):
@@ -1829,6 +2089,10 @@ def moveaxis(x, source, destination):
1829
2089
  return OpenVINOKerasTensor(ov_opset.transpose(x, axes_const).output(0))
1830
2090
 
1831
2091
 
2092
+ def nansum(x, axis=None, keepdims=False):
2093
+ raise NotImplementedError("`nansum` is not supported with openvino backend")
2094
+
2095
+
1832
2096
  def nan_to_num(x, nan=0.0, posinf=None, neginf=None):
1833
2097
  x = get_ov_output(x)
1834
2098
  dtype = x.get_element_type()
@@ -1979,6 +2243,10 @@ def prod(x, axis=None, keepdims=False, dtype=None):
1979
2243
  return OpenVINOKerasTensor(result)
1980
2244
 
1981
2245
 
2246
+ def ptp(x, axis=None, keepdims=False):
2247
+ raise NotImplementedError("`ptp` is not supported with openvino backend")
2248
+
2249
+
1982
2250
  def quantile(x, q, axis=None, method="linear", keepdims=False):
1983
2251
  raise NotImplementedError(
1984
2252
  "`quantile` is not supported with openvino backend"
@@ -2115,7 +2383,14 @@ def sinh(x):
2115
2383
 
2116
2384
 
2117
2385
  def size(x):
2118
- raise NotImplementedError("`size` is not supported with openvino backend")
2386
+ x = get_ov_output(x)
2387
+ shape_tensor = ov_opset.shape_of(x, output_type=Type.i64)
2388
+ final_size = ov_opset.reduce_prod(
2389
+ shape_tensor,
2390
+ ov_opset.constant([0], Type.i64),
2391
+ keep_dims=False,
2392
+ )
2393
+ return OpenVINOKerasTensor(final_size.output(0))
2119
2394
 
2120
2395
 
2121
2396
  def sort(x, axis=-1):
@@ -2257,9 +2532,20 @@ def std(x, axis=None, keepdims=False):
2257
2532
 
2258
2533
 
2259
2534
  def swapaxes(x, axis1, axis2):
2260
- raise NotImplementedError(
2261
- "`swapaxes` is not supported with openvino backend"
2262
- )
2535
+ x = get_ov_output(x)
2536
+ x_shape = x.get_partial_shape()
2537
+ if x_shape.rank.is_dynamic:
2538
+ raise ValueError(
2539
+ "`swapaxes` does not support tensors with dynamic rank for the "
2540
+ "OpenVINO backend."
2541
+ )
2542
+ rank = x_shape.rank.get_length()
2543
+ axis1 = canonicalize_axis(axis1, rank)
2544
+ axis2 = canonicalize_axis(axis2, rank)
2545
+ axes = list(range(rank))
2546
+ axes[axis1], axes[axis2] = axes[axis2], axes[axis1]
2547
+ result = ov_opset.transpose(x, ov_opset.constant(axes, Type.i32))
2548
+ return OpenVINOKerasTensor(result.output(0))
2263
2549
 
2264
2550
 
2265
2551
  def take(x, indices, axis=None):
@@ -2378,7 +2664,8 @@ def tile(x, repeats):
2378
2664
 
2379
2665
 
2380
2666
  def trace(x, offset=0, axis1=0, axis2=1):
2381
- raise NotImplementedError("`trace` is not supported with openvino backend")
2667
+ x = diagonal(x, offset=offset, axis1=axis1, axis2=axis2)
2668
+ return sum(x, axis=-1)
2382
2669
 
2383
2670
 
2384
2671
  def tri(N, M=None, k=0, dtype=None):
@@ -2572,6 +2859,12 @@ def negative(x):
2572
2859
  return OpenVINOKerasTensor(ov_opset.negative(x).output(0))
2573
2860
 
2574
2861
 
2862
+ def nextafter(x1, x2):
2863
+ raise NotImplementedError(
2864
+ "`nextafter` is not supported with openvino backend"
2865
+ )
2866
+
2867
+
2575
2868
  def square(x):
2576
2869
  x = get_ov_output(x)
2577
2870
  x_type = x.get_element_type()
@@ -2905,6 +3198,66 @@ def slogdet(x):
2905
3198
 
2906
3199
 
2907
3200
  def argpartition(x, kth, axis=-1):
2908
- raise NotImplementedError(
2909
- "`argpartition` is not supported with openvino backend"
3201
+ x = get_ov_output(x)
3202
+ x_shape = x.get_partial_shape()
3203
+ rank = x_shape.rank.get_length()
3204
+ axis = canonicalize_axis(axis, rank)
3205
+ axes = list(range(rank))
3206
+ axes[axis], axes[-1] = axes[-1], axes[axis]
3207
+ x = ov_opset.transpose(x, ov_opset.constant(axes))
3208
+ x_shape_tensor = ov_opset.shape_of(x)
3209
+ n = ov_opset.gather(
3210
+ x_shape_tensor,
3211
+ ov_opset.constant(-1),
3212
+ ov_opset.constant(0),
3213
+ )
3214
+ if isinstance(kth, int) and kth < 0:
3215
+ kth_tensor = ov_opset.add(
3216
+ n,
3217
+ ov_opset.constant(kth, n.get_element_type()),
3218
+ )
3219
+ else:
3220
+ kth_tensor = ov_opset.constant(kth, n.get_element_type())
3221
+ one = ov_opset.constant(1, kth_tensor.get_element_type())
3222
+ k_val = ov_opset.add(kth_tensor, one)
3223
+ bottom_ind = ov_opset.topk(
3224
+ ov_opset.negative(x),
3225
+ k=k_val,
3226
+ axis=-1,
3227
+ mode="max",
3228
+ sort="value",
3229
+ ).output(1)
3230
+ one_hot_mask = ov_opset.one_hot(
3231
+ bottom_ind,
3232
+ n,
3233
+ ov_opset.constant(1),
3234
+ ov_opset.constant(0),
3235
+ axis=-1,
3236
+ )
3237
+ mask = ov_opset.reduce_sum(
3238
+ one_hot_mask,
3239
+ ov_opset.constant([-2]),
3240
+ keep_dims=False,
2910
3241
  )
3242
+ ones = ov_opset.broadcast(
3243
+ ov_opset.constant(1),
3244
+ x_shape_tensor,
3245
+ )
3246
+ proxy = ov_opset.subtract(ones, mask)
3247
+ remaining_k = ov_opset.subtract(n, k_val)
3248
+ top_ind = ov_opset.topk(
3249
+ proxy,
3250
+ k=remaining_k,
3251
+ axis=-1,
3252
+ mode="max",
3253
+ sort="value",
3254
+ ).output(1)
3255
+ result = ov_opset.concat([bottom_ind, top_ind], axis=-1)
3256
+ inv_axes = [0] * rank
3257
+ for i, a in enumerate(axes):
3258
+ inv_axes[a] = i
3259
+ result = ov_opset.transpose(
3260
+ result,
3261
+ ov_opset.constant(inv_axes),
3262
+ ).output(0)
3263
+ return OpenVINOKerasTensor(result)
@@ -2125,6 +2125,22 @@ def moveaxis(x, source, destination):
2125
2125
  return tf.transpose(x, perm)
2126
2126
 
2127
2127
 
2128
+ def nansum(x, axis=None, keepdims=False):
2129
+ x = convert_to_tensor(x)
2130
+ dtype = standardize_dtype(x.dtype)
2131
+ x_clean = tf.where(
2132
+ tf.math.is_nan(cast(x, config.floatx())), tf.zeros((), dtype=dtype), x
2133
+ )
2134
+
2135
+ if dtype in ("bool", "int8", "int16"):
2136
+ dtype = "int32"
2137
+ elif dtype in ("uint8", "uint16"):
2138
+ dtype = "uint32"
2139
+ x_clean = cast(x_clean, dtype)
2140
+
2141
+ return tf.reduce_sum(x_clean, axis=axis, keepdims=keepdims)
2142
+
2143
+
2128
2144
  def nan_to_num(x, nan=0.0, posinf=None, neginf=None):
2129
2145
  x = convert_to_tensor(x)
2130
2146
 
@@ -2151,7 +2167,7 @@ def nan_to_num(x, nan=0.0, posinf=None, neginf=None):
2151
2167
 
2152
2168
  def ndim(x):
2153
2169
  x = convert_to_tensor(x)
2154
- return x.ndim
2170
+ return x.shape.rank
2155
2171
 
2156
2172
 
2157
2173
  def nonzero(x):
@@ -2215,6 +2231,13 @@ def prod(x, axis=None, keepdims=False, dtype=None):
2215
2231
  return tf.reduce_prod(x, axis=axis, keepdims=keepdims)
2216
2232
 
2217
2233
 
2234
+ def ptp(x, axis=None, keepdims=False):
2235
+ x = convert_to_tensor(x)
2236
+ return tf.reduce_max(x, axis=axis, keepdims=keepdims) - tf.reduce_min(
2237
+ x, axis=axis, keepdims=keepdims
2238
+ )
2239
+
2240
+
2218
2241
  def _quantile(x, q, axis=None, method="linear", keepdims=False):
2219
2242
  # ref: tfp.stats.percentile
2220
2243
  # float64 is needed here and below, else we get the wrong index if the array
@@ -3017,6 +3040,16 @@ def negative(x):
3017
3040
  return tf.negative(x)
3018
3041
 
3019
3042
 
3043
+ def nextafter(x1, x2):
3044
+ x1 = convert_to_tensor(x1)
3045
+ x2 = convert_to_tensor(x2)
3046
+
3047
+ dtype = dtypes.result_type(x1.dtype, x2.dtype, float)
3048
+ x1 = tf.cast(x1, tf.float64)
3049
+ x2 = tf.cast(x2, tf.float64)
3050
+ return tf.cast(tf.math.nextafter(x1, x2), dtype)
3051
+
3052
+
3020
3053
  @sparse.elementwise_unary
3021
3054
  def square(x):
3022
3055
  x = convert_to_tensor(x)
@@ -539,11 +539,21 @@ def _do_lstm_arguments_support_cudnn(
539
539
 
540
540
 
541
541
  def _has_fully_masked_sequence(mask):
542
- # Cudnn kernel will error out if the input sequence contains any
543
- # fully masked data. We walk around this issue by rerouting the computation
544
- # to standard kernel, until the issue on cudnn side has been fixed. For a
545
- # fully masked sequence, it will contain all Falses. To make it easy to
546
- # check, we inverse the boolean, check if any of the sequence has all True.
542
+ """Check if input sequence contains any fully masked data.
543
+
544
+ cuDNN kernel will error out if the input sequence contains any fully masked
545
+ data. We work around this issue by rerouting the computation to the
546
+ standard kernel until the issue on the cuDNN side has been fixed. For a
547
+ fully masked sequence, it will contain all `False` values. To make it easy
548
+ to check, we invert the boolean and check if any of the sequences has all
549
+ `True` values.
550
+
551
+ Args:
552
+ mask: The mask tensor.
553
+
554
+ Returns:
555
+ A boolean tensor, `True` if the mask contains a fully masked sequence.
556
+ """
547
557
  return tf.reduce_any(
548
558
  tf.reduce_all(tf.logical_not(tf.cast(mask, dtype="bool")), axis=1)
549
559
  )
@@ -900,8 +910,8 @@ def _cudnn_lstm(
900
910
 
901
911
  if tf.sysconfig.get_build_info()["is_rocm_build"]:
902
912
  # ROCm MIOpen's weight sequence for LSTM is different from both
903
- # canonical and Cudnn format
904
- # MIOpen: [i, f, o, c] Cudnn/Canonical: [i, f, c, o]
913
+ # canonical and cuDNN format
914
+ # MIOpen: [i, f, o, c] cuDNN/Canonical: [i, f, c, o]
905
915
  # i is input gate weights.
906
916
  # f is forget gate weights.
907
917
  # o is output gate weights.
@@ -1272,6 +1272,20 @@ def moveaxis(x, source, destination):
1272
1272
  return torch.moveaxis(x, source=source, destination=destination)
1273
1273
 
1274
1274
 
1275
+ def nansum(x, axis=None, keepdims=False):
1276
+ if isinstance(x, (list, tuple)):
1277
+ x = stack(x)
1278
+ x = convert_to_tensor(x)
1279
+ dtype = standardize_dtype(x.dtype)
1280
+
1281
+ if dtype in ("bool", "uint8", "int8", "int16"):
1282
+ dtype = "int32"
1283
+
1284
+ if axis == () or axis == []:
1285
+ return cast(torch.nan_to_num(x, nan=0), dtype)
1286
+ return cast(torch.nansum(x, dim=axis, keepdim=keepdims), dtype)
1287
+
1288
+
1275
1289
  def nan_to_num(x, nan=0.0, posinf=None, neginf=None):
1276
1290
  x = convert_to_tensor(x)
1277
1291
  return torch.nan_to_num(x, nan=nan, posinf=posinf, neginf=neginf)
@@ -1382,6 +1396,18 @@ def prod(x, axis=None, keepdims=False, dtype=None):
1382
1396
  return x
1383
1397
 
1384
1398
 
1399
+ def ptp(x, axis=None, keepdims=False):
1400
+ x = convert_to_tensor(x)
1401
+ if axis is None:
1402
+ return x.max() - x.min()
1403
+ elif axis == ():
1404
+ return torch.zeros_like(x)
1405
+ else:
1406
+ return torch.amax(x, dim=axis, keepdim=keepdims) - torch.amin(
1407
+ x, dim=axis, keepdim=keepdims
1408
+ )
1409
+
1410
+
1385
1411
  def quantile(x, q, axis=None, method="linear", keepdims=False):
1386
1412
  x = convert_to_tensor(x)
1387
1413
  q = convert_to_tensor(q)
@@ -1793,6 +1819,16 @@ def negative(x):
1793
1819
  return torch.negative(x)
1794
1820
 
1795
1821
 
1822
+ def nextafter(x1, x2):
1823
+ x1 = convert_to_tensor(x1)
1824
+ x2 = convert_to_tensor(x2)
1825
+
1826
+ dtype = dtypes.result_type(x1.dtype, x2.dtype, float)
1827
+ x1 = cast(x1, torch.float64)
1828
+ x2 = cast(x2, torch.float64)
1829
+ return cast(torch.nextafter(x1, x2), dtype)
1830
+
1831
+
1796
1832
  def square(x):
1797
1833
  x = convert_to_tensor(x)
1798
1834
  if standardize_dtype(x.dtype) == "bool":
@@ -413,11 +413,21 @@ def _is_sequence_right_padded(mask):
413
413
 
414
414
 
415
415
  def _has_fully_masked_sequence(mask):
416
- # Cudnn kernel will error out if the input sequence contains any
417
- # fully masked data. We walk around this issue by rerouting the computation
418
- # to standard kernel, until the issue on cudnn side has been fixed. For a
419
- # fully masked sequence, it will contain all Falses. To make it easy to
420
- # check, we inverse the boolean, check if any of the sequence has all True.
416
+ """Check if input sequence contains any fully masked data.
417
+
418
+ cuDNN kernel will error out if the input sequence contains any fully masked
419
+ data. We work around this issue by rerouting the computation to the
420
+ standard kernel until the issue on the cuDNN side has been fixed. For a
421
+ fully masked sequence, it will contain all `False` values. To make it easy
422
+ to check, we invert the boolean and check if any of the sequences has all
423
+ `True` values.
424
+
425
+ Args:
426
+ mask: The mask tensor.
427
+
428
+ Returns:
429
+ A boolean tensor, `True` if the mask contains a fully masked sequence.
430
+ """
421
431
  return torch.any(torch.all(~mask, dim=1))
422
432
 
423
433
 
@@ -447,8 +457,8 @@ def _compute_sequence_length_from_mask(mask, batch_first):
447
457
  The masking tensor is a 2D boolean tensor with shape [batch, timestep]. For
448
458
  any timestep that should be masked, the corresponding field will be False.
449
459
  Consider the following example:
450
- a = [[True, True, False, False]
451
- [True, True, True, False]]
460
+ a = [[True, True, False, False]
461
+ [True, True, True, False]]
452
462
  It is a (2, 4) tensor, and the corresponding sequence length result should
453
463
  be 1D tensor with value [2, 3]. Note that the masking tensor must be right
454
464
  padded that could be checked by, e.g., `is_sequence_right_padded()`.
@@ -467,12 +477,19 @@ def _compute_sequence_length_from_mask(mask, batch_first):
467
477
 
468
478
 
469
479
  def prepare_lstm_weights(lstm, kernel, recurrent_kernel, bias, device):
470
- """Copies kernel and recurrent kernel weights in the Pytorch format
480
+ """Copies kernel and recurrent kernel weights into the PyTorch format.
481
+
471
482
  We split the kernel and recurrent kernel weights, create associated
472
- torch tensors adapted to be in line with the Cudnn optimization.
473
- After we have copied the weights, we ensure the paramters are on
474
- the same device and memory layout is optimized for Cudnn.
483
+ torch tensors adapted to be in line with the cuDNN optimization.
484
+ After we have copied the weights, we ensure the parameters are on
485
+ the same device and memory layout is optimized for cuDNN.
475
486
 
487
+ Args:
488
+ lstm: The PyTorch LSTM layer to prepare weights for.
489
+ kernel: The kernel weights tensor.
490
+ recurrent_kernel: The recurrent kernel weights tensor.
491
+ bias: The bias tensor.
492
+ device: The device to place the tensors on.
476
493
  """
477
494
 
478
495
  lstm = lstm.to(device)