54template <
typename T, std::
size_t Alignment>
74 static_assert(Alignment >=
alignof(
void*),
"alignment must satisfy allocator requirements");
75 static_assert((Alignment & (Alignment - 1)) == 0,
"alignment must be a power of two");
76 if (n == 0)
return nullptr;
77 if (n > (std::numeric_limits<std::size_t>::max)() /
sizeof(T)) {
78 throw std::bad_alloc();
82 const std::size_t bytes = n *
sizeof(T);
84 ptr = _aligned_malloc(bytes, Alignment);
85 if (!ptr)
throw std::bad_alloc();
87 if (::posix_memalign(&ptr, Alignment, bytes) != 0) {
88 throw std::bad_alloc();
91 return static_cast<T*
>(ptr);
102 template <
typename U>
107 template <
typename U>
115 if (ptr ==
nullptr)
return false;
116 return (
reinterpret_cast<std::uintptr_t
>(ptr) %
alignof(T)) == 0;
121 return is_pointer_aligned<T>(ptr) ?
static_cast<T*
>(ptr) :
nullptr;
125[[nodiscard]]
inline const T*
aligned_ptr(
const void* ptr)
noexcept {
126 return is_pointer_aligned<T>(ptr) ?
static_cast<const T*
>(ptr) :
nullptr;
131 auto* ptr =
static_cast<std::uint8_t*
>(base) + offset;
132 return aligned_ptr<T>(ptr);
136[[nodiscard]]
inline const T*
aligned_ptr_at(
const void* base, std::size_t offset)
noexcept {
137 auto* ptr =
static_cast<const std::uint8_t*
>(base) + offset;
138 return aligned_ptr<T>(ptr);
224 if (
dims.empty())
return 1;
226 for (
auto d :
dims) {
227 if (d <= 0)
return -1;
228 if (product > INT64_MAX / d)
return -1;
235 [[nodiscard]]
size_t ndim() const noexcept {
return dims.size(); }
239 return dims.empty() || (
dims.size() == 1 &&
dims[0] == 1);
287 size_t byte_stride = 0) noexcept
289 , shape_(std::move(
shape))
291 , byte_stride_(byte_stride) {}
296 size_t byte_stride = 0) noexcept
297 : data_(const_cast<
void*>(
data))
298 , shape_(std::move(
shape))
300 , byte_stride_(byte_stride) {}
305 [[nodiscard]]
void*
data() noexcept {
return data_; }
307 [[nodiscard]]
const void*
data() const noexcept {
return data_; }
312 template <
typename T>
314 return detail::aligned_ptr<T>(data_);
319 template <
typename T>
321 return detail::aligned_ptr<T>(data_);
345 if (n <= 0)
return 0;
352 if (byte_stride_ != 0)
return byte_stride_;
356 for (
size_t i = 1; i < shape_.
ndim(); ++i) {
357 inner_size *=
static_cast<size_t>(shape_.
dims[i]);
369 template <
typename T>
370 [[nodiscard]] T&
at(int64_t i) {
372 throw std::out_of_range(
"TensorView::at(i): index out of range");
373 if (byte_stride_ != 0) {
374 auto* elem = detail::aligned_ptr_at<T>(data_,
static_cast<size_t>(i) * byte_stride_);
376 throw std::runtime_error(
"TensorView::at(i): misaligned tensor access");
379 auto* ptr = typed_data<T>();
381 throw std::runtime_error(
"TensorView::at(i): misaligned tensor access");
390 template <
typename T>
391 [[nodiscard]]
const T&
at(int64_t i)
const {
393 throw std::out_of_range(
"TensorView::at(i): index out of range");
394 if (byte_stride_ != 0) {
395 const auto* elem = detail::aligned_ptr_at<T>(data_,
static_cast<size_t>(i) * byte_stride_);
397 throw std::runtime_error(
"TensorView::at(i): misaligned tensor access");
400 const auto* ptr = typed_data<T>();
402 throw std::runtime_error(
"TensorView::at(i): misaligned tensor access");
412 template <
typename T>
413 [[nodiscard]] T&
at(int64_t row, int64_t col) {
414 if (data_ ==
nullptr || shape_.
ndim() != 2 ||
415 row < 0 || row >= shape_.
dims[0] ||
416 col < 0 || col >= shape_.
dims[1])
417 throw std::out_of_range(
"TensorView::at(row,col): index out of range");
418 const int64_t cols = shape_.
dims[1];
419 if (byte_stride_ != 0) {
420 auto* row_ptr = detail::aligned_ptr_at<T>(data_,
static_cast<size_t>(row) * byte_stride_);
421 if (row_ptr ==
nullptr)
422 throw std::runtime_error(
"TensorView::at(row,col): misaligned tensor access");
425 auto* ptr = typed_data<T>();
427 throw std::runtime_error(
"TensorView::at(row,col): misaligned tensor access");
428 return ptr[row * cols + col];
437 template <
typename T>
438 [[nodiscard]]
const T&
at(int64_t row, int64_t col)
const {
439 if (data_ ==
nullptr || shape_.
ndim() != 2 ||
440 row < 0 || row >= shape_.
dims[0] ||
441 col < 0 || col >= shape_.
dims[1])
442 throw std::out_of_range(
"TensorView::at(row,col): index out of range");
443 const int64_t cols = shape_.
dims[1];
444 if (byte_stride_ != 0) {
445 const auto* row_ptr = detail::aligned_ptr_at<T>(data_,
static_cast<size_t>(row) * byte_stride_);
446 if (row_ptr ==
nullptr)
447 throw std::runtime_error(
"TensorView::at(row,col): misaligned tensor access");
450 const auto* ptr = typed_data<T>();
452 throw std::runtime_error(
"TensorView::at(row,col): misaligned tensor access");
453 return ptr[row * cols + col];
460 return byte_stride_ == 0;
465 return data_ !=
nullptr;
479 if (data_ ==
nullptr || shape_.
ndim() < 1 ||
480 start < 0 || count < 0 || start + count > shape_.
dims[0])
485 auto* base =
static_cast<uint8_t*
>(
const_cast<void*
>(data_));
486 void* slice_data = base +
static_cast<size_t>(start) * stride;
491 new_shape.dims[0] = count;
493 return TensorView(slice_data, std::move(new_shape), dtype_, byte_stride_);
504 "cannot reshape a non-contiguous tensor view"};
508 "reshape: total elements mismatch ("
512 return TensorView(data_, std::move(new_shape), dtype_, 0);
516 void* data_ =
nullptr;
519 size_t byte_stride_ = 0;
538 : shape_(std::move(
shape))
546 const size_t sz =
static_cast<size_t>(
num_elements) * element_size;
547 buffer_.resize(sz, 0);
557 : shape_(std::move(
shape))
564 const size_t sz =
static_cast<size_t>(
num_elements) * element_size;
566 if (
data && sz > 0) {
567 std::memcpy(buffer_.data(),
data, sz);
582 copy.buffer_ = buffer_;
583 copy.shape_ = shape_;
584 copy.dtype_ = dtype_;
592 return TensorView(buffer_.data(), shape_, dtype_, 0);
598 const_cast<uint8_t*
>(buffer_.data()), shape_, dtype_, 0);
604 [[nodiscard]]
void*
data() noexcept {
return buffer_.data(); }
606 [[nodiscard]]
const void*
data() const noexcept {
return buffer_.data(); }
610 template <
typename T>
612 return detail::aligned_ptr<T>(buffer_.data());
617 template <
typename T>
619 return detail::aligned_ptr<T>(buffer_.data());
628 [[nodiscard]]
size_t byte_size() const noexcept {
return buffer_.size(); }
636 [[nodiscard]]
bool is_valid() const noexcept {
return !buffer_.empty(); }
639 using Buffer = std::vector<uint8_t,
682 const void* column_data,
685 int32_t type_length = -1) {
686 if (!column_data || num_values <= 0) {
688 "wrap_column: null data or non-positive count"};
691 switch (physical_type) {
713 if (type_length <= 0) {
715 "wrap_column: FIXED_LEN_BYTE_ARRAY requires "
716 "positive type_length"};
721 static_cast<int64_t
>(type_length)},
729 "wrap_column: BOOLEAN columns require copy "
730 "(bit-packed, not byte-addressable)"};
734 "wrap_column: BYTE_ARRAY (variable-length) "
735 "cannot be zero-copy wrapped as a tensor"};
739 "wrap_column: INT96 is deprecated and "
740 "not supported for tensor wrapping"};
744 "wrap_column: unknown physical type"};
761 const void* column_data,
763 uint32_t dimension) {
764 if (!column_data || num_vectors <= 0) {
766 "wrap_vectors: null data or non-positive count"};
768 if (dimension == 0) {
770 "wrap_vectors: dimension must be > 0"};
775 static_cast<int64_t
>(dimension)},
796 const void* column_data,
800 int32_t type_length = -1) {
801 if (!column_data || num_values <= 0) {
803 "copy_column: null data or non-positive count"};
809 "copy_column: BYTE_ARRAY (strings) cannot be "
810 "converted to a dense tensor"};
814 "copy_column: INT96 is deprecated and not supported"};
820 if (type_length <= 0) {
822 "copy_column: FIXED_LEN_BYTE_ARRAY requires "
823 "positive type_length"};
827 TensorShape{num_values *
static_cast<int64_t
>(type_length)},
832 static_cast<int64_t
>(type_length)},
834 std::memcpy(out.data(), column_data,
835 static_cast<size_t>(num_values) *
836 static_cast<size_t>(type_length));
841 if (type_length %
static_cast<int32_t
>(
sizeof(
float)) == 0) {
842 int64_t dim = type_length /
static_cast<int32_t
>(
sizeof(float));
846 return cast(float_src, target_dtype);
849 "copy_column: FIXED_LEN_BYTE_ARRAY with type_length "
850 "not a multiple of 4 can only be copied as UINT8"};
855 if (!src_dtype_result) {
856 return Error{src_dtype_result.error().
code,
857 src_dtype_result.error().message};
864 if (src_dtype == target_dtype) {
869 return cast(src, target_dtype);
890 "cast: source tensor is null"};
894 "cast: source tensor must be contiguous"};
898 if (src.
dtype() == target_dtype) {
906 bool ok = dispatch_cast(src.
data(), src.
dtype(),
907 out.
data(), target_dtype, n);
910 std::string(
"cast: unsupported conversion from ")
935 "BYTE_ARRAY has no fixed tensor type mapping"};
938 "INT96 has no tensor type mapping"};
941 "unknown PhysicalType"};
948 template <
typename T>
949 static inline T read_element(
const void* data, int64_t idx) {
950 return static_cast<const T*
>(data)[idx];
954 template <
typename T>
955 static inline void write_element(
void* data, int64_t idx, T val) {
956 static_cast<T*
>(data)[idx] = val;
960 template <
typename Src,
typename Dst>
961 static inline void convert_loop(
const void* src,
void* dst, int64_t n) {
962 const auto* s =
static_cast<const Src*
>(src);
963 auto* d =
static_cast<Dst*
>(dst);
964 for (int64_t i = 0; i < n; ++i) {
965 d[i] =
static_cast<Dst
>(s[i]);
970 template <
typename Src>
971 static inline bool dispatch_target(
const void* src,
void* dst,
982 default:
return false;
987 static inline bool dispatch_cast(
const void* src,
TensorDataType src_dtype,
998 default:
return false;
1040 columns_.push_back(ColumnEntry{name, tensor});
1051 const int64_t rows = column_rows(columns_[0].tensor);
1052 int64_t total_cols = 0;
1053 for (
const auto& entry : columns_) {
1054 total_cols += column_width(entry.tensor);
1061 return columns_.size();
1077 if (columns_.empty()) {
1079 "BatchTensorBuilder: no columns added"};
1083 const int64_t rows = column_rows(columns_[0].tensor);
1086 "BatchTensorBuilder: first column has no rows"};
1090 for (
size_t i = 1; i < columns_.size(); ++i) {
1091 const int64_t col_rows = column_rows(columns_[i].tensor);
1092 if (col_rows != rows) {
1094 "BatchTensorBuilder: column '"
1095 + columns_[i].name +
"' has "
1096 + std::to_string(col_rows) +
" rows, expected "
1097 + std::to_string(rows)};
1102 int64_t total_cols = 0;
1103 for (
const auto& entry : columns_) {
1104 total_cols += column_width(entry.tensor);
1113 int64_t col_offset = 0;
1114 for (
const auto& entry : columns_) {
1116 const int64_t width = column_width(src);
1122 copy_column_into(output, rows, total_cols, col_offset,
1123 width, src.
data(), out_elem_size);
1129 "BatchTensorBuilder: failed to cast column '"
1130 + entry.name +
"': "
1131 + cast_result.error().message};
1133 copy_column_into(output, rows, total_cols, col_offset,
1134 width, cast_result.value().
data(),
1138 col_offset += width;
1145 struct ColumnEntry {
1150 std::vector<ColumnEntry> columns_;
1153 static int64_t column_rows(
const TensorView& t)
noexcept {
1154 if (t.shape().ndim() == 0)
return 1;
1160 static int64_t column_width(
const TensorView& t)
noexcept {
1161 if (t.shape().ndim() <= 1)
return 1;
1162 return t.shape().dims[1];
1171 static void copy_column_into(
1172 OwnedTensor& output,
1177 const void* src_data,
1179 auto* dst_base =
static_cast<uint8_t*
>(output.data());
1180 const auto* src_base =
static_cast<const uint8_t*
>(src_data);
1182 const size_t row_byte_stride =
static_cast<size_t>(total_cols) * elem_size;
1183 const size_t src_row_bytes =
static_cast<size_t>(width) * elem_size;
1184 const size_t col_byte_offset =
static_cast<size_t>(col_offset) * elem_size;
1186 for (int64_t r = 0; r < rows; ++r) {
1187 const size_t dst_offset =
static_cast<size_t>(r) * row_byte_stride
1189 const size_t src_offset =
static_cast<size_t>(r) * src_row_bytes;
1190 std::memcpy(dst_base + dst_offset,
1191 src_base + src_offset,
Builds a single contiguous 2D tensor from multiple column tensors, suitable for passing to an ML infe...
size_t num_features() const noexcept
Number of feature sources (columns) added.
BatchTensorBuilder()=default
Default constructor: creates an empty builder with no columns.
TensorShape expected_shape() const
Compute the expected output shape based on currently added columns.
BatchTensorBuilder & add_column(const std::string &name, const TensorView &tensor)
Add a column tensor as a feature source.
expected< OwnedTensor > build(TensorDataType output_dtype=TensorDataType::FLOAT32)
Build the final batch tensor.
Provides static methods to convert Parquet column data into tensor form.
static expected< OwnedTensor > cast(const TensorView &src, TensorDataType target_dtype)
Cast a tensor view to a different element type, producing an OwnedTensor.
static expected< TensorDataType > parquet_to_tensor_dtype(PhysicalType pt)
Map a Parquet physical type to the natural TensorDataType.
static expected< TensorView > wrap_column(const void *column_data, int64_t num_values, PhysicalType physical_type, int32_t type_length=-1)
Wrap a contiguous numeric Parquet column as a 1D TensorView.
static expected< OwnedTensor > copy_column(const void *column_data, int64_t num_values, PhysicalType physical_type, TensorDataType target_dtype, int32_t type_length=-1)
Read column data and produce an OwnedTensor with the requested type.
static expected< TensorView > wrap_vectors(const void *column_data, int64_t num_vectors, uint32_t dimension)
Wrap a contiguous FLOAT32_VECTOR column as a 2D TensorView.
An owning tensor that manages its own memory via a std::vector<uint8_t> buffer.
OwnedTensor(TensorShape shape, TensorDataType dtype)
Allocate an uninitialized tensor with the given shape and type.
OwnedTensor clone() const
Deep-copy this tensor.
const T * typed_data() const noexcept
Typed const pointer to the tensor buffer.
TensorView view()
Get a mutable non-owning view.
OwnedTensor(OwnedTensor &&) noexcept=default
TensorDataType dtype() const noexcept
The element data type.
OwnedTensor(const void *data, TensorShape shape, TensorDataType dtype)
Allocate and copy data into the tensor.
void * data() noexcept
Raw mutable pointer to the tensor buffer.
size_t byte_size() const noexcept
Total byte size of the tensor buffer.
const void * data() const noexcept
Raw const pointer to the tensor buffer.
bool is_valid() const noexcept
True if the tensor has been allocated (non-empty buffer).
const TensorShape & shape() const noexcept
The shape of this tensor.
T * typed_data() noexcept
Typed mutable pointer to the tensor buffer.
OwnedTensor()=default
Default constructor: creates an invalid (empty) tensor.
int64_t num_elements() const noexcept
Total number of elements.
TensorView view() const
Get a const non-owning view.
A lightweight, non-owning view into a contiguous block of typed memory, interpreted as a multi-dimens...
T & at(int64_t i)
Access a single element in a 1D tensor (mutable).
size_t effective_byte_stride() const noexcept
Effective stride in bytes along the first dimension.
const T & at(int64_t row, int64_t col) const
Access a single element in a 2D tensor by (row, col) (const).
bool is_valid() const noexcept
True if the view points to valid data.
const T * typed_data() const noexcept
Reinterpret the data pointer as a typed const pointer.
bool is_contiguous() const noexcept
True if the data is densely packed (no stride gaps).
int64_t num_elements() const noexcept
Total number of elements.
TensorView(const void *data, TensorShape shape, TensorDataType dtype, size_t byte_stride=0) noexcept
Construct a const view (stores as void* internally, constness enforced by the const overloads of data...
TensorView(void *data, TensorShape shape, TensorDataType dtype, size_t byte_stride=0) noexcept
Construct a view over existing memory.
TensorView()=default
Default constructor: creates an invalid (null) view.
TensorView slice(int64_t start, int64_t count) const
Slice along the first dimension: returns a view over rows [start, start+count).
size_t byte_size() const noexcept
Total byte size of the tensor data (num_elements * element_size).
const T & at(int64_t i) const
Access a single element in a 1D tensor (const).
const TensorShape & shape() const noexcept
The shape of this tensor view.
T * typed_data() noexcept
Reinterpret the data pointer as a typed mutable pointer.
TensorDataType dtype() const noexcept
The element data type.
size_t element_size() const noexcept
Bytes per element.
const void * data() const noexcept
Raw const pointer to the underlying data buffer.
T & at(int64_t row, int64_t col)
Access a single element in a 2D tensor by (row, col) (mutable).
expected< TensorView > reshape(TensorShape new_shape) const
Reshape the view to a new shape with the same total number of elements.
void * data() noexcept
Raw mutable pointer to the underlying data buffer.
std::true_type is_always_equal
T * allocate(std::size_t n)
bool operator==(const AlignedAllocator< U, Alignment > &) const noexcept
void deallocate(T *ptr, std::size_t) noexcept
AlignedAllocator() noexcept=default
bool operator!=(const AlignedAllocator< U, Alignment > &) const noexcept
std::ptrdiff_t difference_type
std::true_type propagate_on_container_move_assignment
A lightweight result type that holds either a success value of type T or an Error.
T * aligned_ptr(void *ptr) noexcept
bool is_pointer_aligned(const void *ptr) noexcept
T * aligned_ptr_at(void *base, std::size_t offset) noexcept
PhysicalType
Parquet physical (storage) types as defined in parquet.thrift.
@ INT96
96-bit value (deprecated — legacy Impala timestamps).
@ FIXED_LEN_BYTE_ARRAY
Fixed-length byte array (UUID, vectors, decimals).
@ INT64
64-bit signed integer (little-endian).
@ INT32
32-bit signed integer (little-endian).
@ BOOLEAN
1-bit boolean, bit-packed in pages.
@ BYTE_ARRAY
Variable-length byte sequence (strings, binary).
@ FLOAT
IEEE 754 single-precision float.
@ DOUBLE
IEEE 754 double-precision float.
const char * tensor_dtype_name(TensorDataType dtype) noexcept
Returns a human-readable name for a TensorDataType.
@ UNSUPPORTED_TYPE
The file contains a Parquet physical or logical type that is not implemented.
@ SCHEMA_MISMATCH
The requested column name or type does not match the file schema.
@ INTERNAL_ERROR
An unexpected internal error that does not fit any other category.
TensorDataType
Element data type for tensor storage, mapping to ONNX/PyTorch/TF type enums.
@ FLOAT64
IEEE 754 double-precision (8 bytes)
@ INT64
Signed 64-bit integer.
@ INT16
Signed 16-bit integer.
@ INT32
Signed 32-bit integer.
@ FLOAT32
IEEE 754 single-precision (4 bytes)
@ FLOAT16
IEEE 754 half-precision (2 bytes)
@ UINT8
Unsigned 8-bit integer.
@ INT8
Signed 8-bit integer.
constexpr size_t tensor_element_size(TensorDataType dtype) noexcept
Returns the byte size of a single element of the given tensor data type.
Lightweight error value carrying an ErrorCode and a human-readable message.
ErrorCode code
The machine-readable error category.
Describes the shape of a tensor as a vector of dimension sizes.
bool operator==(const TensorShape &other) const
Equality comparison (element-wise dimension match).
TensorShape()=default
Default constructor: scalar shape (empty dims).
int64_t num_elements() const noexcept
Total number of elements (product of all dimensions).
size_t ndim() const noexcept
Number of dimensions.
bool is_vector() const noexcept
True if this is a 1D vector.
bool is_scalar() const noexcept
True if this is a scalar (no dimensions, or a single dimension of 1).
bool operator!=(const TensorShape &other) const
Inequality comparison.
bool is_matrix() const noexcept
True if this is a 2D matrix.
TensorShape(std::vector< int64_t > d)
Construct from a vector of dimensions.
std::vector< int64_t > dims
Dimension sizes (e.g. {32, 768} for a 32x768 matrix)
TensorShape(std::initializer_list< int64_t > il)
Construct from an initializer list (e.g.
Parquet format enumerations, type traits, and statistics structs.