usearch 2.26.1 → 2.26.2
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.
- package/README.md +1 -1
- package/include/usearch/index.hpp +5 -1
- package/numkong/CITATION.cff +13 -9
- package/numkong/CONTRIBUTING.md +29 -24
- package/numkong/Cargo.toml +1 -1
- package/numkong/README.md +21 -7
- package/numkong/binding.gyp +47 -31
- package/numkong/build.rs +15 -5
- package/numkong/c/dispatch.h +0 -1
- package/numkong/c/parallel.c +139 -0
- package/numkong/c/parallel.h +47 -0
- package/numkong/cmake/toolchain-wasi.cmake +39 -5
- package/numkong/include/README.md +24 -17
- package/numkong/include/numkong/attention/sapphireamx.h +6 -6
- package/numkong/include/numkong/attention/sme.h +2 -2
- package/numkong/include/numkong/capabilities.h +2 -2
- package/numkong/include/numkong/cast/README.md +9 -7
- package/numkong/include/numkong/cast/loongsonasx.h +48 -40
- package/numkong/include/numkong/cast/serial.h +89 -11
- package/numkong/include/numkong/cast.h +3 -3
- package/numkong/include/numkong/curved/README.md +10 -10
- package/numkong/include/numkong/curved/neon.h +1 -0
- package/numkong/include/numkong/curved/neonbfdot.h +1 -0
- package/numkong/include/numkong/curved/smef64.h +1 -0
- package/numkong/include/numkong/dot/README.md +18 -18
- package/numkong/include/numkong/dot/alder.h +8 -8
- package/numkong/include/numkong/dot/haswell.h +8 -8
- package/numkong/include/numkong/dot/icelake.h +12 -12
- package/numkong/include/numkong/dot/loongsonasx.h +6 -6
- package/numkong/include/numkong/dot/neon.h +2 -1
- package/numkong/include/numkong/dot/neonbfdot.h +1 -0
- package/numkong/include/numkong/dot/neonfhm.h +1 -0
- package/numkong/include/numkong/dot/neonsdot.h +1 -0
- package/numkong/include/numkong/dot/powervsx.h +3 -3
- package/numkong/include/numkong/dot/serial.h +19 -6
- package/numkong/include/numkong/dot/skylake.h +4 -4
- package/numkong/include/numkong/dot/svehalf.h +1 -1
- package/numkong/include/numkong/dot/v128relaxed.h +24 -23
- package/numkong/include/numkong/dots/README.md +26 -26
- package/numkong/include/numkong/dots/genoa.h +3 -1
- package/numkong/include/numkong/dots/icelake.h +1 -0
- package/numkong/include/numkong/dots/powervsx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +32 -33
- package/numkong/include/numkong/dots/sierra.h +4 -3
- package/numkong/include/numkong/each/README.md +5 -3
- package/numkong/include/numkong/each/sapphire.h +2 -0
- package/numkong/include/numkong/each/skylake.h +1 -0
- package/numkong/include/numkong/geospatial/README.md +6 -6
- package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
- package/numkong/include/numkong/maxsim/README.md +5 -5
- package/numkong/include/numkong/maxsim/genoa.h +4 -0
- package/numkong/include/numkong/maxsim/icelake.h +1 -0
- package/numkong/include/numkong/maxsim/serial.h +22 -9
- package/numkong/include/numkong/maxsim/sme.h +3 -2
- package/numkong/include/numkong/mesh/README.md +7 -4
- package/numkong/include/numkong/mesh/genoa.h +2 -2
- package/numkong/include/numkong/mesh/haswell.h +8 -8
- package/numkong/include/numkong/mesh/neon.h +6 -6
- package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
- package/numkong/include/numkong/mesh/neonfhm.h +3 -2
- package/numkong/include/numkong/mesh/rvv.h +4 -4
- package/numkong/include/numkong/mesh/serial.h +17 -17
- package/numkong/include/numkong/mesh/skylake.h +4 -4
- package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
- package/numkong/include/numkong/mesh.hpp +2 -2
- package/numkong/include/numkong/numkong.h +1 -1
- package/numkong/include/numkong/numkong.hpp +1 -1
- package/numkong/include/numkong/probability/README.md +17 -15
- package/numkong/include/numkong/probability/skylake.h +1 -1
- package/numkong/include/numkong/reduce/README.md +27 -21
- package/numkong/include/numkong/reduce/genoa.h +2 -1
- package/numkong/include/numkong/reduce/icelake.h +1 -0
- package/numkong/include/numkong/reduce/neon.h +1 -1
- package/numkong/include/numkong/reduce/serial.h +9 -9
- package/numkong/include/numkong/reduce/skylake.h +4 -4
- package/numkong/include/numkong/reduce.hpp +38 -0
- package/numkong/include/numkong/scalar/README.md +29 -28
- package/numkong/include/numkong/set/README.md +17 -14
- package/numkong/include/numkong/set/haswell.h +15 -16
- package/numkong/include/numkong/set/icelake.h +15 -16
- package/numkong/include/numkong/set/loongsonasx.h +12 -10
- package/numkong/include/numkong/set/neon.h +15 -19
- package/numkong/include/numkong/set/powervsx.h +15 -21
- package/numkong/include/numkong/set/serial.h +19 -20
- package/numkong/include/numkong/set/v128relaxed.h +10 -8
- package/numkong/include/numkong/sets/icelake.h +1 -0
- package/numkong/include/numkong/sparse/README.md +4 -4
- package/numkong/include/numkong/sparse/icelake.h +1 -0
- package/numkong/include/numkong/sparse/neon.h +1 -0
- package/numkong/include/numkong/sparse/serial.h +10 -3
- package/numkong/include/numkong/sparse/turin.h +1 -0
- package/numkong/include/numkong/spatial/README.md +25 -24
- package/numkong/include/numkong/spatial/alder.h +2 -1
- package/numkong/include/numkong/spatial/haswell.h +48 -40
- package/numkong/include/numkong/spatial/icelake.h +2 -1
- package/numkong/include/numkong/spatial/neon.h +54 -46
- package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
- package/numkong/include/numkong/spatial/powervsx.h +54 -46
- package/numkong/include/numkong/spatial/serial.h +56 -46
- package/numkong/include/numkong/spatial/sierra.h +4 -3
- package/numkong/include/numkong/spatial/skylake.h +27 -22
- package/numkong/include/numkong/spatial/sve.h +6 -6
- package/numkong/include/numkong/spatial/svebfdot.h +1 -1
- package/numkong/include/numkong/spatial/svehalf.h +1 -1
- package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
- package/numkong/include/numkong/spatials/README.md +15 -15
- package/numkong/include/numkong/spatials/genoa.h +3 -0
- package/numkong/include/numkong/spatials/graniteamx.h +2 -0
- package/numkong/include/numkong/spatials/icelake.h +1 -0
- package/numkong/include/numkong/spatials/sierra.h +1 -0
- package/numkong/include/numkong/tensor.hpp +236 -123
- package/numkong/include/numkong/trigonometry/README.md +4 -4
- package/numkong/include/numkong/trigonometry/serial.h +6 -6
- package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
- package/numkong/include/numkong/types.hpp +46 -16
- package/numkong/include/numkong/vector.hpp +116 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/README.md +18 -16
- package/numkong/javascript/numkong.c +70 -40
- package/numkong/package.json +8 -8
- package/numkong/rust/README.md +19 -9
- package/numkong/rust/cast.rs +52 -1
- package/numkong/rust/each.rs +480 -7
- package/numkong/rust/matrix.rs +877 -216
- package/numkong/rust/numkong.rs +12 -8
- package/numkong/rust/reduce.rs +433 -5
- package/numkong/rust/tensor.rs +450 -842
- package/numkong/rust/vector.rs +520 -101
- package/numkong/swift/README.md +5 -3
- package/numkong/test/README.md +4 -4
- package/numkong/test/test-wasi.mjs +17 -0
- package/numkong/test/test_each.cpp +30 -1
- package/numkong/test/test_each.py +74 -1
- package/numkong/test/test_reduce.cpp +51 -0
- package/numkong/test/test_tensor.cpp +2 -63
- package/numkong/test/test_tensor.py +93 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
#include <array> // `std::array`
|
|
32
32
|
#include <cstdio> // `std::fprintf`, `stderr`
|
|
33
33
|
#include <cstdlib> // `std::abort`
|
|
34
|
-
#include <cstring> // `std::memset`
|
|
34
|
+
#include <cstring> // `std::memcpy`, `std::memcmp`, `std::memset`
|
|
35
35
|
#include <span> // `std::span`
|
|
36
36
|
#include <tuple> // `std::tuple_element_t`
|
|
37
37
|
#include <type_traits>
|
|
@@ -117,6 +117,22 @@ struct shape_storage_ {
|
|
|
117
117
|
return offset;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* @brief Reset the spare slots [rank, max_rank_) to a harmless 1x0 layout.
|
|
122
|
+
*
|
|
123
|
+
* `tensor_flat_lookup_` iterates a runtime `rank` bound (`flat % extents[d]; flat /= extents[d]`),
|
|
124
|
+
* but under `-flto` the compiler unrolls/vectorises that loop to `max_rank_` iterations and
|
|
125
|
+
* speculatively evaluates `flat / extents[d]` for every `d` - including `d >= rank`. Leaving
|
|
126
|
+
* those extents at the in-class `= {}` zero turns the unused speculative quotient into a
|
|
127
|
+
* hardware divide-by-zero trap. Any builder that produces a reduced-rank shape must call this.
|
|
128
|
+
*/
|
|
129
|
+
constexpr void normalize_spare_() noexcept {
|
|
130
|
+
for (std::size_t d = rank; d < max_rank_; ++d) {
|
|
131
|
+
extents[d] = 1;
|
|
132
|
+
strides[d] = 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
120
136
|
/** @brief Create contiguous (row-major) shape storage. */
|
|
121
137
|
static constexpr shape_storage_ contiguous(std::size_t const *exts, std::size_t rank_val,
|
|
122
138
|
std::size_t elem_bytes) noexcept {
|
|
@@ -128,6 +144,7 @@ struct shape_storage_ {
|
|
|
128
144
|
s.strides[i - 1] = stride;
|
|
129
145
|
stride *= static_cast<std::ptrdiff_t>(exts[i - 1]);
|
|
130
146
|
}
|
|
147
|
+
s.normalize_spare_();
|
|
131
148
|
return s;
|
|
132
149
|
}
|
|
133
150
|
};
|
|
@@ -160,6 +177,7 @@ constexpr shape_storage_<max_rank_> make_contiguous_shape_(std::size_t const *ex
|
|
|
160
177
|
: static_cast<std::size_t>(exts[i - 1]);
|
|
161
178
|
stride *= static_cast<std::ptrdiff_t>(extent_factor);
|
|
162
179
|
}
|
|
180
|
+
s.normalize_spare_();
|
|
163
181
|
return s;
|
|
164
182
|
}
|
|
165
183
|
|
|
@@ -294,6 +312,7 @@ struct tensor_view {
|
|
|
294
312
|
sub.extents[d] = shape_.extents[d + 1];
|
|
295
313
|
sub.strides[d] = shape_.strides[d + 1];
|
|
296
314
|
}
|
|
315
|
+
sub.normalize_spare_(); // keep [rank, max_rank_) at 1x0 so -flto's speculative divides are harmless
|
|
297
316
|
return {data_ + offset, sub};
|
|
298
317
|
}
|
|
299
318
|
|
|
@@ -361,10 +380,11 @@ struct tensor_view {
|
|
|
361
380
|
return {data_, shape_.extents[0], shape_.strides[0]};
|
|
362
381
|
}
|
|
363
382
|
|
|
364
|
-
/** @brief
|
|
383
|
+
/** @brief Narrow an already-2D handle to a rank-2 type. Requires rank == 2; use reshape<2>({...}) to collapse a
|
|
384
|
+
* higher-rank tensor. */
|
|
365
385
|
tensor_view<value_type_, 2> as_matrix() const noexcept {
|
|
366
|
-
nk_assert_(shape_.rank
|
|
367
|
-
if (shape_.rank
|
|
386
|
+
nk_assert_(shape_.rank == 2);
|
|
387
|
+
if (shape_.rank != 2) return {};
|
|
368
388
|
shape_storage_<2> matrix_shape;
|
|
369
389
|
matrix_shape.rank = 2;
|
|
370
390
|
matrix_shape.extents[0] = shape_.extents[0];
|
|
@@ -391,12 +411,14 @@ struct tensor_view {
|
|
|
391
411
|
return {data_, transposed};
|
|
392
412
|
}
|
|
393
413
|
|
|
394
|
-
/** @brief Reshape to new extents (requires contiguous layout and matching element count).
|
|
414
|
+
/** @brief Reshape to new extents (requires contiguous layout and matching element count). The output rank may
|
|
415
|
+
* differ from the source; pass it as the template argument when narrowing or widening.
|
|
395
416
|
* Returns an empty view if the tensor is not contiguous or element counts don't match. */
|
|
396
|
-
|
|
417
|
+
template <std::size_t out_rank_ = max_rank_>
|
|
418
|
+
tensor_view<value_type_, out_rank_> reshape(std::initializer_list<size_type> new_extents) const noexcept {
|
|
397
419
|
auto new_rank = new_extents.size();
|
|
398
|
-
if (!is_contiguous() || new_rank >
|
|
399
|
-
auto new_shape = make_contiguous_shape_<value_type,
|
|
420
|
+
if (!is_contiguous() || new_rank > out_rank_ || new_rank == 0) return {};
|
|
421
|
+
auto new_shape = make_contiguous_shape_<value_type, out_rank_>(new_extents.begin(), new_rank);
|
|
400
422
|
if (storage_values_for_shape_<value_type>(new_shape) != storage_values_for_shape_<value_type>(shape_))
|
|
401
423
|
return {};
|
|
402
424
|
return {data_, new_shape};
|
|
@@ -444,6 +466,7 @@ struct tensor_view {
|
|
|
444
466
|
result.strides[0] = static_cast<difference_type>(sizeof(value_type));
|
|
445
467
|
}
|
|
446
468
|
result.rank = new_rank;
|
|
469
|
+
result.normalize_spare_(); // the copy of `shape_` may carry zeros in [new_rank, max_rank_)
|
|
447
470
|
return {data_, result};
|
|
448
471
|
}
|
|
449
472
|
};
|
|
@@ -459,6 +482,9 @@ struct tensor_view {
|
|
|
459
482
|
*/
|
|
460
483
|
template <typename value_type_, std::size_t max_rank_ = 8>
|
|
461
484
|
struct tensor_span {
|
|
485
|
+
static_assert(!std::is_const_v<value_type_>,
|
|
486
|
+
"tensor_span requires a non-const value_type_; use tensor_view<value_type_> for read-only access");
|
|
487
|
+
|
|
462
488
|
using value_type = value_type_;
|
|
463
489
|
using size_type = std::size_t;
|
|
464
490
|
using difference_type = std::ptrdiff_t;
|
|
@@ -492,11 +518,9 @@ struct tensor_span {
|
|
|
492
518
|
constexpr bool empty() const noexcept { return data_ == nullptr || shape_.numel() == 0; }
|
|
493
519
|
|
|
494
520
|
/** @brief Raw byte pointer. */
|
|
495
|
-
constexpr char *byte_data() noexcept { return data_; }
|
|
496
|
-
constexpr char const *byte_data() const noexcept { return data_; }
|
|
521
|
+
constexpr char *byte_data() const noexcept { return data_; }
|
|
497
522
|
/** @brief Typed pointer (assumes data is contiguous from this pointer). */
|
|
498
|
-
constexpr value_type *data() noexcept { return reinterpret_cast<value_type *>(data_); }
|
|
499
|
-
constexpr value_type const *data() const noexcept { return reinterpret_cast<value_type const *>(data_); }
|
|
523
|
+
constexpr value_type *data() const noexcept { return reinterpret_cast<value_type *>(data_); }
|
|
500
524
|
/** @brief Access the shape storage. */
|
|
501
525
|
constexpr shape_storage_<max_rank_> const &shape() const noexcept { return shape_; }
|
|
502
526
|
|
|
@@ -518,6 +542,7 @@ struct tensor_span {
|
|
|
518
542
|
sub.extents[d] = shape_.extents[d + 1];
|
|
519
543
|
sub.strides[d] = shape_.strides[d + 1];
|
|
520
544
|
}
|
|
545
|
+
sub.normalize_spare_(); // keep [rank, max_rank_) at 1x0 so -flto's speculative divides are harmless
|
|
521
546
|
return {data_ + offset, sub};
|
|
522
547
|
}
|
|
523
548
|
|
|
@@ -529,40 +554,22 @@ struct tensor_span {
|
|
|
529
554
|
|
|
530
555
|
/** @brief Flat logical scalar access. */
|
|
531
556
|
template <std::integral index_type_>
|
|
532
|
-
decltype(auto) operator[](index_type_ idx) noexcept {
|
|
533
|
-
return tensor_flat_lookup_(*this, idx);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/** @brief Const flat logical scalar access. */
|
|
537
|
-
template <std::integral index_type_>
|
|
538
557
|
decltype(auto) operator[](index_type_ idx) const noexcept {
|
|
539
|
-
return tensor_flat_lookup_(
|
|
558
|
+
return tensor_flat_lookup_(*this, idx);
|
|
540
559
|
}
|
|
541
560
|
|
|
542
561
|
/** @brief Exact multi-dimensional scalar lookup via call syntax (C++20-portable). */
|
|
543
562
|
template <std::integral... index_types_>
|
|
544
563
|
requires(sizeof...(index_types_) >= 2)
|
|
545
|
-
decltype(auto) operator()(index_types_... idxs) noexcept {
|
|
564
|
+
decltype(auto) operator()(index_types_... idxs) const noexcept {
|
|
546
565
|
nk_assert_(shape_.rank == sizeof...(index_types_));
|
|
547
566
|
auto coords = resolve_tensor_indices_<value_type_>(shape_, std::index_sequence_for<index_types_...> {},
|
|
548
567
|
idxs...);
|
|
549
568
|
return tensor_lookup_resolved_(*this, std::span<std::size_t const, sizeof...(index_types_)>(coords));
|
|
550
569
|
}
|
|
551
570
|
|
|
552
|
-
/** @brief Const full-coordinate lookup via call syntax. */
|
|
553
|
-
template <std::integral... index_types_>
|
|
554
|
-
requires(sizeof...(index_types_) >= 2)
|
|
555
|
-
decltype(auto) operator()(index_types_... idxs) const noexcept {
|
|
556
|
-
return static_cast<tensor_view<value_type_, max_rank_>>(*this)(idxs...);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
571
|
#if NK_HAS_MULTIDIMENSIONAL_SUBSCRIPT_
|
|
560
572
|
/** @brief C++23 sugar: multi-arg `[]` scalar lookup, delegates to `operator()`. */
|
|
561
|
-
template <std::integral... index_types_>
|
|
562
|
-
requires(sizeof...(index_types_) >= 2)
|
|
563
|
-
decltype(auto) operator[](index_types_... idxs) noexcept {
|
|
564
|
-
return (*this)(idxs...);
|
|
565
|
-
}
|
|
566
573
|
template <std::integral... index_types_>
|
|
567
574
|
requires(sizeof...(index_types_) >= 2)
|
|
568
575
|
decltype(auto) operator[](index_types_... idxs) const noexcept {
|
|
@@ -571,43 +578,26 @@ struct tensor_span {
|
|
|
571
578
|
#endif
|
|
572
579
|
|
|
573
580
|
/** @brief Trailing `slice` returns the same span. */
|
|
574
|
-
constexpr tensor_span operator[](tensor_slice_t) noexcept { return *this; }
|
|
575
|
-
constexpr tensor_view<value_type_, max_rank_> operator[](tensor_slice_t) const noexcept {
|
|
576
|
-
return static_cast<tensor_view<value_type_, max_rank_>>(*this);
|
|
577
|
-
}
|
|
581
|
+
constexpr tensor_span operator[](tensor_slice_t) const noexcept { return *this; }
|
|
578
582
|
|
|
579
583
|
/** @brief Prefix leading-axis slicing via call syntax (C++20-portable). */
|
|
580
584
|
template <typename first_type_, typename second_type_, typename... rest_types_>
|
|
581
585
|
requires(trailing_tensor_slice_args_v<first_type_, second_type_, rest_types_...>)
|
|
582
|
-
tensor_span operator()(first_type_ first, second_type_ second, rest_types_... rest) noexcept {
|
|
586
|
+
tensor_span operator()(first_type_ first, second_type_ second, rest_types_... rest) const noexcept {
|
|
583
587
|
return tensor_slice_suffix_(*this, first, second, rest...);
|
|
584
588
|
}
|
|
585
589
|
|
|
586
|
-
/** @brief Const prefix leading-axis slicing via call syntax. */
|
|
587
|
-
template <typename first_type_, typename second_type_, typename... rest_types_>
|
|
588
|
-
requires(trailing_tensor_slice_args_v<first_type_, second_type_, rest_types_...>)
|
|
589
|
-
tensor_view<value_type_, max_rank_> operator()(first_type_ first, second_type_ second,
|
|
590
|
-
rest_types_... rest) const noexcept {
|
|
591
|
-
return tensor_slice_suffix_(static_cast<tensor_view<value_type_, max_rank_>>(*this), first, second, rest...);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
590
|
#if NK_HAS_MULTIDIMENSIONAL_SUBSCRIPT_
|
|
595
591
|
/** @brief C++23 sugar: multi-arg `[]` slicing, delegates to `operator()`. */
|
|
596
592
|
template <typename first_type_, typename second_type_, typename... rest_types_>
|
|
597
593
|
requires(trailing_tensor_slice_args_v<first_type_, second_type_, rest_types_...>)
|
|
598
|
-
tensor_span operator[](first_type_ first, second_type_ second, rest_types_... rest) noexcept {
|
|
599
|
-
return (*this)(first, second, rest...);
|
|
600
|
-
}
|
|
601
|
-
template <typename first_type_, typename second_type_, typename... rest_types_>
|
|
602
|
-
requires(trailing_tensor_slice_args_v<first_type_, second_type_, rest_types_...>)
|
|
603
|
-
tensor_view<value_type_, max_rank_> operator[](first_type_ first, second_type_ second,
|
|
604
|
-
rest_types_... rest) const noexcept {
|
|
594
|
+
tensor_span operator[](first_type_ first, second_type_ second, rest_types_... rest) const noexcept {
|
|
605
595
|
return (*this)(first, second, rest...);
|
|
606
596
|
}
|
|
607
597
|
#endif
|
|
608
598
|
|
|
609
599
|
/** @brief Rank-0 mutable scalar access. */
|
|
610
|
-
decltype(auto) scalar_ref() noexcept {
|
|
600
|
+
decltype(auto) scalar_ref() const noexcept {
|
|
611
601
|
nk_assert_(shape_.rank == 0);
|
|
612
602
|
nk_assert_(data_ != nullptr);
|
|
613
603
|
return *reinterpret_cast<value_type_ *>(data_);
|
|
@@ -617,23 +607,17 @@ struct tensor_span {
|
|
|
617
607
|
decltype(auto) scalar() const noexcept { return static_cast<tensor_view<value_type_, max_rank_>>(*this).scalar(); }
|
|
618
608
|
|
|
619
609
|
/** @brief Convert to vector_span (requires rank == 1). */
|
|
620
|
-
vector_span<value_type> as_vector() noexcept {
|
|
610
|
+
vector_span<value_type> as_vector() const noexcept {
|
|
621
611
|
nk_assert_(shape_.rank == 1);
|
|
622
612
|
if (shape_.rank != 1) return {};
|
|
623
613
|
return {data_, shape_.extents[0], shape_.strides[0]};
|
|
624
614
|
}
|
|
625
615
|
|
|
626
|
-
/** @brief
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/** @brief Reinterpret as a 2D matrix span. Requires rank >= 2. */
|
|
634
|
-
tensor_span<value_type_, 2> as_matrix() noexcept {
|
|
635
|
-
nk_assert_(shape_.rank >= 2);
|
|
636
|
-
if (shape_.rank < 2) return {};
|
|
616
|
+
/** @brief Narrow an already-2D handle to a rank-2 type. Requires rank == 2; use reshape<2>({...}) to collapse a
|
|
617
|
+
* higher-rank tensor. */
|
|
618
|
+
tensor_span<value_type_, 2> as_matrix() const noexcept {
|
|
619
|
+
nk_assert_(shape_.rank == 2);
|
|
620
|
+
if (shape_.rank != 2) return {};
|
|
637
621
|
shape_storage_<2> matrix_shape;
|
|
638
622
|
matrix_shape.rank = 2;
|
|
639
623
|
matrix_shape.extents[0] = shape_.extents[0];
|
|
@@ -643,24 +627,11 @@ struct tensor_span {
|
|
|
643
627
|
return {data_, matrix_shape};
|
|
644
628
|
}
|
|
645
629
|
|
|
646
|
-
/** @brief Reinterpret as a 2D const matrix view. Requires rank >= 2. */
|
|
647
|
-
tensor_view<value_type_, 2> as_matrix() const noexcept {
|
|
648
|
-
nk_assert_(shape_.rank >= 2);
|
|
649
|
-
if (shape_.rank < 2) return {};
|
|
650
|
-
shape_storage_<2> matrix_shape;
|
|
651
|
-
matrix_shape.rank = 2;
|
|
652
|
-
matrix_shape.extents[0] = shape_.extents[0];
|
|
653
|
-
matrix_shape.extents[1] = shape_.extents[1];
|
|
654
|
-
matrix_shape.strides[0] = shape_.strides[0];
|
|
655
|
-
matrix_shape.strides[1] = shape_.strides[1];
|
|
656
|
-
return {static_cast<char const *>(data_), matrix_shape};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
630
|
/** @brief Check if contiguous in memory. */
|
|
660
631
|
constexpr bool is_contiguous() const noexcept { return is_tensor_contiguous_<value_type>(shape_); }
|
|
661
632
|
|
|
662
633
|
/** @brief Transpose: reverse the order of all dimensions (swap extents and strides). */
|
|
663
|
-
constexpr tensor_span transpose() noexcept {
|
|
634
|
+
constexpr tensor_span transpose() const noexcept {
|
|
664
635
|
if constexpr (dimensions_per_value<value_type>() > 1) {
|
|
665
636
|
if (shape_.rank >= 2) return {};
|
|
666
637
|
}
|
|
@@ -673,12 +644,14 @@ struct tensor_span {
|
|
|
673
644
|
return {data_, transposed};
|
|
674
645
|
}
|
|
675
646
|
|
|
676
|
-
/** @brief Reshape to new extents (requires contiguous layout and matching element count).
|
|
647
|
+
/** @brief Reshape to new extents (requires contiguous layout and matching element count). The output rank may
|
|
648
|
+
* differ from the source; pass it as the template argument when narrowing or widening.
|
|
677
649
|
* Returns an empty span if not contiguous or element counts don't match. */
|
|
678
|
-
|
|
650
|
+
template <std::size_t out_rank_ = max_rank_>
|
|
651
|
+
tensor_span<value_type_, out_rank_> reshape(std::initializer_list<size_type> new_extents) const noexcept {
|
|
679
652
|
auto new_rank = new_extents.size();
|
|
680
|
-
if (!is_contiguous() || new_rank >
|
|
681
|
-
auto new_shape = make_contiguous_shape_<value_type,
|
|
653
|
+
if (!is_contiguous() || new_rank > out_rank_ || new_rank == 0) return {};
|
|
654
|
+
auto new_shape = make_contiguous_shape_<value_type, out_rank_>(new_extents.begin(), new_rank);
|
|
682
655
|
if (storage_values_for_shape_<value_type>(new_shape) != storage_values_for_shape_<value_type>(shape_))
|
|
683
656
|
return {};
|
|
684
657
|
return {data_, new_shape};
|
|
@@ -687,56 +660,37 @@ struct tensor_span {
|
|
|
687
660
|
/** @brief Range of mutable sub-spans along the leading dimension. */
|
|
688
661
|
struct rows_spans_t {
|
|
689
662
|
tensor_span parent;
|
|
690
|
-
axis_iterator<tensor_span> begin() noexcept { return {parent, 0}; }
|
|
691
|
-
axis_iterator<tensor_span> end() noexcept { return {parent, parent.extent(0)}; }
|
|
663
|
+
axis_iterator<tensor_span> begin() const noexcept { return {parent, 0}; }
|
|
664
|
+
axis_iterator<tensor_span> end() const noexcept { return {parent, parent.extent(0)}; }
|
|
692
665
|
};
|
|
693
666
|
|
|
694
|
-
rows_spans_t rows() noexcept { return {*this}; }
|
|
695
|
-
|
|
696
|
-
/** @brief Range of immutable sub-views along the leading dimension. */
|
|
697
|
-
struct rows_views_t {
|
|
698
|
-
tensor_view<value_type_, max_rank_> parent;
|
|
699
|
-
axis_iterator<tensor_view<value_type_, max_rank_>> begin() const noexcept { return {parent, 0}; }
|
|
700
|
-
axis_iterator<tensor_view<value_type_, max_rank_>> end() const noexcept { return {parent, parent.extent(0)}; }
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
rows_views_t rows() const noexcept {
|
|
704
|
-
tensor_view<value_type_, max_rank_> v = *this;
|
|
705
|
-
return {v};
|
|
706
|
-
}
|
|
667
|
+
rows_spans_t rows() const noexcept { return {*this}; }
|
|
707
668
|
|
|
708
669
|
static constexpr std::size_t max_rank = max_rank_;
|
|
709
670
|
|
|
710
671
|
/** @brief Mutable element iterator (begin): yields `(position, ref_or_proxy)` pairs. */
|
|
711
|
-
tensor_span_iterator_<tensor_span> begin() noexcept { return {*this}; }
|
|
672
|
+
tensor_span_iterator_<tensor_span> begin() const noexcept { return {const_cast<tensor_span &>(*this)}; }
|
|
712
673
|
/** @brief Mutable element iterator (end). */
|
|
713
|
-
tensor_span_iterator_<tensor_span> end() noexcept { return {*this, true}; }
|
|
714
|
-
/** @brief Const element iterator (begin): yields `(position, scalar)` pairs. */
|
|
715
|
-
tensor_view_iterator_<tensor_view<value_type_, max_rank_>> begin() const noexcept {
|
|
716
|
-
return {static_cast<tensor_view<value_type_, max_rank_>>(*this)};
|
|
717
|
-
}
|
|
718
|
-
/** @brief Const element iterator (end). */
|
|
719
|
-
tensor_view_iterator_<tensor_view<value_type_, max_rank_>> end() const noexcept {
|
|
720
|
-
return {static_cast<tensor_view<value_type_, max_rank_>>(*this), true};
|
|
721
|
-
}
|
|
674
|
+
tensor_span_iterator_<tensor_span> end() const noexcept { return {const_cast<tensor_span &>(*this), true}; }
|
|
722
675
|
/** @brief Number of logical scalar elements. */
|
|
723
676
|
constexpr size_type size() const noexcept { return numel(); }
|
|
724
677
|
/** @brief Mutable dimension-only view. */
|
|
725
|
-
tensor_dims_view_<tensor_span_iterator_<tensor_span>> dims() noexcept {
|
|
726
|
-
return {tensor_span_iterator_<tensor_span> {*this}, numel()};
|
|
727
|
-
}
|
|
728
|
-
/** @brief Const dimension-only view. */
|
|
729
|
-
tensor_dims_view_<tensor_view_iterator_<tensor_view<value_type_, max_rank_>>> dims() const noexcept {
|
|
730
|
-
return {tensor_view_iterator_<tensor_view<value_type_, max_rank_>> {
|
|
731
|
-
static_cast<tensor_view<value_type_, max_rank_>>(*this)},
|
|
732
|
-
numel()};
|
|
678
|
+
tensor_dims_view_<tensor_span_iterator_<tensor_span>> dims() const noexcept {
|
|
679
|
+
return {tensor_span_iterator_<tensor_span> {const_cast<tensor_span &>(*this)}, numel()};
|
|
733
680
|
}
|
|
734
681
|
|
|
735
682
|
/** @brief Flatten to 1D span (requires contiguous layout). Returns empty span if not contiguous. */
|
|
736
|
-
tensor_span flatten() noexcept { return reshape({numel()}); }
|
|
683
|
+
tensor_span flatten() const noexcept { return reshape({numel()}); }
|
|
684
|
+
|
|
685
|
+
/** @brief Zero-fill every element (declared here, defined after the free `fill_zeros`). */
|
|
686
|
+
bool fill_zeros() noexcept;
|
|
687
|
+
/** @brief Fill every element with `value` (declared here, defined after the free `fill`). */
|
|
688
|
+
bool fill(value_type value) noexcept;
|
|
689
|
+
/** @brief Copy from a same-shape view (declared here, defined after the free `copy`). */
|
|
690
|
+
bool copy_from(tensor_view<value_type_, max_rank_> input) noexcept;
|
|
737
691
|
|
|
738
692
|
/** @brief Remove dimensions of size 1. */
|
|
739
|
-
tensor_span squeeze() noexcept {
|
|
693
|
+
tensor_span squeeze() const noexcept {
|
|
740
694
|
auto result = shape_;
|
|
741
695
|
size_type new_rank = 0;
|
|
742
696
|
for (size_type i = 0; i < shape_.rank; ++i) {
|
|
@@ -752,6 +706,7 @@ struct tensor_span {
|
|
|
752
706
|
result.strides[0] = static_cast<difference_type>(sizeof(value_type));
|
|
753
707
|
}
|
|
754
708
|
result.rank = new_rank;
|
|
709
|
+
result.normalize_spare_(); // the copy of `shape_` may carry zeros in [new_rank, max_rank_)
|
|
755
710
|
return {data_, result};
|
|
756
711
|
}
|
|
757
712
|
};
|
|
@@ -887,6 +842,7 @@ tensor_type_ tensor_slice_suffix_(tensor_type_ input, all_t, rest_types_... rest
|
|
|
887
842
|
result_shape.extents[1 + d] = inner.extent(d);
|
|
888
843
|
result_shape.strides[1 + d] = inner.stride_bytes(d);
|
|
889
844
|
}
|
|
845
|
+
result_shape.normalize_spare_(); // see shape_storage_::normalize_spare_ — -flto speculative-divide guard
|
|
890
846
|
|
|
891
847
|
// The data pointer is the inner slice's offset relative to the first row,
|
|
892
848
|
// applied to the original data pointer.
|
|
@@ -926,6 +882,7 @@ tensor_type_ tensor_slice_suffix_(tensor_type_ input, range r, rest_types_... re
|
|
|
926
882
|
result_shape.extents[d] = input.extent(d);
|
|
927
883
|
result_shape.strides[d] = input.stride_bytes(d);
|
|
928
884
|
}
|
|
885
|
+
result_shape.normalize_spare_();
|
|
929
886
|
using byte_ptr = decltype(input.byte_data());
|
|
930
887
|
return {const_cast<byte_ptr>(input.byte_data() + data_offset), result_shape};
|
|
931
888
|
}
|
|
@@ -942,6 +899,7 @@ tensor_type_ tensor_slice_suffix_(tensor_type_ input, range r, rest_types_... re
|
|
|
942
899
|
result_shape.extents[1 + d] = inner.extent(d);
|
|
943
900
|
result_shape.strides[1 + d] = inner.stride_bytes(d);
|
|
944
901
|
}
|
|
902
|
+
result_shape.normalize_spare_();
|
|
945
903
|
|
|
946
904
|
using byte_ptr = decltype(input.byte_data());
|
|
947
905
|
auto inner_byte_offset = inner.byte_data() - first_row.byte_data();
|
|
@@ -1555,13 +1513,19 @@ struct tensor {
|
|
|
1555
1513
|
/** @brief Transpose: reverse dimension order (mutable span). */
|
|
1556
1514
|
span_type transpose() noexcept { return span().transpose(); }
|
|
1557
1515
|
|
|
1558
|
-
/** @brief Reshape (immutable view).
|
|
1559
|
-
|
|
1560
|
-
|
|
1516
|
+
/** @brief Reshape (immutable view). The output rank may differ from the source; pass it as the template
|
|
1517
|
+
* argument when narrowing or widening. Requires contiguous layout and matching element count. */
|
|
1518
|
+
template <std::size_t out_rank_ = max_rank_>
|
|
1519
|
+
tensor_view<value_type_, out_rank_> reshape(std::initializer_list<size_type> new_extents) const noexcept {
|
|
1520
|
+
return view().template reshape<out_rank_>(new_extents);
|
|
1561
1521
|
}
|
|
1562
1522
|
|
|
1563
|
-
/** @brief Reshape (mutable span).
|
|
1564
|
-
|
|
1523
|
+
/** @brief Reshape (mutable span). The output rank may differ from the source; pass it as the template
|
|
1524
|
+
* argument when narrowing or widening. Requires contiguous layout and matching element count. */
|
|
1525
|
+
template <std::size_t out_rank_ = max_rank_>
|
|
1526
|
+
tensor_span<value_type_, out_rank_> reshape(std::initializer_list<size_type> new_extents) noexcept {
|
|
1527
|
+
return span().template reshape<out_rank_>(new_extents);
|
|
1528
|
+
}
|
|
1565
1529
|
|
|
1566
1530
|
/** @brief Check if contiguous in memory. Always true for freshly-constructed tensors. */
|
|
1567
1531
|
constexpr bool is_contiguous() const noexcept { return view().is_contiguous(); }
|
|
@@ -1685,6 +1649,15 @@ struct tensor {
|
|
|
1685
1649
|
|
|
1686
1650
|
/** @brief Squeeze (mutable span). Removes size-1 dimensions. */
|
|
1687
1651
|
span_type squeeze() noexcept { return span().squeeze(); }
|
|
1652
|
+
|
|
1653
|
+
/** @brief Zero-fill every element. */
|
|
1654
|
+
bool fill_zeros() noexcept { return span().fill_zeros(); }
|
|
1655
|
+
|
|
1656
|
+
/** @brief Fill every element with `value`. */
|
|
1657
|
+
bool fill(value_type value) noexcept { return span().fill(value); }
|
|
1658
|
+
|
|
1659
|
+
/** @brief Copy from a same-shape view. */
|
|
1660
|
+
bool copy_from(view_type input) noexcept { return span().copy_from(input); }
|
|
1688
1661
|
};
|
|
1689
1662
|
|
|
1690
1663
|
/** @brief Non-member swap. */
|
|
@@ -1856,6 +1829,7 @@ bool for_each_axis_lane_(tensor_view<value_type_, max_rank_> input, std::size_t
|
|
|
1856
1829
|
lane_shape.rank = 1;
|
|
1857
1830
|
lane_shape.extents[0] = input.extent(axis);
|
|
1858
1831
|
lane_shape.strides[0] = input.stride_bytes(axis);
|
|
1832
|
+
lane_shape.normalize_spare_();
|
|
1859
1833
|
|
|
1860
1834
|
std::size_t remaining_dims[max_rank_] = {};
|
|
1861
1835
|
std::size_t remaining_count = 0;
|
|
@@ -1949,6 +1923,7 @@ tensor_view<value_type_, max_rank_> collapse_contiguous_tail_(tensor_view<value_
|
|
|
1949
1923
|
for (std::size_t i = input.rank() - tail_dims; i < input.rank(); ++i) product *= input.extent(i);
|
|
1950
1924
|
s.extents[s.rank - 1] = product;
|
|
1951
1925
|
s.strides[s.rank - 1] = static_cast<std::ptrdiff_t>(sizeof(value_type_));
|
|
1926
|
+
s.normalize_spare_();
|
|
1952
1927
|
return {input.byte_data(), s};
|
|
1953
1928
|
}
|
|
1954
1929
|
|
|
@@ -1966,6 +1941,7 @@ tensor_span<value_type_, max_rank_> collapse_contiguous_tail_(tensor_span<value_
|
|
|
1966
1941
|
for (std::size_t i = input.rank() - tail_dims; i < input.rank(); ++i) product *= input.extent(i);
|
|
1967
1942
|
s.extents[s.rank - 1] = product;
|
|
1968
1943
|
s.strides[s.rank - 1] = static_cast<std::ptrdiff_t>(sizeof(value_type_));
|
|
1944
|
+
s.normalize_spare_();
|
|
1969
1945
|
return {input.byte_data(), s};
|
|
1970
1946
|
}
|
|
1971
1947
|
|
|
@@ -2058,8 +2034,145 @@ bool elementwise_into_(tensor_view<value_type_, max_rank_> a, tensor_view<value_
|
|
|
2058
2034
|
return true;
|
|
2059
2035
|
}
|
|
2060
2036
|
|
|
2037
|
+
/** @brief Output-only traversal: walks a span, collapses contiguous tail dims, and invokes
|
|
2038
|
+
* `leaf(byte_data, byte_count)` on each maximal contiguous byte run. Strided rank-1 leaves
|
|
2039
|
+
* invoke `leaf` once per element. Sub-byte dtypes require a fully contiguous span. */
|
|
2040
|
+
template <typename value_type_, std::size_t max_rank_, typename leaf_function_>
|
|
2041
|
+
bool span_for_each_contiguous_run_(tensor_span<value_type_, max_rank_> output, leaf_function_ &&leaf) noexcept {
|
|
2042
|
+
if (!tensor_layout_supported_(output)) return false;
|
|
2043
|
+
if (output.empty()) return true;
|
|
2044
|
+
if (output.rank() >= 2) {
|
|
2045
|
+
auto tail_dims = shared_contiguous_tail_dims_<value_type_, max_rank_>(output.rank(), output.shape().extents,
|
|
2046
|
+
{output.shape().strides});
|
|
2047
|
+
if (tail_dims >= 2)
|
|
2048
|
+
return span_for_each_contiguous_run_<value_type_, max_rank_>(collapse_contiguous_tail_(output, tail_dims),
|
|
2049
|
+
std::forward<leaf_function_>(leaf));
|
|
2050
|
+
for (std::size_t row_index = 0; row_index < output.extent(0); ++row_index) {
|
|
2051
|
+
auto signed_index = static_cast<std::ptrdiff_t>(row_index);
|
|
2052
|
+
if (!span_for_each_contiguous_run_<value_type_, max_rank_>(output.slice_leading(signed_index), leaf))
|
|
2053
|
+
return false;
|
|
2054
|
+
}
|
|
2055
|
+
return true;
|
|
2056
|
+
}
|
|
2057
|
+
if (output.byte_data() == nullptr) return false;
|
|
2058
|
+
auto element_count = output.extent(0);
|
|
2059
|
+
if (element_count == 0) return true;
|
|
2060
|
+
if constexpr (dimensions_per_value<value_type_>() > 1) {
|
|
2061
|
+
if (!output.is_contiguous()) return false;
|
|
2062
|
+
auto byte_count = dims_to_values_<value_type_>(element_count) * sizeof(value_type_);
|
|
2063
|
+
leaf(output.byte_data(), byte_count);
|
|
2064
|
+
return true;
|
|
2065
|
+
}
|
|
2066
|
+
else {
|
|
2067
|
+
auto element_stride_bytes = output.stride_bytes(0);
|
|
2068
|
+
if (element_stride_bytes == static_cast<std::ptrdiff_t>(sizeof(value_type_))) {
|
|
2069
|
+
leaf(output.byte_data(), element_count * sizeof(value_type_));
|
|
2070
|
+
return true;
|
|
2071
|
+
}
|
|
2072
|
+
auto *base_byte_data = output.byte_data();
|
|
2073
|
+
for (std::size_t element_index = 0; element_index < element_count; ++element_index)
|
|
2074
|
+
leaf(base_byte_data + static_cast<std::ptrdiff_t>(element_index) * element_stride_bytes,
|
|
2075
|
+
sizeof(value_type_));
|
|
2076
|
+
return true;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2061
2080
|
#pragma endregion Helpers
|
|
2062
2081
|
|
|
2082
|
+
#pragma region Tensor Fill and Copy
|
|
2083
|
+
|
|
2084
|
+
/** @brief Zero-fill every element of `output` via memset on each maximal contiguous run.
|
|
2085
|
+
* Works for every dtype since `is_memset_zero_safe_v` is true across all numeric_dtypes.
|
|
2086
|
+
* Returns false on unsupported layout. */
|
|
2087
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2088
|
+
bool fill_zeros(tensor_span<value_type_, max_rank_> output) noexcept {
|
|
2089
|
+
static_assert(is_memset_zero_safe_v<value_type_>,
|
|
2090
|
+
"fill_zeros requires a dtype whose binary-zero is the value-zero");
|
|
2091
|
+
return span_for_each_contiguous_run_<value_type_, max_rank_>(
|
|
2092
|
+
output, [](char *byte_data, std::size_t byte_count) noexcept {
|
|
2093
|
+
std::memset(static_cast<void *>(byte_data), 0, byte_count);
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/** @brief Fill every element of `output` with `value`. Two-step strategy: first memsets the
|
|
2098
|
+
* buffer to binary zero (avoiding NaN/inf propagation from uninitialised memory), then
|
|
2099
|
+
* overlays the value via memset (1-byte storage) or a typed scalar broadcast loop
|
|
2100
|
+
* (multi-byte storage). Returns false on unsupported layout. */
|
|
2101
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2102
|
+
bool fill(tensor_span<value_type_, max_rank_> output, value_type_ value) noexcept {
|
|
2103
|
+
if (!fill_zeros<value_type_, max_rank_>(output)) return false;
|
|
2104
|
+
// Skip the overlay when `value` is bitwise-equal to a default-constructed `value_type_`,
|
|
2105
|
+
// since the memset above has already produced exactly that pattern.
|
|
2106
|
+
value_type_ const default_value {};
|
|
2107
|
+
if (std::memcmp(&value, &default_value, sizeof(value_type_)) == 0) return true;
|
|
2108
|
+
if constexpr (sizeof(value_type_) == 1) {
|
|
2109
|
+
unsigned char byte_pattern;
|
|
2110
|
+
std::memcpy(&byte_pattern, &value, 1);
|
|
2111
|
+
return span_for_each_contiguous_run_<value_type_, max_rank_>(
|
|
2112
|
+
output, [byte_pattern](char *byte_data, std::size_t byte_count) noexcept {
|
|
2113
|
+
std::memset(static_cast<void *>(byte_data), byte_pattern, byte_count);
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
else {
|
|
2117
|
+
return span_for_each_contiguous_run_<value_type_, max_rank_>(
|
|
2118
|
+
output, [&value](char *byte_data, std::size_t byte_count) noexcept {
|
|
2119
|
+
auto *typed_data = reinterpret_cast<value_type_ *>(byte_data);
|
|
2120
|
+
auto element_count = byte_count / sizeof(value_type_);
|
|
2121
|
+
for (std::size_t element_index = 0; element_index < element_count; ++element_index)
|
|
2122
|
+
typed_data[element_index] = value;
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
/** @brief Copy `input` element-by-element into `output`. Recursive traversal collapses
|
|
2128
|
+
* contiguous tail dims into one memcpy per leaf; strided outer dims still recurse.
|
|
2129
|
+
* Returns false on shape mismatch or unsupported layout. */
|
|
2130
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2131
|
+
bool copy(tensor_view<value_type_, max_rank_> input, tensor_span<value_type_, max_rank_> output) noexcept {
|
|
2132
|
+
return elementwise_into_<value_type_, max_rank_>(
|
|
2133
|
+
input, output,
|
|
2134
|
+
[](tensor_view<value_type_, max_rank_> input_leaf, tensor_span<value_type_, max_rank_> output_leaf) {
|
|
2135
|
+
auto byte_count = dims_to_values_<value_type_>(input_leaf.extent(0)) * sizeof(value_type_);
|
|
2136
|
+
std::memcpy(static_cast<void *>(output_leaf.byte_data()), static_cast<void const *>(input_leaf.byte_data()),
|
|
2137
|
+
byte_count);
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
/** @brief Allocating copy: returns a fresh contiguous tensor with the same shape and contents
|
|
2142
|
+
* as `input`. Returns an empty tensor on allocation failure or empty input. */
|
|
2143
|
+
template <typename value_type_, std::size_t max_rank_ = 8, typename allocator_type_ = aligned_allocator<value_type_>>
|
|
2144
|
+
[[nodiscard]] tensor<value_type_, allocator_type_, max_rank_> try_copy(
|
|
2145
|
+
tensor_view<value_type_, max_rank_> input) noexcept {
|
|
2146
|
+
using out_tensor_t = tensor<value_type_, allocator_type_, max_rank_>;
|
|
2147
|
+
if (input.empty()) return out_tensor_t {};
|
|
2148
|
+
auto const &input_shape = input.shape();
|
|
2149
|
+
auto result = out_tensor_t::try_empty(input_shape.extents, input_shape.rank);
|
|
2150
|
+
if (result.empty()) return result;
|
|
2151
|
+
if (!copy<value_type_, max_rank_>(input, result.span())) return out_tensor_t {};
|
|
2152
|
+
return result;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
#pragma endregion Tensor Fill and Copy
|
|
2156
|
+
|
|
2157
|
+
#pragma region Tensor Span Member Sugar
|
|
2158
|
+
|
|
2159
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2160
|
+
bool tensor_span<value_type_, max_rank_>::fill_zeros() noexcept {
|
|
2161
|
+
return ashvardanian::numkong::fill_zeros<value_type_, max_rank_>(*this);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2165
|
+
bool tensor_span<value_type_, max_rank_>::fill(value_type value) noexcept {
|
|
2166
|
+
return ashvardanian::numkong::fill<value_type_, max_rank_>(*this, value);
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
template <typename value_type_, std::size_t max_rank_>
|
|
2170
|
+
bool tensor_span<value_type_, max_rank_>::copy_from(tensor_view<value_type_, max_rank_> input) noexcept {
|
|
2171
|
+
return ashvardanian::numkong::copy<value_type_, max_rank_>(input, *this);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
#pragma endregion Tensor Span Member Sugar
|
|
2175
|
+
|
|
2063
2176
|
} // namespace ashvardanian::numkong
|
|
2064
2177
|
|
|
2065
2178
|
#endif // NK_TENSOR_HPP
|
|
@@ -49,16 +49,16 @@ def atan(a: np.ndarray) -> np.ndarray:
|
|
|
49
49
|
|
|
50
50
|
### Cody-Waite Range Reduction
|
|
51
51
|
|
|
52
|
-
All trigonometric kernels reduce the input angle to $[-\pi/
|
|
52
|
+
All trigonometric kernels reduce the input angle to $[-\pi/2, \pi/2]$ before polynomial evaluation using Cody-Waite argument reduction.
|
|
53
53
|
The constant $\pi$ is split into high and low parts ($\pi_{\text{hi}} + \pi_{\text{lo}}$) to maintain precision during the subtraction $x - n\pi$: `reduced = (x - n * pi_hi) - n * pi_lo`.
|
|
54
54
|
Single-part subtraction would lose ~3 bits of precision for large multiples of $\pi$; the two-part split preserves the full mantissa.
|
|
55
|
-
The
|
|
55
|
+
The multiple $n = \text{round}(x / \pi)$ decides the sign of the result through its parity alone; cosine reuses the same sine polynomial by folding an extra $\pi/2$ into the reduction offset.
|
|
56
56
|
|
|
57
57
|
### Minimax Polynomial Approximation
|
|
58
58
|
|
|
59
59
|
`nk_each_sin_f32_serial`, `nk_each_cos_f32_serial` evaluate degree-9 minimax polynomials via Horner's method after range reduction.
|
|
60
|
-
The polynomial coefficients are precomputed to minimize maximum error over $[-\pi/
|
|
61
|
-
Horner evaluation: `p = c9*x^2 + c7; p = p*x^2 + c5; p = p*x^2 + c3;
|
|
60
|
+
The polynomial coefficients are precomputed to minimize maximum error over $[-\pi/2, \pi/2]$ — a minimax fit rather than a plain Taylor truncation.
|
|
61
|
+
Horner evaluation: `p = c9; p = p*x^2 + c7; p = p*x^2 + c5; p = p*x^2 + c3; result = p*x^3 + x` — 3 FMA operations for the polynomial plus one more against the cubed angle.
|
|
62
62
|
`nk_each_sin_f64_serial` uses degree-19 polynomials for 52-bit mantissa coverage.
|
|
63
63
|
|
|
64
64
|
### Vectorized Polynomial Evaluation
|