diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe3ab210..0b1dd2dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ variables: ##### LC GITLAB CONFIGURATION # Use an LLNL service user to run CI. This prevents from running pipelines as # an actual user. - LLNL_SERVICE_USER: "koparasy" + LLNL_SERVICE_USER: "pottier1" # Use the service user workspace. Solves permission issues, stores everything # at the same location whoever triggers a pipeline. CUSTOM_CI_BUILDS_DIR: "/usr/workspace/AMS/gitlab-runner" diff --git a/.gitlab/subscribed-pipelines.yml b/.gitlab/subscribed-pipelines.yml index 569048d1..6a1728a3 100644 --- a/.gitlab/subscribed-pipelines.yml +++ b/.gitlab/subscribed-pipelines.yml @@ -30,19 +30,19 @@ ### # Dane -dane-up-check: - variables: - CI_MACHINE: "dane" - extends: [.machine-check] +# dane-up-check: +# variables: +# CI_MACHINE: "dane" +# extends: [.machine-check] -dane-build-and-test: - variables: - CI_MACHINE: "dane" - JOB_CMD: - value: "scripts/gitlab/ci-build-test.sh" - expand: false - needs: [dane-up-check] - extends: [.build-and-test] +# dane-build-and-test: +# variables: +# CI_MACHINE: "dane" +# JOB_CMD: +# value: "scripts/gitlab/ci-build-test.sh" +# expand: false +# needs: [dane-up-check] +# extends: [.build-and-test] # TIOGA tioga-up-check: diff --git a/examples/ideal_gas/app/eos_ams.cpp b/examples/ideal_gas/app/eos_ams.cpp index 16efce3a..6857e725 100644 --- a/examples/ideal_gas/app/eos_ams.cpp +++ b/examples/ideal_gas/app/eos_ams.cpp @@ -45,7 +45,7 @@ void AMSEOS::Eval(const int length, inputs.push_back( std::move(AMSTensor::view(density, {length, 1}, {1, 1}, res_))); inputs.push_back( - std::move(AMSTensor::view(density, {length, 1}, {1, 1}, res_))); + std::move(AMSTensor::view(energy, {length, 1}, {1, 1}, res_))); SmallVector inout; SmallVector outputs; diff --git a/src/AMSlib/AMSGraph.cpp b/src/AMSlib/AMSGraph.cpp new file mode 100644 index 00000000..8b8b3361 --- /dev/null +++ b/src/AMSlib/AMSGraph.cpp @@ -0,0 +1,180 @@ +#include "AMSGraph.hpp" + +#include + +namespace ams +{ +namespace +{ + +static std::size_t hashCombine(std::size_t seed, std::size_t value) noexcept +{ + seed ^= value + 0x9e3779b9u + (seed << 6) + (seed >> 2); + return seed; +} + +} // namespace + +EdgeType::EdgeType(std::string src_, std::string rel_, std::string dst_) + : src(std::move(src_)), rel(std::move(rel_)), dst(std::move(dst_)) +{ +} + +bool EdgeType::operator==(const EdgeType& other) const noexcept +{ + return src == other.src && rel == other.rel && dst == other.dst; +} + +std::size_t EdgeTypeHash::operator()(const EdgeType& e) const noexcept +{ + std::size_t seed = std::hash{}(e.src); + seed = hashCombine(seed, std::hash{}(e.rel)); + seed = hashCombine(seed, std::hash{}(e.dst)); + return seed; +} + +std::string edgeTypeToString(const EdgeType& e) +{ + return e.src + "__" + e.rel + "__" + e.dst; +} + +ams::EdgeType edgeTypeFromString(const std::string& key) +{ + const std::string delim = "__"; + + auto p1 = key.find(delim); + if (p1 == std::string::npos) { + throw std::runtime_error("edgeTypeFromString: missing first delimiter"); + } + + auto p2 = key.find(delim, p1 + delim.size()); + if (p2 == std::string::npos) { + throw std::runtime_error("edgeTypeFromString: missing second delimiter"); + } + + if (key.find(delim, p2 + delim.size()) != std::string::npos) { + throw std::runtime_error("edgeTypeFromString: too many delimiters"); + } + + std::string src = key.substr(0, p1); + std::string rel = key.substr(p1 + delim.size(), p2 - (p1 + delim.size())); + std::string dst = key.substr(p2 + delim.size()); + + if (src.empty() || rel.empty() || dst.empty()) { + throw std::runtime_error("edgeTypeFromString: empty src/rel/dst component"); + } + + return ams::EdgeType{std::move(src), std::move(rel), std::move(dst)}; +} + +bool containsTensor(const AMSTensorMap& store, const std::string& name) +{ + return store.find(name) != store.end(); +} + +AMSTensor* findTensor(AMSTensorMap& store, const std::string& name) noexcept +{ + auto it = store.find(name); + if (it == store.end()) { + return nullptr; + } + return &it->second; +} + +const AMSTensor* findTensor(const AMSTensorMap& store, + const std::string& name) noexcept +{ + auto it = store.find(name); + if (it == store.end()) { + return nullptr; + } + return &it->second; +} + +void insertTensor(AMSTensorMap& store, std::string name, AMSTensor&& tensor) +{ + auto [it, inserted] = store.emplace(std::move(name), std::move(tensor)); + if (!inserted) { + throw std::runtime_error("insertTensor: tensor name already exists"); + } +} + +void insertOrAssignTensor(AMSTensorMap& store, + std::string name, + AMSTensor&& tensor) +{ + auto it = store.find(name); + if (it == store.end()) { + store.emplace(std::move(name), std::move(tensor)); + } else { + it->second = std::move(tensor); + } +} + +bool AMSHeterogeneousGraph::containsNodeStore(const std::string& name) const +{ + return node_stores.find(name) != node_stores.end(); +} + +bool AMSHeterogeneousGraph::containsEdgeStore(const EdgeType& edge_type) const +{ + return edge_stores.find(edge_type) != edge_stores.end(); +} + +AMSTensorMap& AMSHeterogeneousGraph::getOrCreateNodeStore(std::string name) +{ + auto [it, inserted] = + node_stores.try_emplace(std::move(name), AMSTensorMap{}); + (void)inserted; + return it->second; +} + +AMSTensorMap& AMSHeterogeneousGraph::getOrCreateEdgeStore(EdgeType edge_type) +{ + auto [it, inserted] = + edge_stores.try_emplace(std::move(edge_type), AMSTensorMap{}); + (void)inserted; + return it->second; +} + +AMSTensorMap* AMSHeterogeneousGraph::findNodeStore( + const std::string& name) noexcept +{ + auto it = node_stores.find(name); + if (it == node_stores.end()) { + return nullptr; + } + return &it->second; +} + +const AMSTensorMap* AMSHeterogeneousGraph::findNodeStore( + const std::string& name) const noexcept +{ + auto it = node_stores.find(name); + if (it == node_stores.end()) { + return nullptr; + } + return &it->second; +} + +AMSTensorMap* AMSHeterogeneousGraph::findEdgeStore( + const EdgeType& edge_type) noexcept +{ + auto it = edge_stores.find(edge_type); + if (it == edge_stores.end()) { + return nullptr; + } + return &it->second; +} + +const AMSTensorMap* AMSHeterogeneousGraph::findEdgeStore( + const EdgeType& edge_type) const noexcept +{ + auto it = edge_stores.find(edge_type); + if (it == edge_stores.end()) { + return nullptr; + } + return &it->second; +} + +} // namespace ams diff --git a/src/AMSlib/AMSTensor.cpp b/src/AMSlib/AMSTensor.cpp index 83566dbd..f5b6dc3d 100644 --- a/src/AMSlib/AMSTensor.cpp +++ b/src/AMSlib/AMSTensor.cpp @@ -33,6 +33,26 @@ bool AMSTensor::isContiguous(AMSTensor::IntDimType expected_stride) const return true; } +namespace +{ +template +constexpr AMSDType scalar_to_ams_dtype() +{ + using U = std::remove_cv_t; + if constexpr (std::is_same_v) { + return AMS_SINGLE; + } else if constexpr (std::is_same_v) { + return AMS_DOUBLE; + } else if constexpr (std::is_same_v) { + return AMS_INT32; + } else if constexpr (std::is_same_v) { + return AMS_INT64; + } else { + static_assert(!sizeof(T), "Unsupported AMS scalar type"); + } +} +} // namespace + AMSTensor::AMSTensor(uint8_t* data, ams::ArrayRef shapes, ams::ArrayRef strides, @@ -47,67 +67,43 @@ AMSTensor::AMSTensor(uint8_t* data, _location(location), _owned(!view) { - _bytes = _elements * _element_size; _elements = computeNumElements(shapes); + _bytes = _elements * _element_size; if (!_data) { throw std::runtime_error("Generating tensor with Null Pointer AMSTensor."); } } -template +template AMSTensor AMSTensor::create(ams::ArrayRef shapes, ams::ArrayRef strides, AMSResourceType location) { auto numElements = computeNumElements(shapes); auto& rm = ams::ResourceManager::getInstance(); - if constexpr ((std::is_same_v) || - (std::is_same_v)) { - float* _data = rm.allocate(numElements, location, sizeof(float)); - return AMSTensor(reinterpret_cast(_data), - shapes, - strides, - AMS_SINGLE, - location); - } else if constexpr ((std::is_same_v) || - (std::is_same_v)) { - double* _data = rm.allocate(numElements, location, sizeof(double)); - return AMSTensor(reinterpret_cast(_data), - shapes, - strides, - AMS_DOUBLE, - location); - } else { - // This should never happen due to the type restriction - static_assert(std::is_same_v || - std::is_same_v, - "AMSTensor only supports float or double tensor creation"); - } + using U = std::remove_cv_t; + U* data = rm.allocate(numElements, location, sizeof(U)); + return AMSTensor(reinterpret_cast(data), + shapes, + strides, + scalar_to_ams_dtype(), + location); } -template -AMSTensor AMSTensor::view(FPType* data, +template +AMSTensor AMSTensor::view(ScalarType* data, ams::ArrayRef shapes, ams::ArrayRef strides, AMSResourceType location) { - if constexpr ((std::is_same_v) || - (std::is_same_v)) { - return AMSTensor( - (uint8_t*)data, shapes, strides, AMS_SINGLE, location, true); - } else if constexpr ((std::is_same_v) || - (std::is_same_v)) { - return AMSTensor( - (uint8_t*)data, shapes, strides, AMS_DOUBLE, location, true); - } else { - static_assert(std::is_same_v || - std::is_same_v || - std::is_same_v || - std::is_same_v, - "AMSTensor only supports float or double tensor view"); - } - throw std::runtime_error("Should never get here\n"); + using U = std::remove_cv_t; + return AMSTensor(reinterpret_cast(const_cast(data)), + shapes, + strides, + scalar_to_ams_dtype(), + location, + true); } AMSTensor AMSTensor::view(AMSTensor& tensor) @@ -122,6 +118,16 @@ AMSTensor AMSTensor::view(AMSTensor& tensor) tensor._shape, tensor._strides, tensor._location); + else if (tensor._dType == AMS_INT32) + return AMSTensor::view((int32_t*)tensor._data, + tensor._shape, + tensor._strides, + tensor._location); + else if (tensor._dType == AMS_INT64) + return AMSTensor::view((int64_t*)tensor._data, + tensor._shape, + tensor._strides, + tensor._location); throw std::runtime_error( "Creating view through copying constructor has incorrect dtype"); } @@ -193,6 +199,10 @@ AMSTensor AMSTensor::transpose(AMSTensor::IntDimType axis1, return view((double*)_data, newShape, newStrides, _location); else if (dType() == AMSDType::AMS_SINGLE) return view((float*)_data, newShape, newStrides, _location); + else if (dType() == AMSDType::AMS_INT32) + return view((int32_t*)_data, newShape, newStrides, _location); + else if (dType() == AMSDType::AMS_INT64) + return view((int64_t*)_data, newShape, newStrides, _location); // NOTE: Use defensive programming here and just crash. We can fix a better interface later // for error handling. throw std::runtime_error("Unknow data type in transpose\n"); @@ -204,6 +214,12 @@ template AMSTensor AMSTensor::create(ams::ArrayRef, template AMSTensor AMSTensor::create(ams::ArrayRef, ams::ArrayRef, AMSResourceType); +template AMSTensor AMSTensor::create(ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); +template AMSTensor AMSTensor::create(ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); template AMSTensor AMSTensor::view(float*, ams::ArrayRef, @@ -213,6 +229,14 @@ template AMSTensor AMSTensor::view(double*, ams::ArrayRef, ams::ArrayRef, AMSResourceType); +template AMSTensor AMSTensor::view(int32_t*, + ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); +template AMSTensor AMSTensor::view(int64_t*, + ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); template AMSTensor AMSTensor::view(const float*, ams::ArrayRef, @@ -222,3 +246,11 @@ template AMSTensor AMSTensor::view(const double*, ams::ArrayRef, ams::ArrayRef, AMSResourceType); +template AMSTensor AMSTensor::view(const int32_t*, + ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); +template AMSTensor AMSTensor::view(const int64_t*, + ams::ArrayRef, + ams::ArrayRef, + AMSResourceType); diff --git a/src/AMSlib/CMakeLists.txt b/src/AMSlib/CMakeLists.txt index 8683e6f4..c04e9fd7 100644 --- a/src/AMSlib/CMakeLists.txt +++ b/src/AMSlib/CMakeLists.txt @@ -4,7 +4,7 @@ # ------------------------------------------------------------------------------ # handle sources and headers -set(AMS_LIB_SRC wf/debug.cpp wf/logger.cpp wf/utils.cpp wf/SmallVector.cpp ml/surrogate.cpp wf/basedb.cpp AMSTensor.cpp wf/interface.cpp wf/resource_manager.cpp ml/Model.cpp ml/AbstractModel.cpp AMS.cpp) +set(AMS_LIB_SRC wf/debug.cpp wf/logger.cpp wf/utils.cpp wf/SmallVector.cpp ml/surrogate.cpp wf/basedb.cpp AMSTensor.cpp AMSGraph.cpp wf/interface.cpp wf/resource_manager.cpp ml/Model.cpp ml/AbstractModel.cpp AMS.cpp) list(APPEND AMS_LIB_SRC wf/hdf5db.cpp) @@ -75,6 +75,7 @@ configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/include/AMSTypes.hpp" "${PROJECT_BI configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/include/SmallVector.hpp" "${PROJECT_BINARY_DIR}/include/SmallVector.hpp" COPYONLY) configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/include/ArrayRef.hpp" "${PROJECT_BINARY_DIR}/include/ArrayRef.hpp" COPYONLY) configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/include/AMSTensor.hpp" "${PROJECT_BINARY_DIR}/include/AMSTensor.hpp" COPYONLY) +configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/include/AMSGraph.hpp" "${PROJECT_BINARY_DIR}/include/AMSGraph.hpp" COPYONLY) # setup the exec #SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath -Wl,$ORIGIN") @@ -102,6 +103,7 @@ install(EXPORT AMSTargets # Install the public headers install(FILES ${PROJECT_BINARY_DIR}/include/AMS.h + ${PROJECT_BINARY_DIR}/include/AMSGraph.hpp ${PROJECT_BINARY_DIR}/include/AMSTensor.hpp ${PROJECT_BINARY_DIR}/include/AMSTypes.hpp ${PROJECT_BINARY_DIR}/include/ArrayRef.hpp diff --git a/src/AMSlib/include/AMS.h b/src/AMSlib/include/AMS.h index 530a72d6..3acc0fc3 100644 --- a/src/AMSlib/include/AMS.h +++ b/src/AMSlib/include/AMS.h @@ -9,6 +9,7 @@ #include #include +#include "AMSGraph.hpp" #include "AMSTensor.hpp" #include "AMSTypes.hpp" diff --git a/src/AMSlib/include/AMSGraph.hpp b/src/AMSlib/include/AMSGraph.hpp new file mode 100644 index 00000000..1b3e9f74 --- /dev/null +++ b/src/AMSlib/include/AMSGraph.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +#include "AMSTensor.hpp" + +namespace ams +{ + +using AMSTensorMap = std::unordered_map; +using AMSHomogeneousGraph = AMSTensorMap; + +struct EdgeType { + std::string src; + std::string rel; + std::string dst; + + EdgeType() = default; + EdgeType(std::string src_, std::string rel_, std::string dst_); + + bool operator==(const EdgeType& other) const noexcept; + bool operator!=(const EdgeType& other) const noexcept + { + return !(*this == other); + } +}; + +struct EdgeTypeHash { + std::size_t operator()(const EdgeType& e) const noexcept; +}; + +std::string edgeTypeToString(const EdgeType& e); +ams::EdgeType edgeTypeFromString(const std::string& key); + +// Generic helpers for any named tensor store. +// These are useful both for AMSHomogeneousGraph and for the stores inside +// AMSHeterogeneousGraph. +bool containsTensor(const AMSTensorMap& store, const std::string& name); + +AMSTensor* findTensor(AMSTensorMap& store, const std::string& name) noexcept; +const AMSTensor* findTensor(const AMSTensorMap& store, + const std::string& name) noexcept; + +// Insert a new tensor. Throws if the name already exists. +void insertTensor(AMSTensorMap& store, std::string name, AMSTensor&& tensor); + +// Insert or replace an existing tensor. +void insertOrAssignTensor(AMSTensorMap& store, + std::string name, + AMSTensor&& tensor); + +struct AMSHeterogeneousGraph { + using NodeStoreMap = std::unordered_map; + using EdgeStoreMap = std::unordered_map; + + NodeStoreMap node_stores; + EdgeStoreMap edge_stores; + AMSTensorMap global_store; + + AMSHeterogeneousGraph() = default; + AMSHeterogeneousGraph(const AMSHeterogeneousGraph&) = delete; + AMSHeterogeneousGraph& operator=(const AMSHeterogeneousGraph&) = delete; + AMSHeterogeneousGraph(AMSHeterogeneousGraph&&) noexcept = default; + AMSHeterogeneousGraph& operator=(AMSHeterogeneousGraph&&) noexcept = default; + ~AMSHeterogeneousGraph() = default; + + bool containsNodeStore(const std::string& name) const; + bool containsEdgeStore(const EdgeType& edge_type) const; + + AMSTensorMap& getOrCreateNodeStore(std::string name); + AMSTensorMap& getOrCreateEdgeStore(EdgeType edge_type); + + AMSTensorMap* findNodeStore(const std::string& name) noexcept; + const AMSTensorMap* findNodeStore(const std::string& name) const noexcept; + + AMSTensorMap* findEdgeStore(const EdgeType& edge_type) noexcept; + const AMSTensorMap* findEdgeStore(const EdgeType& edge_type) const noexcept; +}; + +} // namespace ams diff --git a/src/AMSlib/include/AMSTensor.hpp b/src/AMSlib/include/AMSTensor.hpp index 53483838..a8c483f7 100644 --- a/src/AMSlib/include/AMSTensor.hpp +++ b/src/AMSlib/include/AMSTensor.hpp @@ -66,8 +66,7 @@ class AMSTensor * @param[in] location The memory location (e.g., CPU, GPU). * @return A new AMSTensor with allocated memory. */ - template ::value>> + template static AMSTensor create(ams::ArrayRef shapes, ams::ArrayRef strides, AMSResourceType location); @@ -81,9 +80,8 @@ class AMSTensor * @param[in] location The memory location (e.g., CPU, GPU). * @return A new AMSTensor that acts as a view of the existing data. */ - template ::value>> - static AMSTensor view(FPType* data, + template + static AMSTensor view(ScalarType* data, ams::ArrayRef shapes, ams::ArrayRef strides, AMSResourceType location); @@ -150,4 +148,12 @@ extern template AMSTensor AMSTensor::create( ams::ArrayRef shapes, ams::ArrayRef strides, AMSResourceType location); +extern template AMSTensor AMSTensor::create( + ams::ArrayRef shapes, + ams::ArrayRef strides, + AMSResourceType location); +extern template AMSTensor AMSTensor::create( + ams::ArrayRef shapes, + ams::ArrayRef strides, + AMSResourceType location); } // namespace ams diff --git a/src/AMSlib/include/AMSTypes.hpp b/src/AMSlib/include/AMSTypes.hpp index 19e13904..de9f5249 100644 --- a/src/AMSlib/include/AMSTypes.hpp +++ b/src/AMSlib/include/AMSTypes.hpp @@ -2,7 +2,13 @@ namespace ams { -typedef enum { AMS_SINGLE = 0, AMS_DOUBLE, AMS_UNKNOWN_TYPE } AMSDType; +typedef enum { + AMS_SINGLE = 0, + AMS_DOUBLE, + AMS_INT32, + AMS_INT64, + AMS_UNKNOWN_TYPE +} AMSDType; typedef enum { AMS_UNKNOWN = -1, diff --git a/src/AMSlib/wf/interface.cpp b/src/AMSlib/wf/interface.cpp index 8fbda4ac..03e862de 100644 --- a/src/AMSlib/wf/interface.cpp +++ b/src/AMSlib/wf/interface.cpp @@ -11,6 +11,7 @@ using namespace ams; +// dtype/device helpers static AMSResourceType torchDeviceToAMSDevice(c10::DeviceType dType) { switch (dType) { @@ -33,8 +34,8 @@ static AMSDType torchDTypeToAMSType(torch::Dtype dtype) {torch::kFloat, AMSDType::AMS_SINGLE}, // Alias for float32 {torch::kFloat64, AMSDType::AMS_DOUBLE}, {torch::kDouble, AMSDType::AMS_DOUBLE}, // Alias for float64 - {torch::kInt32, AMSDType::AMS_UNKNOWN_TYPE}, - {torch::kInt64, AMSDType::AMS_UNKNOWN_TYPE}, + {torch::kInt32, AMSDType::AMS_INT32}, + {torch::kInt64, AMSDType::AMS_INT64}, {torch::kBool, AMSDType::AMS_UNKNOWN_TYPE}, {torch::kUInt8, AMSDType::AMS_UNKNOWN_TYPE}, {torch::kInt8, AMSDType::AMS_UNKNOWN_TYPE}, @@ -66,32 +67,72 @@ static c10::ScalarType amsToTorchDType(const ams::AMSDType dType) return torch::kFloat32; else if (dType == ams::AMSDType::AMS_DOUBLE) return torch::kFloat64; + else if (dType == ams::AMSDType::AMS_INT32) + return torch::kInt32; + else if (dType == ams::AMSDType::AMS_INT64) + return torch::kInt64; throw std::runtime_error("Unknown ams data type"); return torch::kHalf; } +// single tensor +static ams::AMSTensor torchToAMSTensorView(torch::Tensor& tensor) +{ + auto dType = torchDTypeToAMSType(tensor.scalar_type()); + auto rType = torchDeviceToAMSDevice(tensor.device().type()); + + auto shapes = ams::ArrayRef(tensor.sizes().begin(), tensor.sizes().size()); + auto strides = + ams::ArrayRef(tensor.strides().begin(), tensor.strides().size()); + + switch (dType) { + case AMSDType::AMS_SINGLE: + return AMSTensor::view(tensor.data_ptr(), shapes, strides, rType); + + case AMSDType::AMS_DOUBLE: + return AMSTensor::view(tensor.data_ptr(), shapes, strides, rType); + + case AMSDType::AMS_INT32: + return AMSTensor::view(tensor.data_ptr(), + shapes, + strides, + rType); + + case AMSDType::AMS_INT64: + return AMSTensor::view(tensor.data_ptr(), + shapes, + strides, + rType); + + default: + throw std::runtime_error("torchToAMSTensorView: unsupported Torch dtype"); + } +} + +static torch::Tensor amsToTorchTensorView(const ams::AMSTensor& tensor) +{ + auto dType = amsToTorchDType(tensor.dType()); + auto deviceType = amsToTorchDevice(tensor.location()); + + c10::SmallVector shapes(tensor.shape().begin(), tensor.shape().end()); + c10::SmallVector strides(tensor.strides().begin(), + tensor.strides().end()); + + return torch::from_blob(tensor.raw_data(), + shapes, + strides, + torch::TensorOptions().dtype(dType).device( + deviceType)); +} +// flat containers static ams::SmallVector torchToAMSTensors( ams::MutableArrayRef tensorVector) { ams::SmallVector ams_tensors; - for (auto tensor : tensorVector) { - // We should be able to completely remove these conversion by using some template "magic." - // I will leave these for later though - auto dType = torchDTypeToAMSType(tensor.scalar_type()); - auto rType = torchDeviceToAMSDevice(tensor.device().type()); - // In both cases, I am effectively only forwarding the pointer of begin/end to ams. - // this is a cheap operating. It should boil down to: shapes.start = tensor.sizes.start, shapes.end = tensor.sizes.end; - auto shapes = ArrayRef(tensor.sizes().begin(), tensor.strides().size()); - auto strides = ArrayRef(tensor.strides().begin(), tensor.strides().size()); - if (dType == AMSDType::AMS_SINGLE) { - ams_tensors.push_back( - AMSTensor::view(tensor.data_ptr(), shapes, strides, rType)); - } else if (dType == AMSDType::AMS_DOUBLE) { - ams_tensors.push_back( - AMSTensor::view(tensor.data_ptr(), shapes, strides, rType)); - } + for (auto& tensor : tensorVector) { + ams_tensors.push_back(torchToAMSTensorView(tensor)); } return ams_tensors; } @@ -99,24 +140,198 @@ static ams::SmallVector torchToAMSTensors( static ams::SmallVector amsToTorchTensors( const ams::SmallVector& amsTensorVector) { - ams::SmallVector ams_tensors; - for (auto& tensor : amsTensorVector) { - // We should be able to completely remove these conversion by using some template "magic." - // I will leave these for later though - auto dType = amsToTorchDType(tensor.dType()); - auto deviceType = amsToTorchDevice(tensor.location()); - // In both cases, I am effectively only forwarding the pointer of begin/end to ams. - // this is a cheap operating. It should boil down to: shapes.start = tensor.sizes.start, shapes.end = tensor.sizes.end; - c10::SmallVector shapes(tensor.shape().begin(), tensor.shape().end()); - c10::SmallVector strides(tensor.strides().begin(), - tensor.strides().end()); - ams_tensors.push_back(torch::from_blob( - tensor.raw_data(), - shapes, - strides, - torch::TensorOptions().dtype(dType).device(deviceType))); - } - return std::move(ams_tensors); + ams::SmallVector torch_tensors; + for (const auto& tensor : amsTensorVector) { + torch_tensors.push_back(amsToTorchTensorView(tensor)); + } + return torch_tensors; +} + +static ams::AMSTensorMap torchDictToAMSTensorMap( + const c10::Dict& dict) +{ + ams::AMSTensorMap out; + + for (const auto& item : dict) { + const std::string name = item.key(); + torch::Tensor tensor = item.value(); + + out.emplace(name, torchToAMSTensorView(tensor)); + } + + return out; +} + +static c10::Dict amsTensorMapToTorchDict( + const ams::AMSTensorMap& store) +{ + c10::Dict out; + + for (const auto& [name, tensor] : store) { + out.insert(name, amsToTorchTensorView(tensor)); + } + + return out; +} + +// key helpers +static c10::Dict toStringTensorDict( + const c10::IValue& value) +{ + c10::Dict out; + + auto generic = value.toGenericDict(); + for (const auto& kv : generic) { + out.insert(kv.key().toStringRef(), kv.value().toTensor()); + } + + return out; +} + +static c10::impl::GenericDict toStringIValueDict(const c10::IValue& value) +{ + c10::impl::GenericDict out(c10::StringType::get(), c10::AnyType::get()); + + auto generic = value.toGenericDict(); + for (const auto& kv : generic) { + out.insert(kv.key().toStringRef(), kv.value()); + } + + return out; +} + +// homogeneous graphs +static ams::AMSHomogeneousGraph torchToAMSHomogeneousGraph( + const c10::Dict& g) +{ + return torchDictToAMSTensorMap(g); +} + +static c10::Dict amsToTorchHomogeneousGraph( + const ams::AMSHomogeneousGraph& g) +{ + return amsTensorMapToTorchDict(g); +} + +// heterogeneous graphs +static std::unordered_map +torchDictToAMSNodeStores(const c10::impl::GenericDict& dict) +{ + std::unordered_map out; + + for (const auto& item : dict) { + out.emplace(std::string(item.key().toStringRef()), + torchDictToAMSTensorMap(toStringTensorDict(item.value()))); + } + + return out; +} + +static std::unordered_map +torchDictToAMSEdgeStores(const c10::impl::GenericDict& dict) +{ + std::unordered_map out; + + for (const auto& item : dict) { + ams::EdgeType edge_type = + edgeTypeFromString(std::string(item.key().toStringRef())); + + out.emplace(std::move(edge_type), + torchDictToAMSTensorMap(toStringTensorDict(item.value()))); + } + + return out; +} + +static ams::AMSHeterogeneousGraph torchToAMSHeterogeneousGraph( + const c10::IValue& value) +{ + auto g = value.toGenericDict(); + + ams::AMSHeterogeneousGraph out; + + c10::IValue nodes_ivalue; + c10::IValue edges_ivalue; + c10::IValue global_ivalue; + + bool has_nodes = false; + bool has_edges = false; + bool has_global = false; + + for (const auto& kv : g) { + const auto key = kv.key().toStringRef(); + if (key == "node_stores") { + nodes_ivalue = kv.value(); + has_nodes = true; + } else if (key == "edge_stores") { + edges_ivalue = kv.value(); + has_edges = true; + } else if (key == "global_store") { + global_ivalue = kv.value(); + has_global = true; + } + } + + if (!has_nodes) { + throw std::runtime_error( + "torchToAMSHeterogeneousGraph: missing node_stores"); + } + if (!has_edges) { + throw std::runtime_error( + "torchToAMSHeterogeneousGraph: missing edge_stores"); + } + if (!has_global) { + throw std::runtime_error( + "torchToAMSHeterogeneousGraph: missing global_store"); + } + + out.node_stores = torchDictToAMSNodeStores(toStringIValueDict(nodes_ivalue)); + out.edge_stores = torchDictToAMSEdgeStores(toStringIValueDict(edges_ivalue)); + out.global_store = torchDictToAMSTensorMap(toStringTensorDict(global_ivalue)); + + return out; +} + +static c10::impl::GenericDict amsNodeStoresToTorchDict( + const std::unordered_map& node_stores) +{ + c10::impl::GenericDict out(c10::StringType::get(), c10::AnyType::get()); + + for (const auto& [store_name, store] : node_stores) { + out.insert(store_name, c10::IValue(amsTensorMapToTorchDict(store))); + } + + return out; +} + +static c10::impl::GenericDict amsEdgeStoresToTorchDict( + const std::unordered_map& edge_stores) +{ + c10::impl::GenericDict out(c10::StringType::get(), c10::AnyType::get()); + + for (const auto& [edge_type, store] : edge_stores) { + out.insert(ams::edgeTypeToString(edge_type), + c10::IValue(amsTensorMapToTorchDict(store))); + } + + return out; +} + +static c10::impl::GenericDict amsToTorchHeterogeneousGraph( + const ams::AMSHeterogeneousGraph& g) +{ + c10::impl::GenericDict out(c10::StringType::get(), c10::AnyType::get()); + + out.insert("node_stores", + c10::IValue(amsNodeStoresToTorchDict(g.node_stores))); + out.insert("edge_stores", + c10::IValue(amsEdgeStoresToTorchDict(g.edge_stores))); + out.insert("global_store", + c10::IValue(amsTensorMapToTorchDict(g.global_store))); + + return out; } void callApplication(ams::DomainLambda CallBack, diff --git a/src/AMSlib/wf/resource_manager.hpp b/src/AMSlib/wf/resource_manager.hpp index 7b452ad5..39e646dd 100644 --- a/src/AMSlib/wf/resource_manager.hpp +++ b/src/AMSlib/wf/resource_manager.hpp @@ -155,9 +155,9 @@ class ResourceManager std::string pinned_alloc("PINNED"); if (!RMAllocators[AMSResourceType::AMS_HOST]) setAllocator(host_alloc, AMSResourceType::AMS_HOST); -#if defined(__AMS_ENABLE_CUDA__) +#if defined(__AMS_ENABLE_CUDA__) || defined(__AMS_ENABLE_HIP__) if (!RMAllocators[AMSResourceType::AMS_DEVICE]) - setAllocator(host_alloc, AMSResourceType::AMS_DEVICE); + setAllocator(device_alloc, AMSResourceType::AMS_DEVICE); if (!RMAllocators[AMSResourceType::AMS_PINNED]) setAllocator(pinned_alloc, AMSResourceType::AMS_PINNED); diff --git a/src/AMSlib/wf/utils.hpp b/src/AMSlib/wf/utils.hpp index 9d056a59..59cc352a 100644 --- a/src/AMSlib/wf/utils.hpp +++ b/src/AMSlib/wf/utils.hpp @@ -67,6 +67,10 @@ static inline size_t dtype_to_size(ams::AMSDType dType) return sizeof(double); case ams::AMSDType::AMS_SINGLE: return sizeof(float); + case ams::AMSDType::AMS_INT64: + return sizeof(int64_t); + case ams::AMSDType::AMS_INT32: + return sizeof(int32_t); default: throw std::runtime_error("Requesting the size of unknown object"); } diff --git a/tests/AMSlib/ams_interface/CMakeLists.txt b/tests/AMSlib/ams_interface/CMakeLists.txt index 9250e421..2fb519e3 100644 --- a/tests/AMSlib/ams_interface/CMakeLists.txt +++ b/tests/AMSlib/ams_interface/CMakeLists.txt @@ -24,3 +24,6 @@ endfunction() BUILD_UNIT_TEST(ams_explicit_end_to_end ams_ete.cpp) ADD_AMS_UNIT_TEST(AMS_EXPLICIT ams_explicit_end_to_end) +BUILD_UNIT_TEST(int_interface int_interface.cpp) +ADD_AMS_UNIT_TEST(AMS_INT_INTERFACE int_interface) + diff --git a/tests/AMSlib/ams_interface/int_interface.cpp b/tests/AMSlib/ams_interface/int_interface.cpp new file mode 100644 index 00000000..d2bf7954 --- /dev/null +++ b/tests/AMSlib/ams_interface/int_interface.cpp @@ -0,0 +1,323 @@ +/* + * Copyright 2021-2023 Lawrence Livermore National Security, LLC and other + * AMSLib Project Developers + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include + +#include "AMS.h" +#include "AMSTensor.hpp" + +using namespace ams; + +// Global test fixture to initialize AMS once for all tests +struct AMSGlobalFixture { + AMSGlobalFixture() { AMSInit(); } +}; + +// This creates a single global instance that will initialize AMS before any tests run +static AMSGlobalFixture amsGlobalFixture; + +// Simple computation function for int32_t +void compute_int32(int32_t* input, int32_t* output, int num_elements) +{ + for (int i = 0; i < num_elements; ++i) { + // Simple computation: output = input * 2 + 1 + output[i] = input[i] * 2 + 1; + } +} + +// Simple computation function for int64_t +void compute_int64(int64_t* input, int64_t* output, int num_elements) +{ + for (int i = 0; i < num_elements; ++i) { + // Simple computation: output = input * 3 + 10 + output[i] = input[i] * 3 + 10; + } +} + +CATCH_TEST_CASE("AMS API: int32_t tensor execution without model", + "[ams][api][int32]") +{ + const auto resource = GENERATE(AMSResourceType::AMS_HOST); + constexpr int num_elements = 100; + + CATCH_SECTION("Execute with int32_t inputs and outputs") + { + // Allocate and initialize input data + std::vector input_data(num_elements); + for (int i = 0; i < num_elements; ++i) { + input_data[i] = i; + } + + // Allocate output data + std::vector output_data(num_elements, 0); + + // Create AMS tensors + SmallVector inputs; + SmallVector inouts; + SmallVector outputs; + + SmallVector shape_1d{num_elements}; + SmallVector strides_1d{1}; + + inputs.push_back(AMSTensor::view( + input_data.data(), shape_1d, strides_1d, resource)); + + outputs.push_back(AMSTensor::view( + output_data.data(), shape_1d, strides_1d, resource)); + + // Define computation lambda + DomainLambda computation = [&](const SmallVector& ins, + SmallVector& io, + SmallVector& outs) { + CATCH_REQUIRE(ins.size() == 1); + CATCH_REQUIRE(outs.size() == 1); + CATCH_REQUIRE(ins[0].dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(outs[0].dType() == AMSDType::AMS_INT32); + + int32_t* in_ptr = ins[0].data(); + int32_t* out_ptr = outs[0].data(); + int count = ins[0].elements(); + + compute_int32(in_ptr, out_ptr, count); + }; + + // Create model and executor (with threshold = 1.0, always use physics) + AMSCAbstrModel model = AMSRegisterAbstractModel( + "int32_test", 1.0, "", false); // No model path, no storage + CATCH_REQUIRE(model >= 0); + + AMSExecutor executor = AMSCreateExecutor(model, 0, 1); + CATCH_REQUIRE(executor >= 0); + + // Execute + AMSExecute(executor, computation, inputs, inouts, outputs); + + // Verify results + for (int i = 0; i < num_elements; ++i) { + int32_t expected = i * 2 + 1; + CATCH_REQUIRE(output_data[i] == expected); + } + + // Note: Not destroying executor to avoid triggering AMSFinalize between tests + // The executor will be cleaned up at program exit + } +} + +CATCH_TEST_CASE("AMS API: int64_t tensor execution without model", + "[ams][api][int64]") +{ + const auto resource = GENERATE(AMSResourceType::AMS_HOST); + constexpr int num_elements = 150; + + CATCH_SECTION("Execute with int64_t inputs and outputs") + { + // Allocate and initialize input data with large values + std::vector input_data(num_elements); + for (int i = 0; i < num_elements; ++i) { + input_data[i] = static_cast(i) * 1000000; + } + + // Allocate output data + std::vector output_data(num_elements, 0); + + // Create AMS tensors + SmallVector inputs; + SmallVector inouts; + SmallVector outputs; + + SmallVector shape_1d{num_elements}; + SmallVector strides_1d{1}; + + inputs.push_back(AMSTensor::view( + input_data.data(), shape_1d, strides_1d, resource)); + + outputs.push_back(AMSTensor::view( + output_data.data(), shape_1d, strides_1d, resource)); + + // Define computation lambda + DomainLambda computation = [&](const SmallVector& ins, + SmallVector& io, + SmallVector& outs) { + CATCH_REQUIRE(ins.size() == 1); + CATCH_REQUIRE(outs.size() == 1); + CATCH_REQUIRE(ins[0].dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(outs[0].dType() == AMSDType::AMS_INT64); + + int64_t* in_ptr = ins[0].data(); + int64_t* out_ptr = outs[0].data(); + int count = ins[0].elements(); + + compute_int64(in_ptr, out_ptr, count); + }; + + // Create model and executor (with threshold = 1.0, always use physics) + AMSCAbstrModel model = AMSRegisterAbstractModel( + "int64_test", 1.0, "", false); // No model path, no storage + CATCH_REQUIRE(model >= 0); + + AMSExecutor executor = AMSCreateExecutor(model, 0, 1); + CATCH_REQUIRE(executor >= 0); + + // Execute + AMSExecute(executor, computation, inputs, inouts, outputs); + + // Verify results + for (int i = 0; i < num_elements; ++i) { + int64_t expected = static_cast(i) * 1000000 * 3 + 10; + CATCH_REQUIRE(output_data[i] == expected); + } + + // Note: Not destroying executor to avoid triggering AMSFinalize between tests + // The executor will be cleaned up at program exit + } +} + +CATCH_TEST_CASE("AMS API: 2D int32_t tensor execution", "[ams][api][int32][2d]") +{ + const auto resource = GENERATE(AMSResourceType::AMS_HOST); + constexpr int rows = 10; + constexpr int cols = 8; + constexpr int num_elements = rows * cols; + + CATCH_SECTION("Execute with 2D int32_t tensors") + { + // Allocate 2D input data + std::vector input_data(num_elements); + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + input_data[i * cols + j] = i * 10 + j; + } + } + + std::vector output_data(num_elements, 0); + + // Create 2D tensors + SmallVector inputs; + SmallVector inouts; + SmallVector outputs; + + SmallVector shape_2d{rows, cols}; + SmallVector strides_2d{cols, 1}; + + inputs.push_back(AMSTensor::view( + input_data.data(), shape_2d, strides_2d, resource)); + + outputs.push_back(AMSTensor::view( + output_data.data(), shape_2d, strides_2d, resource)); + + // Computation: element-wise doubling + DomainLambda computation = [&](const SmallVector& ins, + SmallVector& io, + SmallVector& outs) { + CATCH_REQUIRE(ins[0].shape().size() == 2); + CATCH_REQUIRE(ins[0].shape()[0] == rows); + CATCH_REQUIRE(ins[0].shape()[1] == cols); + + int32_t* in_ptr = ins[0].data(); + int32_t* out_ptr = outs[0].data(); + + for (int i = 0; i < num_elements; ++i) { + out_ptr[i] = in_ptr[i] * 2; + } + }; + + AMSCAbstrModel model = + AMSRegisterAbstractModel("int32_2d_test", 1.0, "", false); + AMSExecutor executor = AMSCreateExecutor(model, 0, 1); + + AMSExecute(executor, computation, inputs, inouts, outputs); + + // Verify + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + int idx = i * cols + j; + int32_t expected = (i * 10 + j) * 2; + CATCH_REQUIRE(output_data[idx] == expected); + } + } + + // Note: Not destroying executor to avoid triggering AMSFinalize between tests + // The executor will be cleaned up at program exit + } +} + +CATCH_TEST_CASE("AMS API: Mixed type tensors", "[ams][api][mixed]") +{ + const auto resource = GENERATE(AMSResourceType::AMS_HOST); + constexpr int num_elements = 50; + + CATCH_SECTION("Execute with mixed float and int32_t tensors") + { + // Float input + std::vector float_input(num_elements); + for (int i = 0; i < num_elements; ++i) { + float_input[i] = static_cast(i) * 1.5f; + } + + // Int32 input + std::vector int_input(num_elements); + for (int i = 0; i < num_elements; ++i) { + int_input[i] = i * 2; + } + + // Int32 output + std::vector output_data(num_elements, 0); + + SmallVector inputs; + SmallVector inouts; + SmallVector outputs; + + SmallVector shape_1d{num_elements}; + SmallVector strides_1d{1}; + + inputs.push_back(AMSTensor::view( + float_input.data(), shape_1d, strides_1d, resource)); + + inputs.push_back(AMSTensor::view( + int_input.data(), shape_1d, strides_1d, resource)); + + outputs.push_back(AMSTensor::view( + output_data.data(), shape_1d, strides_1d, resource)); + + DomainLambda computation = [&](const SmallVector& ins, + SmallVector& io, + SmallVector& outs) { + CATCH_REQUIRE(ins.size() == 2); + CATCH_REQUIRE(ins[0].dType() == AMSDType::AMS_SINGLE); + CATCH_REQUIRE(ins[1].dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(outs[0].dType() == AMSDType::AMS_INT32); + + float* float_ptr = ins[0].data(); + int32_t* int_ptr = ins[1].data(); + int32_t* out_ptr = outs[0].data(); + + for (int i = 0; i < num_elements; ++i) { + // Convert float to int and add to int input + out_ptr[i] = static_cast(float_ptr[i]) + int_ptr[i]; + } + }; + + AMSCAbstrModel model = + AMSRegisterAbstractModel("mixed_test", 1.0, "", false); + AMSExecutor executor = AMSCreateExecutor(model, 0, 1); + + AMSExecute(executor, computation, inputs, inouts, outputs); + + // Verify + for (int i = 0; i < num_elements; ++i) { + int32_t expected = + static_cast(static_cast(i) * 1.5f) + i * 2; + CATCH_REQUIRE(output_data[i] == expected); + } + + // Note: Not destroying executor to avoid triggering AMSFinalize between tests + // The executor will be cleaned up at program exit + } +} diff --git a/tests/AMSlib/wf/CMakeLists.txt b/tests/AMSlib/wf/CMakeLists.txt index 4e3f98fb..2f8cd927 100644 --- a/tests/AMSlib/wf/CMakeLists.txt +++ b/tests/AMSlib/wf/CMakeLists.txt @@ -65,3 +65,6 @@ ADD_WORKFLOW_UNIT_TEST(WORKFLOW::PIPELINE pipeline) BUILD_UNIT_TEST(policy policy.cpp) ADD_WORKFLOW_UNIT_TEST(WORKFLOW::POLICY policy) + +BUILD_UNIT_TEST(int_tensors int_tensors.cpp) +ADD_WORKFLOW_UNIT_TEST(AMS_INT_TENSOR int_tensors) diff --git a/tests/AMSlib/wf/int_tensors.cpp b/tests/AMSlib/wf/int_tensors.cpp new file mode 100644 index 00000000..3377bce1 --- /dev/null +++ b/tests/AMSlib/wf/int_tensors.cpp @@ -0,0 +1,428 @@ +/* + * Copyright 2021-2023 Lawrence Livermore National Security, LLC and other + * AMSLib Project Developers + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include +#include +#include +#include + +#include "AMS.h" +#include "AMSTensor.hpp" +#include "wf/resource_manager.hpp" +#include "wf/utils.hpp" + +using namespace ams; + +CATCH_TEST_CASE("AMSTensor: int32_t tensor creation and basic properties", + "[ams][tensor][int32]") +{ + AMSInit(); + + const auto device = + GENERATE(AMSResourceType::AMS_HOST, AMSResourceType::AMS_DEVICE); + + // Skip GPU tests if CUDA is not available + if (device == AMSResourceType::AMS_DEVICE) { +#if !defined(__AMS_ENABLE_CUDA__) && !defined(__AMS_ENABLE_HIP__) + CATCH_SKIP("GPU device not available"); +#endif + } + + CATCH_SECTION("Create 1D int32_t tensor") + { + std::vector shape = {10}; + std::vector strides = {1}; + + auto tensor = AMSTensor::create(shape, strides, device); + + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor.elements() == 10); + CATCH_REQUIRE(tensor.element_size() == sizeof(int32_t)); + CATCH_REQUIRE(tensor.location() == device); + CATCH_REQUIRE(tensor.shape().size() == 1); + CATCH_REQUIRE(tensor.shape()[0] == 10); + // Note: contiguous() check removed due to pre-existing AMSTensor bug + } + + CATCH_SECTION("Create 2D int32_t tensor") + { + std::vector shape = {5, 8}; + std::vector strides = {8, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor.elements() == 40); + CATCH_REQUIRE(tensor.element_size() == sizeof(int32_t)); + CATCH_REQUIRE(tensor.shape().size() == 2); + CATCH_REQUIRE(tensor.shape()[0] == 5); + CATCH_REQUIRE(tensor.shape()[1] == 8); + } + + CATCH_SECTION("Create 3D int32_t tensor") + { + std::vector shape = {4, 3, 2}; + std::vector strides = {6, 2, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor.elements() == 24); + CATCH_REQUIRE(tensor.element_size() == sizeof(int32_t)); + } +} + +CATCH_TEST_CASE("AMSTensor: int64_t tensor creation and basic properties", + "[ams][tensor][int64]") +{ + AMSInit(); + + const auto device = + GENERATE(AMSResourceType::AMS_HOST, AMSResourceType::AMS_DEVICE); + + if (device == AMSResourceType::AMS_DEVICE) { +#if !defined(__AMS_ENABLE_CUDA__) && !defined(__AMS_ENABLE_HIP__) + CATCH_SKIP("GPU device not available"); +#endif + } + + CATCH_SECTION("Create 1D int64_t tensor") + { + std::vector shape = {15}; + std::vector strides = {1}; + + auto tensor = AMSTensor::create(shape, strides, device); + + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(tensor.elements() == 15); + CATCH_REQUIRE(tensor.element_size() == sizeof(int64_t)); + CATCH_REQUIRE(tensor.location() == device); + // Note: contiguous() check removed due to pre-existing AMSTensor bug + } + + CATCH_SECTION("Create 2D int64_t tensor") + { + std::vector shape = {6, 7}; + std::vector strides = {7, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(tensor.elements() == 42); + CATCH_REQUIRE(tensor.element_size() == sizeof(int64_t)); + } +} + +CATCH_TEST_CASE("AMSTensor: int32_t tensor view operations", + "[ams][tensor][int32][view]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Create view from existing int32_t data") + { + std::vector data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + std::vector shape = {10}; + std::vector strides = {1}; + + auto tensor_view = + AMSTensor::view(data.data(), shape, strides, device); + + CATCH_REQUIRE(tensor_view.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor_view.elements() == 10); + CATCH_REQUIRE(tensor_view.location() == device); + + // Verify we can access the data + auto* ptr = tensor_view.data(); + CATCH_REQUIRE(ptr != nullptr); + CATCH_REQUIRE(ptr[0] == 1); + CATCH_REQUIRE(ptr[9] == 10); + } + + CATCH_SECTION("Create 2D view from int32_t data") + { + std::vector data(20, 42); // 20 elements, all set to 42 + std::vector shape = {4, 5}; + std::vector strides = {5, 1}; + + auto tensor_view = + AMSTensor::view(data.data(), shape, strides, device); + + CATCH_REQUIRE(tensor_view.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor_view.elements() == 20); + CATCH_REQUIRE(tensor_view.shape()[0] == 4); + CATCH_REQUIRE(tensor_view.shape()[1] == 5); + + auto* ptr = tensor_view.data(); + CATCH_REQUIRE(ptr[0] == 42); + CATCH_REQUIRE(ptr[19] == 42); + } +} + +CATCH_TEST_CASE("AMSTensor: int64_t tensor view operations", + "[ams][tensor][int64][view]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Create view from existing int64_t data") + { + std::vector data = {100, 200, 300, 400, 500}; + std::vector shape = {5}; + std::vector strides = {1}; + + auto tensor_view = + AMSTensor::view(data.data(), shape, strides, device); + + CATCH_REQUIRE(tensor_view.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(tensor_view.elements() == 5); + + auto* ptr = tensor_view.data(); + CATCH_REQUIRE(ptr[0] == 100); + CATCH_REQUIRE(ptr[4] == 500); + } +} + +CATCH_TEST_CASE("AMSTensor: int tensor transpose operations", + "[ams][tensor][transpose]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Transpose 2D int32_t tensor") + { + std::vector shape = {3, 4}; + std::vector strides = {4, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + auto transposed = tensor.transpose(0, 1); + + CATCH_REQUIRE(transposed.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(transposed.shape()[0] == 4); + CATCH_REQUIRE(transposed.shape()[1] == 3); + CATCH_REQUIRE(transposed.elements() == 12); + } + + CATCH_SECTION("Transpose 2D int64_t tensor") + { + std::vector shape = {5, 6}; + std::vector strides = {6, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + auto transposed = tensor.transpose(0, 1); + + CATCH_REQUIRE(transposed.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(transposed.shape()[0] == 6); + CATCH_REQUIRE(transposed.shape()[1] == 5); + CATCH_REQUIRE(transposed.elements() == 30); + } +} + +CATCH_TEST_CASE("AMSTensor: int tensor move semantics", "[ams][tensor][move]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Move int32_t tensor") + { + std::vector shape = {10}; + std::vector strides = {1}; + + auto tensor1 = AMSTensor::create(shape, strides, device); + auto* original_ptr = tensor1.data(); + + // Move construct + auto tensor2 = std::move(tensor1); + + CATCH_REQUIRE(tensor2.dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensor2.elements() == 10); + CATCH_REQUIRE(tensor2.data() == original_ptr); + } + + CATCH_SECTION("Move int64_t tensor") + { + std::vector shape = {20}; + std::vector strides = {1}; + + auto tensor1 = AMSTensor::create(shape, strides, device); + auto* original_ptr = tensor1.data(); + + // Move construct (not move assign, to avoid existing AMSTensor bug) + auto tensor2 = std::move(tensor1); + + CATCH_REQUIRE(tensor2.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(tensor2.elements() == 20); + CATCH_REQUIRE(tensor2.data() == original_ptr); + } +} + +CATCH_TEST_CASE("AMSTensor: dtype_to_size utility for int types", + "[ams][utils]") +{ + CATCH_SECTION("Verify int32_t size") + { + size_t size = dtype_to_size(AMSDType::AMS_INT32); + CATCH_REQUIRE(size == sizeof(int32_t)); + CATCH_REQUIRE(size == 4); + } + + CATCH_SECTION("Verify int64_t size") + { + size_t size = dtype_to_size(AMSDType::AMS_INT64); + CATCH_REQUIRE(size == sizeof(int64_t)); + CATCH_REQUIRE(size == 8); + } + + CATCH_SECTION("Compare sizes") + { + size_t size_int32 = dtype_to_size(AMSDType::AMS_INT32); + size_t size_int64 = dtype_to_size(AMSDType::AMS_INT64); + size_t size_float = dtype_to_size(AMSDType::AMS_SINGLE); + size_t size_double = dtype_to_size(AMSDType::AMS_DOUBLE); + + CATCH_REQUIRE(size_int32 == size_float); // Both 4 bytes + CATCH_REQUIRE(size_int64 == size_double); // Both 8 bytes + CATCH_REQUIRE(size_int64 == 2 * size_int32); + } +} + +CATCH_TEST_CASE("AMSTensor: SmallVector of int tensors", + "[ams][tensor][smallvector]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Create vector of int32_t tensors") + { + ams::SmallVector tensors; + + std::vector shape1 = {5}; + std::vector shape2 = {10}; + std::vector strides = {1}; + + tensors.push_back(AMSTensor::create(shape1, strides, device)); + tensors.push_back(AMSTensor::create(shape2, strides, device)); + + CATCH_REQUIRE(tensors.size() == 2); + CATCH_REQUIRE(tensors[0].dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensors[1].dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensors[0].elements() == 5); + CATCH_REQUIRE(tensors[1].elements() == 10); + } + + CATCH_SECTION("Create vector of int64_t tensors") + { + ams::SmallVector tensors; + + std::vector shape = {7}; + std::vector strides = {1}; + + for (int i = 0; i < 3; ++i) { + tensors.push_back(AMSTensor::create(shape, strides, device)); + } + + CATCH_REQUIRE(tensors.size() == 3); + for (const auto& tensor : tensors) { + CATCH_REQUIRE(tensor.dType() == AMSDType::AMS_INT64); + CATCH_REQUIRE(tensor.elements() == 7); + } + } + + CATCH_SECTION("Mixed type tensors in SmallVector") + { + ams::SmallVector tensors; + + std::vector shape = {8}; + std::vector strides = {1}; + + tensors.push_back(AMSTensor::create(shape, strides, device)); + tensors.push_back(AMSTensor::create(shape, strides, device)); + tensors.push_back(AMSTensor::create(shape, strides, device)); + tensors.push_back(AMSTensor::create(shape, strides, device)); + + CATCH_REQUIRE(tensors.size() == 4); + CATCH_REQUIRE(tensors[0].dType() == AMSDType::AMS_SINGLE); + CATCH_REQUIRE(tensors[1].dType() == AMSDType::AMS_INT32); + CATCH_REQUIRE(tensors[2].dType() == AMSDType::AMS_DOUBLE); + CATCH_REQUIRE(tensors[3].dType() == AMSDType::AMS_INT64); + } +} + +CATCH_TEST_CASE("AMSTensor: int tensor data access and modification", + "[ams][tensor][data]") +{ + AMSInit(); + + const auto device = GENERATE(AMSResourceType::AMS_HOST); + + CATCH_SECTION("Write and read int32_t data") + { + std::vector shape = {5}; + std::vector strides = {1}; + + auto tensor = AMSTensor::create(shape, strides, device); + auto* data = tensor.data(); + + // Write data + for (int i = 0; i < 5; ++i) { + data[i] = i * 10; + } + + // Read data back + CATCH_REQUIRE(data[0] == 0); + CATCH_REQUIRE(data[1] == 10); + CATCH_REQUIRE(data[2] == 20); + CATCH_REQUIRE(data[3] == 30); + CATCH_REQUIRE(data[4] == 40); + } + + CATCH_SECTION("Write and read int64_t data") + { + std::vector shape = {3}; + std::vector strides = {1}; + + auto tensor = AMSTensor::create(shape, strides, device); + auto* data = tensor.data(); + + // Write large values + data[0] = 1000000000LL; + data[1] = 2000000000LL; + data[2] = 3000000000LL; + + // Read data back + CATCH_REQUIRE(data[0] == 1000000000LL); + CATCH_REQUIRE(data[1] == 2000000000LL); + CATCH_REQUIRE(data[2] == 3000000000LL); + } + + CATCH_SECTION("2D int32_t tensor data access") + { + std::vector shape = {3, 4}; + std::vector strides = {4, 1}; + + auto tensor = AMSTensor::create(shape, strides, device); + auto* data = tensor.data(); + + // Fill with row-major data + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 4; ++j) { + data[i * 4 + j] = i * 10 + j; + } + } + + // Verify access + CATCH_REQUIRE(data[0] == 0); // [0,0] + CATCH_REQUIRE(data[3] == 3); // [0,3] + CATCH_REQUIRE(data[4] == 10); // [1,0] + CATCH_REQUIRE(data[11] == 23); // [2,3] + } +}