diff --git a/PhysicsTools/PyTorch/BuildFile.xml b/PhysicsTools/PyTorch/BuildFile.xml deleted file mode 100644 index 511f4697bbabe..0000000000000 --- a/PhysicsTools/PyTorch/BuildFile.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/PhysicsTools/PyTorch/test/BuildFile.xml b/PhysicsTools/PyTorch/test/BuildFile.xml index 0e49e06c50c12..dcd42286ea9bd 100644 --- a/PhysicsTools/PyTorch/test/BuildFile.xml +++ b/PhysicsTools/PyTorch/test/BuildFile.xml @@ -34,3 +34,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PhysicsTools/PyTorch/test/create_simple_dnn.py b/PhysicsTools/PyTorch/test/create_simple_dnn.py old mode 100644 new mode 100755 index aeb2a16449f75..4ff9cfe01b45d --- a/PhysicsTools/PyTorch/test/create_simple_dnn.py +++ b/PhysicsTools/PyTorch/test/create_simple_dnn.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import sys import os import torch diff --git a/PhysicsTools/PyTorch/test/test_simple_dnn.py b/PhysicsTools/PyTorch/test/test_simple_dnn.py new file mode 100755 index 0000000000000..dd01f4a1d7ed9 --- /dev/null +++ b/PhysicsTools/PyTorch/test/test_simple_dnn.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import sys +import os +import torch + +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") + +ptfile = os.path.join(datadir, "simple_dnn.pt") +print("loading:", ptfile) + +tm = torch.jit.load(ptfile) +tm.eval() + +# dummy input (same shape used during trace: 10) +x = torch.ones(10) + +# optional: run on gpu if available +device = "cuda" if torch.cuda.is_available() else "cpu" +tm.to(device) +x = x.to(device) + +with torch.no_grad(): + y = tm(x) + +print("ok. output:", y.item()) +print("device:", device) diff --git a/PhysicsTools/PyTorch/test/torch-control-flow.py b/PhysicsTools/PyTorch/test/torch-control-flow.py new file mode 100755 index 0000000000000..106594be6d766 --- /dev/null +++ b/PhysicsTools/PyTorch/test/torch-control-flow.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import random +import torch +import math + + +class DynamicNet(torch.nn.Module): + def __init__(self): + """ + In the constructor we instantiate five parameters and assign them as members. + """ + super().__init__() + self.a = torch.nn.Parameter(torch.randn(())) + self.b = torch.nn.Parameter(torch.randn(())) + self.c = torch.nn.Parameter(torch.randn(())) + self.d = torch.nn.Parameter(torch.randn(())) + self.e = torch.nn.Parameter(torch.randn(())) + + def forward(self, x): + """ + For the forward pass of the model, we randomly choose either 4, 5 + and reuse the e parameter to compute the contribution of these orders. + + Since each forward pass builds a dynamic computation graph, we can use normal + Python control-flow operators like loops or conditional statements when + defining the forward pass of the model. + + Here we also see that it is perfectly safe to reuse the same parameter many + times when defining a computational graph. + """ + y = self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3 + for exp in range(4, random.randint(4, 6)): + y = y + self.e * x ** exp + return y + + def string(self): + """ + Just like any class in Python, you can also define custom method on PyTorch modules + """ + return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3 + {self.e.item()} x^4 ? + {self.e.item()} x^5 ?' + + +# Create Tensors to hold input and outputs. +x = torch.linspace(-math.pi, math.pi, 2000) +y = torch.sin(x) + +# Construct our model by instantiating the class defined above +model = DynamicNet() + +# Construct our loss function and an Optimizer. Training this strange model with +# vanilla stochastic gradient descent is tough, so we use momentum +criterion = torch.nn.MSELoss(reduction='sum') +optimizer = torch.optim.SGD(model.parameters(), lr=1e-8, momentum=0.9) +for t in range(30000): + # Forward pass: Compute predicted y by passing x to the model + y_pred = model(x) + + # Compute and print loss + loss = criterion(y_pred, y) + if t % 2000 == 1999: + print(t, loss.item()) + + # Zero gradients, perform a backward pass, and update the weights. + optimizer.zero_grad() + loss.backward() + optimizer.step() + +print(f'Result: {model.string()}') diff --git a/PhysicsTools/PyTorch/test/torch-cpu-cuda.py b/PhysicsTools/PyTorch/test/torch-cpu-cuda.py new file mode 100755 index 0000000000000..76170efd89b26 --- /dev/null +++ b/PhysicsTools/PyTorch/test/torch-cpu-cuda.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import time +import torch + +def test(device): + print("Testing on", device) + x = torch.rand(8000, 8000, device=device) + y = torch.rand(8000, 8000, device=device) + + torch.cuda.synchronize() if device=="cuda" else None + t0 = time.time() + torch.mm(x, y) + torch.cuda.synchronize() if device=="cuda" else None + print("Time:", time.time()-t0, "seconds") + +if torch.cuda.is_available(): + test("cpu") + test("cuda") +else: + print("no cuda") + exit(1) diff --git a/PhysicsTools/PyTorch/test/torch-cuda.py b/PhysicsTools/PyTorch/test/torch-cuda.py new file mode 100755 index 0000000000000..3e170581ffd0c --- /dev/null +++ b/PhysicsTools/PyTorch/test/torch-cuda.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import torch +print("cuda available:", torch.cuda.is_available()) +print("device count:", torch.cuda.device_count()) +print("current device:", torch.cuda.current_device() if torch.cuda.is_available() else None) +print("device name:", torch.cuda.get_device_name() if torch.cuda.is_available() else None) + +# small compute test +if torch.cuda.is_available(): + x = torch.rand(10000, 10000, device="cuda") + y = torch.rand(10000, 10000, device="cuda") + z = torch.mm(x, y) + print("OK. Computed on CUDA. Result:", z[0][0].item()) +else: + print("NO CUDA") + exit(1) diff --git a/PhysicsTools/PyTorch/test/torch-mini-nn.py b/PhysicsTools/PyTorch/test/torch-mini-nn.py new file mode 100755 index 0000000000000..90df21de50a89 --- /dev/null +++ b/PhysicsTools/PyTorch/test/torch-mini-nn.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +import sys +import torch +import torch.nn as nn +import torch.optim as optim + +device = 'cuda' if torch.cuda.is_available() else 'cpu' +print("device:", device) +if (device == "cpu") and (len(sys.argv) > 1) and (sys.argv[1] != "cpu"): + pritn("Unable to find accelerator",sys.argv[1]) + exit(1) + +# simple fully connected network +model = nn.Sequential( + nn.Linear(1000, 2000), + nn.ReLU(), + nn.Linear(2000, 1) +).to(device) + +# random data +x = torch.randn(5000, 1000, device=device) +y = torch.randn(5000, 1, device=device) + +opt = optim.Adam(model.parameters(), lr=1e-3) + +# train 5 steps +for i in range(5): + opt.zero_grad() + pred = model(x) + loss = ((pred - y)**2).mean() + loss.backward() + opt.step() + print("step:", i, "loss:", loss.item()) + diff --git a/PhysicsTools/PyTorch/test/torch-tensor.py b/PhysicsTools/PyTorch/test/torch-tensor.py new file mode 100755 index 0000000000000..8b2bfbc88c943 --- /dev/null +++ b/PhysicsTools/PyTorch/test/torch-tensor.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +import torch +import math +import sys + +# We want to be able to train our model on an `accelerator `__ +# such as CUDA, MPS, MTIA, or XPU. If the current accelerator is available, we will use it. Otherwise, we use the CPU. + +dtype = torch.float +device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu" +print(f"Using {device} device") +if (device == "cpu") and (len(sys.argv) > 1) and (sys.argv[1] != "cpu"): + pritn("Unable to find accelerator",sys.argv[1]) + exit(1) +torch.set_default_device(device) + +# Create Tensors to hold input and outputs. +# By default, requires_grad=False, which indicates that we do not need to +# compute gradients with respect to these Tensors during the backward pass. +x = torch.linspace(-1, 1, 2000, dtype=dtype) +y = torch.exp(x) # A Taylor expansion would be 1 + x + (1/2) x**2 + (1/3!) x**3 + ... + +# Create random Tensors for weights. For a third order polynomial, we need +# 4 weights: y = a + b x + c x^2 + d x^3 +# Setting requires_grad=True indicates that we want to compute gradients with +# respect to these Tensors during the backward pass. +a = torch.randn((), dtype=dtype, requires_grad=True) +b = torch.randn((), dtype=dtype, requires_grad=True) +c = torch.randn((), dtype=dtype, requires_grad=True) +d = torch.randn((), dtype=dtype, requires_grad=True) + +initial_loss = 1. +learning_rate = 1e-5 +for t in range(5000): + # Forward pass: compute predicted y using operations on Tensors. + y_pred = a + b * x + c * x ** 2 + d * x ** 3 + + # Compute and print loss using operations on Tensors. + # Now loss is a Tensor of shape (1,) + # loss.item() gets the scalar value held in the loss. + loss = (y_pred - y).pow(2).sum() + + # Calculare initial loss, so we can report loss relative to it + if t==0: + initial_loss=loss.item() + + if t % 100 == 99: + print(f'Iteration t = {t:4d} loss(t)/loss(0) = {round(loss.item()/initial_loss, 6):10.6f} a = {a.item():10.6f} b = {b.item():10.6f} c = {c.item():10.6f} d = {d.item():10.6f}') + + # Use autograd to compute the backward pass. This call will compute the + # gradient of loss with respect to all Tensors with requires_grad=True. + # After this call a.grad, b.grad. c.grad and d.grad will be Tensors holding + # the gradient of the loss with respect to a, b, c, d respectively. + loss.backward() + + # Manually update weights using gradient descent. Wrap in torch.no_grad() + # because weights have requires_grad=True, but we don't need to track this + # in autograd. + with torch.no_grad(): + a -= learning_rate * a.grad + b -= learning_rate * b.grad + c -= learning_rate * c.grad + d -= learning_rate * d.grad + + # Manually zero the gradients after updating weights + a.grad = None + b.grad = None + c.grad = None + d.grad = None + +print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x^2 + {d.item()} x^3')