diff --git a/PhysicsTools/PyTorch/BuildFile.xml b/PhysicsTools/PyTorch/BuildFile.xml new file mode 100644 index 0000000000000..511f4697bbabe --- /dev/null +++ b/PhysicsTools/PyTorch/BuildFile.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/PhysicsTools/PyTorch/test/BuildFile.xml b/PhysicsTools/PyTorch/test/BuildFile.xml new file mode 100644 index 0000000000000..d498ab13d4ead --- /dev/null +++ b/PhysicsTools/PyTorch/test/BuildFile.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PhysicsTools/PyTorch/test/create_simple_dnn.py b/PhysicsTools/PyTorch/test/create_simple_dnn.py new file mode 100644 index 0000000000000..aeb2a16449f75 --- /dev/null +++ b/PhysicsTools/PyTorch/test/create_simple_dnn.py @@ -0,0 +1,31 @@ +import sys +import os +import torch + +# prepare the datadir +if len(sys.argv) >= 2: + datadir = sys.argv[1] +else: + thisdir = os.path.dirname(os.path.abspath(__file__)) + datadir = os.path.join(os.path.dirname(thisdir), "bin", "data") + +os.makedirs(datadir, exist_ok=True) + +class MyModule(torch.nn.Module): + def __init__(self, N, M): + super(MyModule, self).__init__() + self.weight = torch.nn.Parameter(torch.ones(N, M)) + self.bias = torch.nn.Parameter(torch.ones(N)) + + def forward(self, input): + return torch.sum(torch.nn.functional.elu(self.weight.mv(input) + self.bias)) + + +module = MyModule(10, 10) +x = torch.ones(10) + +tm = torch.jit.trace(module.eval(), x) + +tm.save(f"{datadir}/simple_dnn.pt") + +print("simple_dnn.pt created successfully!") diff --git a/PhysicsTools/PyTorch/test/testBase.h b/PhysicsTools/PyTorch/test/testBase.h new file mode 100644 index 0000000000000..e2a17506e6d58 --- /dev/null +++ b/PhysicsTools/PyTorch/test/testBase.h @@ -0,0 +1,68 @@ +/* + * Base class for tests. + * + */ + +#ifndef PHYSICSTOOLS_PYTORCH_TEST_TESTBASE_H +#define PHYSICSTOOLS_PYTORCH_TEST_TESTBASE_H + +#include +#include +#include +#include + +class testBasePyTorch : public CppUnit::TestFixture { +public: + std::string dataPath_; + + void setUp(); + void tearDown(); + std::string cmsswPath(std::string path); + + virtual void test() = 0; + + virtual std::string pyScript() const = 0; +}; + +void testBasePyTorch::setUp() { + dataPath_ = + cmsswPath("/test/" + std::string(std::getenv("SCRAM_ARCH")) + "/" + boost::filesystem::unique_path().string()); + + // create the graph using apptainer + std::string testPath = cmsswPath("/src/PhysicsTools/PyTorch/test"); + std::string cmd = "apptainer exec -B " + cmsswPath("") + + " /cvmfs/unpacked.cern.ch/registry.hub.docker.com/cmsml/cmsml:3.11 python " + testPath + "/" + + pyScript() + " " + dataPath_; + std::cout << "cmd: " << cmd << std::endl; + std::array buffer; + std::string result; + std::shared_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) { + throw std::runtime_error("Failed to run apptainer to prepare the PyTorch test model: " + cmd); + } + while (!feof(pipe.get())) { + if (fgets(buffer.data(), 128, pipe.get()) != NULL) { + result += buffer.data(); + } + } + std::cout << std::endl << result << std::endl; +} + +void testBasePyTorch::tearDown() { + if (std::filesystem::exists(dataPath_)) { + std::filesystem::remove_all(dataPath_); + } +} + +std::string testBasePyTorch::cmsswPath(std::string path) { + if (path.size() > 0 && path.substr(0, 1) != "/") { + path = "/" + path; + } + + std::string base = std::string(std::getenv("CMSSW_BASE")); + std::string releaseBase = std::string(std::getenv("CMSSW_RELEASE_BASE")); + + return (std::filesystem::exists(base.c_str()) ? base : releaseBase) + path; +} + +#endif // PHYSICSTOOLS_PYTORCH_TEST_TESTBASE_H diff --git a/PhysicsTools/PyTorch/test/testBaseCUDA.h b/PhysicsTools/PyTorch/test/testBaseCUDA.h new file mode 100644 index 0000000000000..2feb042ee5a41 --- /dev/null +++ b/PhysicsTools/PyTorch/test/testBaseCUDA.h @@ -0,0 +1,78 @@ +/* + * Base class for tests. + * + */ + +#ifndef PHYSICSTOOLS_PYTORCH_TEST_TESTBASECUDA_H +#define PHYSICSTOOLS_PYTORCH_TEST_TESTBASECUDA_H + +#include +#include +#include +#include + +#include "FWCore/ParameterSet/interface/ParameterSet.h" +#include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h" +#include "FWCore/ParameterSetReader/interface/ParameterSetReader.h" +#include "FWCore/PluginManager/interface/PluginManager.h" +#include "FWCore/PluginManager/interface/standard.h" +#include "FWCore/ServiceRegistry/interface/Service.h" +#include "FWCore/ServiceRegistry/interface/ServiceRegistry.h" +#include "FWCore/ServiceRegistry/interface/ServiceToken.h" +#include "FWCore/Utilities/interface/Exception.h" +#include "FWCore/Utilities/interface/ResourceInformation.h" + +class testBasePyTorchCUDA : public CppUnit::TestFixture { +public: + std::string dataPath_; + + void setUp(); + void tearDown(); + std::string cmsswPath(std::string path); + + virtual std::string pyScript() const = 0; + + virtual void test() = 0; +}; + +void testBasePyTorchCUDA::setUp() { + dataPath_ = + cmsswPath("/test/" + std::string(std::getenv("SCRAM_ARCH")) + "/" + boost::filesystem::unique_path().string()); + + // create the graph using apptainer + std::string testPath = cmsswPath("/src/PhysicsTools/PyTorch/test"); + std::string cmd = "apptainer exec -B " + cmsswPath("") + + " /cvmfs/unpacked.cern.ch/registry.hub.docker.com/cmsml/cmsml:3.11 python " + testPath + "/" + + pyScript() + " " + dataPath_; + std::cout << "cmd: " << cmd << std::endl; + std::array buffer; + std::string result; + std::shared_ptr pipe(popen(cmd.c_str(), "r"), pclose); + if (!pipe) { + throw std::runtime_error("Failed to run apptainer to prepare the PyTorch test model: " + cmd); + } + while (!feof(pipe.get())) { + if (fgets(buffer.data(), 128, pipe.get()) != NULL) { + result += buffer.data(); + } + } + std::cout << std::endl << result << std::endl; +} +void testBasePyTorchCUDA::tearDown() { + if (std::filesystem::exists(dataPath_)) { + std::filesystem::remove_all(dataPath_); + } +} + +std::string testBasePyTorchCUDA::cmsswPath(std::string path) { + if (path.size() > 0 && path.substr(0, 1) != "/") { + path = "/" + path; + } + + std::string base = std::string(std::getenv("CMSSW_BASE")); + std::string releaseBase = std::string(std::getenv("CMSSW_RELEASE_BASE")); + + return (std::filesystem::exists(base.c_str()) ? base : releaseBase) + path; +} + +#endif // PHYSICSTOOLS_PYTORCH_TEST_TESTBASE_H diff --git a/PhysicsTools/PyTorch/test/testRunner.cc b/PhysicsTools/PyTorch/test/testRunner.cc new file mode 100644 index 0000000000000..1482cf9a9ce85 --- /dev/null +++ b/PhysicsTools/PyTorch/test/testRunner.cc @@ -0,0 +1 @@ +#include diff --git a/PhysicsTools/PythonAnalysis/test/testTorch.cc b/PhysicsTools/PyTorch/test/testTorch.cc similarity index 100% rename from PhysicsTools/PythonAnalysis/test/testTorch.cc rename to PhysicsTools/PyTorch/test/testTorch.cc diff --git a/PhysicsTools/PyTorch/test/testTorchSimpleDnn.cc b/PhysicsTools/PyTorch/test/testTorchSimpleDnn.cc new file mode 100644 index 0000000000000..3e1d8d650dab2 --- /dev/null +++ b/PhysicsTools/PyTorch/test/testTorchSimpleDnn.cc @@ -0,0 +1,42 @@ +#include +#include "testBase.h" +#include +#include +#include + +class testSimpleDNN : public testBasePyTorch { + CPPUNIT_TEST_SUITE(testSimpleDNN); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + +public: + std::string pyScript() const override; + void test() override; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(testSimpleDNN); + +std::string testSimpleDNN::pyScript() const { return "create_simple_dnn.py"; } + +void testSimpleDNN::test() { + std::string model_path = dataPath_ + "/simple_dnn.pt"; + torch::Device device(torch::kCPU); + torch::jit::script::Module module; + try { + // Deserialize the ScriptModule from a file using torch::jit::load(). + module = torch::jit::load(model_path); + module.to(device); + } catch (const c10::Error& e) { + std::cerr << "error loading the model\n" << e.what() << std::endl; + CPPUNIT_ASSERT(false); + } + // Create a vector of inputs. + std::vector inputs; + inputs.push_back(torch::ones(10, device)); + + // Execute the model and turn its output into a tensor. + at::Tensor output = module.forward(inputs).toTensor(); + std::cout << "output: " << output << '\n'; + CPPUNIT_ASSERT(output.item() == 110.); + std::cout << "ok\n"; +} diff --git a/PhysicsTools/PyTorch/test/testTorchSimpleDnnCUDA.cc b/PhysicsTools/PyTorch/test/testTorchSimpleDnnCUDA.cc new file mode 100644 index 0000000000000..9f009e3798c27 --- /dev/null +++ b/PhysicsTools/PyTorch/test/testTorchSimpleDnnCUDA.cc @@ -0,0 +1,64 @@ +#include +#include "testBaseCUDA.h" +#include +#include +#include +#include "HeterogeneousCore/CUDAServices/interface/CUDAInterface.h" + +class testSimpleDNNCUDA : public testBasePyTorchCUDA { + CPPUNIT_TEST_SUITE(testSimpleDNNCUDA); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + +public: + std::string pyScript() const override; + void test() override; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(testSimpleDNNCUDA); + +std::string testSimpleDNNCUDA::pyScript() const { return "create_simple_dnn.py"; } + +void testSimpleDNNCUDA::test() { + std::vector psets; + edm::ServiceToken serviceToken = edm::ServiceRegistry::createSet(psets); + edm::ServiceRegistry::Operate operate(serviceToken); + + // Setup the CUDA Service + edmplugin::PluginManager::configure(edmplugin::standard::config()); + + std::string const config = R"_(import FWCore.ParameterSet.Config as cms +process = cms.Process('Test') +process.add_(cms.Service('ResourceInformationService')) +process.add_(cms.Service('CUDAService')) +)_"; + std::unique_ptr params; + edm::makeParameterSets(config, params); + edm::ServiceToken tempToken(edm::ServiceRegistry::createServicesFromConfig(std::move(params))); + edm::ServiceRegistry::Operate operate2(tempToken); + edm::Service cuda; + std::cout << "CUDA service enabled: " << cuda->enabled() << std::endl; + + std::cout << "Testing CUDA backend" << std::endl; + + std::string model_path = dataPath_ + "/simple_dnn.pt"; + torch::Device device(torch::kCUDA); + torch::jit::script::Module module; + try { + // Deserialize the ScriptModule from a file using torch::jit::load(). + module = torch::jit::load(model_path); + module.to(device); + } catch (const c10::Error& e) { + std::cerr << "error loading the model\n" << e.what() << std::endl; + CPPUNIT_ASSERT(false); + } + // Create a vector of inputs. + std::vector inputs; + inputs.push_back(torch::ones(10, device)); + + // Execute the model and turn its output into a tensor. + at::Tensor output = module.forward(inputs).toTensor(); + std::cout << "output: " << output << '\n'; + CPPUNIT_ASSERT(output.item() == 110.); + std::cout << "ok\n"; +} diff --git a/PhysicsTools/PythonAnalysis/test/time_serie_prediction.cpp b/PhysicsTools/PyTorch/test/time_serie_prediction.cpp similarity index 91% rename from PhysicsTools/PythonAnalysis/test/time_serie_prediction.cpp rename to PhysicsTools/PyTorch/test/time_serie_prediction.cpp index efe4b7fb6c1ea..3ff09fc398741 100644 --- a/PhysicsTools/PythonAnalysis/test/time_serie_prediction.cpp +++ b/PhysicsTools/PyTorch/test/time_serie_prediction.cpp @@ -26,7 +26,7 @@ int main(int /*argc*/, char* /*argv*/[]) { std::cout << pair.key() << ": " << pair.value() << std::endl; } - std::cout << net.forward(torch::ones({2, 4})) << std::endl; + std::cout << net.forward(torch::ones({2, 4}).to(device)) << std::endl; return 0; } diff --git a/PhysicsTools/PythonAnalysis/test/BuildFile.xml b/PhysicsTools/PythonAnalysis/test/BuildFile.xml index ca00df05a0690..22e25233e2d5e 100644 --- a/PhysicsTools/PythonAnalysis/test/BuildFile.xml +++ b/PhysicsTools/PythonAnalysis/test/BuildFile.xml @@ -130,11 +130,3 @@ - - - - - - - -