diff --git a/cpp/open3d/CMakeLists.txt b/cpp/open3d/CMakeLists.txt index 00b04684b2c..f8ff798ed7e 100644 --- a/cpp/open3d/CMakeLists.txt +++ b/cpp/open3d/CMakeLists.txt @@ -59,6 +59,12 @@ configure_file("${PROJECT_SOURCE_DIR}/cpp/open3d/Open3DConfig.h.in" add_library(Open3D) +if (BUILD_PYTORCH_OPS) + target_compile_definitions(Open3D PUBLIC OPEN3D_BUILD_PYTORCH_OPS=1) +else() + target_compile_definitions(Open3D PUBLIC OPEN3D_BUILD_PYTORCH_OPS=0) +endif() + add_subdirectory(camera) add_subdirectory(core) add_subdirectory(data) diff --git a/cpp/open3d/ml/CMakeLists.txt b/cpp/open3d/ml/CMakeLists.txt index 35f0b65112a..9836a88334c 100644 --- a/cpp/open3d/ml/CMakeLists.txt +++ b/cpp/open3d/ml/CMakeLists.txt @@ -11,4 +11,8 @@ if (BUILD_PYTORCH_OPS) add_subdirectory(pytorch) endif() +target_sources(Open3D PRIVATE + Model.cpp +) + add_subdirectory(contrib) diff --git a/cpp/open3d/ml/Model.cpp b/cpp/open3d/ml/Model.cpp new file mode 100644 index 00000000000..7904fe64883 --- /dev/null +++ b/cpp/open3d/ml/Model.cpp @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// Copyright (c) 2018-2024 www.open3d.org +// SPDX-License-Identifier: MIT +// ---------------------------------------------------------------------------- + +#include "open3d/ml/Model.h" + +#include "open3d/utility/Logging.h" + +namespace open3d { +namespace ml { + +struct Model::Impl { + std::string artifact_path_; + bool loaded_ = false; +}; + +Model::Model() : impl_(std::make_unique()) {} + +Model::~Model() = default; + +void Model::LoadModel(const std::string& artifact_path) { + // TODO: Implement dlopen of libtorch and AOTInductor runner loading. + // For now, just store the path and mark as loaded for testing purposes. + impl_->artifact_path_ = artifact_path; + impl_->loaded_ = true; + utility::LogInfo("Model::LoadModel called with path: {}", artifact_path); +} + +std::vector Model::Forward( + const std::vector& inputs) const { + if (!impl_->loaded_) { + utility::LogError("Model not loaded. Call LoadModel() first."); + } + // TODO: Implement actual inference using AOTInductor runner. + // For now, return empty vector as placeholder. + utility::LogInfo("Model::Forward called with {} input tensors", + inputs.size()); + return {}; +} + +bool IsPyTorchRuntimeEnabled() { +#if OPEN3D_BUILD_PYTORCH_OPS + return true; +#else + return false; +#endif +} + +} // namespace ml +} // namespace open3d diff --git a/cpp/open3d/ml/Model.h b/cpp/open3d/ml/Model.h new file mode 100644 index 00000000000..209e4ef9bae --- /dev/null +++ b/cpp/open3d/ml/Model.h @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// Copyright (c) 2018-2024 www.open3d.org +// SPDX-License-Identifier: MIT +// ---------------------------------------------------------------------------- + +#pragma once +#include +#include +#include + +#include "open3d/core/Tensor.h" + +namespace open3d { +namespace ml { + +/// \brief Minimal holder for lazily loaded torch models compiled through +/// AOTInductor. The class is intentionally lightweight so the API can stabilize +/// before the heavy runtime integration lands. +class Model { +public: + Model(); + ~Model(); + + /// Loads a compiled model artifact from disk. The concrete format is + /// expected to match the PyTorch AOTInductor runner output. + void LoadModel(const std::string& artifact_path); + + /// Runs inference on the loaded model. Inputs are Open3D tensors so we can + /// take advantage of the existing DLPack bridges. + std::vector Forward( + const std::vector& inputs) const; + +private: + struct Impl; + std::unique_ptr impl_; +}; + +/// Returns true when Open3D was configured with BUILD_PYTORCH_OPS. +bool IsPyTorchRuntimeEnabled(); + +} // namespace ml +} // namespace open3d \ No newline at end of file diff --git a/cpp/tests/ml/CMakeLists.txt b/cpp/tests/ml/CMakeLists.txt index 6eac847ea9a..8bf129d5a4c 100644 --- a/cpp/tests/ml/CMakeLists.txt +++ b/cpp/tests/ml/CMakeLists.txt @@ -1,3 +1,4 @@ target_sources(tests PRIVATE + Model.cpp ShapeChecking.cpp ) diff --git a/cpp/tests/ml/Model.cpp b/cpp/tests/ml/Model.cpp new file mode 100644 index 00000000000..a5f7a5bdb5e --- /dev/null +++ b/cpp/tests/ml/Model.cpp @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// Copyright (c) 2018-2024 www.open3d.org +// SPDX-License-Identifier: MIT +// ---------------------------------------------------------------------------- + +#include "open3d/ml/Model.h" + +#include "open3d/core/Tensor.h" +#include "tests/Tests.h" + +namespace open3d { +namespace tests { + +TEST(Model, Constructor) { + ml::Model model; + // Should construct without throwing +} + +TEST(Model, IsPyTorchRuntimeEnabled) { + bool enabled = ml::IsPyTorchRuntimeEnabled(); +#if OPEN3D_BUILD_PYTORCH_OPS + EXPECT_TRUE(enabled); +#else + EXPECT_FALSE(enabled); +#endif +} + +TEST(Model, ForwardWithoutLoad) { + ml::Model model; + std::vector inputs; + // Forward without LoadModel should throw/log error + EXPECT_ANY_THROW(model.Forward(inputs)); +} + +TEST(Model, LoadModelAndForward) { + ml::Model model; + // LoadModel with dummy path (stub implementation just stores path) + EXPECT_NO_THROW(model.LoadModel("/tmp/dummy_model.pt")); + // Forward with empty inputs (stub returns empty vector) + std::vector inputs; + auto outputs = model.Forward(inputs); + EXPECT_TRUE(outputs.empty()); // Stub returns empty +} + +TEST(Model, ForwardWithInputTensors) { + ml::Model model; + model.LoadModel("/tmp/dummy_model.pt"); + // Create test input tensors + std::vector inputs; + inputs.push_back(core::Tensor::Ones({2, 3}, core::Float32)); + inputs.push_back(core::Tensor::Zeros({4, 5}, core::Float32)); + // Stub should accept inputs without crashing + auto outputs = model.Forward(inputs); + EXPECT_TRUE(outputs.empty()); // Stub returns empty +} + +} // namespace tests +} // namespace open3d