Repository: pfnet-research/pfhedge (language: python)

Issue:
Not accurate calculation of entripic_risk_measure when the value is significantlly big
```
from pfhedge.nn import EntropicRiskMeasure

loss = EntropicRiskMeasure()
input = -torch.arange(4.0)-100
loss(input)
＃tensor(inf)
```
ref.: https://pfnet-research.github.io/pfhedge/generated/pfhedge.nn.EntropicRiskMeasure.html

This result has to be between -100 and -104.
This calculation accuracy can be improved in the same manner as the usual softmax calculation.



Discussion / hints:

Fix patch:
diff --git a/Makefile b/Makefile
index 5a5b447a..4ee0861c 100644
--- a/Makefile
+++ b/Makefile
@@ -13,15 +13,15 @@ test: doctest pytest
 
 .PHONY: doctest
 doctest:
-	$(RUN) pytest --doctest-modules $(PROJECT_NAME)
+	$(RUN) pytest --doctest-modules $(PROJECT_NAME) -m "not gpu"
 
 .PHONY: pytest
 pytest:
-	$(RUN) pytest --doctest-modules tests
+	$(RUN) pytest --doctest-modules tests -m "not gpu"
 
 .PHONY: test-cov
 test-cov:
-	$(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=xml
+	$(RUN) pytest --cov=$(PROJECT_NAME) --cov-report=xml -m "not gpu"
 
 .PHONY: lint
 lint: lint-pysen
diff --git a/pyproject.toml b/pyproject.toml
index 17a009bc..c4ec357a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,6 +30,9 @@ flake8 = "4.0.1"
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
 
+[tool.pytest.ini_options]
+markers = ["gpu: tests using gpu"]
+
 [tool.pysen]
 version = "0.10"
 builder = "lint.py"


Test patch:
diff --git a/tests/_utils/test_bisect.py b/tests/_utils/test_bisect.py
index b1e928de..265868c2 100644
--- a/tests/_utils/test_bisect.py
+++ b/tests/_utils/test_bisect.py
@@ -6,44 +6,77 @@
 from pfhedge._utils.bisect import bisect
 
 
-def test_bisect():
+def test_bisect(device: str = "cpu"):
     f = torch.sigmoid
-    targets = torch.linspace(0.1, 0.9, 10)
-    roots = bisect(f, targets, -6.0, 6.0)
+    targets = torch.linspace(0.1, 0.9, 10).to(device)
+    roots = bisect(
+        f, targets, torch.tensor(-6.0).to(device), torch.tensor(6.0).to(device)
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
     def f(inputs):
         return -torch.sigmoid(inputs)
 
-    targets = -torch.linspace(0.1, 0.9, 10)
-    roots = bisect(f, targets, -6.0, 6.0)
+    targets = -torch.linspace(0.1, 0.9, 10).to(device)
+    roots = bisect(
+        f, targets, torch.tensor(-6.0).to(device), torch.tensor(6.0).to(device)
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
     f = fn.tanhshrink
-    targets = torch.linspace(-0.4, 0.4, 10)
-    roots = bisect(f, targets, -6.0, 6.0, max_iter=100)
+    targets = torch.linspace(-0.4, 0.4, 10).to(device)
+    roots = bisect(
+        f,
+        targets,
+        torch.tensor(-6.0).to(device),
+        torch.tensor(6.0).to(device),
+        max_iter=100,
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
     def f(inputs):
         return -fn.tanhshrink(inputs)
 
-    targets = -torch.linspace(-0.4, 0.4, 10)
-    roots = bisect(f, targets, -6.0, 6.0, max_iter=100)
+    targets = -torch.linspace(-0.4, 0.4, 10).to(device)
+    roots = bisect(
+        f,
+        targets,
+        torch.tensor(-6.0).to(device),
+        torch.tensor(6.0).to(device),
+        max_iter=100,
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
     f = torch.tanh
-    targets = torch.linspace(-0.9, 0.9, 10)
-    roots = bisect(f, targets, -6.0, 6.0, max_iter=100)
+    targets = torch.linspace(-0.9, 0.9, 10).to(device)
+    roots = bisect(
+        f,
+        targets,
+        torch.tensor(-6.0).to(device),
+        torch.tensor(6.0).to(device),
+        max_iter=100,
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
     def f(inputs):
         return -torch.tanh(inputs)
 
-    targets = -torch.linspace(-0.9, 0.9, 10)
-    roots = bisect(f, targets, -6.0, 6.0, max_iter=100)
+    targets = -torch.linspace(-0.9, 0.9, 10).to(device)
+    roots = bisect(
+        f,
+        targets,
+        torch.tensor(-6.0).to(device),
+        torch.tensor(6.0).to(device),
+        max_iter=100,
+    )
     assert_close(f(roots), targets, atol=1e-4, rtol=1e-4)
 
 
+@pytest.mark.gpu
+def test_bisect_gpu():
+    test_bisect(device="cuda")
+
+
 def test_bisect_error():
     f = torch.sigmoid
     with pytest.raises(ValueError):
diff --git a/tests/_utils/test_lazy.py b/tests/_utils/test_lazy.py
index 2bdbdf0b..5d57506a 100644
--- a/tests/_utils/test_lazy.py
+++ b/tests/_utils/test_lazy.py
@@ -1,3 +1,4 @@
+import pytest
 from torch.nn import LazyLinear
 from torch.nn import Linear
 from torch.nn import Sequential
@@ -5,15 +6,20 @@
 from pfhedge._utils.lazy import has_lazy
 
 
-def test_has_lazy():
-    m = LazyLinear(1)
+def test_has_lazy(device: str = "cpu"):
+    m = LazyLinear(1).to(device)
     assert has_lazy(m)
 
-    m = Linear(1, 1)
+    m = Linear(1, 1).to(device)
     assert not has_lazy(m)
 
-    m = Sequential(Linear(1, 1), Linear(1, 1))
+    m = Sequential(Linear(1, 1).to(device), Linear(1, 1).to(device))
     assert not has_lazy(m)
 
-    m = Sequential(Linear(1, 1), LazyLinear(1))
+    m = Sequential(Linear(1, 1).to(device), LazyLinear(1).to(device))
     assert has_lazy(m)
+
+
+@pytest.mark.gpu
+def test_has_lazy_gpu():
+    test_has_lazy(device="cuda")
diff --git a/tests/_utils/test_operations.py b/tests/_utils/test_operations.py
index 95b7fa89..56d5434c 100644
--- a/tests/_utils/test_operations.py
+++ b/tests/_utils/test_operations.py
@@ -1,19 +1,26 @@
+import pytest
 import torch
 
 from pfhedge._utils.operations import ensemble_mean
 
 
 class F:
-    def __init__(self):
+    def __init__(self, device: str = "cpu"):
         self.value = 0.0
+        self.device = device
 
     def f(self) -> float:
         self.value += 1.0
-        return torch.tensor(self.value)
+        return torch.tensor(self.value).to(self.device)
 
 
-def test_ensemble_mean():
-    f = F()
+def test_ensemble_mean(device: str = "cpu"):
+    f = F(device=device)
     result = ensemble_mean(f.f, n_times=10)
-    expect = torch.tensor(5.5)
+    expect = torch.tensor(5.5).to(device)
     assert result == expect
+
+
+@pytest.mark.gpu
+def test_ensemble_mean_gpu():
+    test_ensemble_mean(device="cuda")
diff --git a/tests/_utils/test_parse.py b/tests/_utils/test_parse.py
index 5f136538..46aab6ff 100644
--- a/tests/_utils/test_parse.py
+++ b/tests/_utils/test_parse.py
@@ -7,11 +7,11 @@
 from pfhedge._utils.parse import parse_volatility
 
 
-def test_parse_spot():
+def test_parse_spot(device: str = "cpu"):
     torch.manual_seed(42)
 
-    spot = torch.randn(10).exp()
-    strike = torch.randn(10).exp()
+    spot = torch.randn(10).to(device).exp()
+    strike = torch.randn(10).to(device).exp()
     moneyness = spot / strike
     log_moneyness = moneyness.log()
 
@@ -32,10 +32,15 @@ def test_parse_spot():
         _ = parse_spot(log_moneyness=log_moneyness)
 
 
-def test_parse_volatility():
+@pytest.mark.gpu
+def test_parse_spot_gpu():
+    test_parse_spot(device="cuda")
+
+
+def test_parse_volatility(device: str = "cpu"):
     torch.manual_seed(42)
 
-    volatility = torch.randn(10).exp()
+    volatility = torch.randn(10).to(device).exp()
     variance = volatility.square()
 
     result = parse_volatility(volatility=volatility)
@@ -49,10 +54,15 @@ def test_parse_volatility():
         _ = parse_volatility(spot=volatility)
 
 
-def test_parse_time_to_maturity():
+@pytest.mark.gpu
+def test_parse_volatility_gpu():
+    test_parse_volatility(device="cuda")
+
+
+def test_parse_time_to_maturity(device: str = "cpu"):
     torch.manual_seed(42)
 
-    time_to_maturity = torch.randn(10).exp()
+    time_to_maturity = torch.randn(10).to(device).exp()
 
     result = parse_time_to_maturity(time_to_maturity=time_to_maturity)
     assert_close(result, time_to_maturity)
@@ -61,3 +71,8 @@ def test_parse_time_to_maturity():
         _ = parse_time_to_maturity()
     with pytest.raises(ValueError):
         _ = parse_time_to_maturity(spot=time_to_maturity)
+
+
+@pytest.mark.gpu
+def test_parse_time_to_maturity_gpu():
+    test_parse_time_to_maturity(device="cuda")
diff --git a/tests/features/test_features.py b/tests/features/test_features.py
index f3850a06..5dda2af2 100644
--- a/tests/features/test_features.py
+++ b/tests/features/test_features.py
@@ -27,8 +27,14 @@
 
 
 class _TestFeature:
-    def assert_same_dtype(self, feature, derivative, dtype):
-        derivative.to(dtype).simulate()
+    def assert_same_dtype(
+        self,
+        feature,
+        derivative,
+        dtype,
+        device: str = "cpu",
+    ):
+        derivative.to(dtype=dtype, device=device).simulate()
         f = feature.of(derivative)
         assert f.get(0).dtype == dtype
 
@@ -36,28 +42,28 @@ def assert_same_dtype(self, feature, derivative, dtype):
 class TestMoneyness(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
     @pytest.mark.parametrize("log", [True, False])
-    def test_value(self, strike, log):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
-        spot = torch.arange(1.0, 7.0).reshape(2, 3)
+    def test_value(self, strike, log, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
+        spot = torch.arange(1.0, 7.0).to(device).reshape(2, 3)
         # tensor([[1., 2., 3.],
         #         [4., 5., 6.]])
         derivative.underlier.register_buffer("spot", spot)
         f = Moneyness(log=log).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([[1.0], [4.0]]) / strike
+        expect = torch.tensor([[1.0], [4.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([[2.0], [5.0]]) / strike
+        expect = torch.tensor([[2.0], [5.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([[3.0], [6.0]]) / strike
+        expect = torch.tensor([[3.0], [6.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
@@ -68,175 +74,239 @@ def test_value(self, strike, log):
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    @pytest.mark.parametrize("log", [True, False])
+    def test_value_gpu(self, strike, log):
+        self.test_value(strike, log, device="cuda")
+
     def test_str(self):
         assert str(Moneyness()) == "moneyness"
         assert str(Moneyness(log=True)) == "log_moneyness"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Moneyness(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Moneyness(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Moneyness().of(derivative, hedger)
         assert not f.is_state_dependent()
 
-    # def test_getitem_deprecation_warning(self):
-    #     derivative = EuropeanOption(BrownianStock())
-    #     spot = torch.zeros(2, 3)
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
+    # def test_getitem_deprecation_warning(
+    #     self, device: str = "cpu"
+    # ):
+    #     derivative = EuropeanOption(BrownianStock()).to(device)
+    #     spot = torch.zeros(2, 3).to(device)
     #     derivative.underlier.register_buffer("spot", spot)
     #     f = Moneyness().of(derivative)
-
+    #
     #     with pytest.raises(DeprecationWarning):
     #         _ = f[0]
+    #
+    # @pytest.mark.gpu
+    # def test_getitem_deprecation_warning_gpu(self):
+    #     self.test_getitem_deprecation_warning(device="cuda")
 
     @pytest.mark.filterwarnings("ignore")
-    def test_getitem_get(self):
-        derivative = EuropeanOption(BrownianStock())
-        spot = torch.zeros(2, 3)
+    def test_getitem_get(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        spot = torch.zeros(2, 3).to(device)
         derivative.underlier.register_buffer("spot", spot)
         f = Moneyness().of(derivative)
         assert_close(f.get(0), f[0])
 
+    @pytest.mark.gpu
+    @pytest.mark.filterwarnings("ignore")
+    def test_getitem_get_gpu(self):
+        self.test_getitem_get(device="cuda")
+
 
 class TestLogMoneyness(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
-    def test_value(self, strike):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
-        derivative.ul().register_buffer("spot", torch.arange(1.0, 7.0).reshape(2, 3))
+    def test_value(self, strike, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
+        derivative.ul().register_buffer(
+            "spot", torch.arange(1.0, 7.0).to(device).reshape(2, 3)
+        )
         # tensor([[1., 2., 3.],
         #         [4., 5., 6.]])
         f = LogMoneyness().of(derivative)
 
         result = f.get(0)
-        expect = (torch.tensor([[1.0], [4.0]]) / strike).log()
+        expect = (torch.tensor([[1.0], [4.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = (torch.tensor([[2.0], [5.0]]) / strike).log()
+        expect = (torch.tensor([[2.0], [5.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = (torch.tensor([[3.0], [6.0]]) / strike).log()
+        expect = (torch.tensor([[3.0], [6.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    def test_value_gpu(self, strike):
+        self.test_value(strike, device="cuda")
+
     def test_str(self):
         assert str(LogMoneyness()) == "log_moneyness"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(LogMoneyness(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(LogMoneyness(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = LogMoneyness().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestTimeToMaturity(_TestFeature):
-    def test(self):
-        derivative = EuropeanOption(BrownianStock(dt=0.1), maturity=0.2)
-        derivative.ul().register_buffer("spot", torch.zeros(2, 3))
+    def test(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(dt=0.1), maturity=0.2).to(device)
+        derivative.ul().register_buffer("spot", torch.zeros(2, 3).to(device))
         f = TimeToMaturity().of(derivative)
 
         result = f.get(0)
-        expect = torch.full((2, 1), 0.2)
+        expect = torch.full((2, 1), 0.2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(1)
-        expect = torch.full((2, 1), 0.1)
+        expect = torch.full((2, 1), 0.1).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(2)
-        expect = torch.full((2, 1), 0.0)
+        expect = torch.full((2, 1), 0.0).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get()
-        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]])
+        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]]).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
-    def test_2(self):
-        derivative = EuropeanOption(BrownianStock(dt=0.1), maturity=0.15)
-        derivative.underlier.register_buffer("spot", torch.zeros(2, 3))
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
+    def test_2(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(dt=0.1), maturity=0.15).to(device)
+        derivative.underlier.register_buffer("spot", torch.zeros(2, 3).to(device))
         f = TimeToMaturity().of(derivative)
 
         result = f.get(0)
-        expect = torch.full((2, 1), 0.2)
+        expect = torch.full((2, 1), 0.2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(1)
-        expect = torch.full((2, 1), 0.1)
+        expect = torch.full((2, 1), 0.1).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(2)
-        expect = torch.full((2, 1), 0.0)
+        expect = torch.full((2, 1), 0.0).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get()
-        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]])
+        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]]).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_2_gpu(self):
+        self.test_2(device="cuda")
+
     def test_str(self):
         assert str(TimeToMaturity()) == "time_to_maturity"
         assert str(ExpiryTime()) == "expiry_time"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(TimeToMaturity(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(TimeToMaturity(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = TimeToMaturity().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestVolatility(_TestFeature):
     @pytest.mark.parametrize("sigma", [0.2, 0.1])
-    def test_constant_volatility(self, sigma):
-        derivative = EuropeanOption(BrownianStock(sigma=sigma))
-        derivative.underlier.register_buffer("spot", torch.zeros(2, 3))
+    def test_constant_volatility(self, sigma, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(sigma=sigma)).to(device)
+        derivative.underlier.register_buffer("spot", torch.zeros(2, 3).to(device))
 
         f = Volatility().of(derivative)
 
         result = f.get(0)
-        expect = torch.full((2, 1), sigma)
+        expect = torch.full((2, 1), sigma).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(1)
-        expect = torch.full((2, 1), sigma)
+        expect = torch.full((2, 1), sigma).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get(2)
-        expect = torch.full((2, 1), sigma)
+        expect = torch.full((2, 1), sigma).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f.get()
-        expect = torch.full((2, 3), sigma)
+        expect = torch.full((2, 3), sigma).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
-    def test_stochastic_volatility(self):
-        derivative = EuropeanOption(HestonStock(dt=0.1), maturity=0.2)
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("sigma", [0.2, 0.1])
+    def test_constant_volatility_gpu(self, sigma):
+        self.test_constant_volatility(sigma, device="cuda")
+
+    def test_stochastic_volatility(self, device: str = "cpu"):
+        derivative = EuropeanOption(HestonStock(dt=0.1), maturity=0.2).to(device)
         derivative.simulate(n_paths=2)
         variance = derivative.ul().variance
 
@@ -257,51 +327,69 @@ def test_stochastic_volatility(self):
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_stochastic_volatility_gpu(self):
+        self.test_stochastic_volatility(device="cuda")
+
     def test_str(self):
         assert str(Volatility()) == "volatility"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Volatility(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Volatility(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Volatility().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestVariance(_TestFeature):
     @pytest.mark.parametrize("sigma", [0.2, 0.1])
-    def test_constant_volatility(self, sigma):
-        derivative = EuropeanOption(BrownianStock(sigma=sigma))
-        derivative.underlier.register_buffer("spot", torch.zeros(2, 3))
+    def test_constant_volatility(self, sigma, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(sigma=sigma)).to(device)
+        derivative.underlier.register_buffer("spot", torch.zeros(2, 3).to(device))
 
         f = Variance().of(derivative)
 
         result = f[0]
-        expect = torch.full((2, 1), sigma ** 2)
+        expect = torch.full((2, 1), sigma ** 2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f[1]
-        expect = torch.full((2, 1), sigma ** 2)
+        expect = torch.full((2, 1), sigma ** 2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f[2]
-        expect = torch.full((2, 1), sigma ** 2)
+        expect = torch.full((2, 1), sigma ** 2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
         result = f[None]
-        expect = torch.full((2, 3), sigma ** 2)
+        expect = torch.full((2, 3), sigma ** 2).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
-    def test_stochastic_volatility(self):
-        derivative = EuropeanOption(HestonStock(dt=0.1), maturity=0.2)
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("sigma", [0.2, 0.1])
+    def test_constant_volatility_gpu(self, sigma):
+        self.test_constant_volatility(sigma, device="cuda")
+
+    def test_stochastic_volatility(self, device: str = "cpu"):
+        derivative = EuropeanOption(HestonStock(dt=0.1), maturity=0.2).to(device)
         derivative.simulate(n_paths=2)
         variance = derivative.ul().variance
 
@@ -322,29 +410,42 @@ def test_stochastic_volatility(self):
         expect = expect.unsqueeze(-1)
         assert_close(result, expect, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_stochastic_volatility_gpu(self):
+        self.test_stochastic_volatility(device="cuda")
+
     def test_str(self):
         assert str(Variance()) == "variance"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Variance(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Variance(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Variance().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestPrevHedge(_TestFeature):
     @pytest.mark.parametrize("volatility", [0.2, 0.1])
-    def test(self, volatility):
+    def test(self, volatility, device: str = "cpu"):
         N, T = 10, 20
         torch.manual_seed(42)
-        derivative = EuropeanOption(BrownianStock(volatility))
-        derivative.ul().register_buffer("spot", torch.randn(N, T))
-        hedger = Hedger(Linear(2, 1), ["empty", "empty"])
+        derivative = EuropeanOption(BrownianStock(volatility)).to(device)
+        derivative.ul().register_buffer("spot", torch.randn(N, T).to(device))
+        hedger = Hedger(Linear(2, 1), ["empty", "empty"]).to(device)
 
         f = PrevHedge().of(derivative, hedger)
 
@@ -354,37 +455,50 @@ def test(self, volatility):
             # assert_close(result, expect)
 
         # input = torch.cat([feature[0] for feature in hedger.inputs], dim=-1)
-        input = torch.randn(N, 1, 2)
+        input = torch.randn(N, 1, 2).to(device)
         expect = hedger(input)
         result = f.get(1)
         assert_close(result, expect)
 
         # input = torch.cat([feature[1] for feature in hedger.inputs], dim=-1)
-        input = torch.randn(N, 1, 2)
+        input = torch.randn(N, 1, 2).to(device)
         expect = hedger(input)
         result = f.get(2)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("volatility", [0.2, 0.1])
+    def test_gpu(self, volatility):
+        self.test(volatility, device="cuda")
+
     def test_str(self):
         assert str(PrevHedge()) == "prev_hedge"
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = PrevHedge().of(derivative, hedger)
         assert f.is_state_dependent()
 
-    def test_error_time_step_is_none(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
+    def test_error_time_step_is_none(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = PrevHedge().of(derivative, hedger)
         with pytest.raises(ValueError):
             _ = f.get(None)
 
+    @pytest.mark.gpu
+    def test_error_time_step_is_none_gpu(self):
+        self.test_error_time_step_is_none(device="cuda")
+
 
 class TestBarrier(_TestFeature):
-    def test(self):
-        derivative = EuropeanOption(BrownianStock())
+    def test(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.ul().register_buffer(
             "spot",
             torch.tensor(
@@ -394,39 +508,43 @@ def test(self):
                     [3.0, 4.0, 5.0, 6.0],
                     [1.0, 1.1, 1.2, 1.3],
                 ]
-            ),
+            ).to(device),
         )
         f = Barrier(2.0, up=True).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([0.0, 1.0, 1.0, 0.0])
+        expect = torch.tensor([0.0, 1.0, 1.0, 0.0]).to(device)
         expect = expect.unsqueeze(-1).unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([0.0, 1.0, 1.0, 0.0])
+        expect = torch.tensor([0.0, 1.0, 1.0, 0.0]).to(device)
         expect = expect.unsqueeze(-1).unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([1.0, 1.0, 1.0, 0.0])
+        expect = torch.tensor([1.0, 1.0, 1.0, 0.0]).to(device)
         expect = expect.unsqueeze(-1).unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(3)
-        expect = torch.tensor([1.0, 1.0, 1.0, 0.0])
+        expect = torch.tensor([1.0, 1.0, 1.0, 0.0]).to(device)
         expect = expect.unsqueeze(-1).unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get()
-        expect = torch.tensor(
-            [
-                [0.0, 0.0, 1.0, 1.0],
-                [1.0, 1.0, 1.0, 1.0],
-                [1.0, 1.0, 1.0, 1.0],
-                [0.0, 0.0, 0.0, 0.0],
-            ]
-        ).unsqueeze(-1)
+        expect = (
+            torch.tensor(
+                [
+                    [0.0, 0.0, 1.0, 1.0],
+                    [1.0, 1.0, 1.0, 1.0],
+                    [1.0, 1.0, 1.0, 1.0],
+                    [0.0, 0.0, 0.0, 0.0],
+                ]
+            )
+            .to(device)
+            .unsqueeze(-1)
+        )
         assert_close(result, expect)
 
         derivative = EuropeanOption(BrownianStock())
@@ -439,41 +557,49 @@ def test(self):
                     [6.0, 5.0, 4.0, 3.0],
                     [1.3, 1.2, 1.1, 1.0],
                 ]
-            ),
+            ).to(device),
         )
         f = Barrier(2.0, up=False).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([0.0, 1.0, 0.0, 1.0]).reshape(-1, 1)
+        expect = torch.tensor([0.0, 1.0, 0.0, 1.0]).to(device).reshape(-1, 1)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).reshape(-1, 1)
+        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).to(device).reshape(-1, 1)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).reshape(-1, 1)
+        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).to(device).reshape(-1, 1)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(3)
-        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).reshape(-1, 1)
+        expect = torch.tensor([1.0, 1.0, 0.0, 1.0]).to(device).reshape(-1, 1)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(None)
-        expect = torch.tensor(
-            [
-                [0.0, 1.0, 1.0, 1.0],
-                [1.0, 1.0, 1.0, 1.0],
-                [0.0, 0.0, 0.0, 0.0],
-                [1.0, 1.0, 1.0, 1.0],
-            ]
-        ).unsqueeze(-1)
+        expect = (
+            torch.tensor(
+                [
+                    [0.0, 1.0, 1.0, 1.0],
+                    [1.0, 1.0, 1.0, 1.0],
+                    [0.0, 0.0, 0.0, 0.0],
+                    [1.0, 1.0, 1.0, 1.0],
+                ]
+            )
+            .to(device)
+            .unsqueeze(-1)
+        )
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
     def test_repr(self):
         assert repr(Barrier(1.0, up=True)) == "Barrier(1., up=True)"
         assert repr(Barrier(2.0, up=True)) == "Barrier(2., up=True)"
@@ -481,69 +607,91 @@ def test_repr(self):
         assert repr(Barrier(2.0, up=False)) == "Barrier(2., up=False)"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Barrier(1.0), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Barrier(1.0), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Barrier(1.0).of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestZeros(_TestFeature):
     """
     pfhedge.features.Zeros
     """
 
-    def test(self):
+    def test(self, device: str = "cpu"):
         torch.manual_seed(42)
-        derivative = EuropeanOption(BrownianStock())
-        derivative.ul().register_buffer("spot", torch.zeros(2, 3))
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        derivative.ul().register_buffer("spot", torch.zeros(2, 3).to(device))
 
         f = Zeros().of(derivative)
 
         result = f.get(0)
-        expect = torch.zeros((2, 1))
+        expect = torch.zeros((2, 1)).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.zeros((2, 1))
+        expect = torch.zeros((2, 1)).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.zeros((2, 1))
+        expect = torch.zeros((2, 1)).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(None)
-        expect = torch.zeros((2, 3))
+        expect = torch.zeros((2, 3)).to(device)
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
     def test_str(self):
         assert str(Zeros()) == "zeros"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Zeros(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Zeros(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Zeros().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestEmpty(_TestFeature):
-    def test(self):
+    def test(self, device: str = "cpu"):
         torch.manual_seed(42)
-        derivative = EuropeanOption(BrownianStock())
-        derivative.underlier.register_buffer("spot", torch.zeros(2, 3))
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        derivative.underlier.register_buffer("spot", torch.zeros(2, 3).to(device))
 
         f = Empty().of(derivative)
 
@@ -559,127 +707,177 @@ def test(self):
         result = f.get(None)
         assert result.size() == torch.Size((2, 3, 1))
 
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
     def test_str(self):
         assert str(Empty()) == "empty"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(Empty(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(Empty(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Empty().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestMaxMoneyness(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
     @pytest.mark.parametrize("log", [True, False])
-    def test(self, strike, log):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
+    def test(self, strike, log, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
         derivative.ul().register_buffer(
-            "spot", torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]])
+            "spot",
+            torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]]).to(
+                device
+            ),
         )
 
         f = MaxMoneyness(log=log).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([[1.0], [2.0], [3.0]]) / strike
+        expect = torch.tensor([[1.0], [2.0], [3.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([[2.0], [3.0], [3.0]]) / strike
+        expect = torch.tensor([[2.0], [3.0], [3.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([[2.0], [4.0], [3.0]]) / strike
+        expect = torch.tensor([[2.0], [4.0], [3.0]]).to(device) / strike
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(None)
         expect = (
-            torch.tensor([[1.0, 2.0, 2.0], [2.0, 3.0, 4.0], [3.0, 3.0, 3.0]]) / strike
+            torch.tensor([[1.0, 2.0, 2.0], [2.0, 3.0, 4.0], [3.0, 3.0, 3.0]]).to(device)
+            / strike
         )
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    @pytest.mark.parametrize("log", [True, False])
+    def test_gpu(self, strike, log):
+        self.test(strike, log, device="cuda")
+
     def test_str(self):
         assert str(MaxMoneyness()) == "max_moneyness"
         assert str(MaxMoneyness(log=True)) == "max_log_moneyness"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(MaxMoneyness(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(MaxMoneyness(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = MaxMoneyness().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestMaxLogMoneyness(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
-    def test(self, strike):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
+    def test(self, strike, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
         derivative.ul().register_buffer(
-            "spot", torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]])
+            "spot",
+            torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]]).to(
+                device
+            ),
         )
 
         f = MaxLogMoneyness().of(derivative)
 
         result = f.get(0)
-        expect = (torch.tensor([[1.0], [2.0], [3.0]]) / strike).log()
+        expect = (torch.tensor([[1.0], [2.0], [3.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = (torch.tensor([[2.0], [3.0], [3.0]]) / strike).log()
+        expect = (torch.tensor([[2.0], [3.0], [3.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = (torch.tensor([[2.0], [4.0], [3.0]]) / strike).log()
+        expect = (torch.tensor([[2.0], [4.0], [3.0]]).to(device) / strike).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(None)
         expect = (
-            torch.tensor([[1.0, 2.0, 2.0], [2.0, 3.0, 4.0], [3.0, 3.0, 3.0]]) / strike
+            torch.tensor([[1.0, 2.0, 2.0], [2.0, 3.0, 4.0], [3.0, 3.0, 3.0]]).to(device)
+            / strike
         ).log()
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    def test_gpu(self, strike):
+        self.test(strike, device="cuda")
+
     def test_str(self):
         assert str(MaxLogMoneyness()) == "max_log_moneyness"
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock())
-        self.assert_same_dtype(MaxLogMoneyness(), derivative, dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        self.assert_same_dtype(MaxLogMoneyness(), derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = MaxLogMoneyness().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestModuleOutput(_TestFeature):
-    def test(self):
-        derivative = EuropeanOption(BrownianStock())
+    def test(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
 
-        module = torch.nn.Linear(2, 1)
+        module = torch.nn.Linear(2, 1).to(device)
         x1, x2 = Moneyness(), TimeToMaturity()
         f = ModuleOutput(module, [x1, x2]).of(derivative)
 
@@ -701,6 +899,10 @@ def test(self):
         )
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
     def test_repr(self):
         module = torch.nn.Linear(2, 1)
         x1, x2 = Moneyness(), TimeToMaturity()
@@ -714,24 +916,33 @@ def test_repr(self):
         assert repr(f) == expect
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock()).to(dtype)
-        m = Linear(2, 1).to(derivative.dtype)
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device, dtype)
+        m = Linear(2, 1).to(derivative.device, derivative.dtype)
         f = ModuleOutput(m, [Moneyness(), TimeToMaturity()])
-        self.assert_same_dtype(f, derivative, dtype)
+        self.assert_same_dtype(f, derivative, dtype, device)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
 
-        f = ModuleOutput(Linear(2, 1), [Moneyness(), TimeToMaturity()])
+        f = ModuleOutput(Linear(2, 1).to(device), [Moneyness(), TimeToMaturity()])
         f = f.of(derivative, hedger)
         assert not f.is_state_dependent()
 
-        f = ModuleOutput(Linear(2, 1), [Moneyness(), PrevHedge()])
+        f = ModuleOutput(Linear(2, 1).to(device), [Moneyness(), PrevHedge()])
         f = f.of(derivative, hedger)
         assert f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestFeatureList:
     def test_repr(self):
@@ -747,12 +958,12 @@ def test_len(self):
         f = FeatureList(["empty", "empty"])
         assert len(f) == 2
 
-    def test_value(self):
+    def test_value(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        derivative = EuropeanOption(BrownianStock())
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
-        hedger = Hedger(Naked(), inputs=["empty"])
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
 
         f0 = Moneyness().of(derivative, hedger)
         f1 = ExpiryTime().of(derivative, hedger)
@@ -776,20 +987,24 @@ def test_value(self):
         f1 = PrevHedge().of(derivative, hedger)
         f = FeatureList([f0, f1]).of(derivative, hedger)
 
-        hedger(torch.ones(derivative.ul().spot.size(0), 1, 2))
+        hedger(torch.ones(derivative.ul().spot.size(0), 1, 2).to(device))
         result = f.get(1)
         expect = torch.cat([f0.get(1), f1.get(1)], dim=-1)
         assert_close(result, expect)
 
-        hedger(torch.ones(derivative.ul().spot.size(0), 1, 2))
+        hedger(torch.ones(derivative.ul().spot.size(0), 1, 2).to(device))
         result = f.get(2)
         expect = torch.cat([f0.get(2), f1.get(2)], dim=-1)
         assert_close(result, expect)
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
+    @pytest.mark.gpu
+    def test_value_gpu(self):
+        self.test_value(device="cuda")
+
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
-        hedger = Hedger(Naked(), inputs=["empty"])
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
 
         f = FeatureList(["moneyness", "expiry_time"])
         f = f.of(derivative, hedger)
@@ -801,32 +1016,36 @@ def test_is_state_dependent(self):
 
         assert f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestUnderlierSpot(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
     @pytest.mark.parametrize("log", [True, False])
-    def test_value(self, strike, log):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
-        spot = torch.arange(1.0, 7.0).reshape(2, 3)
+    def test_value(self, strike, log, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
+        spot = torch.arange(1.0, 7.0).to(device).reshape(2, 3)
         # tensor([[1., 2., 3.],
         #         [4., 5., 6.]])
         derivative.underlier.register_buffer("spot", spot)
         f = UnderlierSpot(log=log).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([[1.0], [4.0]])
+        expect = torch.tensor([[1.0], [4.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([[2.0], [5.0]])
+        expect = torch.tensor([[2.0], [5.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([[3.0], [6.0]])
+        expect = torch.tensor([[3.0], [6.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
@@ -837,41 +1056,51 @@ def test_value(self, strike, log):
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    @pytest.mark.parametrize("log", [True, False])
+    def test_value_gpu(self, strike, log):
+        self.test_value(strike, log, device="cuda")
+
     def test_str(self):
         assert str(UnderlierSpot()) == "underlier_spot"
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = UnderlierSpot().of(derivative, hedger)
         assert not f.is_state_dependent()
 
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
+
 
 class TestSpot(_TestFeature):
     @pytest.mark.parametrize("strike", [1.0, 2.0])
     @pytest.mark.parametrize("log", [True, False])
-    def test_value(self, strike, log):
-        derivative = EuropeanOption(BrownianStock(), strike=strike)
-        spot = torch.arange(1.0, 7.0).reshape(2, 3)
+    def test_value(self, strike, log, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=strike).to(device)
+        spot = torch.arange(1.0, 7.0).to(device).reshape(2, 3)
         # tensor([[1., 2., 3.],
         #         [4., 5., 6.]])
         derivative.list(lambda _: spot)
         f = Spot(log=log).of(derivative)
 
         result = f.get(0)
-        expect = torch.tensor([[1.0], [4.0]])
+        expect = torch.tensor([[1.0], [4.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(1)
-        expect = torch.tensor([[2.0], [5.0]])
+        expect = torch.tensor([[2.0], [5.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
         result = f.get(2)
-        expect = torch.tensor([[3.0], [6.0]])
+        expect = torch.tensor([[3.0], [6.0]]).to(device)
         expect = expect.log() if log else expect
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
@@ -882,11 +1111,21 @@ def test_value(self, strike, log):
         expect = expect.unsqueeze(-1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    @pytest.mark.parametrize("log", [True, False])
+    def test_value_gpu(self, strike, log):
+        self.test_value(strike, log, device="cuda")
+
     def test_str(self):
         assert str(Spot()) == "spot"
 
-    def test_is_state_dependent(self):
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), inputs=["empty"])
+    def test_is_state_dependent(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Naked(), inputs=["empty"]).to(device)
         f = Spot().of(derivative, hedger)
         assert not f.is_state_dependent()
+
+    @pytest.mark.gpu
+    def test_is_state_dependent_gpu(self):
+        self.test_is_state_dependent(device="cuda")
diff --git a/tests/instruments/derivative/test_american_binary.py b/tests/instruments/derivative/test_american_binary.py
index 3d4a3453..4026d3ec 100644
--- a/tests/instruments/derivative/test_american_binary.py
+++ b/tests/instruments/derivative/test_american_binary.py
@@ -17,16 +17,18 @@ class TestAmericanBinaryOption:
     def setup_class(cls):
         torch.manual_seed(42)
 
-    def test_payoff(self):
-        derivative = AmericanBinaryOption(BrownianStock(), strike=2.0)
+    def test_payoff(self, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), strike=2.0).to(device)
         derivative.underlier.register_buffer(
             "spot",
             torch.tensor(
                 [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, 1.0, 2.0], [1.9, 2.0, 2.1, 1.0]]
-            ).T,
+            )
+            .to(device)
+            .T,
         )
         result = derivative.payoff()
-        expect = torch.tensor([0.0, 1.0, 1.0, 1.0])
+        expect = torch.tensor([0.0, 1.0, 1.0, 1.0]).to(device)
         assert_close(result, expect)
 
         derivative = AmericanBinaryOption(BrownianStock(), strike=1.0, call=False)
@@ -34,23 +36,36 @@ def test_payoff(self):
             "spot",
             torch.tensor(
                 [[2.0, 2.0, 2.0, 2.0], [2.0, 2.0, 2.0, 1.0], [1.1, 1.0, 0.9, 2.0]]
-            ).T,
+            )
+            .to(device)
+            .T,
         )
         result = derivative.payoff()
-        expect = torch.tensor([0.0, 1.0, 1.0, 1.0])
+        expect = torch.tensor([0.0, 1.0, 1.0, 1.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_payoff_gpu(self):
+        self.test_payoff(device="cuda")
+
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = AmericanBinaryOption(BrownianStock(dtype=dtype))
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(dtype=dtype, device=device))
         assert derivative.dtype == dtype
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
-        derivative = AmericanBinaryOption(BrownianStock()).to(dtype=dtype)
+        derivative = AmericanBinaryOption(BrownianStock()).to(
+            dtype=dtype, device=device
+        )
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
     @pytest.mark.parametrize("device", ["cuda:0", "cuda:1"])
     def test_device(self, device):
         derivative = AmericanBinaryOption(BrownianStock(device=device))
diff --git a/tests/instruments/derivative/test_cliquet.py b/tests/instruments/derivative/test_cliquet.py
index ef29d179..7d69c9f9 100644
--- a/tests/instruments/derivative/test_cliquet.py
+++ b/tests/instruments/derivative/test_cliquet.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 from torch.testing import assert_close
 
@@ -13,31 +14,45 @@ class TestEuropeanOption:
     def setup_class(cls):
         torch.manual_seed(42)
 
-    def test_payoff_start0(self):
-        stock = BrownianStock()
-        derivative = EuropeanForwardStartOption(stock, start=0.0)
-        european = EuropeanOption(stock)
+    def test_payoff_start0(self, device: str = "cpu"):
+        stock = BrownianStock().to(device)
+        derivative = EuropeanForwardStartOption(stock, start=0.0).to(device)
+        european = EuropeanOption(stock).to(device)
         derivative.simulate(n_paths=10)
         assert_close(derivative.payoff(), european.payoff())
 
-    def test_payoff_start_end(self):
-        stock = BrownianStock()
+    @pytest.mark.gpu
+    def test_payoff_start0_gpu(self):
+        self.test_payoff_start0(device="cuda")
+
+    def test_payoff_start_end(self, device: str = "cpu"):
+        stock = BrownianStock().to(device)
         dt = stock.dt
         spot = torch.tensor(
             [[1.0, 2.0, 3.0, 6.0], [1.0, 3.0, 2.0, 6.0], [1.0, 7.0, 1.0, 6.0]]
-        )
-        derivative = EuropeanForwardStartOption(stock, maturity=3 * dt, start=1 * dt)
+        ).to(device)
+        derivative = EuropeanForwardStartOption(
+            stock, maturity=3 * dt, start=1 * dt
+        ).to(device)
         derivative.underlier.register_buffer("spot", spot)
-        assert_close(derivative.payoff(), torch.tensor([2.0, 1.0, 0.0]))
+        assert_close(derivative.payoff(), torch.tensor([2.0, 1.0, 0.0]).to(device))
 
-        derivative = EuropeanForwardStartOption(stock, maturity=3 * dt, start=2 * dt)
+        derivative = EuropeanForwardStartOption(
+            stock, maturity=3 * dt, start=2 * dt
+        ).to(device)
         derivative.underlier.register_buffer("spot", spot)
-        assert_close(derivative.payoff(), torch.tensor([1.0, 2.0, 5.0]))
+        assert_close(derivative.payoff(), torch.tensor([1.0, 2.0, 5.0]).to(device))
 
         # start is rounded off
-        derivative = EuropeanForwardStartOption(stock, maturity=3 * dt, start=2.9 * dt)
+        derivative = EuropeanForwardStartOption(
+            stock, maturity=3 * dt, start=2.9 * dt
+        ).to(device)
         derivative.underlier.register_buffer("spot", spot)
-        assert_close(derivative.payoff(), torch.tensor([1.0, 2.0, 5.0]))
+        assert_close(derivative.payoff(), torch.tensor([1.0, 2.0, 5.0]).to(device))
+
+    @pytest.mark.gpu
+    def test_payoff_start_end_gpu(self):
+        self.test_payoff_start_end(device="cuda")
 
     def test_repr(self):
         derivative = EuropeanForwardStartOption(BrownianStock(), maturity=1.0)
diff --git a/tests/instruments/derivative/test_european.py b/tests/instruments/derivative/test_european.py
index 4389d614..a6cfebbe 100644
--- a/tests/instruments/derivative/test_european.py
+++ b/tests/instruments/derivative/test_european.py
@@ -15,25 +15,41 @@ class TestEuropeanOption:
     def setup_class(cls):
         torch.manual_seed(42)
 
-    def test_payoff(self):
-        derivative = EuropeanOption(BrownianStock(), strike=2.0)
+    def test_payoff(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), strike=2.0).to(device)
         spot = torch.tensor(
             [[1.0, 1.0, 1.9], [1.0, 1.0, 2.0], [1.0, 1.0, 2.1], [1.0, 1.0, 3.0]]
-        )
+        ).to(device)
         derivative.underlier.register_buffer("spot", spot)
         result = derivative.payoff()
-        expect = torch.tensor([0.0, 0.0, 0.1, 1.0])
+        expect = torch.tensor([0.0, 0.0, 0.1, 1.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_payoff_gpu(self):
+        self.test_payoff(device="cuda")
+
     @pytest.mark.parametrize("volatility", [0.20, 0.10])
     @pytest.mark.parametrize("strike", [1.0, 0.5, 2.0])
     @pytest.mark.parametrize("maturity", [0.1, 1.0])
     @pytest.mark.parametrize("n_paths", [100])
     @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9])
-    def test_put_call_parity(self, volatility, strike, maturity, n_paths, init_spot):
-        stock = BrownianStock(volatility)
-        co = EuropeanOption(stock, strike=strike, maturity=maturity, call=True)
-        po = EuropeanOption(stock, strike=strike, maturity=maturity, call=False)
+    def test_put_call_parity(
+        self,
+        volatility,
+        strike,
+        maturity,
+        n_paths,
+        init_spot,
+        device: str = "cpu",
+    ):
+        stock = BrownianStock(volatility).to(device)
+        co = EuropeanOption(stock, strike=strike, maturity=maturity, call=True).to(
+            device
+        )
+        po = EuropeanOption(stock, strike=strike, maturity=maturity, call=False).to(
+            device
+        )
         co.simulate(n_paths=n_paths, init_state=(init_spot,))
         po.simulate(n_paths=n_paths, init_state=(init_spot,))
 
@@ -43,10 +59,23 @@ def test_put_call_parity(self, volatility, strike, maturity, n_paths, init_spot)
 
         assert ((c - p) == s - strike).all()
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("volatility", [0.20, 0.10])
+    @pytest.mark.parametrize("strike", [1.0, 0.5, 2.0])
+    @pytest.mark.parametrize("maturity", [0.1, 1.0])
+    @pytest.mark.parametrize("n_paths", [100])
+    @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9])
+    def test_put_call_parity_gpu(
+        self, volatility, strike, maturity, n_paths, init_spot
+    ):
+        self.test_put_call_parity(
+            volatility, strike, maturity, n_paths, init_spot, device="cuda"
+        )
+
     @pytest.mark.parametrize("strike", [1.0, 2.0])
-    def test_moneyness(self, strike):
-        stock = BrownianStock()
-        derivative = EuropeanOption(stock, strike=strike)
+    def test_moneyness(self, strike, device: str = "cpu"):
+        stock = BrownianStock().to(device)
+        derivative = EuropeanOption(stock, strike=strike).to(device)
         derivative.simulate()
 
         result = derivative.moneyness()
@@ -65,83 +94,110 @@ def test_moneyness(self, strike):
         expect = (stock.spot[:, [0]] / strike).log()
         assert_close(result, expect)
 
-    def test_max_log_moneyness(self):
-        derivative = EuropeanOption(BrownianStock())
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("strike", [1.0, 2.0])
+    def test_moneyness_gpu(self, strike):
+        self.test_moneyness(strike, device="cuda")
+
+    def test_max_log_moneyness(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
 
         result = derivative.max_log_moneyness(10)
         expect = derivative.max_moneyness(10).log()
         assert_close(result, expect)
 
-    def test_time_to_maturity(self):
-        stock = BrownianStock(dt=0.1)
-        derivative = EuropeanOption(stock, maturity=0.2)
+    @pytest.mark.gpu
+    def test_max_log_moneyness_gpu(self):
+        self.test_max_log_moneyness(device="cuda")
+
+    def test_time_to_maturity(self, device: str = "cpu"):
+        stock = BrownianStock(dt=0.1).to(device)
+        derivative = EuropeanOption(stock, maturity=0.2).to(device)
         derivative.simulate(n_paths=2)
 
         result = derivative.time_to_maturity()
-        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]])
+        expect = torch.tensor([[0.2, 0.1, 0.0], [0.2, 0.1, 0.0]]).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(0)
-        expect = torch.full((2, 1), 0.2)
+        expect = torch.full((2, 1), 0.2).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(1)
-        expect = torch.full((2, 1), 0.1)
+        expect = torch.full((2, 1), 0.1).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(-1)
-        expect = torch.full((2, 1), 0.0)
+        expect = torch.full((2, 1), 0.0).to(device)
         assert_close(result, expect, check_stride=False)
 
-    def test_time_to_maturity_2(self):
-        stock = BrownianStock(dt=0.1)
-        derivative = EuropeanOption(stock, maturity=0.25)
+    @pytest.mark.gpu
+    def test_time_to_maturity_gpu(self):
+        self.test_time_to_maturity(device="cuda")
+
+    def test_time_to_maturity_2(self, device: str = "cpu"):
+        stock = BrownianStock(dt=0.1).to(device)
+        derivative = EuropeanOption(stock, maturity=0.25).to(device)
         derivative.simulate(n_paths=2)
 
         result = derivative.time_to_maturity()
-        expect = torch.tensor([[0.3, 0.2, 0.1, 0.0], [0.3, 0.2, 0.1, 0.0]])
+        expect = torch.tensor([[0.3, 0.2, 0.1, 0.0], [0.3, 0.2, 0.1, 0.0]]).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(0)
-        expect = torch.full((2, 1), 0.3)
+        expect = torch.full((2, 1), 0.3).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(1)
-        expect = torch.full((2, 1), 0.2)
+        expect = torch.full((2, 1), 0.2).to(device)
         assert_close(result, expect, check_stride=False)
 
         result = derivative.time_to_maturity(-1)
-        expect = torch.full((2, 1), 0.0)
+        expect = torch.full((2, 1), 0.0).to(device)
         assert_close(result, expect, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_time_to_maturity_2_gpu(self):
+        self.test_time_to_maturity_2(device="cuda")
+
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_init_dtype(self, dtype):
-        derivative = EuropeanOption(BrownianStock(dtype=dtype))
+    def test_init_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(dtype=dtype, device=device))
         assert derivative.dtype == dtype
 
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_to_dtype(self, dtype):
+    def test_init_dtype_gpu(self, dtype):
+        self.test_init_dtype(dtype, device="cuda")
+
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_to_dtype(self, dtype, device: str = "cpu"):
         # to(dtype)
-        derivative = EuropeanOption(BrownianStock()).to(dtype)
+        derivative = EuropeanOption(BrownianStock()).to(dtype).to(device)
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
         # to(instrument)
-        instrument = BrownianStock(dtype=dtype)
-        derivative = EuropeanOption(BrownianStock()).to(instrument)
+        instrument = BrownianStock(dtype=dtype).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(instrument).to(device)
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
-        instrument = EuropeanOption(BrownianStock(dtype=dtype))
-        instrument = BrownianStock(dtype=dtype)
-        derivative = EuropeanOption(BrownianStock()).to(instrument)
+        instrument = EuropeanOption(BrownianStock(dtype=dtype)).to(device)
+        instrument = BrownianStock(dtype=dtype).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(instrument).to(device)
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_to_dtype_gpu(self, dtype):
+        self.test_to_dtype(dtype, device="cuda")
+
     @pytest.mark.parametrize("device", ["cpu", "cuda:0", "cuda:1"])
     def test_init_device(self, device):
         # init
@@ -207,11 +263,11 @@ def test_repr(self):
 )"""
         assert repr(derivative) == expect
 
-    def test_spot_not_listed(self):
-        derivative = EuropeanOption(BrownianStock())
+    def test_spot_not_listed(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock()).to(device)
         with pytest.raises(ValueError):
             _ = derivative.spot
-        spot = torch.arange(1.0, 7.0).reshape(2, 3)
+        spot = torch.arange(1.0, 7.0).to(device).reshape(2, 3)
         # tensor([[1., 2., 3.],
         #         [4., 5., 6.]])
         derivative.list(lambda _: spot)
@@ -220,20 +276,29 @@ def test_spot_not_listed(self):
         with pytest.raises(ValueError):
             _ = derivative.spot
 
-    def test_us_listed(self):
-        derivative = EuropeanOption(BrownianStock())
+    @pytest.mark.gpu
+    def test_spot_not_listed_gpu(self):
+        self.test_spot_not_listed(device="cuda")
+
+    def test_us_listed(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(device=device))
         assert not derivative.is_listed
         derivative.list(pricer=lambda x: x)
         assert derivative.is_listed
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_us_listed_gpu(self, dtype):
+        self.test_us_listed(device="cuda")
+
     def test_init_dtype_deprecated(self):
         with pytest.raises(DeprecationWarning):
             _ = EuropeanOption(BrownianStock(), dtype=torch.float64)
 
-    def test_clause(self):
+    def test_clause(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        derivative = cls(BrownianStock())
+        derivative = cls(BrownianStock()).to(device)
         derivative.simulate()
         strike = derivative.ul().spot.max(-1).values.mean()
 
@@ -248,8 +313,12 @@ def knockout(derivative: BaseDerivative, payoff: Tensor) -> Tensor:
         expect = derivative.payoff_fn().where(max >= strike, torch.zeros_like(max))
         assert_close(result, expect)
 
-    def test_add_clause_error(self):
-        derivative = cls(BrownianStock())
+    @pytest.mark.gpu
+    def test_clause_gpu(self):
+        self.test_clause(device="cuda")
+
+    def test_add_clause_error(self, device: str = "cpu"):
+        derivative = cls(BrownianStock()).to(device)
 
         def knockout(derivative: BaseDerivative, payoff: Tensor) -> Tensor:
             max = derivative.ul().spot.max(-1).values
@@ -263,3 +332,7 @@ def knockout(derivative: BaseDerivative, payoff: Tensor) -> Tensor:
             derivative.add_clause("a.b", knockout)
         with pytest.raises(KeyError):
             derivative.add_clause("", knockout)
+
+    @pytest.mark.gpu
+    def test_add_clause_error_gpu(self):
+        self.test_add_clause_error(device="cuda")
diff --git a/tests/instruments/derivative/test_european_binary.py b/tests/instruments/derivative/test_european_binary.py
index 551724db..9089e102 100644
--- a/tests/instruments/derivative/test_european_binary.py
+++ b/tests/instruments/derivative/test_european_binary.py
@@ -13,30 +13,48 @@ class TestEuropeanBinaryOption:
     def setup_class(cls):
         torch.manual_seed(42)
 
-    def test_payoff(self):
-        derivative = EuropeanBinaryOption(BrownianStock(), strike=2.0)
+    def test_payoff(self, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), strike=2.0).to(device)
         derivative.underlier.register_buffer(
             "spot",
             torch.tensor(
                 [[1.0, 1.0, 1.0, 1.0], [3.0, 1.0, 1.0, 1.0], [1.9, 2.0, 2.1, 3.0]]
-            ).T,
+            )
+            .to(device)
+            .T,
         )
         result = derivative.payoff()
-        expect = torch.tensor([0.0, 1.0, 1.0, 1.0])
+        expect = torch.tensor([0.0, 1.0, 1.0, 1.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_payoff_gpu(self):
+        self.test_payoff(device="cuda")
+
     @pytest.mark.parametrize("volatility", [0.20, 0.10])
     @pytest.mark.parametrize("strike", [1.0, 0.5, 2.0])
     @pytest.mark.parametrize("maturity", [0.1, 1.0])
     @pytest.mark.parametrize("n_paths", [100])
     @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9])
-    def test_parity(self, volatility, strike, maturity, n_paths, init_spot):
+    def test_parity(
+        self,
+        volatility,
+        strike,
+        maturity,
+        n_paths,
+        init_spot,
+        device: str = "cpu",
+    ):
         """
         Test put-call parity.
         """
-        stock = BrownianStock(volatility)
-        co = EuropeanBinaryOption(stock, strike=strike, maturity=maturity, call=True)
-        po = EuropeanBinaryOption(stock, strike=strike, maturity=maturity, call=False)
+        stock = BrownianStock(volatility).to(device)
+        co = EuropeanBinaryOption(
+            stock, strike=strike, maturity=maturity, call=True
+        ).to(device)
+        po = EuropeanBinaryOption(
+            stock, strike=strike, maturity=maturity, call=False
+        ).to(device)
         co.simulate(n_paths=n_paths, init_state=(init_spot,))
         po.simulate(n_paths=n_paths, init_state=(init_spot,))
 
@@ -45,17 +63,33 @@ def test_parity(self, volatility, strike, maturity, n_paths, init_spot):
 
         assert (c + p == 1.0).all()
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("volatility", [0.20, 0.10])
+    @pytest.mark.parametrize("strike", [1.0, 0.5, 2.0])
+    @pytest.mark.parametrize("maturity", [0.1, 1.0])
+    @pytest.mark.parametrize("n_paths", [100])
+    @pytest.mark.parametrize("init_spot", [1.0, 1.1, 0.9])
+    def test_parity_gpu(self, volatility, strike, maturity, n_paths, init_spot):
+        self.test_parity(
+            volatility, strike, maturity, n_paths, init_spot, device="cuda"
+        )
+
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = EuropeanBinaryOption(BrownianStock(dtype=dtype))
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(dtype=dtype)).to(device)
         assert derivative.dtype == dtype
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
-        derivative = EuropeanBinaryOption(BrownianStock()).to(dtype=dtype)
+        derivative = EuropeanBinaryOption(BrownianStock()).to(dtype=dtype).to(device)
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
     @pytest.mark.parametrize("device", ["cuda:0", "cuda:1"])
     def test_device(self, device):
         derivative = EuropeanBinaryOption(BrownianStock(device=device))
diff --git a/tests/instruments/derivative/test_lookback.py b/tests/instruments/derivative/test_lookback.py
index ad67d198..6dedc620 100644
--- a/tests/instruments/derivative/test_lookback.py
+++ b/tests/instruments/derivative/test_lookback.py
@@ -17,36 +17,55 @@ class TestLookbackOption:
     def setup_class(cls):
         torch.manual_seed(42)
 
-    def test_payoff(self):
-        derivative = LookbackOption(BrownianStock(), strike=3.0)
+    def test_payoff(self, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), strike=3.0).to(device)
         derivative.ul().register_buffer(
-            "spot", torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]])
+            "spot",
+            torch.tensor([[1.0, 2.0, 1.5], [2.0, 3.0, 4.0], [3.0, 2.0, 1.0]]).to(
+                device
+            ),
         )
         # max [2.0, 4.0, 3.0]
         result = derivative.payoff()
-        expect = torch.tensor([0.0, 1.0, 0.0])
+        expect = torch.tensor([0.0, 1.0, 0.0]).to(device)
         assert_close(result, expect)
 
-    def test_payoff_put(self):
-        derivative = LookbackOption(BrownianStock(), strike=3.0, call=False)
+    @pytest.mark.gpu
+    def test_payoff_gpu(self):
+        self.test_payoff(device="cuda")
+
+    def test_payoff_put(self, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), strike=3.0, call=False).to(device)
         derivative.ul().register_buffer(
-            "spot", torch.tensor([[3.0, 2.0, 2.5], [6.0, 5.0, 4.0], [3.0, 4.0, 5.0]])
+            "spot",
+            torch.tensor([[3.0, 2.0, 2.5], [6.0, 5.0, 4.0], [3.0, 4.0, 5.0]]).to(
+                device
+            ),
         )
         # min [2.0, 4.0, 3.0]
         result = derivative.payoff()
-        expect = torch.tensor([1.0, 0.0, 0.0])
+        expect = torch.tensor([1.0, 0.0, 0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_payoff_put_gpu(self):
+        self.test_payoff_put(device="cuda")
+
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_dtype(self, dtype):
-        derivative = LookbackOption(BrownianStock(dtype=dtype))
+    def test_dtype(self, dtype, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(dtype=dtype, device=device))
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
-        derivative = LookbackOption(BrownianStock()).to(dtype=dtype)
+        derivative = LookbackOption(BrownianStock()).to(dtype=dtype, device=device)
         derivative.simulate()
         assert derivative.payoff().dtype == dtype
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_dtype_gpu(self, dtype):
+        self.test_dtype(dtype, device="cuda")
+
     def test_repr(self):
         derivative = LookbackOption(BrownianStock(), maturity=1.0)
         expect = """\
diff --git a/tests/instruments/derivative/test_variance_swap.py b/tests/instruments/derivative/test_variance_swap.py
index cbb0c4c6..e4daf744 100644
--- a/tests/instruments/derivative/test_variance_swap.py
+++ b/tests/instruments/derivative/test_variance_swap.py
@@ -12,16 +12,16 @@
 
 
 class TestVarianceSwap:
-    def test_payoff(self):
-        derivative = VarianceSwap(BrownianStock(), strike=0.04)
-        derivative.ul().register_buffer("spot", torch.ones(2, 3))
+    def test_payoff(self, device: str = "cpu"):
+        derivative = VarianceSwap(BrownianStock(), strike=0.04).to(device)
+        derivative.ul().register_buffer("spot", torch.ones(2, 3).to(device))
         result = derivative.payoff()
         expect = torch.full_like(result, -0.04)
         assert_close(result, expect)
 
-        derivative = VarianceSwap(BrownianStock(dt=0.01), strike=0)
+        derivative = VarianceSwap(BrownianStock(dt=0.01), strike=0).to(device)
         var = 0.04
-        log_return = torch.full((2, 10), sqrt(var * derivative.ul().dt))
+        log_return = torch.full((2, 10), sqrt(var * derivative.ul().dt)).to(device)
         log_return[:, 0] = 0.0
         spot = log_return.cumsum(-1).exp()
         derivative.ul().register_buffer("spot", spot)
@@ -36,6 +36,10 @@ def test_payoff(self):
         expect = torch.full_like(result, -0.04)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_payoff_gpu(self):
+        self.test_payoff(device="cuda")
+
     def test_repr(self):
         derivative = VarianceSwap(BrownianStock())
 
diff --git a/tests/instruments/primary/test_brownian.py b/tests/instruments/primary/test_brownian.py
index ad0f829c..6a40ae90 100644
--- a/tests/instruments/primary/test_brownian.py
+++ b/tests/instruments/primary/test_brownian.py
@@ -47,11 +47,11 @@ def test_register_buffer(self):
         with pytest.raises(TypeError):
             s.register_buffer("a", torch.nn.ReLU())
 
-    def test_buffers(self):
+    def test_buffers(self, device: str = "cpu"):
         torch.manual_seed(42)
-        a = torch.randn(10)
-        b = torch.randn(10)
-        c = torch.randn(10)
+        a = torch.randn(10).to(device)
+        b = torch.randn(10).to(device)
+        c = torch.randn(10).to(device)
 
         s = BrownianStock()
         s.register_buffer("a", a)
@@ -66,6 +66,10 @@ def test_buffers(self):
         expect = [a, b, c]
         assert result == expect
 
+    @pytest.mark.gpu
+    def test_buffers_gpu(self):
+        self.test_buffers(device="cuda")
+
     def test_buffer_attribute_error(self):
         class MyPrimary(BasePrimary):
             # Primary without super().__init__()
@@ -79,55 +83,60 @@ def simulate(self):
             MyPrimary().simulate()
 
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_init_dtype(self, dtype):
-        s = BrownianStock(dtype=dtype)
+    def test_init_dtype(self, dtype, device: str = "cpu"):
+        s = BrownianStock(dtype=dtype, device=device)
         s.simulate()
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_init_dtype_gpu(self, dtype):
+        self.test_init_dtype(dtype, device="cuda")
+
     @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
-    def test_to_dtype(self, dtype):
+    def test_to_dtype(self, dtype, device: str = "cpu"):
         # to(dtype) before simulate()
-        s = BrownianStock().to(dtype)
+        s = BrownianStock().to(dtype=dtype, device=device)
         s.simulate()
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
         # to(dtype) after simulate()
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
-        s.to(dtype)
+        s.to(dtype=dtype)
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
         # to(instrument) before simulate()
-        instrument = BrownianStock(dtype=dtype)
+        instrument = BrownianStock(dtype=dtype, device=device)
         s = BrownianStock().to(instrument)
         s.simulate()
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
-        instrument = BrownianStock(dtype=dtype)
+        instrument = BrownianStock(dtype=dtype, device=device)
         s = BrownianStock().to(instrument=instrument)  # Use kwargs
         s.simulate()
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
-        instrument = EuropeanOption(BrownianStock(dtype=dtype))
+        instrument = EuropeanOption(BrownianStock(dtype=dtype, device=device))
         s = BrownianStock().to(instrument)
         s.simulate()
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
         # to(instrument) after simulate()
-        instrument = BrownianStock(dtype=dtype)
+        instrument = BrownianStock(dtype=dtype, device=device)
         s = BrownianStock()
         s.simulate()
         s.to(instrument)
         assert s.dtype == dtype
         assert s.spot.dtype == dtype
 
-        instrument = EuropeanOption(BrownianStock(dtype=dtype))
+        instrument = EuropeanOption(BrownianStock(dtype=dtype, device=device))
         s = BrownianStock()
         s.simulate()
         s.to(instrument)
@@ -140,60 +149,69 @@ def test_to_dtype(self, dtype):
 
         # Aliases
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.double()
         assert s.dtype == torch.float64
         assert s.spot.dtype == torch.float64
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.float64()
         assert s.dtype == torch.float64
         assert s.spot.dtype == torch.float64
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.float()
         assert s.dtype == torch.float32
         assert s.spot.dtype == torch.float32
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.float32()
         assert s.dtype == torch.float32
         assert s.spot.dtype == torch.float32
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.half()
         assert s.dtype == torch.float16
         assert s.spot.dtype == torch.float16
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.float16()
         assert s.dtype == torch.float16
         assert s.spot.dtype == torch.float16
 
-        s = BrownianStock()
+        s = BrownianStock().to(device)
         s.simulate()
         s.bfloat16()
         assert s.dtype == torch.bfloat16
         assert s.spot.dtype == torch.bfloat16
 
-    def test_simulate_shape(self):
-        s = BrownianStock(dt=0.1)
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
+    def test_to_dtype_gpu(self, dtype):
+        self.test_to_dtype(dtype, device="cuda")
+
+    def test_simulate_shape(self, device: str = "cpu"):
+        s = BrownianStock(dt=0.1).to(device)
         s.simulate(time_horizon=0.2, n_paths=10)
         assert s.spot.size() == torch.Size((10, 3))
 
-        s = BrownianStock(dt=0.1)
+        s = BrownianStock(dt=0.1).to(device)
         s.simulate(time_horizon=0.25, n_paths=10)
         assert s.spot.size() == torch.Size((10, 4))
 
+    @pytest.mark.gpu
+    def test_simulate_shape_gpu(self):
+        self.test_simulate_shape(device="cuda")
+
     @pytest.mark.parametrize("sigma", [0.2, 0.1])
-    def test_volatility(self, sigma):
-        s = BrownianStock(sigma=sigma)
+    def test_volatility(self, sigma, device: str = "cpu"):
+        s = BrownianStock(sigma=sigma).to(device)
         s.simulate()
         result = s.volatility
         expect = torch.full_like(s.spot, sigma)
@@ -203,6 +221,11 @@ def test_volatility(self, sigma):
         expect = torch.full_like(s.spot, sigma ** 2)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("sigma", [0.2, 0.1])
+    def test_volatility_gpu(self, sigma):
+        self.test_volatility(sigma, device="cuda")
+
     def test_init_device(self):
         s = BrownianStock(device=torch.device("cuda:0"))
         assert s.cpu().device == torch.device("cpu")
diff --git a/tests/instruments/primary/test_cir.py b/tests/instruments/primary/test_cir.py
index cddd9db6..9fc8ae8c 100644
--- a/tests/instruments/primary/test_cir.py
+++ b/tests/instruments/primary/test_cir.py
@@ -6,25 +6,34 @@
 
 class TestCIRRate:
     @pytest.mark.parametrize("seed", range(1))
-    def test_values_are_finite(self, seed):
+    def test_values_are_finite(self, seed, device: str = "cpu"):
         torch.manual_seed(seed)
 
-        s = CIRRate()
+        s = CIRRate().to(device)
         s.simulate(n_paths=1000)
 
         assert not s.spot.isnan().any()
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("seed", range(1))
+    def test_values_are_finite_gpu(self, seed):
+        self.test_values_are_finite(seed, device="cuda")
+
     def test_repr(self):
         s = CIRRate(cost=1e-4)
         expect = """\
 CIRRate(kappa=1., theta=0.0400, sigma=0.2000, cost=1.0000e-04, dt=0.0040)"""
         assert repr(s) == expect
 
-    def test_simulate_shape(self):
-        s = CIRRate(dt=0.1)
+    def test_simulate_shape(self, device: str = "cpu"):
+        s = CIRRate(dt=0.1).to(device)
         s.simulate(time_horizon=0.2, n_paths=10)
         assert s.spot.size() == torch.Size((10, 3))
 
-        s = CIRRate(dt=0.1)
+        s = CIRRate(dt=0.1).to(device)
         s.simulate(time_horizon=0.25, n_paths=10)
         assert s.spot.size() == torch.Size((10, 4))
+
+    @pytest.mark.gpu
+    def test_simulate_shape_gpu(self):
+        self.test_simulate_shape(device="cuda")
diff --git a/tests/instruments/primary/test_heston.py b/tests/instruments/primary/test_heston.py
index f4112f28..b2e4eda7 100644
--- a/tests/instruments/primary/test_heston.py
+++ b/tests/instruments/primary/test_heston.py
@@ -6,27 +6,36 @@
 
 class TestHestonStock:
     @pytest.mark.parametrize("seed", range(1))
-    def test_values_are_finite(self, seed):
+    def test_values_are_finite(self, seed, device: str = "cpu"):
         torch.manual_seed(seed)
 
-        s = HestonStock()
+        s = HestonStock().to(device)
         s.simulate(n_paths=1000)
 
         assert not s.variance.isnan().any()
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("seed", range(1))
+    def test_values_are_finite_gpu(self, seed):
+        self.test_values_are_finite(seed, device="cuda")
+
     def test_repr(self):
         s = HestonStock(cost=1e-4)
         expect = "HestonStock(\
 kappa=1., theta=0.0400, sigma=0.2000, rho=-0.7000, cost=1.0000e-04, dt=0.0040)"
         assert repr(s) == expect
 
-    def test_simulate_shape(self):
-        s = HestonStock(dt=0.1)
+    def test_simulate_shape(self, device: str = "cpu"):
+        s = HestonStock(dt=0.1).to(device)
         s.simulate(time_horizon=0.2, n_paths=10)
         assert s.spot.size() == torch.Size((10, 3))
         assert s.variance.size() == torch.Size((10, 3))
 
-        s = HestonStock(dt=0.1)
+        s = HestonStock(dt=0.1).to(device)
         s.simulate(time_horizon=0.25, n_paths=10)
         assert s.spot.size() == torch.Size((10, 4))
         assert s.variance.size() == torch.Size((10, 4))
+
+    @pytest.mark.gpu
+    def test_simulate_shape_gpu(self):
+        self.test_simulate_shape(device="cuda")
diff --git a/tests/instruments/primary/test_local_volatility.py b/tests/instruments/primary/test_local_volatility.py
index 3741e71d..e3e4d74b 100644
--- a/tests/instruments/primary/test_local_volatility.py
+++ b/tests/instruments/primary/test_local_volatility.py
@@ -1,27 +1,33 @@
+import pytest
 import torch
 from torch.testing import assert_close
 
 from pfhedge.instruments import LocalVolatilityStock
 
 
-def test_local_volatility_zero_volatility():
+def test_local_volatility_zero_volatility(device: str = "cpu"):
     def zeros(time, spot):
         return torch.zeros_like(time)
 
-    stock = LocalVolatilityStock(zeros)
+    stock = LocalVolatilityStock(zeros).to(device)
     stock.simulate()
 
     assert_close(stock.spot, torch.ones_like(stock.spot))
     assert_close(stock.volatility, torch.zeros_like(stock.volatility))
 
 
-def test_local_volatility():
+@pytest.mark.gpu
+def test_local_volatility_zero_volatility_gpu():
+    test_local_volatility_zero_volatility(device="cuda")
+
+
+def test_local_volatility(device: str = "cpu"):
     def zeros(time, spot):
         zero = torch.zeros_like(time)
         nonzero = torch.full_like(time, 0.2)
         return torch.where(time > 10 / 250, zero, nonzero)
 
-    stock = LocalVolatilityStock(zeros)
+    stock = LocalVolatilityStock(zeros).to(device)
     stock.simulate()
 
     result = stock.spot[:, 11:]
@@ -31,3 +37,8 @@ def zeros(time, spot):
     result = stock.volatility[:, 10:]
     expect = torch.zeros_like(stock.volatility[:, 10:])
     assert_close(result, expect)
+
+
+@pytest.mark.gpu
+def test_local_volatility_gpu():
+    test_local_volatility(device="cuda")
diff --git a/tests/instruments/primary/test_vasicek.py b/tests/instruments/primary/test_vasicek.py
index 31351be8..3c60e549 100644
--- a/tests/instruments/primary/test_vasicek.py
+++ b/tests/instruments/primary/test_vasicek.py
@@ -6,25 +6,34 @@
 
 class TestVasicekRate:
     @pytest.mark.parametrize("seed", range(1))
-    def test_values_are_finite(self, seed):
+    def test_values_are_finite(self, seed, device: str = "cpu"):
         torch.manual_seed(seed)
 
-        s = VasicekRate()
+        s = VasicekRate().to(device)
         s.simulate(n_paths=1000)
 
         assert not s.spot.isnan().any()
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("seed", range(1))
+    def test_values_are_finite_gpu(self, seed):
+        self.test_values_are_finite(seed, device="cuda")
+
     def test_repr(self):
         s = VasicekRate(cost=1e-4)
         expect = """\
 VasicekRate(kappa=1., theta=0.0400, sigma=0.0400, cost=1.0000e-04, dt=0.0040)"""
         assert repr(s) == expect
 
-    def test_simulate_shape(self):
-        s = VasicekRate(dt=0.1)
+    def test_simulate_shape(self, device: str = "cpu"):
+        s = VasicekRate(dt=0.1).to(device)
         s.simulate(time_horizon=0.2, n_paths=10)
         assert s.spot.size() == torch.Size((10, 3))
 
-        s = VasicekRate(dt=0.1)
+        s = VasicekRate(dt=0.1).to(device)
         s.simulate(time_horizon=0.25, n_paths=10)
         assert s.spot.size() == torch.Size((10, 4))
+
+    @pytest.mark.gpu
+    def test_simulate_shape_gpu(self):
+        self.test_simulate_shape(device="cuda")
diff --git a/tests/nn/modules/bs/_base.py b/tests/nn/modules/bs/_base.py
index 37ebc658..f70e09a1 100644
--- a/tests/nn/modules/bs/_base.py
+++ b/tests/nn/modules/bs/_base.py
@@ -2,7 +2,7 @@
 
 
 class _TestBSModule:
-    def _assert_shape(self, module, method_name):
+    def _assert_shape(self, module, method_name, device: str = "cpu"):
         method = getattr(module, method_name)
 
         N = 2
@@ -10,47 +10,47 @@ def _assert_shape(self, module, method_name):
         M_2 = 11
         H_in = len(module.inputs())
 
-        input = torch.zeros((N, H_in))
+        input = torch.zeros((N, H_in)).to(device)
         out = method(*(input[..., i] for i in range(H_in)))
         assert out.size() == torch.Size((N,))
 
-        input = torch.zeros((N, M_1, H_in))
+        input = torch.zeros((N, M_1, H_in)).to(device)
         out = method(*(input[..., i] for i in range(H_in)))
         assert out.size() == torch.Size((N, M_1))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
         out = method(*(input[..., i] for i in range(H_in)))
         assert out.size() == torch.Size((N, M_1, M_2))
 
-    def assert_shape_delta(self, module):
-        self._assert_shape(module, "delta")
+    def assert_shape_delta(self, module, device: str = "cpu"):
+        self._assert_shape(module, "delta", device=device)
 
-    def assert_shape_gamma(self, module):
-        self._assert_shape(module, "gamma")
+    def assert_shape_gamma(self, module, device: str = "cpu"):
+        self._assert_shape(module, "gamma", device=device)
 
-    def assert_shape_vega(self, module):
-        self._assert_shape(module, "vega")
+    def assert_shape_vega(self, module, device: str = "cpu"):
+        self._assert_shape(module, "vega", device=device)
 
-    def assert_shape_theta(self, module):
-        self._assert_shape(module, "theta")
+    def assert_shape_theta(self, module, device: str = "cpu"):
+        self._assert_shape(module, "theta", device=device)
 
-    def assert_shape_price(self, module):
-        self._assert_shape(module, "price")
+    def assert_shape_price(self, module, device: str = "cpu"):
+        self._assert_shape(module, "price", device=device)
 
-    def assert_shape_forward(self, module):
+    def assert_shape_forward(self, module, device: str = "cpu"):
         N = 2
         M_1 = 10
         M_2 = 11
         H_in = len(module.inputs())
 
-        input = torch.zeros((N, H_in))
+        input = torch.zeros((N, H_in)).to(device)
         out = module(input)
         assert out.size() == torch.Size((N, 1))
 
-        input = torch.zeros((N, M_1, H_in))
+        input = torch.zeros((N, M_1, H_in)).to(device)
         out = module(input)
         assert out.size() == torch.Size((N, M_1, 1))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
         out = module(input)
         assert out.size() == torch.Size((N, M_1, M_2, 1))
diff --git a/tests/nn/modules/bs/test_american_binary.py b/tests/nn/modules/bs/test_american_binary.py
index 7d275d27..bdd2d923 100644
--- a/tests/nn/modules/bs/test_american_binary.py
+++ b/tests/nn/modules/bs/test_american_binary.py
@@ -38,63 +38,91 @@ def test_error_put(self):
             derivative = AmericanBinaryOption(BrownianStock(), call=False)
             BSAmericanBinaryOption.from_derivative(derivative)
 
-    def test_check_delta(self):
-        m = BSAmericanBinaryOption()
+    def test_check_delta(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
 
         # delta = 0 for max --> +0
-        result = compute_delta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for max > 0
-        result = compute_delta(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for time --> +0
-        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for volatility --> +0
-        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_delta_gpu(self):
+        self.test_check_delta(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_delta_2(self, call: bool):
-        m = BSAmericanBinaryOption(call=call)
+    def test_delta_2(self, call: bool, device: str = "cpu"):
+        m = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.delta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.delta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_delta_3(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_delta_2_gpu(self, call: bool):
+        self.test_delta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_delta_3(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
             m.delta()
         with pytest.raises(AttributeError):
-            m.delta(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.delta(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.delta(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.delta(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.delta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.delta(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.delta(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.delta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.delta()
@@ -162,61 +190,94 @@ def test_delta_3(self, call: bool):
                 None,
             )
 
-    def test_check_gamma(self):
-        m = BSAmericanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_delta_3_gpu(self, call: bool):
+        self.test_delta_3(call, device="cuda")
+
+    def test_check_gamma(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
 
         # gamma = 0 for max --> +0
-        result = compute_gamma(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for max > 0
-        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for time --> +0
-        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for volatility --> +0
-        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_gamma_gpu(self):
+        self.test_check_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_gamma_2(self, call: bool):
-        m = BSAmericanBinaryOption(call=call)
+    def test_gamma_2(self, call: bool, device: str = "cpu"):
+        m = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.gamma(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.gamma(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_gamma_3(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_gamma_2_gpu(self, call: bool):
+        self.test_gamma_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_gamma_3(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.gamma(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.gamma(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.gamma(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.gamma(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.gamma(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.gamma(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.gamma(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.gamma(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.gamma()
@@ -285,61 +346,94 @@ def test_gamma_3(self, call: bool):
                 None,
             )
 
-    def test_check_vega(self):
-        m = BSAmericanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_gamma_3_gpu(self, call: bool):
+        self.test_gamma_3(call, device="cuda")
+
+    def test_check_vega(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
 
         # vega = 0 for max --> +0
-        result = compute_vega(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for max > 0
-        result = compute_vega(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for time --> +0
-        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for volatility --> +0
-        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_vega_gpu(self):
+        self.test_check_vega(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_vega_2(self, call: bool):
-        m = BSAmericanBinaryOption(call=call)
+    def test_vega_2(self, call: bool, device: str = "cpu"):
+        m = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.vega(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.vega(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_vega_3(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_vega_2_gpu(self, call: bool):
+        self.test_vega_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_3(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.vega(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.vega(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.vega(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.vega(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.vega(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.vega(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.vega()
@@ -408,61 +502,94 @@ def test_vega_3(self, call: bool):
                 None,
             )
 
-    def test_check_theta(self):
-        m = BSAmericanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_3_gpu(self, call: bool):
+        self.test_vega_3(call, device="cuda")
+
+    def test_check_theta(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
 
         # vega = 0 for max --> +0
-        result = compute_theta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for max > 0
-        result = compute_theta(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for time --> +0
-        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # vega = 0 for volatility --> +0
-        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_theta_gpu(self):
+        self.test_check_theta(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_theta_2(self, call: bool):
-        m = BSAmericanBinaryOption(call=call)
+    def test_theta_2(self, call: bool, device: str = "cpu"):
+        m = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.theta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.theta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_theta_3(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_theta_2_gpu(self, call: bool):
+        self.test_theta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_theta_3(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.theta(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.theta(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.theta(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.theta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.theta(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.theta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.theta()
@@ -531,61 +658,94 @@ def test_theta_3(self, call: bool):
                 None,
             )
 
-    def test_check_price(self):
-        m = BSAmericanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_theta_3_gpu(self, call: bool):
+        self.test_theta_3(call, device="cuda")
+
+    def test_check_price(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
 
         # price = 0 for max --> +0
-        result = compute_price(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # price = 1 for max > 0
-        result = compute_price(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]))
-        expect = torch.tensor([1.0])
+        result = compute_price(m, torch.tensor([[0.0, 0.01, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([1.0]).to(device)
         assert_close(result, expect)
 
         # price = 0 for time --> +0
-        result = compute_price(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # price = 0 for volatility --> +0
-        result = compute_price(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_price_gpu(self):
+        self.test_check_price(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_price_3(self, call: bool):
-        m = BSAmericanBinaryOption(call=call)
+    def test_price_3(self, call: bool, device: str = "cpu"):
+        m = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.price(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.price(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_price_4(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_price_3_gpu(self, call: bool):
+        self.test_price_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_price_4(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.price(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.price(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.price(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.price(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.price(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.price(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.price()
@@ -653,17 +813,22 @@ def test_price_4(self, call: bool):
                 None,
             )
 
-    def test_check_price_monte_carlo(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_price_4_gpu(self, call: bool):
+        self.test_price_4(call, device="cuda")
+
+    def test_check_price_monte_carlo(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         # Monte Carlo evaluation of a lookback option needs small dt
         k = 1.01
-        d = AmericanBinaryOption(BrownianStock(dt=1e-5), strike=k)
+        d = AmericanBinaryOption(BrownianStock(dt=1e-5), strike=k).to(device)
         m = BSAmericanBinaryOption.from_derivative(d)
         d.simulate(n_paths=int(1e4), init_state=(1.0,))
 
-        s = torch.tensor([1.0 / k]).log()
-        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]])
+        s = torch.tensor([1.0 / k]).to(device).log()
+        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
         assert_close(result, expect, rtol=1e-2, atol=0.0)
@@ -674,33 +839,43 @@ def test_check_price_monte_carlo(self):
         # Mathematical Finance, 7(4), pp.325-349.
         beta = 0.5825971579  # -zeta(1/2) / sqrt(2 pi)
         k = 1.01
-        d = AmericanBinaryOption(BrownianStock(), strike=k)
+        d = AmericanBinaryOption(BrownianStock(), strike=k).to(device)
         m = BSAmericanBinaryOption.from_derivative(d)
         d.simulate(n_paths=int(1e5), init_state=(1.0,))
 
-        k_shift = k * torch.tensor(beta * d.ul().sigma * sqrt(d.ul().dt)).exp()
+        k_shift = (
+            k * torch.tensor(beta * d.ul().sigma * sqrt(d.ul().dt)).to(device).exp()
+        )
 
-        s = torch.tensor([1.0 / k_shift]).log()
-        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]])
+        s = torch.tensor([1.0 / k_shift]).to(device).log()
+        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
-        assert_close(result, expect, rtol=1e-3, atol=0.0)
+        assert_close(result, expect, rtol=1e-2, atol=0.0)
 
-    def test_vega_and_gamma(self):
-        m = BSAmericanBinaryOption()
+    @pytest.mark.gpu
+    def test_check_price_monte_carlo_gpu(self):
+        self.test_check_price_monte_carlo(device="cuda")
+
+    def test_vega_and_gamma(self, device: str = "cpu"):
+        m = BSAmericanBinaryOption().to(device)
         # vega = spot^2 * sigma * (T - t) * gamma
         # See Chapter 5 Appendix A, Bergomi "Stochastic volatility modeling"
-        spot = torch.tensor([0.90, 0.94, 0.98])
-        t = torch.tensor([0.1, 0.2, 0.3])
-        v = torch.tensor([0.1, 0.2, 0.3])
+        spot = torch.tensor([0.90, 0.94, 0.98]).to(device)
+        t = torch.tensor([0.1, 0.2, 0.3]).to(device)
+        v = torch.tensor([0.1, 0.2, 0.3]).to(device)
         vega = m.vega(spot.log(), spot.log(), t, v)
         gamma = m.gamma(spot.log(), spot.log(), t, v)
         assert_close(vega, spot.square() * v * t * gamma, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_vega_and_gamma_gpu(self):
+        self.test_vega_and_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_vega_and_gamma_2(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
+    def test_vega_and_gamma_2(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         vega = m.vega()
@@ -718,6 +893,11 @@ def test_vega_and_gamma_2(self, call: bool):
             rtol=0,
         )
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_and_gamma_2_gpu(self, call: bool):
+        self.test_vega_and_gamma_2(call, device="cuda")
+
     def test_features(self):
         m = BSAmericanBinaryOption()
         assert m.inputs() == [
@@ -728,7 +908,7 @@ def test_features(self):
         ]
         _ = [get_feature(f) for f in m.inputs()]
 
-    def test_implied_volatility(self):
+    def test_implied_volatility(self, device: str = "cpu"):
         # log_moneyness, max_log_moneyness, time_to_maturity, price
         input = torch.tensor(
             [
@@ -736,8 +916,8 @@ def test_implied_volatility(self):
                 [-0.01, -0.01, 0.1, 0.6],
                 [-0.01, -0.01, 0.1, 0.7],
             ]
-        )
-        m = BSAmericanBinaryOption()
+        ).to(device)
+        m = BSAmericanBinaryOption().to(device)
         iv = m.implied_volatility(input[:, 0], input[:, 1], input[:, 2], input[:, 3])
 
         result = BSAmericanBinaryOption().price(
@@ -746,26 +926,43 @@ def test_implied_volatility(self):
         expect = input[:, -1]
         assert_close(result, expect, atol=1e-4, rtol=1e-4, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_implied_volatility_gpu(self):
+        self.test_implied_volatility(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_implied_volatility_2(self, call: bool):
-        derivative = AmericanBinaryOption(BrownianStock(), call=call)
-        m = BSAmericanBinaryOption.from_derivative(derivative)
-        m2 = BSAmericanBinaryOption(call=call)
+    def test_implied_volatility_2(self, call: bool, device: str = "cpu"):
+        derivative = AmericanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSAmericanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSAmericanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
             m.implied_volatility(
-                None, torch.tensor(1), torch.tensor(2), torch.tensor(3)
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
             )
         with pytest.raises(AttributeError):
             m.implied_volatility(
-                torch.tensor(1), None, torch.tensor(2), torch.tensor(3)
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
             )
         with pytest.raises(AttributeError):
             m.implied_volatility(
-                torch.tensor(1), torch.tensor(2), None, torch.tensor(3)
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(ValueError):
+            m.implied_volatility(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
             )
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.implied_volatility(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         with pytest.raises(ValueError):
@@ -821,13 +1018,22 @@ def test_implied_volatility_2(self, call: bool):
                 derivative.underlier.spot,
             )
 
-    def test_shape(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_implied_volatility_2_gpu(self, call: bool):
+        self.test_implied_volatility_2(call, device="cuda")
+
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        m = BSAmericanBinaryOption()
-        self.assert_shape_delta(m)
-        self.assert_shape_gamma(m)
-        self.assert_shape_vega(m)
-        self.assert_shape_theta(m)
-        self.assert_shape_price(m)
-        self.assert_shape_forward(m)
+        m = BSAmericanBinaryOption().to(device)
+        self.assert_shape_delta(m, device=device)
+        self.assert_shape_gamma(m, device=device)
+        self.assert_shape_vega(m, device=device)
+        self.assert_shape_theta(m, device=device)
+        self.assert_shape_price(m, device=device)
+        self.assert_shape_forward(m, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/bs/test_bs.py b/tests/nn/modules/bs/test_bs.py
index 3d48dfe3..408fbac3 100644
--- a/tests/nn/modules/bs/test_bs.py
+++ b/tests/nn/modules/bs/test_bs.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 
 from pfhedge.instruments import BrownianStock
@@ -35,22 +36,26 @@ def test_init(self):
         # assert m.strike == 2.0
         # assert not m.call
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        deriv = EuropeanOption(BrownianStock())
-        m = BlackScholes(deriv)
+        deriv = EuropeanOption(BrownianStock()).to(device)
+        m = BlackScholes(deriv).to(device)
 
         N = 10
         H_in = len(m.inputs())
         M_1 = 12
         M_2 = 13
 
-        input = torch.zeros((N, H_in))
+        input = torch.zeros((N, H_in)).to(device)
         assert m(input).size() == torch.Size((N, 1))
 
-        input = torch.zeros((N, M_1, H_in))
+        input = torch.zeros((N, M_1, H_in)).to(device)
         assert m(input).size() == torch.Size((N, M_1, 1))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/bs/test_european.py b/tests/nn/modules/bs/test_european.py
index ca0b4bf5..3110168c 100644
--- a/tests/nn/modules/bs/test_european.py
+++ b/tests/nn/modules/bs/test_european.py
@@ -27,220 +27,292 @@ def test_features(self):
         assert m.inputs() == ["log_moneyness", "time_to_maturity", "volatility"]
         _ = [get_feature(f) for f in m.inputs()]
 
-    def test_delta_limit(self):
+    def test_delta_limit(self, device: str = "cpu"):
         EPSILON = 1e-10
-        c = BSEuropeanOption()
-        p = BSEuropeanOption(call=False)
+        c = BSEuropeanOption().to(device)
+        p = BSEuropeanOption(call=False).to(device)
 
         # delta = 0 (call), -1 (put) for spot --> +0
-        result = compute_delta(c, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([-1.0]))
+        result = compute_delta(c, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([-1.0]).to(device))
 
         # delta = 1 (call), 0 (put) for spot --> +inf
-        result = compute_delta(c, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_delta(p, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_delta(p, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # delta = 0 (call), -1 (put) for spot < k and time --> +0
-        result = compute_delta(c, torch.tensor([[-0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([-1.0]))
+        result = compute_delta(c, torch.tensor([[-0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([-1.0]).to(device))
 
         # delta = 1 (call), 0 (put) for spot > k and time --> +0
-        result = compute_delta(c, torch.tensor([[0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_delta(p, torch.tensor([[0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_delta(p, torch.tensor([[0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # delta = 0 (call), -1 (put) for spot < k and volatility --> +0
-        result = compute_delta(c, torch.tensor([[-0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([-1.0]))
+        result = compute_delta(c, torch.tensor([[-0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([-1.0]).to(device))
 
         # delta = 1 (call), 0 (put) for spot > k and volatility --> +0
-        result = compute_delta(c, torch.tensor([[0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_delta(p, torch.tensor([[0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_delta(p, torch.tensor([[0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-    def test_gamma_limit(self):
+    @pytest.mark.gpu
+    def test_delta_limit_gpu(self):
+        self.test_delta_limit(device="cuda")
+
+    def test_gamma_limit(self, device: str = "cpu"):
         EPSILON = 1e-10
-        c = BSEuropeanOption()
-        p = BSEuropeanOption(call=False)
+        c = BSEuropeanOption().to(device)
+        p = BSEuropeanOption(call=False).to(device)
 
         # gamma = 0 for spot --> +0
-        result = compute_gamma(c, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot --> +inf
-        result = compute_gamma(c, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot / k < 1 and time --> +0
-        result = compute_gamma(c, torch.tensor([[-0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot / k > 1 and time --> +0
-        result = compute_gamma(c, torch.tensor([[0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[0.01, EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[0.01, EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot / k < 1 and volatility --> +0
-        result = compute_gamma(c, torch.tensor([[-0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot / k > 1 and volatility --> +0
-        result = compute_gamma(c, torch.tensor([[0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[0.01, 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[0.01, 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+
+    @pytest.mark.gpu
+    def test_gamma_limit_gpu(self):
+        self.test_gamma_limit(device="cuda")
 
-    def test_price_limit(self):
+    def test_price_limit(self, device: str = "cpu"):
         EPSILON = 1e-10
-        c = BSEuropeanOption()
-        p = BSEuropeanOption(call=False)
+        c = BSEuropeanOption().to(device)
+        p = BSEuropeanOption(call=False).to(device)
 
         # price = 0 (call), k - spot (put) for spot --> +0
-        s = torch.tensor([-10.0]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[s.log(), 1.0, 0.2]]))
-        assert_close(result, torch.tensor([1.0 - s]))
+        s = torch.tensor([-10.0]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0 - s]).to(device))
 
         # price = spot - k (call), 0 (put) for spot --> +inf
-        s = torch.tensor([10.0]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), 1.0, 0.2]]))
-        assert_close(result, torch.tensor([s - 1.0]))
-        result = compute_price(p, torch.tensor([[s.log(), 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        s = torch.tensor([10.0]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([s - 1.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # price = 0 (call), k - s (put) for spot < k and time --> +0
-        s = torch.tensor([-0.01]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[s.log(), EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([1.0 - s]))
+        s = torch.tensor([-0.01]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0 - s]).to(device))
 
         # price = spot - k (call), 0 (put) for spot > k and time --> +0
-        s = torch.tensor([0.01]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([s - 1.0]))
-        result = compute_price(p, torch.tensor([[s.log(), EPSILON, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        s = torch.tensor([0.01]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([s - 1.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), EPSILON, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # price = 0 (call), k - spot (put) for spot < k and volatility --> +0
-        s = torch.tensor([-0.01]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[s.log(), 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([1.0 - s]))
+        s = torch.tensor([-0.01]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([1.0 - s]).to(device))
 
         # price = spot - k (call), 0 (put) for spot > k and volatility --> +0
-        s = torch.tensor([0.01]).exp()
-        result = compute_price(c, torch.tensor([[s.log(), 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([s - 1.0]))
-        result = compute_price(p, torch.tensor([[s.log(), 1.0, EPSILON]]))
-        assert_close(result, torch.tensor([0.0]))
-
-    def test_price_monte_carlo(self):
-        d = EuropeanOption(BrownianStock())
-        m = BSEuropeanOption.from_derivative(d)
+        s = torch.tensor([0.01]).to(device).exp()
+        result = compute_price(c, torch.tensor([[s.log(), 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([s - 1.0]).to(device))
+        result = compute_price(p, torch.tensor([[s.log(), 1.0, EPSILON]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+
+    @pytest.mark.gpu
+    def test_price_limit_gpu(self):
+        self.test_price_limit(device="cuda")
+
+    def test_price_monte_carlo(self, device: str = "cpu"):
+        d = EuropeanOption(BrownianStock()).to(device)
+        m = BSEuropeanOption.from_derivative(d).to(device)
         torch.manual_seed(42)
         d.simulate(n_paths=int(1e6))
 
-        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]])
+        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
         print(result, expect)
         assert_close(result, expect, rtol=1e-2, atol=0.0)
 
-        d = EuropeanOption(BrownianStock(), call=False)
-        m = BSEuropeanOption.from_derivative(d)
+        d = EuropeanOption(BrownianStock(), call=False).to(device)
+        m = BSEuropeanOption.from_derivative(d).to(device)
         torch.manual_seed(42)
         d.simulate(n_paths=int(1e6))
 
-        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]])
+        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
         print(result, expect)
         assert_close(result, expect, rtol=1e-2, atol=0.0)
 
-    def test_forward_2(self):
-        m = BSEuropeanOption(call=False)
-        input = torch.tensor([[0.0, 1.0, 0.2]])
+    @pytest.mark.gpu
+    def test_price_monte_carlo_gpu(self):
+        self.test_price_monte_carlo(device="cuda")
+
+    def test_forward_2(self, device: str = "cpu"):
+        m = BSEuropeanOption(call=False).to(device)
+        input = torch.tensor([[0.0, 1.0, 0.2]]).to(device)
         result = m(input)
         expect = torch.full_like(result, -0.4601721)
         assert_close(result, expect)
 
-    def test_forward_3(self):
-        derivative = EuropeanOption(BrownianStock(), call=False)
-        m = BSEuropeanOption.from_derivative(derivative)
-        input = torch.tensor([[0.0, 1.0, 0.2]])
+    @pytest.mark.gpu
+    def test_forward_2_gpu(self):
+        self.test_forward_2(device="cuda")
+
+    def test_forward_3(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=False).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        input = torch.tensor([[0.0, 1.0, 0.2]]).to(device)
         result = m(input)
         expect = torch.full_like(result, -0.4601721)
         assert_close(result, expect)
 
-    def test_delta_1(self):
-        m = BSEuropeanOption()
-        result = m.delta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(0.2))
+    @pytest.mark.gpu
+    def test_forward_3_gpu(self):
+        self.test_forward_3(device="cuda")
+
+    def test_delta_1(self, device: str = "cpu"):
+        m = BSEuropeanOption().to(device)
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, 0.5398278962)
         assert_close(result, expect)
 
-    def test_delta_2(self):
-        m = BSEuropeanOption(call=False)
-        result = m.delta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(0.2))
+    @pytest.mark.gpu
+    def test_delta_1_gpu(self):
+        self.test_delta_1(device="cuda")
+
+    def test_delta_2(self, device: str = "cpu"):
+        m = BSEuropeanOption(call=False).to(device)
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, -0.4601721)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_delta_2_gpu(self):
+        self.test_delta_2(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_delta_3(self, call: bool):
-        m = BSEuropeanOption(call=call)
+    def test_delta_3(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.delta(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.delta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.delta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
-        result = m.delta(torch.tensor(1.0), torch.tensor(1.0), torch.tensor(0))
+            m.delta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
+        )
         expect = torch.full_like(result, 1.0 if call else 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 1.0 if call else 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 1.0 if call else 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.5 if call else -0.5)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.5 if call else -0.5)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_delta_4(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_delta_3_gpu(self, call: bool):
+        self.test_delta_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_delta_4(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.delta(None, torch.tensor(1), torch.tensor(2))
+            m.delta(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.delta(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.delta(torch.tensor(1), torch.tensor(2), None)
+            m.delta(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
+        with pytest.raises(AttributeError):
+            m.delta(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.delta()
@@ -271,53 +343,106 @@ def test_delta_4(self, call: bool):
         with pytest.raises(ValueError):
             m2.delta(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_gamma_1(self):
-        m = BSEuropeanOption()
-        result = m.gamma(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(0.2))
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_delta_4_gpu(self, call: bool):
+        self.test_delta_4(call, device="cuda")
+
+    def test_gamma_1(self, device: str = "cpu"):
+        m = BSEuropeanOption().to(device)
+        result = m.gamma(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, 1.9847627374)
         assert_close(result, expect)
 
-    def test_gamma_2(self):
-        m = BSEuropeanOption(call=False)
-        result = m.gamma(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(0.2))
+    @pytest.mark.gpu
+    def test_gamma_1_gpu(self):
+        self.test_gamma_1(device="cuda")
+
+    def test_gamma_2(self, device: str = "cpu"):
+        m = BSEuropeanOption(call=False).to(device)
+        result = m.gamma(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, 1.9847627374)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_gamma_2_gpu(self):
+        self.test_gamma_2(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_gamma_3(self, call: bool):
-        m = BSEuropeanOption(call=call)
+    def test_gamma_3(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.gamma(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.gamma(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.gamma(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
-        result = m.gamma(torch.tensor(1.0), torch.tensor(1.0), torch.tensor(0))
+            m.gamma(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
+        result = m.gamma(
+            torch.tensor(1.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.gamma(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.gamma(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.gamma(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.gamma(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.gamma(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.gamma(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, float("inf"))
         assert_close(result, expect)
-        result = m.gamma(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.gamma(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, float("inf"))
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_gamma_4(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_gamma_3_gpu(self, call: bool):
+        self.test_gamma_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_gamma_4(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.gamma(None, torch.tensor(1), torch.tensor(2))
+            m.gamma(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.gamma(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.gamma(torch.tensor(1), torch.tensor(2), None)
+            m.gamma(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
+        with pytest.raises(AttributeError):
+            m.gamma(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.gamma()
@@ -348,59 +473,106 @@ def test_gamma_4(self, call: bool):
         with pytest.raises(ValueError):
             m2.gamma(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_price_1(self):
-        m = BSEuropeanOption()
-        result = m.price(0.0, 1.0, 0.2)
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_gamma_4_gpu(self, call: bool):
+        self.test_gamma_4(call, device="cuda")
+
+    def test_price_1(self, device: str = "cpu"):
+        m = BSEuropeanOption().to(device)
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, 0.0796557924)
         assert_close(result, expect)
 
-    def test_price_2(self):
+    @pytest.mark.gpu
+    def test_price_1_gpu(self):
+        self.test_price_1(device="cuda")
+
+    def test_price_2(self, device: str = "cpu"):
         m = BSEuropeanOption(call=False)
-        result = m.price(0.0, 1.0, 0.2)
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
         expect = torch.full_like(result, 0.0796557924)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_price_2_gpu(self):
+        self.test_price_2(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_price_3(self, call: bool):
-        m = BSEuropeanOption(call=call)
+    def test_price_3(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.price(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.price(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.price(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
+            m.price(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(1.0), torch.tensor(0)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
         )
         expect = torch.full_like(result, 1.718282 if call else 0.632121)
         assert_close(result, expect)
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(0.0), torch.tensor(0.1)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
         )
         expect = torch.full_like(result, 1.718282 if call else 0.632121)
         assert_close(result, expect)
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(0.0), torch.tensor(0.0)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
         )
         expect = torch.full_like(result, 1.718282 if call else 0.632121)
         assert_close(result, expect)
-        result = m.price(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.price(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_price_4(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_price_3_gpu(self, call: bool):
+        self.test_price_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_price_4(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.price(None, torch.tensor(1), torch.tensor(2))
+            m.price(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.price(torch.tensor(1), torch.tensor(2), None)
+            m.price(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
+        with pytest.raises(AttributeError):
+            m.price(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.price()
@@ -431,27 +603,43 @@ def test_price_4(self, call: bool):
         with pytest.raises(ValueError):
             m2.price(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_implied_volatility(self):
-        input = torch.tensor([[0.0, 0.1, 0.01], [0.0, 0.1, 0.02], [0.0, 0.1, 0.03]])
-        m = BSEuropeanOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_price_4_gpu(self, call: bool):
+        self.test_price_4(call, device="cuda")
+
+    def test_implied_volatility(self, device: str = "cpu"):
+        input = torch.tensor([[0.0, 0.1, 0.01], [0.0, 0.1, 0.02], [0.0, 0.1, 0.03]]).to(
+            device
+        )
+        m = BSEuropeanOption().to(device)
         iv = m.implied_volatility(input[:, 0], input[:, 1], input[:, 2])
 
-        result = BSEuropeanOption().price(input[:, 0], input[:, 1], iv)
+        result = BSEuropeanOption().to(device).price(input[:, 0], input[:, 1], iv)
         expect = input[:, 2]
         assert_close(result, expect, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_implied_volatility_gpu(self):
+        self.test_implied_volatility(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_implied_volatility_2(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_implied_volatility_2(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.implied_volatility(None, torch.tensor(1), torch.tensor(2))
+            m.implied_volatility(
+                None, torch.tensor(1).to(device), torch.tensor(2).to(device)
+            )
         with pytest.raises(AttributeError):
-            m.implied_volatility(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.implied_volatility(torch.tensor(1), torch.tensor(2), None)
+            m.implied_volatility(
+                torch.tensor(1).to(device), None, torch.tensor(2).to(device)
+            )
+        with pytest.raises(ValueError):
+            m.implied_volatility(
+                torch.tensor(1).to(device), torch.tensor(2).to(device), None
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         with pytest.raises(ValueError):
@@ -480,52 +668,95 @@ def test_implied_volatility_2(self, call: bool):
                 derivative.log_moneyness(), None, derivative.underlier.spot
             )
 
-    def test_vega(self):
-        input = torch.tensor([[0.0, 0.1, 0.2], [0.0, 0.2, 0.2], [0.0, 0.3, 0.2]])
-        m = BSEuropeanOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_implied_volatility_2_gpu(self, call: bool):
+        self.test_implied_volatility_2(call, device="cuda")
+
+    def test_vega(self, device: str = "cpu"):
+        input = torch.tensor([[0.0, 0.1, 0.2], [0.0, 0.2, 0.2], [0.0, 0.3, 0.2]]).to(
+            device
+        )
+        m = BSEuropeanOption().to(device)
         result = m.vega(
             log_moneyness=input[..., 0],
             time_to_maturity=input[..., 1],
             volatility=input[..., 2],
         )
-        expect = torch.tensor([0.1261, 0.1782, 0.2182])
+        expect = torch.tensor([0.1261, 0.1782, 0.2182]).to(device)
         assert_close(result, expect, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_vega_gpu(self):
+        self.test_vega(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_2(self, call: bool):
-        m = BSEuropeanOption(call=call)
+    def test_vega_2(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.vega(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.vega(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.vega(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
-        result = m.vega(torch.tensor(0.1), torch.tensor(1.0), torch.tensor(0))
+            m.vega(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
+        result = m.vega(
+            torch.tensor(0.1).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.vega(torch.tensor(0.1), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.vega(
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.vega(torch.tensor(0.1), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.vega(
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.vega(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.vega(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.vega(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.vega(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_3(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_vega_2_gpu(self, call: bool):
+        self.test_vega_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.vega(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.vega(None, torch.tensor(1), torch.tensor(2))
+            m.vega(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.vega(torch.tensor(1), torch.tensor(2), None)
+            m.vega(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.vega()
@@ -554,21 +785,30 @@ def test_vega_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.vega(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_vega_and_gamma(self):
-        m = BSEuropeanOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_3_gpu(self, call: bool):
+        self.test_vega_3(call, device="cuda")
+
+    def test_vega_and_gamma(self, device: str = "cpu"):
+        m = BSEuropeanOption().to(device)
         # vega = spot^2 * sigma * (T - t) * gamma
         # See Chapter 5 Appendix A, Bergomi "Stochastic volatility modeling"
-        spot = torch.tensor([0.9, 1.0, 1.1, 1.1, 1.1, 1.1])
-        t = torch.tensor([0.1, 0.2, 0.3, 0.0, 0.0, 0.1])
-        v = torch.tensor([0.1, 0.2, 0.3, 0.0, 0.2, 0.0])
+        spot = torch.tensor([0.9, 1.0, 1.1, 1.1, 1.1, 1.1]).to(device)
+        t = torch.tensor([0.1, 0.2, 0.3, 0.0, 0.0, 0.1]).to(device)
+        v = torch.tensor([0.1, 0.2, 0.3, 0.0, 0.2, 0.0]).to(device)
         vega = m.vega(spot.log(), t, v)
         gamma = m.gamma(spot.log(), t, v)
         assert_close(vega, spot.square() * v * t * gamma, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_vega_and_gamma_gpu(self):
+        self.test_vega_and_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_and_gamma_2(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
+    def test_vega_and_gamma_2(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         vega = m.vega()
@@ -583,52 +823,95 @@ def test_vega_and_gamma_2(self, call: bool):
             rtol=0,
         )
 
-    def test_theta(self):
-        input = torch.tensor([[0.0, 0.1, 0.2], [0.0, 0.2, 0.2], [0.0, 0.3, 0.2]])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_and_gamma_2_gpu(self, call: bool):
+        self.test_vega_and_gamma_2(call, device="cuda")
+
+    def test_theta(self, device: str = "cpu"):
+        input = torch.tensor([[0.0, 0.1, 0.2], [0.0, 0.2, 0.2], [0.0, 0.3, 0.2]]).to(
+            device
+        )
         m = BSEuropeanOption(strike=100)
         result = m.theta(
             log_moneyness=input[..., 0],
             time_to_maturity=input[..., 1],
             volatility=input[..., 2],
         )
-        expect = torch.tensor([-12.6094, -8.9117, -7.2727])
+        expect = torch.tensor([-12.6094, -8.9117, -7.2727]).to(device)
         assert_close(result, expect, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_theta_gpu(self):
+        self.test_theta(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_theta_2(self, call: bool):
-        m = BSEuropeanOption(call=call)
+    def test_theta_2(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.theta(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.theta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.theta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
-        result = m.theta(torch.tensor(0.1), torch.tensor(1.0), torch.tensor(0))
+            m.theta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
+        result = m.theta(
+            torch.tensor(0.1).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
+        )
         expect = torch.full_like(result, -0.0)
         assert_close(result, expect)
-        result = m.theta(torch.tensor(0.1), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.theta(
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, -0.0)
         assert_close(result, expect)
-        result = m.theta(torch.tensor(0.1), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.theta(
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, -0.0)
         assert_close(result, expect)
-        result = m.theta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.theta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, -float("inf"))
         assert_close(result, expect)
-        result = m.theta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.theta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, -0.0)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_theta_3(self, call: bool):
-        derivative = EuropeanOption(BrownianStock(), call=call)
-        m = BSEuropeanOption.from_derivative(derivative)
-        m2 = BSEuropeanOption(call=call)
+    def test_theta_2_gpu(self, call: bool):
+        self.test_theta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_theta_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.theta(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.theta(None, torch.tensor(1), torch.tensor(2))
+            m.theta(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.theta(torch.tensor(1), torch.tensor(2), None)
+            m.theta(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.theta()
@@ -659,25 +942,38 @@ def test_theta_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.theta(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_example(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_theta_3_gpu(self, call: bool):
+        self.test_theta_3(call, device="cuda")
+
+    def test_example(self, device: str = "cpu"):
         from pfhedge.instruments import BrownianStock
         from pfhedge.instruments import EuropeanOption
         from pfhedge.nn import Hedger
 
-        derivative = EuropeanOption(BrownianStock())
-        model = BSEuropeanOption()
-        hedger = Hedger(model, model.inputs())
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        model = BSEuropeanOption().to(device)
+        hedger = Hedger(model, model.inputs()).to(device)
         result = hedger.price(derivative)
-        expect = torch.tensor(0.022)
+        expect = torch.tensor(0.022).to(device)
         assert_close(result, expect, atol=1e-3, rtol=1e-3)
 
-    def test_shape(self):
+    @pytest.mark.gpu
+    def test_example_gpu(self):
+        self.test_example(device="cuda")
+
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        m = BSEuropeanOption()
-        self.assert_shape_delta(m)
-        self.assert_shape_gamma(m)
-        self.assert_shape_vega(m)
-        self.assert_shape_theta(m)
-        self.assert_shape_price(m)
-        self.assert_shape_forward(m)
+        m = BSEuropeanOption().to(device)
+        self.assert_shape_delta(m, device=device)
+        self.assert_shape_gamma(m, device=device)
+        self.assert_shape_vega(m, device=device)
+        self.assert_shape_theta(m, device=device)
+        self.assert_shape_price(m, device=device)
+        self.assert_shape_forward(m, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/bs/test_european_binary.py b/tests/nn/modules/bs/test_european_binary.py
index cf740a85..9f020b6e 100644
--- a/tests/nn/modules/bs/test_european_binary.py
+++ b/tests/nn/modules/bs/test_european_binary.py
@@ -35,147 +35,163 @@ def test_features(self):
         assert m.inputs() == ["log_moneyness", "time_to_maturity", "volatility"]
         _ = [get_feature(f) for f in m.inputs()]
 
-    def test_delta_limit(self):
-        c = BSEuropeanBinaryOption()
-        p = BSEuropeanBinaryOption(call=False)
+    def test_delta_limit(self, device: str = "cpu"):
+        c = BSEuropeanBinaryOption().to(device)
+        p = BSEuropeanBinaryOption(call=False).to(device)
 
         # delta = 0 for spot --> +0
-        result = compute_delta(c, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # delta = 0 for spot --> +inf
-        result = compute_delta(c, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # delta = 0 for time --> +0
-        result = compute_delta(c, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-        result = compute_delta(c, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # delta = 0 for volatility --> +0
-        result = compute_delta(c, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-        result = compute_delta(c, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_delta(p, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_delta(c, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_delta(p, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-    def test_gamma_limit(self):
-        c = BSEuropeanBinaryOption()
-        p = BSEuropeanBinaryOption(call=False)
+    @pytest.mark.gpu
+    def test_delta_limit_gpu(self):
+        self.test_delta_limit(device="cuda")
+
+    def test_gamma_limit(self, device: str = "cpu"):
+        c = BSEuropeanBinaryOption().to(device)
+        p = BSEuropeanBinaryOption(call=False).to(device)
 
         # gamma = 0 for spot --> +0
-        result = compute_gamma(c, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for spot --> +inf
-        result = compute_gamma(c, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for time --> +0
-        result = compute_gamma(c, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-        result = compute_gamma(c, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # gamma = 0 for volatility --> +0
-        result = compute_gamma(c, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_gamma(c, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+
+        result = compute_gamma(c, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_gamma(p, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
-        result = compute_gamma(c, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_gamma(p, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
+    @pytest.mark.gpu
+    def test_gamma_limit_gpu(self):
+        self.test_gamma_limit(device="cuda")
 
-    def test_price_limit(self):
-        c = BSEuropeanBinaryOption()
-        p = BSEuropeanBinaryOption(call=False)
+    def test_price_limit(self, device: str = "cpu"):
+        c = BSEuropeanBinaryOption().to(device)
+        p = BSEuropeanBinaryOption(call=False).to(device)
 
         # price = 0 (call), 1 (put) for spot --> +0
-        result = compute_price(c, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[-10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
+        result = compute_price(c, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[-10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
 
         # price = 1 (call), 1 (put) for spot --> +inf
-        result = compute_price(c, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_price(p, torch.tensor([[10.0, 1.0, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_price(c, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_price(p, torch.tensor([[10.0, 1.0, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # price = 0 (call), 1 (put) for spot < strike and time --> +0
-        result = compute_price(c, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[-0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
+        result = compute_price(c, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[-0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
 
         # price = 1 (call), 0 (put) for spot > strike and time --> +0
-        result = compute_price(c, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_price(p, torch.tensor([[0.01, 1e-10, 0.2]]))
-        assert_close(result, torch.tensor([0.0]))
+        result = compute_price(c, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_price(p, torch.tensor([[0.01, 1e-10, 0.2]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
 
         # price = 0 (call), 1 (put) for spot < strike and volatility --> +0
-        result = compute_price(c, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-        result = compute_price(p, torch.tensor([[-0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([1.0]))
+        result = compute_price(c, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+        result = compute_price(p, torch.tensor([[-0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
 
         # price = 0 (call), 1 (put) for spot > strike and volatility --> +0
-        result = compute_price(c, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([1.0]))
-        result = compute_price(p, torch.tensor([[0.01, 1.0, 1e-10]]))
-        assert_close(result, torch.tensor([0.0]))
-
-    def test_price_monte_carlo(self):
-        d = EuropeanBinaryOption(BrownianStock())
-        m = BSEuropeanBinaryOption.from_derivative(d)
+        result = compute_price(c, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([1.0]).to(device))
+        result = compute_price(p, torch.tensor([[0.01, 1.0, 1e-10]]).to(device))
+        assert_close(result, torch.tensor([0.0]).to(device))
+
+    @pytest.mark.gpu
+    def test_price_limit_gpu(self):
+        self.test_price_limit(device="cuda")
+
+    def test_price_monte_carlo(self, device: str = "cpu"):
+        d = EuropeanBinaryOption(BrownianStock()).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(d).to(device)
         torch.manual_seed(42)
         d.simulate(n_paths=int(1e6))
 
-        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]])
+        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
         assert_close(result, expect, rtol=1e-2, atol=0.0)
 
-        d = EuropeanBinaryOption(BrownianStock(), call=False)
-        m = BSEuropeanBinaryOption.from_derivative(d)
+        d = EuropeanBinaryOption(BrownianStock(), call=False).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(d).to(device)
         torch.manual_seed(42)
         d.simulate(n_paths=int(1e6))
 
-        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]])
+        input = torch.tensor([[0.0, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
         assert_close(result, expect, rtol=1e-2, atol=0.0)
 
+    @pytest.mark.gpu
+    def test_price_monte_carlo_gpu(self):
+        self.test_price_monte_carlo(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_autogreek(self, call):
-        m = BSEuropeanBinaryOption(call=call)
-        s = torch.linspace(-0.5, 0.5, 10)
+    def test_autogreek(self, call, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
+        s = torch.linspace(-0.5, 0.5, 10).to(device)
         t = torch.full_like(s, 1.0)
         v = torch.full_like(s, 0.2)
 
@@ -203,41 +219,78 @@ def test_autogreek(self, call):
         )
         assert_close(result, expect, atol=0, rtol=1e-4)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_delta_2(self, call: bool):
-        m = BSEuropeanBinaryOption(call=call)
+    def test_autogreek_gpu(self, call: bool):
+        self.test_autogreek(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_delta_2(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.delta(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.delta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.delta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
-        result = m.delta(torch.tensor(1.0), torch.tensor(1.0), torch.tensor(0))
+            m.delta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(1.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.delta(
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.0)
         assert_close(result, expect)
-        result = m.delta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, float("inf") if call else -float("inf"))
         assert_close(result, expect)
-        result = m.delta(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.delta(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, float("inf") if call else -float("inf"))
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_delta_3(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_delta_2_gpu(self, call: bool):
+        self.test_delta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_delta_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.delta(None, torch.tensor(1), torch.tensor(2))
+            m.delta(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.delta(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.delta(torch.tensor(1), torch.tensor(2), None)
+            m.delta(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
+        with pytest.raises(AttributeError):
+            m.delta(torch.tensor(1), torch.tensor(2), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.delta()
@@ -268,32 +321,57 @@ def test_delta_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.delta(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_gamma(self):
-        m = BSEuropeanBinaryOption()
-        result = m.gamma(torch.tensor(0.01), torch.tensor(1.0), torch.tensor(0.2))
-        expect = torch.tensor(-1.4645787477493286)
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_delta_3_gpu(self, call: bool):
+        self.test_delta_3(call, device="cuda")
+
+    def test_gamma(self, device: str = "cpu"):
+        m = BSEuropeanBinaryOption().to(device)
+        result = m.gamma(
+            torch.tensor(0.01).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0.2).to(device),
+        )
+        expect = torch.tensor(-1.4645787477493286).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_gamma_gpu(self):
+        self.test_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_gamma_valueerror(self, call: bool):
-        m = BSEuropeanBinaryOption(call=call)
+    def test_gamma_valueerror(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.gamma(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.gamma(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.gamma(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
+            m.gamma(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_gamma_3(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_gamma_valueerror_gpu(self, call: bool):
+        self.test_gamma_valueerror(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_gamma_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.gamma(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.gamma(None, torch.tensor(1), torch.tensor(2))
+            m.gamma(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.gamma(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.gamma(torch.tensor(1), torch.tensor(2), None)
+            m.gamma(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.gamma()
@@ -325,26 +403,43 @@ def test_gamma_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.gamma(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_valueerror(self, call: bool):
-        m = BSEuropeanBinaryOption(call=call)
+    def test_gamma_3_gpu(self, call: bool):
+        self.test_gamma_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_valueerror(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.vega(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.vega(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.vega(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
+            m.vega(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_3(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_vega_valueerror_gpu(self, call: bool):
+        self.test_vega_valueerror(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.vega(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.vega(None, torch.tensor(1), torch.tensor(2))
+            m.vega(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.vega(torch.tensor(1), torch.tensor(2), None)
+            m.vega(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.vega()
@@ -374,21 +469,30 @@ def test_vega_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.vega(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_vega_and_gamma(self):
-        m = BSEuropeanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_vega_3_gpu(self, call: bool):
+        self.test_vega_3(call, device="cuda")
+
+    def test_vega_and_gamma(self, device: str = "cpu"):
+        m = BSEuropeanBinaryOption().to(device)
         # vega = spot^2 * sigma * (T - t) * gamma
         # See Chapter 5 Appendix A, Bergomi "Stochastic volatility modeling"
-        spot = torch.tensor([0.9, 1.0, 1.1])
-        t = torch.tensor([0.1, 0.2, 0.3])
-        v = torch.tensor([0.1, 0.2, 0.3])
+        spot = torch.tensor([0.9, 1.0, 1.1]).to(device)
+        t = torch.tensor([0.1, 0.2, 0.3]).to(device)
+        v = torch.tensor([0.1, 0.2, 0.3]).to(device)
         vega = m.vega(spot.log(), t, v)
         gamma = m.gamma(spot.log(), t, v)
         assert_close(vega, spot.square() * v * t * gamma, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_vega_and_gamma_gpu(self):
+        self.test_vega_and_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_vega_and_gamma_2(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
+    def test_vega_and_gamma_2(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         vega = m.vega()
@@ -402,26 +506,43 @@ def test_vega_and_gamma_2(self, call: bool):
         )[..., :-1]
         assert_close(result, expect, atol=0, rtol=1e-4)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_theta_2(self, call: bool):
-        m = BSEuropeanBinaryOption(call=call)
+    def test_vega_and_gamma_2_gpu(self, call: bool):
+        self.test_vega_and_gamma_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_theta_2(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.theta(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.theta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.theta(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
+            m.theta(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_theta_3(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_theta_2_gpu(self, call: bool):
+        self.test_theta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_theta_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.theta(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.theta(None, torch.tensor(1), torch.tensor(2))
+            m.theta(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.theta(torch.tensor(1), torch.tensor(2), None)
+            m.theta(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.theta()
@@ -453,58 +574,101 @@ def test_theta_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.theta(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_price(self):
-        m = BSEuropeanBinaryOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_theta_3_gpu(self, call: bool):
+        self.test_theta_3(call, device="cuda")
+
+    def test_price(self, device: str = "cpu"):
+        m = BSEuropeanBinaryOption().to(device)
 
-        result = m.price(torch.tensor(0.0), 0.1, 0.2)
-        expect = torch.tensor(0.4874)
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.2).to(device),
+        )
+        expect = torch.tensor(0.4874).to(device)
         assert_close(result, expect, atol=1e-4, rtol=1e-4)
 
-        result = m.price(0.0001, 0.1, 0.2)
-        expect = torch.tensor(0.4880)
+        result = m.price(
+            torch.tensor(0.0001).to(device),
+            torch.tensor(0.1).to(device),
+            torch.tensor(0.2).to(device),
+        )
+        expect = torch.tensor(0.4880).to(device)
         assert_close(result, expect, atol=1e-4, rtol=1e-4)
 
+    @pytest.mark.gpu
+    def test_price_gpu(self):
+        self.test_price(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_price_2(self, call: bool):
-        m = BSEuropeanBinaryOption(call=call)
+    def test_price_2(self, call: bool, device: str = "cpu"):
+        m = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(ValueError):
-            m.price(torch.tensor(0.0), torch.tensor(-1.0), torch.tensor(0.2))
+            m.price(
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
+            )
         with pytest.raises(ValueError):
-            m.price(torch.tensor(0.0), torch.tensor(1.0), torch.tensor(-0.2))
+            m.price(
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
+            )
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(1.0), torch.tensor(0)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(1.0).to(device),
+            torch.tensor(0).to(device),
         )
         expect = torch.full_like(result, 1)
         assert_close(result, expect)
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(0.0), torch.tensor(0.1)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
         )
         expect = torch.full_like(result, 1)
         assert_close(result, expect)
         result = m.price(
-            torch.tensor(1.0 if call else -1.0), torch.tensor(0.0), torch.tensor(0.0)
+            torch.tensor(1.0 if call else -1.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
         )
         expect = torch.full_like(result, 1)
         assert_close(result, expect)
-        result = m.price(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.1))
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.1).to(device),
+        )
         expect = torch.full_like(result, 0.5)
         assert_close(result, expect)
-        result = m.price(torch.tensor(0.0), torch.tensor(0.0), torch.tensor(0.0))
+        result = m.price(
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+            torch.tensor(0.0).to(device),
+        )
         expect = torch.full_like(result, 0.5)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True, False])
-    def test_price_3(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_price_2_gpu(self, call: bool):
+        self.test_price_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True, False])
+    def test_price_3(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.price(None, torch.tensor(1).to(device), torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.price(None, torch.tensor(1), torch.tensor(2))
+            m.price(torch.tensor(1).to(device), None, torch.tensor(2).to(device))
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), None, torch.tensor(2))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.price(torch.tensor(1), torch.tensor(2), None)
+            m.price(torch.tensor(1).to(device), torch.tensor(2).to(device), None)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.price()
@@ -535,31 +699,45 @@ def test_price_3(self, call: bool):
         with pytest.raises(ValueError):
             m2.price(derivative.log_moneyness(), derivative.time_to_maturity(), None)
 
-    def test_implied_volatility(self):
-        lm = torch.full((3,), -0.01)
-        t = torch.full((3,), 0.1)
-        price = torch.tensor([0.40, 0.41, 0.42])
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_price_3_gpu(self, call: bool):
+        self.test_price_3(call, device="cuda")
 
-        m = BSEuropeanBinaryOption()
+    def test_implied_volatility(self, device: str = "cpu"):
+        lm = torch.full((3,), -0.01).to(device)
+        t = torch.full((3,), 0.1).to(device)
+        price = torch.tensor([0.40, 0.41, 0.42]).to(device)
+
+        m = BSEuropeanBinaryOption().to(device)
         iv = m.implied_volatility(lm, t, price)
 
-        result = BSEuropeanBinaryOption().price(lm, t, iv)
+        result = BSEuropeanBinaryOption().to(device).price(lm, t, iv)
         assert_close(result, price, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_implied_volatility_gpu(self):
+        self.test_implied_volatility(device="cuda")
+
     @pytest.mark.parametrize("call", [True, False])
-    def test_implied_volatility_2(self, call: bool):
-        derivative = EuropeanBinaryOption(BrownianStock(), call=call)
-        m = BSEuropeanBinaryOption.from_derivative(derivative)
-        m2 = BSEuropeanBinaryOption(call=call)
+    def test_implied_volatility_2(self, call: bool, device: str = "cpu"):
+        derivative = EuropeanBinaryOption(BrownianStock(), call=call).to(device)
+        m = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        m2 = BSEuropeanBinaryOption(call=call).to(device)
         with pytest.raises(AttributeError):
             m.implied_volatility()
         with pytest.raises(AttributeError):
-            m.implied_volatility(None, torch.tensor(1), torch.tensor(1))
+            m.implied_volatility(
+                None, torch.tensor(1).to(device), torch.tensor(1).to(device)
+            )
         with pytest.raises(AttributeError):
-            m.implied_volatility(torch.tensor(1), None, torch.tensor(1))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.implied_volatility(torch.tensor(0), torch.tensor(0), None)
+            m.implied_volatility(
+                torch.tensor(1).to(device), None, torch.tensor(1).to(device)
+            )
+        with pytest.raises(ValueError):
+            m.implied_volatility(
+                torch.tensor(0).to(device), torch.tensor(0).to(device), None
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         with pytest.raises(ValueError):
@@ -588,27 +766,40 @@ def test_implied_volatility_2(self, call: bool):
                 derivative.log_moneyness(), None, derivative.underlier.spot
             )
 
-    def test_example(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True, False])
+    def test_implied_volatility_2_gpu(self, call: bool):
+        self.test_implied_volatility_2(call, device="cuda")
+
+    def test_example(self, device: str = "cpu"):
         from pfhedge.instruments import BrownianStock
         from pfhedge.instruments import EuropeanBinaryOption
         from pfhedge.nn import Hedger
 
-        derivative = EuropeanBinaryOption(BrownianStock())
-        model = BSEuropeanBinaryOption.from_derivative(derivative)
-        hedger = Hedger(model, model.inputs())
+        derivative = EuropeanBinaryOption(BrownianStock()).to(device)
+        model = BSEuropeanBinaryOption.from_derivative(derivative).to(device)
+        hedger = Hedger(model, model.inputs()).to(device)
         result = hedger.price(derivative)
-        expect = torch.tensor(0.4922)
+        expect = torch.tensor(0.4922).to(device)
         x = hedger.compute_hedge(derivative)
         assert not x.isnan().any()
         assert_close(result, expect, atol=1e-2, rtol=1e-2)
 
-    def test_shape(self):
+    @pytest.mark.gpu
+    def test_example_gpu(self):
+        self.test_example(device="cuda")
+
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        m = BSEuropeanBinaryOption()
-        self.assert_shape_delta(m)
-        self.assert_shape_gamma(m)
-        self.assert_shape_vega(m)
-        self.assert_shape_theta(m)
-        self.assert_shape_price(m)
-        self.assert_shape_forward(m)
+        m = BSEuropeanBinaryOption().to(device)
+        self.assert_shape_delta(m, device=device)
+        self.assert_shape_gamma(m, device=device)
+        self.assert_shape_vega(m, device=device)
+        self.assert_shape_theta(m, device=device)
+        self.assert_shape_price(m, device=device)
+        self.assert_shape_forward(m, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/bs/test_lookback.py b/tests/nn/modules/bs/test_lookback.py
index c5d2b1e8..8f6c0279 100644
--- a/tests/nn/modules/bs/test_lookback.py
+++ b/tests/nn/modules/bs/test_lookback.py
@@ -34,69 +34,97 @@ def test_features(self):
         ]
         _ = [get_feature(f) for f in m.inputs()]
 
-    def test_check_delta(self):
-        m = BSLookbackOption()
+    def test_check_delta(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
 
         # delta = 0 for max --> +0
-        result = compute_delta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for max / spot --> +inf
-        result = compute_delta(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for time --> +0
-        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_delta(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for volatility --> +0
-        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_delta(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_delta(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_delta_gpu(self):
+        self.test_check_delta(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_delta_2(self, call: bool):
-        m = BSLookbackOption(call=call)
+    def test_delta_2(self, call: bool, device: str = "cpu"):
+        m = BSLookbackOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.delta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.delta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_delta_3(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_delta_2_gpu(self, call: bool):
+        self.test_delta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_delta_3(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.delta(None, torch.zeros(1), torch.zeros(2), torch.zeros(3))
+            m.delta(
+                None,
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.delta(torch.zeros(1), None, torch.zeros(2), torch.zeros(3))
+            m.delta(
+                torch.zeros(1).to(device),
+                None,
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.delta(
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                None,
+                torch.zeros(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.delta(torch.zeros(1), torch.zeros(2), None, torch.zeros(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.delta(torch.zeros(1), torch.zeros(2), torch.zeros(3), None)
+            m.delta(
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.delta()
@@ -165,69 +193,102 @@ def test_delta_3(self, call: bool):
                 None,
             )
 
-    def test_check_gamma(self):
-        m = BSLookbackOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_delta_3_gpu(self, call: bool):
+        self.test_delta_3(call, device="cuda")
+
+    def test_check_gamma(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
 
         # gamma = 0 for max --> +0
-        result = compute_gamma(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for max / spot --> +inf
-        result = compute_gamma(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for time --> +0
-        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for spot / k < 1 and volatility --> +0
-        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_gamma(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_gamma_gpu(self):
+        self.test_check_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_gamma_2(self, call: bool):
-        m = BSLookbackOption(call=call)
+    def test_gamma_2(self, call: bool, device: str = "cpu"):
+        m = BSLookbackOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.gamma(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.gamma(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_gamma_3(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_gamma_2_gpu(self, call: bool):
+        self.test_gamma_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_gamma_3(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.gamma(None, torch.zeros(1), torch.zeros(2), torch.zeros(3))
+            m.gamma(
+                None,
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.gamma(torch.zeros(1), None, torch.zeros(2), torch.zeros(3))
+            m.gamma(
+                torch.zeros(1).to(device),
+                None,
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.gamma(torch.zeros(1), torch.zeros(2), None, torch.zeros(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.gamma(torch.zeros(1), torch.zeros(2), torch.zeros(3), None)
+            m.gamma(
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                None,
+                torch.zeros(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.gamma(
+                torch.zeros(1).to(device),
+                torch.zeros(2).to(device),
+                torch.zeros(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.gamma()
@@ -296,69 +357,102 @@ def test_gamma_3(self, call: bool):
                 None,
             )
 
-    def test_check_vega(self):
-        m = BSLookbackOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_gamma_3_gpu(self, call: bool):
+        self.test_gamma_3(call, device="cuda")
+
+    def test_check_vega(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
 
         # gamma = 0 for max --> +0
-        result = compute_vega(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for max / spot --> +inf
-        result = compute_vega(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for time --> +0
-        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_vega(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for spot / k < 1 and volatility --> +0
-        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_vega(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_vega(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_vega_gpu(self):
+        self.test_check_vega(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_vega_2(self, call: bool):
-        m = BSLookbackOption(call=call)
+    def test_vega_2(self, call: bool, device: str = "cpu"):
+        m = BSLookbackOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.vega(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.vega(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_vega_3(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_vega_2_gpu(self, call: bool):
+        self.test_vega_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_3(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.vega(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.vega(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.vega(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.vega(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.vega(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.vega(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.vega(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.vega()
@@ -427,69 +521,102 @@ def test_vega_3(self, call: bool):
                 None,
             )
 
-    def test_check_theta(self):
-        m = BSLookbackOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_3_gpu(self, call: bool):
+        self.test_vega_3(call, device="cuda")
+
+    def test_check_theta(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
 
         # gamma = 0 for max --> +0
-        result = compute_theta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # gamma = 0 for max / spot --> +inf
-        result = compute_theta(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for time --> +0
-        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_theta(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # delta = 0 for spot / k < 1 and volatility --> +0
-        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
-        result = compute_theta(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_theta(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_check_theta_gpu(self):
+        self.test_check_theta(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_theta_2(self, call: bool):
-        m = BSLookbackOption(call=call)
+    def test_theta_2(self, call: bool, device: str = "cpu"):
+        m = BSLookbackOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.theta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.theta(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_theta_3(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_theta_2_gpu(self, call: bool):
+        self.test_theta_2(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_theta_3(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
         with pytest.raises(AttributeError):
-            m.theta(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.theta(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.theta(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.theta(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.theta(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.theta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
+        with pytest.raises(AttributeError):
+            m.theta(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.theta()
@@ -558,76 +685,109 @@ def test_theta_3(self, call: bool):
                 None,
             )
 
-    def test_check_price(self):
-        m = BSLookbackOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_theta_3_gpu(self, call: bool):
+        self.test_theta_3(call, device="cuda")
+
+    def test_check_price(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
 
         # price = 0 for max --> +0
-        result = compute_price(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-10.0, -10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # price = max - strike for max --> +inf
-        result = compute_price(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]))
-        expect = torch.tensor([10.0]).exp() - m.strike
+        result = compute_price(m, torch.tensor([[0.0, 10.0, 0.1, 0.2]]).to(device))
+        expect = torch.tensor([10.0]).to(device).exp() - m.strike
         assert_close(result, expect)
 
         # price = 0 for max < strike and time --> +0
-        result = compute_price(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-0.01, -0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # price = max - strike for max > strike and time --> +0
-        result = compute_price(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]))
-        expect = torch.tensor([0.01]).exp() - m.strike
+        result = compute_price(m, torch.tensor([[0.0, 0.01, 1e-10, 0.2]]).to(device))
+        expect = torch.tensor([0.01]).to(device).exp() - m.strike
         assert_close(result, expect)
 
         # price = 0 for spot < strike and volatility --> +0
-        result = compute_price(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.0])
+        result = compute_price(m, torch.tensor([[-0.01, -0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.0]).to(device)
         assert_close(result, expect)
 
         # price = max - strike for spot > strike and volatility --> +0
-        result = compute_price(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]))
-        expect = torch.tensor([0.01]).exp() - m.strike
+        result = compute_price(m, torch.tensor([[0.0, 0.01, 0.1, 1e-10]]).to(device))
+        expect = torch.tensor([0.01]).to(device).exp() - m.strike
         assert_close(result, expect)
 
         # prices are almost equal for max = strike +- epsilon
-        result0 = compute_price(m, torch.tensor([[-1e-5, -1e-5, 0.1, 0.2]]))
-        result1 = compute_price(m, torch.tensor([[1e-5, 1e-5, 0.1, 0.2]]))
+        result0 = compute_price(m, torch.tensor([[-1e-5, -1e-5, 0.1, 0.2]]).to(device))
+        result1 = compute_price(m, torch.tensor([[1e-5, 1e-5, 0.1, 0.2]]).to(device))
         assert_close(result0, result1, atol=1e-4, rtol=0)
 
+    @pytest.mark.gpu
+    def test_check_price_gpu(self):
+        self.test_check_price(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_price_3(self, call: bool):
-        m = BSLookbackOption(call=call)
+    def test_price_3(self, call: bool, device: str = "cpu"):
+        m = BSLookbackOption(call=call).to(device)
         with pytest.raises(ValueError):
             m.price(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(-1.0),
-                torch.tensor(0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(-1.0).to(device),
+                torch.tensor(0.2).to(device),
             )
         with pytest.raises(ValueError):
             m.price(
-                torch.tensor(0.0),
-                torch.tensor(0.0),
-                torch.tensor(1.0),
-                torch.tensor(-0.2),
+                torch.tensor(0.0).to(device),
+                torch.tensor(0.0).to(device),
+                torch.tensor(1.0).to(device),
+                torch.tensor(-0.2).to(device),
             )
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("call", [True])
-    def test_price_4(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_price_3_gpu(self, call: bool):
+        self.test_price_3(call, device="cuda")
+
+    @pytest.mark.parametrize("call", [True])
+    def test_price_4(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
+        with pytest.raises(AttributeError):
+            m.price(
+                None,
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.price(None, torch.tensor(1), torch.tensor(2), torch.tensor(3))
+            m.price(
+                torch.tensor(1).to(device),
+                None,
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), None, torch.tensor(2), torch.tensor(3))
+            m.price(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                None,
+                torch.tensor(3).to(device),
+            )
         with pytest.raises(AttributeError):
-            m.price(torch.tensor(1), torch.tensor(2), None, torch.tensor(3))
-        # ToDo: #530
-        # with pytest.raises(AttributeError):
-        #     m.price(torch.tensor(1), torch.tensor(2), torch.tensor(3), None)
+            m.price(
+                torch.tensor(1).to(device),
+                torch.tensor(2).to(device),
+                torch.tensor(3).to(device),
+                None,
+            )
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         result = m.price()
@@ -696,65 +856,100 @@ def test_price_4(self, call: bool):
                 None,
             )
 
-    def test_check_price_monte_carlo(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_price_4_gpu(self, call: bool):
+        self.test_price_4(call, device="cuda")
+
+    def test_check_price_monte_carlo(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         # Monte Carlo evaluation of a lookback option needs small dt
         k = 1.01
-        d = LookbackOption(BrownianStock(dt=1e-5), strike=k)
-        m = BSLookbackOption.from_derivative(d)
+        d = LookbackOption(BrownianStock(dt=1e-5), strike=k).to(device)
+        m = BSLookbackOption.from_derivative(d).to(device)
         d.simulate(n_paths=int(1e4), init_state=(1.0,))
 
-        s = torch.tensor([1.0 / k]).log()
-        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]])
+        s = torch.tensor([1.0 / k]).to(device).log()
+        input = torch.tensor([[s, s, d.maturity, d.ul().sigma]]).to(device)
         result = compute_price(m, input)
         expect = d.payoff().mean(0, keepdim=True)
-        assert_close(result, expect, rtol=1e-2, atol=0.0)
+        assert_close(result, expect, rtol=3e-2, atol=0.0)
 
-    def test_forward(self):
-        m = BSLookbackOption()
-        s = torch.tensor(1.00 / 1.03).log()
-        input = torch.tensor([[s, s, 1.0, 0.2]])
+    @pytest.mark.gpu
+    def test_check_price_monte_carlo_gpu(self):
+        self.test_check_price_monte_carlo(device="cuda")
+
+    def test_forward(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
+        s = torch.tensor(1.00 / 1.03).to(device).log()
+        input = torch.tensor([[s, s, 1.0, 0.2]]).to(device)
         result = m(input)
         expect = torch.full_like(result, 1.037)
         assert_close(result, expect, atol=1e-2, rtol=1e-2)
 
-    def test_price(self):
-        m = BSLookbackOption(strike=1.03)
-        s = torch.tensor(1.00 / 1.03).log()
-        result = m.price(s, s, torch.tensor(1.0), torch.tensor(0.2))
-        expect = torch.tensor(0.14)
+    @pytest.mark.gpu
+    def test_forward_gpu(self):
+        self.test_forward(device="cuda")
+
+    def test_price(self, device: str = "cpu"):
+        m = BSLookbackOption(strike=1.03).to(device)
+        s = torch.tensor(1.00 / 1.03).to(device).log()
+        result = m.price(
+            s, s, torch.tensor(1.0).to(device), torch.tensor(0.2).to(device)
+        )
+        expect = torch.tensor(0.14).to(device)
         assert_close(result, expect, atol=1e-2, rtol=1e-2)
 
-    def test_delta(self):
-        m = BSLookbackOption()
-        s = torch.tensor(1.00 / 1.03).log()
-        result = m.delta(s, s, torch.tensor(1.0), torch.tensor(0.2))
-        expect = torch.tensor(1.037)
+    @pytest.mark.gpu
+    def test_price_gpu(self):
+        self.test_price(device="cuda")
+
+    def test_delta(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
+        s = torch.tensor(1.00 / 1.03).to(device).log()
+        result = m.delta(
+            s, s, torch.tensor(1.0).to(device), torch.tensor(0.2).to(device)
+        )
+        expect = torch.tensor(1.037).to(device)
         assert_close(result, expect, atol=1e-2, rtol=1e-2)
 
-    def test_gamma(self):
-        m = BSLookbackOption()
-        s = torch.tensor(1.00 / 1.03).log()
-        result = m.gamma(s, s, torch.tensor(1.0), torch.tensor(0.2))
-        expect = torch.tensor(4.466)
-        assert_close(result, expect, atol=0.8, rtol=0.8)  # ?
+    @pytest.mark.gpu
+    def test_delta_gpu(self):
+        self.test_delta(device="cuda")
+
+    def test_gamma(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
+        s = torch.tensor(1.00 / 1.03).to(device).log()
+        result = m.gamma(
+            s, s, torch.tensor(1.0).to(device), torch.tensor(0.2).to(device)
+        )
+        expect = torch.tensor(4.5998).to(device)
+        assert_close(result, expect, atol=1e-2, rtol=1e-2)
 
-    def test_implied_volatility(self):
+    @pytest.mark.gpu
+    def test_gamma_gpu(self):
+        self.test_gamma(device="cuda")
+
+    def test_implied_volatility(self, device: str = "cpu"):
         input = torch.tensor(
             [[0.0, 0.0, 0.1, 0.01], [0.0, 0.0, 0.1, 0.02], [0.0, 0.0, 0.1, 0.03]]
-        )
-        m = BSLookbackOption()
+        ).to(device)
+        m = BSLookbackOption().to(device)
         iv = m.implied_volatility(input[:, 0], input[:, 1], input[:, 2], input[:, 3])
         result = BSLookbackOption().price(input[:, 0], input[:, 1], input[:, 2], iv)
         expect = input[:, -1]
         assert_close(result, expect, atol=1e-4, rtol=1e-4, check_stride=False)
 
+    @pytest.mark.gpu
+    def test_implied_volatility_gpu(self):
+        self.test_implied_volatility(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_implied_volatility_2(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
-        m2 = BSLookbackOption(call=call)
+    def test_implied_volatility_2(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
+        m2 = BSLookbackOption(call=call).to(device)
         with pytest.raises(AttributeError):
             m.implied_volatility()
         torch.manual_seed(42)
@@ -812,21 +1007,30 @@ def test_implied_volatility_2(self, call: bool):
                 derivative.underlier.spot,
             )
 
-    def test_vega_and_gamma(self):
-        m = BSLookbackOption()
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_implied_volatility_2_gpu(self, call: bool):
+        self.test_implied_volatility_2(call, device="cuda")
+
+    def test_vega_and_gamma(self, device: str = "cpu"):
+        m = BSLookbackOption().to(device)
         # vega = spot^2 * sigma * (T - t) * gamma
         # See Chapter 5 Appendix A, Bergomi "Stochastic volatility modeling"
-        spot = torch.tensor([0.90, 0.94, 0.98])
-        t = torch.tensor([0.1, 0.2, 0.3])
-        v = torch.tensor([0.1, 0.2, 0.3])
+        spot = torch.tensor([0.90, 0.94, 0.98]).to(device)
+        t = torch.tensor([0.1, 0.2, 0.3]).to(device)
+        v = torch.tensor([0.1, 0.2, 0.3]).to(device)
         vega = m.vega(spot.log(), spot.log(), t, v)
         gamma = m.gamma(spot.log(), spot.log(), t, v)
         assert_close(vega, spot.square() * v * t * gamma, atol=1e-3, rtol=0)
 
+    @pytest.mark.gpu
+    def test_vega_and_gamma_gpu(self):
+        self.test_vega_and_gamma(device="cuda")
+
     @pytest.mark.parametrize("call", [True])
-    def test_vega_and_gamma_2(self, call: bool):
-        derivative = LookbackOption(BrownianStock(), call=call)
-        m = BSLookbackOption.from_derivative(derivative)
+    def test_vega_and_gamma_2(self, call: bool, device: str = "cpu"):
+        derivative = LookbackOption(BrownianStock(), call=call).to(device)
+        m = BSLookbackOption.from_derivative(derivative).to(device)
         torch.manual_seed(42)
         derivative.simulate(n_paths=1)
         vega = m.vega()
@@ -844,32 +1048,45 @@ def test_vega_and_gamma_2(self, call: bool):
             rtol=0,
         )
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("call", [True])
+    def test_vega_and_gamma_2_gpu(self, call: bool):
+        self.test_vega_and_gamma_2(call, device="cuda")
+
     def test_put_notimplemented(self):
         with pytest.raises(ValueError):
             # not yet supported
             BSLookbackOption(call=False)
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        m = BSLookbackOption()
-        self.assert_shape_delta(m)
-        self.assert_shape_gamma(m)
-        self.assert_shape_vega(m)
-        self.assert_shape_theta(m)
-        self.assert_shape_price(m)
-        self.assert_shape_forward(m)
-
-    def test_example(self):
+        m = BSLookbackOption().to(device)
+        self.assert_shape_delta(m, device=device)
+        self.assert_shape_gamma(m, device=device)
+        self.assert_shape_vega(m, device=device)
+        self.assert_shape_theta(m, device=device)
+        self.assert_shape_price(m, device=device)
+        self.assert_shape_forward(m, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
+
+    def test_example(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         from pfhedge.instruments import BrownianStock
         from pfhedge.instruments import LookbackOption
         from pfhedge.nn import Hedger
 
-        deriv = LookbackOption(BrownianStock(), strike=1.03)
-        model = BSLookbackOption.from_derivative(deriv)
-        hedger = Hedger(model, model.inputs())
+        deriv = LookbackOption(BrownianStock(), strike=1.03).to(device)
+        model = BSLookbackOption.from_derivative(deriv).to(device)
+        hedger = Hedger(model, model.inputs()).to(device)
         price = hedger.price(deriv)
 
-        assert_close(price, torch.tensor(0.017), atol=1e-3, rtol=1e-3)
+        assert_close(price, torch.tensor(0.017).to(device), atol=1e-2, rtol=1e-2)
+
+    @pytest.mark.gpu
+    def test_example_gpu(self):
+        self.test_example(device="cuda")
diff --git a/tests/nn/modules/test_clamp.py b/tests/nn/modules/test_clamp.py
index 63c1afcd..e67e6986 100644
--- a/tests/nn/modules/test_clamp.py
+++ b/tests/nn/modules/test_clamp.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 from torch.testing import assert_close
 
@@ -6,11 +7,11 @@
 
 
 class TestLeakyClamp:
-    def test_output(self):
-        input = torch.tensor([-1.0, 0.0, 0.5, 1.0, 2.0])
+    def test_output(self, device: str = "cpu"):
+        input = torch.tensor([-1.0, 0.0, 0.5, 1.0, 2.0]).to(device)
 
         result = LeakyClamp(0.1)(input, 0, 1)
-        expect = torch.tensor([-0.1, 0.0, 0.5, 1.0, 1.1])
+        expect = torch.tensor([-0.1, 0.0, 0.5, 1.0, 1.1]).to(device)
         assert_close(result, expect)
 
         result = LeakyClamp(0.01)(input, 0, 0)
@@ -29,6 +30,10 @@ def test_output(self):
         expect = Clamp()(input, 0, 1)
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    def test_output_gpu(self):
+        self.test_output(device="cuda")
+
     def test_repr(self):
         expect = "LeakyClamp(clamped_slope=0.1000)"
         assert repr(LeakyClamp(0.1)) == expect
diff --git a/tests/nn/modules/test_hedger.py b/tests/nn/modules/test_hedger.py
index ff28443b..604467b4 100644
--- a/tests/nn/modules/test_hedger.py
+++ b/tests/nn/modules/test_hedger.py
@@ -49,12 +49,16 @@ def forward(self, input: Tensor):
 
 
 class TestHedger:
-    def test_error_optimizer(self):
-        hedger = Hedger(Linear(2, 1), ["moneyness", "time_to_maturity"])
-        derivative = EuropeanOption(BrownianStock())
+    def test_error_optimizer(self, device: str = "cpu"):
+        hedger = Hedger(Linear(2, 1), ["moneyness", "time_to_maturity"]).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(device)
         with pytest.raises(TypeError):
             hedger._configure_optimizer(derivative, optimizer=Identity)
 
+    @pytest.mark.gpu
+    def test_error_optimizer_gpu(self):
+        self.test_error_optimizer(device="cuda")
+
     def test_repr(self):
         hedger = Hedger(Linear(2, 1), ["moneyness", "time_to_maturity"])
         expect = """\
@@ -76,46 +80,57 @@ def test_repr(self):
 )"""
         assert repr(hedger) == expect
 
-    def test_compute_hedge_error_not_same_size(self):
-        stock0 = BrownianStock()
-        stock1 = BrownianStock()
-        stock0.register_buffer("spot", torch.ones(2, 3))
-        derivative = EuropeanOption(stock0)
-        hedger = Hedger(Naked(), ["empty"])
+    def test_compute_hedge_error_not_same_size(self, device: str = "cpu"):
+        stock0 = BrownianStock().to(device)
+        stock1 = BrownianStock().to(device)
+        stock0.register_buffer("spot", torch.ones(2, 3).to(device))
+        derivative = EuropeanOption(stock0).to(device)
+        hedger = Hedger(Naked(), ["empty"]).to(device)
 
-        stock1.register_buffer("spot", torch.ones(2, 4))
+        stock1.register_buffer("spot", torch.ones(2, 4).to(device))
         with pytest.raises(ValueError):
             _ = hedger.compute_hedge(derivative, hedge=[stock0, stock1])
-        stock1.register_buffer("spot", torch.ones(3, 3))
+        stock1.register_buffer("spot", torch.ones(3, 3).to(device))
         with pytest.raises(ValueError):
             _ = hedger.compute_hedge(derivative, hedge=[stock0, stock1])
 
+    @pytest.mark.gpu
+    def test_compute_hedge_error_not_same_size_gpu(self):
+        self.test_compute_hedge_error_not_same_size(device="cuda")
+
     @pytest.mark.parametrize("hin", [1, 2])
-    def test_compute_pnl_size(self, hin):
+    def test_compute_pnl_size(self, hin, device: str = "cpu"):
         torch.manual_seed(42)
 
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Linear(hin, 1), inputs=["empty"] * hin)
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Linear(hin, 1), inputs=["empty"] * hin).to(device)
         pnl = hedger.compute_pnl(derivative)
 
         N = derivative.ul().spot.size(0)
         assert pnl.size() == torch.Size((N,))
 
         torch.manual_seed(42)
-        derivative = EuropeanOption(BrownianStock())
-        hedger = Hedger(Linear(hin + 1, 1), inputs=["empty"] * hin + ["prev_hedge"])
+        derivative = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(Linear(hin + 1, 1), inputs=["empty"] * hin + ["prev_hedge"]).to(
+            device
+        )
         pnl = hedger.compute_pnl(derivative)
 
         N = derivative.ul().spot.size(0)
         assert pnl.size() == torch.Size((N,))
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("hin", [1, 2])
+    def test_compute_pnl_size_gpu(self, hin):
+        self.test_compute_pnl_size(hin=hin, device="cuda")
+
     @pytest.mark.parametrize("cost", [0.0, 1e-3])
-    def test_compute_pnl_1(self, cost):
+    def test_compute_pnl_1(self, cost, device: str = "cpu"):
         # pnl = -payoff if output = 0
         torch.manual_seed(42)
 
-        deriv = EuropeanOption(BrownianStock(cost=cost))
-        hedger = Hedger(Naked(), ["empty"])
+        deriv = EuropeanOption(BrownianStock(cost=cost)).to(device)
+        hedger = Hedger(Naked(), ["empty"]).to(device)
 
         pnl = hedger.compute_pnl(deriv)
         payoff = deriv.payoff()
@@ -125,16 +140,21 @@ def test_compute_pnl_1(self, cost):
         expect = -deriv.payoff()
         assert_close(result, expect)
 
-    def test_compute_pnl_2(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("cost", [0.0, 1e-3])
+    def test_compute_pnl_1_gpu(self, cost):
+        self.test_compute_pnl_1(cost=cost, device="cuda")
+
+    def test_compute_pnl_2(self, device: str = "cpu"):
         torch.manual_seed(42)
         N, T = 2, 4
-        derivative = ZeroDerivative(BrownianStock())
-        output = torch.randn(N, T - 1)
+        derivative = ZeroDerivative(BrownianStock()).to(device)
+        output = torch.randn(N, T - 1).to(device)
 
-        m = FakeModule(output.unsqueeze(-1))
-        hedger = Hedger(m, ["empty", "prev_hedge"])
+        m = FakeModule(output.unsqueeze(-1)).to(device)
+        hedger = Hedger(m, ["empty", "prev_hedge"]).to(device)
 
-        spot = torch.randn(N, T).exp()
+        spot = torch.randn(N, T).to(device).exp()
         derivative.ul().register_buffer("spot", spot)
         with patch("pfhedge.instruments.BrownianStock.simulate", void):
             # so that the simulation is not performed
@@ -143,18 +163,22 @@ def test_compute_pnl_2(self):
         expect = (spot.diff(dim=-1) * output).sum(-1)
         assert_close(result, expect)
 
-    def test_compute_pnl_2_multiple_hedges(self):
+    @pytest.mark.gpu
+    def test_compute_pnl_2_gpu(self):
+        self.test_compute_pnl_2(device="cuda")
+
+    def test_compute_pnl_2_multiple_hedges(self, device: str = "cpu"):
         torch.manual_seed(42)
         N, T = 2, 4
 
-        stock0 = BrownianStock()
-        stock1 = BrownianStock()
-        stock0.register_buffer("spot", torch.randn(N, T).exp())
-        stock1.register_buffer("spot", torch.randn(N, T).exp())
-        derivative = ZeroDerivative(stock0)
+        stock0 = BrownianStock().to(device)
+        stock1 = BrownianStock().to(device)
+        stock0.register_buffer("spot", torch.randn(N, T).to(device).exp())
+        stock1.register_buffer("spot", torch.randn(N, T).to(device).exp())
+        derivative = ZeroDerivative(stock0).to(device)
 
-        output = torch.randn(N, T - 1, 2)
-        m = FakeModule(output)
+        output = torch.randn(N, T - 1, 2).to(device)
+        m = FakeModule(output).to(device)
         hedger = Hedger(m, ["empty", "prev_hedge"])
 
         with patch("pfhedge.instruments.BrownianStock.simulate", void):
@@ -166,21 +190,25 @@ def test_compute_pnl_2_multiple_hedges(self):
         expect = pnl0 + pnl1
         assert_close(result, expect)
 
-    def test_compute_pnl_2_multiple_hedges_payoff(self):
+    @pytest.mark.gpu
+    def test_compute_pnl_2_multiple_hedges_gpu(self):
+        self.test_compute_pnl_2_multiple_hedges(device="cuda")
+
+    def test_compute_pnl_2_multiple_hedges_payoff(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         N, T = 2, 4
 
-        stock0 = BrownianStock()
-        stock1 = BrownianStock()
-        stock0.register_buffer("spot", torch.randn(N, T).exp())
-        stock1.register_buffer("spot", torch.randn(N, T).exp())
-        derivative = EuropeanOption(stock0)
+        stock0 = BrownianStock().to(device)
+        stock1 = BrownianStock().to(device)
+        stock0.register_buffer("spot", torch.randn(N, T).to(device).exp())
+        stock1.register_buffer("spot", torch.randn(N, T).to(device).exp())
+        derivative = EuropeanOption(stock0).to(device)
         payoff = derivative.payoff()
 
-        output = torch.randn(N, T - 1, 2)
-        m = FakeModule(output)
-        hedger = Hedger(m, ["empty", "prev_hedge"])
+        output = torch.randn(N, T - 1, 2).to(device)
+        m = FakeModule(output).to(device)
+        hedger = Hedger(m, ["empty", "prev_hedge"]).to(device)
         with patch("pfhedge.instruments.BrownianStock.simulate", void):
             result = hedger.compute_pnl(derivative, hedge=[stock0, stock1])
         pnl0 = (stock0.spot.diff(dim=-1) * output[..., 0]).sum(-1)
@@ -188,16 +216,24 @@ def test_compute_pnl_2_multiple_hedges_payoff(self):
         expect = pnl0 + pnl1 - payoff
         assert_close(result, expect)
 
-    def test_compute_pnl_payoff(self):
+    @pytest.mark.gpu
+    def test_compute_pnl_2_multiple_hedges_payoff_gpu(self):
+        self.test_compute_pnl_2_multiple_hedges_payoff(device="cuda")
+
+    def test_compute_pnl_payoff(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         N, T = 10, 20
-        derivative0 = EuropeanOption(BrownianStock())
-        derivative1 = ZeroDerivative(BrownianStock())
+        derivative0 = EuropeanOption(BrownianStock()).to(device)
+        derivative1 = ZeroDerivative(BrownianStock()).to(device)
         output = torch.randn(N, T - 1)
 
-        hedger0 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"])
-        hedger1 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"])
+        hedger0 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"]).to(
+            device
+        )
+        hedger1 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"]).to(
+            device
+        )
 
         spot = torch.randn(N, T).exp()
         derivative0.ul().register_buffer("spot", spot)
@@ -211,17 +247,25 @@ def test_compute_pnl_payoff(self):
         expect = -derivative0.payoff()
         assert_close(result, expect)
 
-    def test_compute_pnl_cost(self):
+    @pytest.mark.gpu
+    def test_compute_pnl_payoff_gpu(self):
+        self.test_compute_pnl_payoff(device="cuda")
+
+    def test_compute_pnl_cost(self, device: str = "cpu"):
         cost = 1e-3
         N, T = 10, 20
-        derivative0 = EuropeanOption(BrownianStock(cost=0.0))
-        derivative1 = EuropeanOption(BrownianStock(cost=cost))
-        output = torch.randn(N, T - 1)
-
-        hedger0 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"])
-        hedger1 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"])
-
-        spot = torch.randn(N, T).exp()
+        derivative0 = EuropeanOption(BrownianStock(cost=0.0)).to(device)
+        derivative1 = EuropeanOption(BrownianStock(cost=cost)).to(device)
+        output = torch.randn(N, T - 1).to(device)
+
+        hedger0 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"]).to(
+            device
+        )
+        hedger1 = Hedger(FakeModule(output.unsqueeze(-1)), ["empty", "prev_hedge"]).to(
+            device
+        )
+
+        spot = torch.randn(N, T).to(device).exp()
         derivative0.ul().register_buffer("spot", spot)
         derivative1.ul().register_buffer("spot", spot)
         with patch("pfhedge.instruments.BrownianStock.simulate", void):
@@ -230,43 +274,51 @@ def test_compute_pnl_cost(self):
             pnl1 = hedger1.compute_pnl(derivative1)
 
         result = pnl0 - pnl1
-        output = torch.cat((torch.zeros(N, 1), output), dim=-1)
+        output = torch.cat((torch.zeros(N, 1).to(device), output), dim=-1)
         expect = cost * (spot[..., :-1] * output.diff(dim=-1).abs()).sum(-1)
         assert_close(result, expect)
 
-    def test_forward_shape(self):
+    @pytest.mark.gpu
+    def test_compute_pnl_cost_gpu(self):
+        self.test_compute_pnl_cost(device="cuda")
+
+    def test_forward_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        deriv = EuropeanOption(BrownianStock())
+        deriv = EuropeanOption(BrownianStock()).to(device)
 
         N = 2
         M_1 = 5
         M_2 = 6
         H_in = 3
 
-        input = torch.zeros((N, M_1, M_2, H_in))
-        m = Hedger(MultiLayerPerceptron(), ["empty"])
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
+        m = Hedger(MultiLayerPerceptron(), ["empty"]).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
 
-        model = BlackScholes(deriv)
-        m = Hedger(model, model.inputs())
+        model = BlackScholes(deriv).to(device)
+        m = Hedger(model, model.inputs()).to(device)
         input = torch.zeros((N, M_1, M_2, len(model.inputs())))
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
 
-        model = WhalleyWilmott(deriv)
-        m = Hedger(model, model.inputs())
+        model = WhalleyWilmott(deriv).to(device)
+        m = Hedger(model, model.inputs()).to(device)
         input = torch.zeros((N, M_1, M_2, len(model.inputs())))
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
 
-        model = Naked()
-        m = Hedger(model, ["empty"])
-        input = torch.zeros((N, M_1, M_2, 10))
+        model = Naked().to(device)
+        m = Hedger(model, ["empty"]).to(device)
+        input = torch.zeros((N, M_1, M_2, 10)).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
 
+    @pytest.mark.gpu
+    def test_forward_shape_gpu(self):
+        self.test_forward_shape(device="cuda")
+
     @pytest.mark.parametrize("h_in", [1, 2, 3])
-    def test_get_input(self, h_in):
-        hedger = Hedger(Naked(), ["zeros"] * h_in)
-        derivative = EuropeanOption(BrownianStock())
+    def test_get_input(self, h_in, device: str = "cpu"):
+        hedger = Hedger(Naked(), ["zeros"] * h_in).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
         _ = hedger.compute_pnl(derivative, n_paths=1)
         N, T = derivative.ul().spot.size()
@@ -275,11 +327,16 @@ def test_get_input(self, h_in):
         input = hedger.get_input(derivative, None)
         assert input.size() == torch.Size((N, T, h_in))
 
+    @pytest.mark.gpu
     @pytest.mark.parametrize("h_in", [1, 2, 3])
-    def test_compute_hedge(self, h_in):
+    def test_get_input_gpu(self, h_in):
+        self.test_get_input(h_in=h_in, device="cuda")
+
+    @pytest.mark.parametrize("h_in", [1, 2, 3])
+    def test_compute_hedge(self, h_in, device: str = "cpu"):
         # test size
-        hedger = Hedger(Naked(), ["zeros"] * h_in)
-        derivative = EuropeanOption(BrownianStock())
+        hedger = Hedger(Naked(), ["zeros"] * h_in).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
         hedge = hedger.compute_hedge(derivative)
         N, T = derivative.ul().spot.size()
@@ -287,24 +344,35 @@ def test_compute_hedge(self, h_in):
         assert hedge.size() == torch.Size((N, H, T))
 
         # test size
-        hedger = Hedger(Naked(), ["zeros"] * h_in + ["prev_hedge"])
-        derivative = EuropeanOption(BrownianStock())
+        hedger = Hedger(Naked(), ["zeros"] * h_in + ["prev_hedge"]).to(device)
+        derivative = EuropeanOption(BrownianStock()).to(device)
         derivative.simulate()
         hedge = hedger.compute_hedge(derivative)
         N, T = derivative.ul().spot.size()
         H = 1
         assert hedge.size() == torch.Size((N, H, T))
 
-    def test_compute_loss(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("h_in", [1, 2, 3])
+    def test_compute_hedge_gpu(self, h_in):
+        self.test_compute_hedge(h_in=h_in, device="cuda")
+
+    def test_compute_loss(self, device: str = "cpu"):
         torch.manual_seed(42)
-        deriv = EuropeanOption(BrownianStock())
-        hedger = Hedger(Naked(), ["log_moneyness", "time_to_maturity", "volatility"])
+        deriv = EuropeanOption(BrownianStock()).to(device)
+        hedger = Hedger(
+            Naked(), ["log_moneyness", "time_to_maturity", "volatility"]
+        ).to(device)
 
         result = hedger.compute_loss(deriv)
-        expect = EntropicRiskMeasure()(-deriv.payoff())
+        expect = EntropicRiskMeasure().to(device)(-deriv.payoff())
         assert_close(result, expect)
 
-    def test_hedging_with_identical_derivative(self):
+    @pytest.mark.gpu
+    def test_compute_loss_gpu(self):
+        self.test_compute_loss(device="cuda")
+
+    def test_hedging_with_identical_derivative(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         class Ones(Module):
@@ -312,15 +380,19 @@ def forward(self, input: Tensor):
                 return torch.ones_like(input[..., :1])
 
         def pricer(derivative: Derivative) -> Tensor:
-            return BlackScholes(derivative).price(
-                log_moneyness=derivative.log_moneyness(),
-                time_to_maturity=derivative.time_to_maturity(),
-                volatility=derivative.ul().volatility,
+            return (
+                BlackScholes(derivative)
+                .to(device)
+                .price(
+                    log_moneyness=derivative.log_moneyness(),
+                    time_to_maturity=derivative.time_to_maturity(),
+                    volatility=derivative.ul().volatility,
+                )
             )
 
-        derivative = EuropeanOption(BrownianStock(), maturity=5 / 250)
+        derivative = EuropeanOption(BrownianStock(), maturity=5 / 250).to(device)
         derivative.list(pricer)
-        hedger = Hedger(Ones(), ["empty", "prev_hedge"])
+        hedger = Hedger(Ones(), ["empty", "prev_hedge"]).to(device)
 
         torch.manual_seed(42)
         result = hedger.compute_pnl(derivative, hedge=[derivative], n_paths=2)
@@ -332,3 +404,7 @@ def pricer(derivative: Derivative) -> Tensor:
         result = hedger.price(derivative, hedge=[derivative], n_paths=2)
         expect = derivative.spot[0, 0]
         assert_close(result, expect, check_stride=False)
+
+    @pytest.mark.gpu
+    def test_hedging_with_identical_derivative_gpu(self):
+        self.test_hedging_with_identical_derivative(device="cuda")
diff --git a/tests/nn/modules/test_loss.py b/tests/nn/modules/test_loss.py
index 79831727..78191776 100644
--- a/tests/nn/modules/test_loss.py
+++ b/tests/nn/modules/test_loss.py
@@ -13,26 +13,26 @@
 from pfhedge.nn.modules.loss import OCE
 
 
-def assert_loss_shape(loss):
+def assert_loss_shape(loss, device: str = "cpu"):
     torch.manual_seed(42)
 
     N = 20
     M_1 = 10
     M_2 = 11
 
-    input = torch.randn((N,)).exp()  # impose > 0
+    input = torch.randn((N,)).to(device).exp()  # impose > 0
     output = loss(input)
     assert output.size() == torch.Size([])
     output = loss.cash(input)
     assert output.size() == torch.Size([])
 
-    input = torch.randn((N, M_1)).exp()
+    input = torch.randn((N, M_1)).to(device).exp()
     output = loss(input)
     assert output.size() == torch.Size((M_1,))
     output = loss.cash(input)
     assert output.size() == torch.Size((M_1,))
 
-    input = torch.randn((N, M_1, M_2)).exp()
+    input = torch.randn((N, M_1, M_2)).to(device).exp()
     output = loss(input)
     assert output.size() == torch.Size((M_1, M_2))
     output = loss.cash(input)
@@ -43,74 +43,118 @@ class TestEntropicRiskMeasure:
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("risk", [1.0, 2.0, 10.0])
     @pytest.mark.parametrize("a", [0.001, 1, 2])
-    def test_nonincreasing(self, n_paths, risk, a):
+    def test_nonincreasing(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicRiskMeasure(risk)
-        x1 = torch.randn(n_paths)
+        loss = EntropicRiskMeasure(risk).to(device)
+        x1 = torch.randn(n_paths).to(device)
         x2 = x1 - a
         assert_monotone(loss, x1, x2, increasing=False)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("risk", [1.0, 2.0, 10.0])
+    @pytest.mark.parametrize("a", [0.001, 1, 2])
+    def test_nonincreasing_gpu(self, n_paths, risk, a):
+        self.test_nonincreasing(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
     @pytest.mark.parametrize("a", [0.1, 0.5])
-    def test_convex(self, n_paths, risk, a):
+    def test_convex(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicRiskMeasure(risk)
-        x1 = torch.randn(n_paths)
-        x2 = torch.randn(n_paths)
+        loss = EntropicRiskMeasure(risk).to(device)
+        x1 = torch.randn(n_paths).to(device)
+        x2 = torch.randn(n_paths).to(device)
         assert_convex(loss, x1, x2, a)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
+    @pytest.mark.parametrize("a", [0.1, 0.5])
+    def test_convex_gpu(self, n_paths, risk, a):
+        self.test_convex(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
-    def test_cash(self, n_paths, a):
+    def test_cash(self, n_paths, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicRiskMeasure(a)
-        x = torch.randn(n_paths)
+        loss = EntropicRiskMeasure(a).to(device)
+        x = torch.randn(n_paths).to(device)
         assert_cash_equivalent(loss, x, float(loss.cash(x).item()))
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
+    def test_cash_gpu(self, n_paths, a):
+        self.test_cash(n_paths, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
     @pytest.mark.parametrize("c", [0.001, 1, 2])
-    def test_cash_equivalent(self, n_paths, risk, c):
+    def test_cash_equivalent(self, n_paths, risk, c, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicRiskMeasure(risk)
+        loss = EntropicRiskMeasure(risk).to(device)
         assert_cash_invariant(loss, torch.randn(n_paths), c)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
+    @pytest.mark.parametrize("c", [0.001, 1, 2])
+    def test_cash_equivalent_gpu(self, n_paths, risk, c):
+        self.test_cash_equivalent(n_paths, risk, c, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
-    def test_value(self, n_paths, a):
+    def test_value(self, n_paths, a, device: str = "cpu"):
         torch.manual_seed(42)
 
         value = 1.0
-        loss = EntropicRiskMeasure(a)
-        result = loss(torch.full((n_paths,), value))
-        expect = torch.log(torch.exp(-a * torch.tensor(value))) / a
+        loss = EntropicRiskMeasure(a).to(device)
+        result = loss(torch.full((n_paths,), value).to(device))
+        expect = torch.log(torch.exp(-a * torch.tensor(value).to(device))) / a
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
+    def test_value_gpu(self, n_paths, a):
+        self.test_value(n_paths, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
     @pytest.mark.parametrize("value", [100.0, -100.0])
-    def test_extreme(self, n_paths, a, value):
+    def test_extreme(self, n_paths, a, value, device: str = "cpu"):
         torch.manual_seed(42)
 
         loss = EntropicRiskMeasure(a)
-        result = loss(torch.full((n_paths,), value))
-        assert_close(result, torch.tensor(-value))
+        result = loss(torch.full((n_paths,), value).to(device))
+        assert_close(result, torch.tensor(-value).to(device))
+
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
+    @pytest.mark.parametrize("value", [100.0, -100.0])
+    def test_extreme_gpu(self, n_paths, a, value):
+        self.test_extreme(n_paths=n_paths, a=a, value=value, device="cuda")
 
-    def test_extreme2(self):
+    def test_extreme2(self, device: str = "cpu"):
         torch.manual_seed(42)
 
         loss = EntropicRiskMeasure(a=1.0)
-        result1 = loss(torch.tensor([1000.0, 0.0]))
-        result2 = loss(torch.tensor([500.0, -500.0]))
-        result3 = loss(torch.tensor([0.0, -1000.0]))
+        result1 = loss(torch.tensor([1000.0, 0.0]).to(device))
+        result2 = loss(torch.tensor([500.0, -500.0]).to(device))
+        result3 = loss(torch.tensor([0.0, -1000.0]).to(device))
         assert_close(result1 + 500, result2)
         assert_close(result1 + 1000, result3)
 
+    @pytest.mark.gpu
+    def test_extreme2_gpu(self):
+        self.test_extreme2(device="cuda")
+
     def test_error_a(self):
         with pytest.raises(ValueError):
             EntropicRiskMeasure(0)
@@ -123,54 +167,84 @@ def test_repr(self):
         loss = EntropicRiskMeasure(a=10.0)
         assert repr(loss) == "EntropicRiskMeasure(a=10.)"
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicRiskMeasure()
-        assert_loss_shape(loss)
+        loss = EntropicRiskMeasure().to(device)
+        assert_loss_shape(loss, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
 
 
 class TestEntropicLoss:
     @pytest.mark.parametrize("n_paths", [1, 10, 100])
     @pytest.mark.parametrize("risk", [1.0, 2.0, 10.0])
     @pytest.mark.parametrize("a", [0.001, 1, 2])
-    def test_nonincreasing(self, n_paths, risk, a):
+    def test_nonincreasing(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicLoss(risk)
-        x1 = torch.randn(n_paths)
+        loss = EntropicLoss(risk).to(device)
+        x1 = torch.randn(n_paths).to(device)
         x2 = x1 - a
         assert_monotone(loss, x1, x2, increasing=False)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [1, 10, 100])
+    @pytest.mark.parametrize("risk", [1.0, 2.0, 10.0])
+    @pytest.mark.parametrize("a", [0.001, 1, 2])
+    def test_nonincreasing_gpu(self, n_paths, risk, a):
+        self.test_nonincreasing(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [1, 10, 100])
     @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
     @pytest.mark.parametrize("a", [0.1, 0.5])
-    def test_convex(self, n_paths, risk, a):
+    def test_convex(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicLoss(risk)
-        input1 = torch.randn(n_paths)
-        input2 = torch.randn(n_paths)
+        loss = EntropicLoss(risk).to(device)
+        input1 = torch.randn(n_paths).to(device)
+        input2 = torch.randn(n_paths).to(device)
         assert_convex(loss, input1, input2, a)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [1, 10, 100])
+    @pytest.mark.parametrize("risk", [1.0, 2.0, 3.0])
+    @pytest.mark.parametrize("a", [0.1, 0.5])
+    def test_convex_gpu(self, n_paths, risk, a):
+        self.test_convex(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
-    def test_cash(self, n_paths, a):
+    def test_cash(self, n_paths, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicLoss(a)
-        x = torch.randn(n_paths)
+        loss = EntropicLoss(a).to(device)
+        x = torch.randn(n_paths).to(device)
         assert_cash_equivalent(loss, x, float(loss.cash(x).item()))
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
+    def test_cash_gpu(self, n_paths, a):
+        self.test_cash(n_paths, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
-    def test_value(self, n_paths, a):
+    def test_value(self, n_paths, a, device: str = "cpu"):
         value = 1.0
-        loss = EntropicLoss(a)
-        result = loss(torch.full((n_paths,), value))
-        expect = torch.exp(-a * torch.tensor(value))
+        loss = EntropicLoss(a).to(device)
+        result = loss(torch.full((n_paths,), value).to(device))
+        expect = torch.exp(-a * torch.tensor(value).to(device))
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("a", [1.0, 2.0, 3.0])
+    def test_value_gpu(self, n_paths, a):
+        self.test_value(n_paths, a, device="cuda")
+
     def test_error_a(self):
         with pytest.raises(ValueError):
             EntropicLoss(0)
@@ -183,46 +257,70 @@ def test_repr(self):
         loss = EntropicLoss(a=10.0)
         assert repr(loss) == "EntropicLoss(a=10.)"
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = EntropicLoss()
-        assert_loss_shape(loss)
+        loss = EntropicLoss().to(device)
+        assert_loss_shape(loss, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
 
 
 class TestIsoelasticLoss:
     @pytest.mark.parametrize("n_paths", [1, 10, 100])
     @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
     @pytest.mark.parametrize("a", [0.001, 1, 2])
-    def test_nonincreasing(self, n_paths, risk, a):
+    def test_nonincreasing(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = IsoelasticLoss(risk)
-        x2 = torch.randn(n_paths).exp()  # force positive
+        loss = IsoelasticLoss(risk).to(device)
+        x2 = torch.randn(n_paths).to(device).exp()  # force positive
         x1 = x2 + a
 
         assert_monotone(loss, x1, x2, increasing=False)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [1, 10, 100])
+    @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
+    @pytest.mark.parametrize("a", [0.001, 1, 2])
+    def test_nonincreasing_gpu(self, n_paths, risk, a):
+        self.test_nonincreasing(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [1, 10, 100])
     @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
     @pytest.mark.parametrize("a", [0.1, 0.5])
-    def test_convex(self, n_paths, risk, a):
+    def test_convex(self, n_paths, risk, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = IsoelasticLoss(risk)
-        x1 = torch.randn(n_paths).exp()
-        x2 = torch.randn(n_paths).exp()
+        loss = IsoelasticLoss(risk).to(device)
+        x1 = torch.randn(n_paths).to(device).exp()
+        x2 = torch.randn(n_paths).to(device).exp()
         assert_convex(loss, x1, x2, a)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [1, 10, 100])
+    @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
+    @pytest.mark.parametrize("a", [0.1, 0.5])
+    def test_convex_gpu(self, n_paths, risk, a):
+        self.test_convex(n_paths, risk, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
-    def test_cash(self, n_paths, risk):
+    def test_cash(self, n_paths, risk, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = IsoelasticLoss(risk)
-        x = torch.randn(n_paths).exp()  # force positive
+        loss = IsoelasticLoss(risk).to(device)
+        x = torch.randn(n_paths).to(device).exp()  # force positive
         assert_cash_equivalent(loss, x, float(loss.cash(x).item()))
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("risk", [0.1, 0.5, 1.0])
+    def test_cash_gpu(self, n_paths, risk):
+        self.test_cash(n_paths, risk, device="cuda")
+
     def test_error_a(self):
         with pytest.raises(ValueError):
             IsoelasticLoss(0.0)
@@ -235,54 +333,85 @@ def test_repr(self):
         loss = IsoelasticLoss(0.5)
         assert repr(loss) == "IsoelasticLoss(a=0.5000)"
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = IsoelasticLoss(0.5)
-        assert_loss_shape(loss)
+        loss = IsoelasticLoss(0.5).to(device)
+        assert_loss_shape(loss, device=device)
 
-        loss = IsoelasticLoss(1.0)
-        assert_loss_shape(loss)
+        loss = IsoelasticLoss(1.0).to(device)
+        assert_loss_shape(loss, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
 
 
 class TestExpectedShortFall:
     @pytest.mark.parametrize("n_paths", [100, 1000])
     @pytest.mark.parametrize("p", [0.5])
     @pytest.mark.parametrize("a", [0.001, 1, 2])
-    def test_nonincreasing(self, n_paths, p, a):
+    def test_nonincreasing(self, n_paths, p, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = ExpectedShortfall(p)
-        x1 = torch.randn(n_paths)
+        loss = ExpectedShortfall(p).to(device)
+        x1 = torch.randn(n_paths).to(device)
         x2 = x1 - 1
         assert_monotone(loss, x1, x2, increasing=False)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [100, 1000])
+    @pytest.mark.parametrize("p", [0.5])
+    @pytest.mark.parametrize("a", [0.001, 1, 2])
+    def test_nonincreasing_gpu(self, n_paths, p, a):
+        self.test_nonincreasing(n_paths, p, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [100, 1000])
     @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
     @pytest.mark.parametrize("a", [0.1, 0.5])
-    def test_convex(self, n_paths, p, a):
+    def test_convex(self, n_paths, p, a, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = ExpectedShortfall(p)
-        x1 = torch.randn(n_paths)
-        x2 = torch.randn(n_paths)
+        loss = ExpectedShortfall(p).to(device)
+        x1 = torch.randn(n_paths).to(device)
+        x2 = torch.randn(n_paths).to(device)
         assert_convex(loss, x1, x2, a)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [100, 1000])
+    @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
+    @pytest.mark.parametrize("a", [0.1, 0.5])
+    def test_convex_gpu(self, n_paths, p, a):
+        self.test_convex(n_paths, p, a, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [100, 1000])
     @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
-    def test_cash(self, n_paths, p):
+    def test_cash(self, n_paths, p, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = ExpectedShortfall(p)
-        x = torch.randn(n_paths)
+        loss = ExpectedShortfall(p).to(device)
+        x = torch.randn(n_paths).to(device)
         assert_cash_equivalent(loss, x, float(loss.cash(x).item()))
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [100, 1000])
+    @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
+    def test_cash_gpu(self, n_paths, p):
+        self.test_cash(n_paths, p, device="cuda")
+
     @pytest.mark.parametrize("n_paths", [10, 100])
     @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
     @pytest.mark.parametrize("eta", [0.001, 1, 2])
-    def test_cash_equivalent(self, n_paths, p, eta):
-        loss = ExpectedShortfall(p)
-        assert_cash_invariant(loss, torch.randn(n_paths), eta)
+    def test_cash_equivalent(self, n_paths, p, eta, device: str = "cpu"):
+        loss = ExpectedShortfall(p).to(device)
+        assert_cash_invariant(loss, torch.randn(n_paths).to(device), eta)
+
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [10, 100])
+    @pytest.mark.parametrize("p", [0.1, 0.5, 0.9])
+    @pytest.mark.parametrize("eta", [0.001, 1, 2])
+    def test_cash_equivalent_gpu(self, n_paths, p, eta):
+        self.test_cash_equivalent(n_paths, p, eta, device="cuda")
 
     def test_error_percentile(self):
         # 1 is allowed
@@ -295,58 +424,71 @@ def test_error_percentile(self):
             _ = ExpectedShortfall(1.1)
 
     @pytest.mark.parametrize("percentile", [0.1, 0.5, 0.9])
-    def test_value(self, percentile):
+    def test_value(self, percentile, device: str = "cpu"):
         torch.manual_seed(42)
 
         n_paths = 100
         k = int(n_paths * percentile)
-        loss = ExpectedShortfall(percentile)
+        loss = ExpectedShortfall(percentile).to(device)
 
-        input = torch.randn(n_paths)
+        input = torch.randn(n_paths).to(device)
 
         result = loss(input)
-        expect = -torch.mean(torch.tensor(sorted(input)[:k]))
+        expect = -torch.mean(torch.tensor(sorted(input)[:k]).to(device))
 
         assert_close(result, expect)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("percentile", [0.1, 0.5, 0.9])
+    def test_value_gpu(self, percentile):
+        self.test_value(percentile, device="cuda")
+
     def test_repr(self):
         loss = ExpectedShortfall(0.1)
         assert repr(loss) == "ExpectedShortfall(0.1)"
         loss = ExpectedShortfall(0.5)
         assert repr(loss) == "ExpectedShortfall(0.5)"
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = ExpectedShortfall()
-        assert_loss_shape(loss)
+        loss = ExpectedShortfall().to(device)
+        assert_loss_shape(loss, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
 
 
 class TestOCE:
-    def train_oce(self, m):
+    def train_oce(self, m, device: str = "cpu"):
         torch.manual_seed(42)
 
         optim = torch.optim.Adam(m.parameters())
 
         for _ in range(1000):
             optim.zero_grad()
-            m(torch.randn(10000)).backward()
+            m(torch.randn(10000).to(device)).backward()
             optim.step()
 
-    def test_fit(self):
+    def test_fit(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        m = OCE(lambda input: 1 - torch.exp(-input))
+        m = OCE(lambda input: 1 - torch.exp(-input)).to(device)
 
-        self.train_oce(m)
+        self.train_oce(m, device=device)
 
-        input = torch.randn(10000)
+        input = torch.randn(10000).to(device)
 
         result = m(input)
         expect = torch.log(EntropicLoss()(input))
 
         assert_close(result, expect, rtol=1e-02, atol=1e-5)
 
+    @pytest.mark.gpu
+    def test_fit_gpu(self):
+        self.test_fit(device="cuda")
+
     def test_repr(self):
         def exp_utility(input):
             return 1 - torch.exp(-input)
@@ -354,8 +496,12 @@ def exp_utility(input):
         loss = OCE(exp_utility)
         assert repr(loss) == "OCE(exp_utility, w=0.)"
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.manual_seed(42)
 
-        loss = OCE(lambda input: 1 - torch.exp(-input))
-        assert_loss_shape(loss)
+        loss = OCE(lambda input: 1 - torch.exp(-input)).to(device)
+        assert_loss_shape(loss, device=device)
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/test_mlp.py b/tests/nn/modules/test_mlp.py
index 3a0c6715..f8fbe59f 100644
--- a/tests/nn/modules/test_mlp.py
+++ b/tests/nn/modules/test_mlp.py
@@ -10,24 +10,40 @@ class TestMultiLayerPerceptron:
     """
 
     @pytest.mark.parametrize("out_features", [1, 2])
-    def test_out_features(self, out_features):
-        m = MultiLayerPerceptron(out_features=out_features)
+    def test_out_features(self, out_features, device: str = "cpu"):
+        m = MultiLayerPerceptron(out_features=out_features).to(device)
         assert m[-2].out_features == out_features
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("out_features", [1, 2])
+    def test_out_features_gpu(self, out_features):
+        self.test_out_features(out_features, device="cuda")
+
     @pytest.mark.parametrize("n_layers", [1, 4, 10])
-    def test_n_layers(self, n_layers):
-        m = MultiLayerPerceptron(n_layers=n_layers)
+    def test_n_layers(self, n_layers, device: str = "cpu"):
+        m = MultiLayerPerceptron(n_layers=n_layers).to(device)
         assert len(m) == 2 * (n_layers + 1)
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_layers", [1, 4, 10])
+    def test_n_layers_gpu(self, n_layers):
+        self.test_n_layers(n_layers, device="cuda")
+
     @pytest.mark.parametrize("n_units", [2, 8, 32])
     @pytest.mark.parametrize("in_features", [1, 2])
     @pytest.mark.parametrize("out_features", [1, 2])
-    def test_n_units(self, n_units, in_features, out_features):
+    def test_n_units(
+        self,
+        n_units,
+        in_features,
+        out_features,
+        device: str = "cpu",
+    ):
         n_layers = 4
         m = MultiLayerPerceptron(
             n_layers=n_layers, n_units=n_units, out_features=out_features
-        )
-        _ = m(torch.zeros((1, in_features)))
+        ).to(device)
+        _ = m(torch.zeros((1, in_features)).to(device))
 
         for i in range(n_layers + 1):
             linear = m[2 * i]
@@ -36,53 +52,79 @@ def test_n_units(self, n_units, in_features, out_features):
             assert linear.in_features == in_expect
             assert linear.out_features == out_expect
 
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_units", [2, 8, 32])
+    @pytest.mark.parametrize("in_features", [1, 2])
+    @pytest.mark.parametrize("out_features", [1, 2])
+    def test_n_units_gpu(self, n_units, in_features, out_features):
+        self.test_n_units(n_units, in_features, out_features, device="cuda")
+
     @pytest.mark.parametrize("activation", [torch.nn.ELU(), torch.nn.CELU()])
     @pytest.mark.parametrize("out_activation", [torch.nn.ELU(), torch.nn.CELU()])
-    def test_activation(self, activation, out_activation):
+    def test_activation(
+        self,
+        activation,
+        out_activation,
+        device: str = "cpu",
+    ):
         n_layers = 4
         m = MultiLayerPerceptron(
             n_layers=n_layers, activation=activation, out_activation=out_activation
-        )
+        ).to(device)
 
         for i in range(n_layers + 1):
             expect = out_activation if i == n_layers else activation
             activ = m[2 * i + 1]
             assert isinstance(activ, expect.__class__)
 
-    def test_shape(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("activation", [torch.nn.ELU(), torch.nn.CELU()])
+    @pytest.mark.parametrize("out_activation", [torch.nn.ELU(), torch.nn.CELU()])
+    def test_activation_gpu(self, activation, out_activation):
+        self.test_activation(activation, out_activation, device="cuda")
+
+    def test_shape(self, device: str = "cpu"):
         N = 10
         H_in = 11
         M_1 = 12
         M_2 = 13
         H_out = 14
 
-        input = torch.zeros((N, H_in))
-        m = MultiLayerPerceptron(H_in, H_out)
+        input = torch.zeros((N, H_in)).to(device)
+        m = MultiLayerPerceptron(H_in, H_out).to(device)
         assert m(input).size() == torch.Size((N, H_out))
 
-        input = torch.zeros((N, M_1, H_in))
-        m = MultiLayerPerceptron(H_in, H_out)
+        input = torch.zeros((N, M_1, H_in)).to(device)
+        m = MultiLayerPerceptron(H_in, H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, H_out))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
-        m = MultiLayerPerceptron(H_in, H_out)
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
+        m = MultiLayerPerceptron(H_in, H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, H_out))
 
-    def test_shape_lazy(self):
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cpu")
+
+    def test_shape_lazy(self, device: str = "cpu"):
         N = 10
         H_in = 11
         M_1 = 12
         M_2 = 13
         H_out = 14
 
-        input = torch.zeros((N, H_in))
-        m = MultiLayerPerceptron(out_features=H_out)
+        input = torch.zeros((N, H_in)).to(device)
+        m = MultiLayerPerceptron(out_features=H_out).to(device)
         assert m(input).size() == torch.Size((N, H_out))
 
-        input = torch.zeros((N, M_1, H_in))
-        m = MultiLayerPerceptron(out_features=H_out)
+        input = torch.zeros((N, M_1, H_in)).to(device)
+        m = MultiLayerPerceptron(out_features=H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, H_out))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
-        m = MultiLayerPerceptron(out_features=H_out)
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
+        m = MultiLayerPerceptron(out_features=H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, H_out))
+
+    @pytest.mark.gpu
+    def test_shape_lazy_gpu(self):
+        self.test_shape_lazy(device="cpu")
diff --git a/tests/nn/modules/test_naked.py b/tests/nn/modules/test_naked.py
index 4d9dde06..1757ac36 100644
--- a/tests/nn/modules/test_naked.py
+++ b/tests/nn/modules/test_naked.py
@@ -11,26 +11,36 @@ class TestNaked:
 
     @pytest.mark.parametrize("n_paths", [1, 10])
     @pytest.mark.parametrize("n_features", [1, 10])
-    def test(self, n_paths, n_features):
-        m = Naked()
-        input = torch.zeros((n_paths, n_features))
-        assert torch.equal(m(input), torch.zeros((n_paths, 1)))
+    def test(self, n_paths, n_features, device: str = "cpu"):
+        m = Naked().to(device)
+        input = torch.zeros((n_paths, n_features)).to(device)
+        assert torch.equal(m(input), torch.zeros((n_paths, 1)).to(device))
 
-    def test_shape(self):
+    @pytest.mark.gpu
+    @pytest.mark.parametrize("n_paths", [1, 10])
+    @pytest.mark.parametrize("n_features", [1, 10])
+    def test_gpu(self, n_paths, n_features):
+        self.test(n_paths, n_features, device="cuda")
+
+    def test_shape(self, device: str = "cpu"):
         N = 11
         H_in = 12
         M_1 = 13
         M_2 = 14
         H_out = 15
 
-        input = torch.zeros((N, H_in))
-        m = Naked(H_out)
+        input = torch.zeros((N, H_in)).to(device)
+        m = Naked(H_out).to(device)
         assert m(input).size() == torch.Size((N, H_out))
 
-        input = torch.zeros((N, M_1, H_in))
-        m = Naked(H_out)
+        input = torch.zeros((N, M_1, H_in)).to(device)
+        m = Naked(H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, H_out))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
-        m = Naked(H_out)
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
+        m = Naked(H_out).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, H_out))
+
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
diff --git a/tests/nn/modules/test_svi.py b/tests/nn/modules/test_svi.py
index 87ac4bcc..7d9dd549 100644
--- a/tests/nn/modules/test_svi.py
+++ b/tests/nn/modules/test_svi.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 from torch.nn.functional import relu
 from torch.testing import assert_allclose
@@ -5,29 +6,34 @@
 from pfhedge.nn import SVIVariance
 
 
-def test_svi():
-    input = torch.linspace(-1.0, 1.0, 10)
+def test_svi(device: str = "cpu"):
+    input = torch.linspace(-1.0, 1.0, 10).to(device)
 
     # for b = 0, output = a
-    m = SVIVariance(a=1.0, b=0, rho=0.1, m=0.2, sigma=0.3)
+    m = SVIVariance(a=1.0, b=0, rho=0.1, m=0.2, sigma=0.3).to(device)
     result = m(input)
     expect = torch.full_like(result, m.a)
     assert_allclose(result, expect)
 
     # for sigma = 0 and rho = -1, output = a + 2 b relu(x)
-    m = SVIVariance(a=1.0, b=0, rho=-1, m=0.0, sigma=0.3)
+    m = SVIVariance(a=1.0, b=0, rho=-1, m=0.0, sigma=0.3).to(device)
     result = m(input)
     expect = m.a + 2 * m.b * relu(input - m.m)
     assert_allclose(result, expect)
 
     # m translates
-    m0 = SVIVariance(a=0.1, b=0.2, rho=0.3, m=0.0, sigma=0.5)
-    m1 = SVIVariance(a=0.1, b=0.2, rho=0.3, m=0.4, sigma=0.5)
+    m0 = SVIVariance(a=0.1, b=0.2, rho=0.3, m=0.0, sigma=0.5).to(device)
+    m1 = SVIVariance(a=0.1, b=0.2, rho=0.3, m=0.4, sigma=0.5).to(device)
     result = m0(input)
     expect = m1(input + m1.m)
     assert_allclose(result, expect)
 
 
+@pytest.mark.gpu
+def test_svi_gpu():
+    test_svi(device="cuda")
+
+
 def test_svi_repr():
     m = SVIVariance(a=1.0, b=0, rho=0.1, m=0.2, sigma=0.3)
     assert repr(m) == "SVIVariance(a=1.0, b=0, rho=0.1, m=0.2, sigma=0.3)"
diff --git a/tests/nn/modules/test_ww.py b/tests/nn/modules/test_ww.py
index 55028283..89118ef8 100644
--- a/tests/nn/modules/test_ww.py
+++ b/tests/nn/modules/test_ww.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 
 from pfhedge import autogreek
@@ -35,40 +36,54 @@ def test_repr(self):
 )"""
         assert repr(m) == expect
 
-    def test_shape(self):
+    def test_shape(self, device: str = "cpu"):
         torch.distributions.Distribution.set_default_validate_args(False)
 
-        deriv = EuropeanOption(BrownianStock())
-        m = WhalleyWilmott(deriv)
+        deriv = EuropeanOption(BrownianStock()).to(device)
+        m = WhalleyWilmott(deriv).to(device)
 
         N = 10
         H_in = len(m.inputs())
         M_1 = 11
         M_2 = 12
 
-        input = torch.zeros((N, H_in))
+        input = torch.zeros((N, H_in)).to(device)
         assert m(input).size() == torch.Size((N, 1))
 
-        input = torch.zeros((N, M_1, H_in))
+        input = torch.zeros((N, M_1, H_in)).to(device)
         assert m(input).size() == torch.Size((N, M_1, 1))
 
-        input = torch.zeros((N, M_1, M_2, H_in))
+        input = torch.zeros((N, M_1, M_2, H_in)).to(device)
         assert m(input).size() == torch.Size((N, M_1, M_2, 1))
 
-    def test(self):
-        derivative = EuropeanOption(BrownianStock(cost=1e-4))
-        model = WhalleyWilmott(derivative)
+    @pytest.mark.gpu
+    def test_shape_gpu(self):
+        self.test_shape(device="cuda")
+
+    def test(self, device: str = "cpu"):
+        derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(device)
+        model = WhalleyWilmott(derivative).to(device)
         hedger = Hedger(model, model.inputs())
         pnl = hedger.compute_pnl(derivative)
         assert not pnl.isnan().any()
 
-    def test_autogreek_generate_nan_for_float64(self):
-        derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(torch.float64)
-        model = WhalleyWilmott(derivative).to(torch.float64)
-        hedger = Hedger(model, model.inputs()).to(torch.float64)
+    @pytest.mark.gpu
+    def test_gpu(self):
+        self.test(device="cuda")
+
+    def test_autogreek_generate_nan_for_float64(self, device: str = "cpu"):
+        derivative = (
+            EuropeanOption(BrownianStock(cost=1e-4)).to(torch.float64).to(device)
+        )
+        model = WhalleyWilmott(derivative).to(torch.float64).to(device)
+        hedger = Hedger(model, model.inputs()).to(torch.float64).to(device)
 
         def pricer(spot):
             return hedger.price(derivative, init_state=(spot,), enable_grad=True)
 
-        delta = autogreek.delta(pricer, spot=torch.tensor(1.0))
+        delta = autogreek.delta(pricer, spot=torch.tensor(1.0).to(device))
         assert not delta.isnan().any()
+
+    @pytest.mark.gpu
+    def test_autogreek_generate_nan_for_float64_gpu(self):
+        self.test_autogreek_generate_nan_for_float64(device="cuda")
diff --git a/tests/nn/test_functional.py b/tests/nn/test_functional.py
index c157a3fb..be1ff3be 100644
--- a/tests/nn/test_functional.py
+++ b/tests/nn/test_functional.py
@@ -19,28 +19,33 @@
 from pfhedge.nn.functional import value_at_risk
 
 
-def test_exp_utility():
-    input = torch.tensor([-1.0, 0.0, 1.0])
+def test_exp_utility(device: str = "cpu"):
+    input = torch.tensor([-1.0, 0.0, 1.0], device=device)
 
     result = exp_utility(input, 1.0)
-    expect = torch.tensor([-2.7183, -1.0000, -0.3679])
+    expect = torch.tensor([-2.7183, -1.0000, -0.3679], device=device)
     assert_close(result, expect, atol=1e-4, rtol=1e-4)
 
     result = exp_utility(input, 2.0)
-    expect = torch.tensor([-7.3891, -1.0000, -0.1353])
+    expect = torch.tensor([-7.3891, -1.0000, -0.1353], device=device)
     assert_close(result, expect, atol=1e-4, rtol=1e-4)
 
     result = exp_utility(input, 0.0)
-    expect = torch.tensor([-1.0000, -1.0000, -1.0000])
+    expect = torch.tensor([-1.0000, -1.0000, -1.0000], device=device)
     assert_close(result, expect, atol=1e-4, rtol=1e-4)
 
 
+@pytest.mark.gpu
+def test_exp_utility_gpu():
+    test_exp_utility(device="cuda")
+
+
 @pytest.mark.parametrize("p", [0.0, 0.1, 0.5, 0.9, 1.0])
 @pytest.mark.parametrize("largest", [True, False])
-def test_topp(p, largest):
+def test_topp(p, largest, device: str = "cpu"):
     torch.manual_seed(42)
 
-    input = torch.randn(100)
+    input = torch.randn(100).to(device)
     k = int(p * 100)
 
     result = topp(input, p, largest=largest).values
@@ -52,37 +57,59 @@ def test_topp(p, largest):
     assert_close(result, expect)
 
 
-def test_topp_error():
+@pytest.mark.gpu
+@pytest.mark.parametrize("p", [0.0, 0.1, 0.5, 0.9, 1.0])
+@pytest.mark.parametrize("largest", [True, False])
+def test_topp_gpu(p, largest):
+    test_topp(p, largest, device="cuda")
+
+
+def test_topp_error(device: str = "cpu"):
     with pytest.raises(RuntimeError):
-        topp(torch.zeros(100), 1.1)
+        topp(torch.zeros(100).to(device), 1.1)
     with pytest.raises(RuntimeError):
-        topp(torch.zeros(100), -0.1)
+        topp(torch.zeros(100).to(device), -0.1)
 
 
-def test_expected_shortfall():
-    input = torch.arange(1.0, 6.0)
+@pytest.mark.gpu
+def test_topp_error_gpu():
+    test_topp_error(device="cuda")
+
+
+def test_expected_shortfall(device: str = "cpu"):
+    input = torch.arange(1.0, 6.0).to(device)
 
     result = expected_shortfall(input, 3 / 5)
-    expect = torch.tensor(-2.0)
+    expect = torch.tensor(-2.0).to(device)
     assert_close(result, expect)
 
 
-def test_value_at_risk():
-    input = -torch.arange(10.0)
+@pytest.mark.gpu
+def test_expected_shortfall_gpu():
+    test_expected_shortfall(device="cuda")
+
+
+def test_value_at_risk(device: str = "cpu"):
+    input = -torch.arange(10.0).to(device)
+
+    assert_close(value_at_risk(input, 0.0), -torch.tensor(9.0).to(device))
+    assert_close(value_at_risk(input, 0.1), -torch.tensor(9.0).to(device))
+    assert_close(value_at_risk(input, 0.2), -torch.tensor(8.0).to(device))
+    assert_close(value_at_risk(input, 0.8), -torch.tensor(2.0).to(device))
+    assert_close(value_at_risk(input, 0.9), -torch.tensor(1.0).to(device))
+    assert_close(value_at_risk(input, 1.0), -torch.tensor(0.0).to(device))
+
 
-    assert_close(value_at_risk(input, 0.0), -torch.tensor(9.0))
-    assert_close(value_at_risk(input, 0.1), -torch.tensor(9.0))
-    assert_close(value_at_risk(input, 0.2), -torch.tensor(8.0))
-    assert_close(value_at_risk(input, 0.8), -torch.tensor(2.0))
-    assert_close(value_at_risk(input, 0.9), -torch.tensor(1.0))
-    assert_close(value_at_risk(input, 1.0), -torch.tensor(0.0))
+@pytest.mark.gpu
+def test_value_at_risk_gpu():
+    test_value_at_risk(device="cuda")
 
 
-def test_leaky_clamp():
-    input = torch.tensor([-1.0, 0.0, 0.5, 1.0, 2.0])
+def test_leaky_clamp(device: str = "cpu"):
+    input = torch.tensor([-1.0, 0.0, 0.5, 1.0, 2.0], device=device)
 
     result = leaky_clamp(input, 0, 1, clamped_slope=0.1)
-    expect = torch.tensor([-0.1, 0.0, 0.5, 1.0, 1.1])
+    expect = torch.tensor([-0.1, 0.0, 0.5, 1.0, 1.1], device=device)
     assert_close(result, expect)
 
     result = leaky_clamp(input, 0, 0, clamped_slope=0.01)
@@ -110,10 +137,15 @@ def test_leaky_clamp():
     assert_close(result, expect)
 
 
-def test_clamp_error_invalid_inverted_output():
-    input = torch.zeros(10)
-    min = torch.zeros(10)
-    max = torch.zeros(10)
+@pytest.mark.gpu
+def test_leaky_clamp_gpu():
+    test_leaky_clamp(device="cuda")
+
+
+def test_clamp_error_invalid_inverted_output(device: str = "cpu"):
+    input = torch.zeros(10).to(device)
+    min = torch.zeros(10).to(device)
+    max = torch.zeros(10).to(device)
     with pytest.raises(ValueError):
         _ = leaky_clamp(input, min, max, inverted_output="min")
     with pytest.raises(ValueError):
@@ -124,10 +156,15 @@ def test_clamp_error_invalid_inverted_output():
         _ = clamp(input, min, max, inverted_output="foo")
 
 
-def test_realized_variance():
+@pytest.mark.gpu
+def test_clamp_error_invalid_inverted_output_gpu():
+    test_clamp_error_invalid_inverted_output(device="cuda")
+
+
+def test_realized_variance(device: str = "cpu"):
     torch.manual_seed(42)
 
-    log_return = 0.01 * torch.randn(2, 10)
+    log_return = 0.01 * torch.randn(2, 10).to(device)
     log_return[:, 0] = 0.0
     log_return -= log_return.mean(dim=-1, keepdim=True)
     input = log_return.cumsum(-1).exp()
@@ -138,10 +175,15 @@ def test_realized_variance():
     assert_close(result, expect)
 
 
-def test_realized_volatility():
+@pytest.mark.gpu
+def test_realized_variance_gpu():
+    test_realized_variance(device="cuda")
+
+
+def test_realized_volatility(device: str = "cpu"):
     torch.manual_seed(42)
 
-    log_return = 0.01 * torch.randn(2, 10)
+    log_return = 0.01 * torch.randn(2, 10).to(device)
     log_return[:, 0] = 0.0
     log_return -= log_return.mean(dim=-1, keepdim=True)
     input = log_return.cumsum(-1).exp()
@@ -152,38 +194,43 @@ def test_realized_volatility():
     assert_close(result, expect)
 
 
-def test_pl():
+@pytest.mark.gpu
+def test_realized_volatility_gpu():
+    test_realized_volatility(device="cuda")
+
+
+def test_pl(device: str = "cpu"):
     N, T = 10, 20
 
     # pl = -payoff if unit = 0
     torch.manual_seed(42)
-    spot = torch.randn((N, 1, T)).exp()
-    unit = torch.zeros((N, 1, T))
-    payoff = torch.randn(N)
+    spot = torch.randn((N, 1, T)).to(device).exp()
+    unit = torch.zeros((N, 1, T)).to(device)
+    payoff = torch.randn(N).to(device)
     result = pl(spot, unit, payoff=payoff)
     expect = -payoff
     assert_close(result, expect)
 
     # cost = 0
     torch.manual_seed(42)
-    spot = torch.randn((N, 1, T)).exp()
-    unit = torch.randn((N, 1, T))
+    spot = torch.randn((N, 1, T)).to(device).exp()
+    unit = torch.randn((N, 1, T)).to(device)
     result = pl(spot, unit)
     expect = ((spot[..., 1:] - spot[..., :-1]) * unit[..., :-1]).sum(-1).squeeze(1)
     assert_close(result, expect)
 
     # diff spot = 0, cost=0 -> value = 0
     torch.manual_seed(42)
-    spot = torch.ones((N, 1, T))
-    unit = torch.randn((N, 1, T))
+    spot = torch.ones((N, 1, T)).to(device)
+    unit = torch.randn((N, 1, T)).to(device)
     result = pl(spot, unit)
-    expect = torch.zeros(N)
+    expect = torch.zeros(N).to(device)
     assert_close(result, expect)
 
     # diff spot = 0, cost > 0 -> value = -cost
     torch.manual_seed(42)
-    spot = torch.ones((N, 1, T))
-    unit = torch.randn((N, 1, T))
+    spot = torch.ones((N, 1, T)).to(device)
+    unit = torch.randn((N, 1, T)).to(device)
     result = pl(spot, unit, cost=[1e-3], deduct_first_cost=False)
     expect = -1e-3 * ((unit[..., 1:] - unit[..., :-1]).abs() * spot[..., :-1]).sum(
         -1
@@ -191,8 +238,8 @@ def test_pl():
     assert_close(result, expect)
 
     torch.manual_seed(42)
-    spot = torch.ones((N, 1, T))
-    unit = torch.randn((N, 1, T))
+    spot = torch.ones((N, 1, T)).to(device)
+    unit = torch.randn((N, 1, T)).to(device)
     value0 = pl(spot, unit, cost=[1e-3], deduct_first_cost=False)
     value1 = pl(spot, unit, cost=[1e-3], deduct_first_cost=True)
     result = value1 - value0
@@ -200,10 +247,15 @@ def test_pl():
     assert_close(result, expect)
 
 
-def test_pl_unmatched_shape():
-    spot = torch.zeros((10, 1, 20))
-    unit = torch.zeros((10, 1, 20))
-    payoff = torch.zeros(10)
+@pytest.mark.gpu
+def test_pl_gpu():
+    test_pl(device="cuda")
+
+
+def test_pl_unmatched_shape(device: str = "cpu"):
+    spot = torch.zeros((10, 1, 20)).to(device)
+    unit = torch.zeros((10, 1, 20)).to(device)
+    payoff = torch.zeros(10).to(device)
     with pytest.raises(RuntimeError):
         _ = pl(spot, unit[:-1])
     with pytest.raises(RuntimeError):
@@ -212,100 +264,156 @@ def test_pl_unmatched_shape():
         _ = pl(spot, unit, payoff=payoff[:-1])
 
 
-def test_pl_additional_dim():
+@pytest.mark.gpu
+def test_pl_unmatched_shape_gpu():
+    test_pl_unmatched_shape(device="cuda")
+
+
+def test_pl_additional_dim(device: str = "cpu"):
     N, M, T = 10, 30, 20
 
     # pnl = -payoff if unit = 0
     torch.manual_seed(42)
-    spot = torch.randn((N, M, T)).exp()
-    unit = torch.zeros((N, M, T))
-    payoff = torch.randn(N)
+    spot = torch.randn((N, M, T)).to(device).exp()
+    unit = torch.zeros((N, M, T)).to(device)
+    payoff = torch.randn(N).to(device)
     result = pl(spot, unit, payoff=payoff)
     expect = -payoff
     assert_close(result, expect)
 
 
+@pytest.mark.gpu
+def test_pl_additional_dim_gpu():
+    test_pl_additional_dim(device="cuda")
+
+
 @pytest.mark.parametrize("log_moneyness", [-1.0, 0, 1.0])
 @pytest.mark.parametrize("time_to_maturity", [1.0, 0, -1])
 @pytest.mark.parametrize("volatility", [0.2, 0.0, -1])
-def test_d1(log_moneyness: float, time_to_maturity: float, volatility: float):
+def test_d1(
+    log_moneyness: float,
+    time_to_maturity: float,
+    volatility: float,
+    device: str = "cpu",
+):
     with pytest.raises(
         ValueError
     ) if time_to_maturity < 0 or volatility < 0 else contextlib.nullcontext():
         result = d1(
-            log_moneyness=torch.as_tensor([log_moneyness]),
-            time_to_maturity=torch.as_tensor(time_to_maturity),
-            volatility=torch.as_tensor(volatility),
+            log_moneyness=torch.as_tensor([log_moneyness]).to(device),
+            time_to_maturity=torch.as_tensor(time_to_maturity).to(device),
+            volatility=torch.as_tensor(volatility).to(device),
         )
         assert not result.isnan()
 
 
-def test_d1_2():
+@pytest.mark.gpu
+@pytest.mark.parametrize("log_moneyness", [-1.0, 0, 1.0])
+@pytest.mark.parametrize("time_to_maturity", [1.0, 0, -1])
+@pytest.mark.parametrize("volatility", [0.2, 0.0, -1])
+def test_d1_gpu(log_moneyness: float, time_to_maturity: float, volatility: float):
+    test_d1(
+        log_moneyness=log_moneyness,
+        time_to_maturity=time_to_maturity,
+        volatility=volatility,
+        device="cuda",
+    )
+
+
+def test_d1_2(device: str = "cpu"):
     results = d1(
-        log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-        time_to_maturity=torch.as_tensor([1.0, 0, 0]),
-        volatility=torch.as_tensor([0.2, 0.2, 0.2]),
+        log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+        time_to_maturity=torch.as_tensor([1.0, 0, 0]).to(device),
+        volatility=torch.as_tensor([0.2, 0.2, 0.2]).to(device),
     )
-    expected = torch.as_tensor([-4.9, 0, float("inf")])
+    expected = torch.as_tensor([-4.9, 0, float("inf")]).to(device)
     assert_close(results, expected)
     with pytest.raises(ValueError):
         d1(
-            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-            time_to_maturity=torch.as_tensor([1.0, 0, -1]),
-            volatility=torch.as_tensor([0.2, 0.2, 0.2]),
+            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+            time_to_maturity=torch.as_tensor([1.0, 0, -1]).to(device),
+            volatility=torch.as_tensor([0.2, 0.2, 0.2]).to(device),
         )
     with pytest.raises(ValueError):
         d1(
-            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-            time_to_maturity=torch.as_tensor([1.0, 0, 0]),
-            volatility=torch.as_tensor([0.2, 0.2, -1.0]),
+            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+            time_to_maturity=torch.as_tensor([1.0, 0, 0]).to(device),
+            volatility=torch.as_tensor([0.2, 0.2, -1.0]).to(device),
         )
 
 
+@pytest.mark.gpu
+def test_d1_2_gpu():
+    test_d1_2(device="cuda")
+
+
 @pytest.mark.parametrize("log_moneyness", [-1.0, 0, 1.0])
 @pytest.mark.parametrize("time_to_maturity", [1.0, 0, -1])
 @pytest.mark.parametrize("volatility", [0.2, 0.0, -1])
-def test_d2(log_moneyness: float, time_to_maturity: float, volatility: float):
+def test_d2(
+    log_moneyness: float,
+    time_to_maturity: float,
+    volatility: float,
+    device: str = "cpu",
+):
     with pytest.raises(
         ValueError
     ) if time_to_maturity < 0 or volatility < 0 else contextlib.nullcontext():
         result = d2(
-            log_moneyness=torch.as_tensor([log_moneyness]),
-            time_to_maturity=torch.as_tensor(time_to_maturity),
-            volatility=torch.as_tensor(volatility),
+            log_moneyness=torch.as_tensor([log_moneyness]).to(device),
+            time_to_maturity=torch.as_tensor(time_to_maturity).to(device),
+            volatility=torch.as_tensor(volatility).to(device),
         )
         assert not result.isnan()
 
 
-def test_d2_2():
+@pytest.mark.gpu
+@pytest.mark.parametrize("log_moneyness", [-1.0, 0, 1.0])
+@pytest.mark.parametrize("time_to_maturity", [1.0, 0, -1])
+@pytest.mark.parametrize("volatility", [0.2, 0.0, -1])
+def test_d2_gpu(log_moneyness: float, time_to_maturity: float, volatility: float):
+    test_d2(
+        log_moneyness=log_moneyness,
+        time_to_maturity=time_to_maturity,
+        volatility=volatility,
+        device="cuda",
+    )
+
+
+def test_d2_2(device: str = "cpu"):
     results = d2(
-        log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-        time_to_maturity=torch.as_tensor([1.0, 0, 0]),
-        volatility=torch.as_tensor([0.2, 0.2, 0.2]),
+        log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+        time_to_maturity=torch.as_tensor([1.0, 0, 0]).to(device),
+        volatility=torch.as_tensor([0.2, 0.2, 0.2]).to(device),
     )
-    expected = torch.as_tensor([-5.1, 0, float("inf")])
+    expected = torch.as_tensor([-5.1, 0, float("inf")]).to(device)
     assert_close(results, expected)
     with pytest.raises(ValueError):
         d2(
-            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-            time_to_maturity=torch.as_tensor([1.0, 0, -1]),
-            volatility=torch.as_tensor([0.2, 0.2, 0.2]),
+            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+            time_to_maturity=torch.as_tensor([1.0, 0, -1]).to(device),
+            volatility=torch.as_tensor([0.2, 0.2, 0.2]).to(device),
         )
     with pytest.raises(ValueError):
         d2(
-            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]),
-            time_to_maturity=torch.as_tensor([1.0, 0, 0]),
-            volatility=torch.as_tensor([0.2, 0.2, -1.0]),
+            log_moneyness=torch.as_tensor([-1.0, 0, 1.0]).to(device),
+            time_to_maturity=torch.as_tensor([1.0, 0, 0]).to(device),
+            volatility=torch.as_tensor([0.2, 0.2, -1.0]).to(device),
         )
 
 
-def test_bilerp():
+@pytest.mark.gpu
+def test_d2_2_gpu():
+    test_d2_2(device="cuda")
+
+
+def test_bilerp(device: str = "cpu"):
     torch.manual_seed(42)
 
-    i1 = torch.randn(2, 3)
-    i2 = torch.randn(2, 3)
-    i3 = torch.randn(2, 3)
-    i4 = torch.randn(2, 3)
+    i1 = torch.randn(2, 3).to(device)
+    i2 = torch.randn(2, 3).to(device)
+    i3 = torch.randn(2, 3).to(device)
+    i4 = torch.randn(2, 3).to(device)
 
     # edge cases
     result = bilerp(i1, i2, i3, i4, 0.0, 0.0)
@@ -327,26 +435,36 @@ def test_bilerp():
     assert_close(result, (i1 + i2 + i3 + i4) / 4)
 
 
-def test_box_muller():
+@pytest.mark.gpu
+def test_bilerp_gpu():
+    test_bilerp(device="cuda")
+
+
+def test_box_muller(device: str = "cpu"):
     torch.manual_seed(42)
 
     # correct radius
-    input1 = torch.rand(10)
-    input2 = torch.rand(10)
+    input1 = torch.rand(10).to(device)
+    input2 = torch.rand(10).to(device)
     output1, output2 = box_muller(input1, input2)
     result = output1.square() + output2.square()
     expect = -2 * input1.clamp(min=1e-10).log()
     assert_close(result, expect)
 
     # correct angle
-    input1 = torch.rand(10)
-    input2 = torch.zeros(10)
+    input1 = torch.rand(10).to(device)
+    input2 = torch.zeros(10).to(device)
     output1, output2 = box_muller(input1, input2)
     assert_close(output2, torch.zeros_like(output2))
 
     # no nan even when input1 is zero
-    input1 = torch.zeros(10)
-    input2 = torch.rand(10)
+    input1 = torch.zeros(10).to(device)
+    input2 = torch.rand(10).to(device)
     output1, output2 = box_muller(input1, input2)
     assert not output1.isnan().any()
     assert not output2.isnan().any()
+
+
+@pytest.mark.gpu
+def test_box_muller_gpu():
+    test_box_muller(device="cuda")
diff --git a/tests/stochastic/test_brownian.py b/tests/stochastic/test_brownian.py
index 6b673055..a0cf768a 100644
--- a/tests/stochastic/test_brownian.py
+++ b/tests/stochastic/test_brownian.py
@@ -1,5 +1,6 @@
 from math import sqrt
 
+import pytest
 import torch
 from torch.testing import assert_close
 
@@ -8,12 +9,13 @@
 from pfhedge.stochastic.engine import RandnSobolBoxMuller
 
 
-def test_generate_brownian_mean():
+def test_generate_brownian_mean(device: str = "cpu"):
     torch.manual_seed(42)
     n_paths = 10000
     n_steps = 250
 
-    output = generate_brownian(n_paths, n_steps)
+    device = torch.device(device) if device else None
+    output = generate_brownian(n_paths, n_steps, device=device)
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].mean()
     expect = torch.zeros_like(result)
@@ -21,53 +23,75 @@ def test_generate_brownian_mean():
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_brownian_nosigma():
+@pytest.mark.gpu
+def test_generate_brownian_mean_gpu():
+    test_generate_brownian_mean(device="cuda")
+
+
+def test_generate_brownian_nosigma(device: str = "cpu"):
     torch.manual_seed(42)
     n_steps = 250
 
-    result = generate_brownian(1, n_steps, sigma=0)
-    expect = torch.zeros(1, n_steps)
+    device = torch.device(device) if device else None
+    result = generate_brownian(1, n_steps, sigma=0, device=device)
+    expect = torch.zeros(1, n_steps).to(device)
     assert_close(result, expect)
 
     mu = 0.1
     dt = 0.01
-    result = generate_brownian(1, n_steps, mu=mu, sigma=0, dt=dt)
-    expect = torch.linspace(0, mu * dt * (n_steps - 1), n_steps).unsqueeze(0)
+    result = generate_brownian(1, n_steps, mu=mu, sigma=0, dt=dt, device=device)
+    expect = torch.linspace(0, mu * dt * (n_steps - 1), n_steps).to(device).unsqueeze(0)
     assert_close(result, expect)
 
 
-def test_generate_brownian_std():
+@pytest.mark.gpu
+def test_generate_brownian_nosigma_gpu():
+    test_generate_brownian_nosigma(device="cuda")
+
+
+def test_generate_brownian_std(device: str = "cpu"):
     torch.manual_seed(42)
     n_paths = 10000
     n_steps = 250
 
-    output = generate_brownian(n_paths, n_steps)
+    device = torch.device(device) if device else None
+    output = generate_brownian(n_paths, n_steps, device=device)
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].std()
     expect = torch.full_like(result, 0.2)
     assert_close(result, expect, atol=0, rtol=0.1)
 
 
-def test_generate_brownian_mean_init_state():
+@pytest.mark.gpu
+def test_generate_brownian_std_gpu():
+    test_generate_brownian_std(device="cuda")
+
+
+def test_generate_brownian_mean_init_state(device: str = "cpu"):
     torch.manual_seed(42)
     n_paths = 10000
     n_steps = 250
 
-    output = generate_brownian(n_paths, n_steps, init_state=1.0)
+    device = torch.device(device) if device else None
+    output = generate_brownian(n_paths, n_steps, init_state=1.0, device=device)
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].mean()
     expect = torch.ones_like(result)
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
-    output = generate_brownian(n_paths, n_steps, init_state=torch.tensor(1.0))
+    output = generate_brownian(
+        n_paths, n_steps, init_state=torch.tensor(1.0).to(device), device=device
+    )
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].mean()
     expect = torch.ones_like(result)
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
-    output = generate_brownian(n_paths, n_steps, init_state=torch.tensor([1.0]))
+    output = generate_brownian(
+        n_paths, n_steps, init_state=torch.tensor([1.0]).to(device), device=device
+    )
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].mean()
     expect = torch.ones_like(result)
@@ -75,26 +99,38 @@ def test_generate_brownian_mean_init_state():
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_brownian_mean_mu():
+@pytest.mark.gpu
+def test_generate_brownian_mean_init_state_gpu():
+    test_generate_brownian_mean_init_state(device="cuda")
+
+
+def test_generate_brownian_mean_mu(device: str = "cpu"):
     torch.manual_seed(42)
     n_paths = 10000
     n_steps = 250
     dt = 1 / 250
     mu = 0.1
 
-    output = generate_brownian(n_paths, n_steps, mu=mu)
+    device = torch.device(device) if device else None
+    output = generate_brownian(n_paths, n_steps, mu=mu, device=device)
     result = output[:, -1].mean()
     expect = torch.full_like(result, mu * dt * n_steps)
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_brownian_sobol_mean():
+@pytest.mark.gpu
+def test_generate_brownian_mean_mu_gpu():
+    test_generate_brownian_mean_mu(device="cuda")
+
+
+def test_generate_brownian_sobol_mean(device: str = "cpu"):
     n_paths = 10000
     n_steps = 250
 
+    device = torch.device(device) if device else None
     engine = RandnSobolBoxMuller(seed=42, scramble=True)
-    output = generate_brownian(n_paths, n_steps, engine=engine)
+    output = generate_brownian(n_paths, n_steps, engine=engine, device=device)
     assert output.size() == torch.Size((n_paths, n_steps))
     result = output[:, -1].mean()
     expect = torch.zeros_like(result)
@@ -102,49 +138,72 @@ def test_generate_brownian_sobol_mean():
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_brownian_dtype():
+@pytest.mark.gpu
+def test_generate_brownian_sobol_mean_gpu():
+    test_generate_brownian_sobol_mean(device="cuda")
+
+
+def test_generate_brownian_dtype(device: str = "cpu"):
     torch.manual_seed(42)
 
-    output = generate_brownian(1, 1, dtype=torch.float32)
+    device = torch.device(device) if device else None
+    output = generate_brownian(1, 1, dtype=torch.float32, device=device)
     assert output.dtype == torch.float32
 
-    output = generate_brownian(1, 1, dtype=torch.float64)
+    output = generate_brownian(1, 1, dtype=torch.float64, device=device)
     assert output.dtype == torch.float64
 
 
-def test_generate_geometric_brownian_mean():
+@pytest.mark.gpu
+def test_generate_brownian_dtype_gpu():
+    test_generate_brownian_dtype(device="cuda")
+
+
+def test_generate_geometric_brownian_mean(device: str = "cpu"):
     torch.manual_seed(42)
 
     n_paths = 10000
     n_steps = 250
 
-    t = generate_geometric_brownian(n_paths, n_steps)
+    device = torch.device(device) if device else None
+    t = generate_geometric_brownian(n_paths, n_steps, device=device)
     result = t[:, -1].mean()
-    expect = torch.ones_like(result)
+    expect = torch.ones_like(result).to(device)
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
-    t = generate_geometric_brownian(n_paths, n_steps, init_state=2.0)
+    t = generate_geometric_brownian(n_paths, n_steps, init_state=2.0, device=device)
     result = t[:, -1].mean()
-    expect = torch.ones_like(result) * 2
+    expect = torch.ones_like(result).to(device) * 2
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
     mu = 0.1
     dt = 1 / 250
 
-    output = generate_geometric_brownian(n_paths, n_steps, mu=mu)
+    output = generate_geometric_brownian(n_paths, n_steps, mu=mu, device=device)
     result = output[:, -1].mean()
-    expect = torch.ones_like(result) * torch.tensor(n_steps * dt * mu).exp()
+    expect = torch.ones_like(result) * torch.tensor(n_steps * dt * mu).to(device).exp()
     std = 0.2 * sqrt(1 / n_paths)
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_geometric_brownian_dtype():
+@pytest.mark.gpu
+def test_generate_geometric_brownian_mean_gpu():
+    test_generate_geometric_brownian_mean(device="cuda")
+
+
+def test_generate_geometric_brownian_dtype(device: str = "cpu"):
     torch.manual_seed(42)
 
-    output = generate_geometric_brownian(1, 1, dtype=torch.float32)
+    device = torch.device(device) if device else None
+    output = generate_geometric_brownian(1, 1, dtype=torch.float32, device=device)
     assert output.dtype == torch.float32
 
-    output = generate_geometric_brownian(1, 1, dtype=torch.float64)
+    output = generate_geometric_brownian(1, 1, dtype=torch.float64, device=device)
     assert output.dtype == torch.float64
+
+
+@pytest.mark.gpu
+def test_generate_geometric_brownian_dtype_gpu():
+    test_generate_geometric_brownian_dtype(device="cuda")
diff --git a/tests/stochastic/test_cir.py b/tests/stochastic/test_cir.py
index 1227b23e..58fb5cdc 100644
--- a/tests/stochastic/test_cir.py
+++ b/tests/stochastic/test_cir.py
@@ -1,5 +1,6 @@
 from math import sqrt
 
+import pytest
 import torch
 from torch.distributions.gamma import Gamma
 from torch.testing import assert_close
@@ -7,7 +8,7 @@
 from pfhedge.stochastic import generate_cir
 
 
-def test_generate_cir_mean_1():
+def test_generate_cir_mean_1(device: str = "cpu"):
     torch.manual_seed(42)
 
     n_paths = 10000
@@ -15,7 +16,8 @@ def test_generate_cir_mean_1():
     sigma = 2.0
     kappa = 1.0
 
-    t = generate_cir(n_paths, 250, kappa=kappa, theta=theta, sigma=sigma)
+    device = torch.device(device) if device else None
+    t = generate_cir(n_paths, 250, kappa=kappa, theta=theta, sigma=sigma, device=device)
     result = t[:, -1].mean()
     # Asymptotic distribution is gamma distribution
     alpha = 2 * kappa * theta / sigma ** 2
@@ -28,7 +30,12 @@ def test_generate_cir_mean_1():
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_generate_cir_mean_2():
+@pytest.mark.gpu
+def test_generate_cir_mean_1_gpu():
+    test_generate_cir_mean_1(device="cuda")
+
+
+def test_generate_cir_mean_2(device: str = "cpu"):
     torch.manual_seed(42)
 
     n_paths = 10000
@@ -36,8 +43,15 @@ def test_generate_cir_mean_2():
     sigma = 2.0
     kappa = 1.0
 
+    device = torch.device(device) if device else None
     t = generate_cir(
-        n_paths, 250, init_state=0.05, kappa=kappa, theta=theta, sigma=sigma
+        n_paths,
+        250,
+        init_state=0.05,
+        kappa=kappa,
+        theta=theta,
+        sigma=sigma,
+        device=device,
     )
     result = t[:, -1].mean()
     # Asymptotic distribution is gamma distribution
@@ -51,9 +65,21 @@ def test_generate_cir_mean_2():
     assert_close(result, expect, atol=3 * std, rtol=0)
 
 
-def test_dtype():
-    output = generate_cir(2, 3, dtype=torch.float32)
+@pytest.mark.gpu
+def test_generate_cir_mean_2_gpu():
+    test_generate_cir_mean_2(device="cuda")
+
+
+def test_dtype(device: str = "cpu"):
+    device = torch.device(device) if device else None
+
+    output = generate_cir(2, 3, dtype=torch.float32, device=device)
     assert output.dtype == torch.float32
 
-    output = generate_cir(2, 3, dtype=torch.float64)
+    output = generate_cir(2, 3, dtype=torch.float64, device=device)
     assert output.dtype == torch.float64
+
+
+@pytest.mark.gpu
+def test_dtype_gpu():
+    test_dtype(device="cuda")
diff --git a/tests/stochastic/test_heston.py b/tests/stochastic/test_heston.py
index 236bce11..2dd204c1 100644
--- a/tests/stochastic/test_heston.py
+++ b/tests/stochastic/test_heston.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 from torch.testing import assert_close
 
@@ -19,8 +20,14 @@ def test_generate_heston_repr():
     assert repr(output) == expect
 
 
-def test_generate_heston_volatility():
+def test_generate_heston_volatility(device: str = "cpu"):
     torch.manual_seed(42)
 
-    output = generate_heston(100, 250)
+    device = torch.device(device) if device else None
+    output = generate_heston(100, 250, device=device)
     assert_close(output.volatility, output.variance.sqrt())
+
+
+@pytest.mark.gpu
+def test_generate_heston_volatility_gpu():
+    test_generate_heston_volatility(device="cuda")
diff --git a/tests/stochastic/test_randn.py b/tests/stochastic/test_randn.py
index 34a68a9a..ddf7d384 100644
--- a/tests/stochastic/test_randn.py
+++ b/tests/stochastic/test_randn.py
@@ -5,11 +5,17 @@
 from pfhedge.stochastic import randn_antithetic
 
 
-def test_randn_antithetic():
+def test_randn_antithetic(device: str = "cpu"):
     torch.manual_seed(42)
-    output = randn_antithetic(200, 100)
+    device = torch.device(device) if device else None
+    output = randn_antithetic(200, 100, device=device)
     assert_close(output.mean(0), torch.zeros_like(output[0]))
 
     with pytest.raises(ValueError):
         # not supported
         output = randn_antithetic((200, 100), dim=1)
+
+
+@pytest.mark.gpu
+def test_randn_antithetic_gpu():
+    test_randn_antithetic(device="cuda")
diff --git a/tests/test_autogreek.py b/tests/test_autogreek.py
index 12ef341a..91069be3 100644
--- a/tests/test_autogreek.py
+++ b/tests/test_autogreek.py
@@ -1,3 +1,4 @@
+import pytest
 import torch
 from torch import Tensor
 from torch.testing import assert_close
@@ -5,7 +6,7 @@
 import pfhedge.autogreek as autogreek
 
 
-def test_gamma_from_delta():
+def test_gamma_from_delta(device: str = "cpu"):
     torch.manual_seed(42)
 
     def price(spot: Tensor):
@@ -14,38 +15,53 @@ def price(spot: Tensor):
     def delta(spot: Tensor):
         return 2 * spot + spot.cos()
 
-    spot = torch.randn(10)
+    spot = torch.randn(10).to(device)
     result = autogreek.gamma_from_delta(delta, spot=spot)
     expect = autogreek.gamma(price, spot=spot)
     assert_close(result, expect)
 
 
-def test_vega():
+@pytest.mark.gpu
+def test_gamma_from_delta_gpu():
+    test_gamma_from_delta(device="cuda")
+
+
+def test_vega(device: str = "cpu"):
     def pricer(volatility, coef):
         return coef * volatility.pow(2)
 
     torch.manual_seed(42)
-    volatility = torch.randn(10).exp()  # make it positive
-    coef = torch.randn(10)
+    volatility = torch.randn(10).to(device).exp()  # make it positive
+    coef = torch.randn(10).to(device)
     result = autogreek.vega(pricer, volatility=volatility, coef=coef)
     expect = 2 * coef * volatility
     assert_close(result, expect)
 
     torch.manual_seed(42)
-    variance = torch.randn(10).exp()  # make it positive
-    coef = torch.randn(10)
+    variance = torch.randn(10).to(device).exp()  # make it positive
+    coef = torch.randn(10).to(device)
     result = autogreek.vega(pricer, variance=variance, coef=coef)
     expect = 2 * coef * variance.sqrt()
     assert_close(result, expect)
 
 
-def test_theta():
+@pytest.mark.gpu
+def test_vega_gpu():
+    test_vega(device="cuda")
+
+
+def test_theta(device: str = "cpu"):
     def pricer(time_to_maturity, coef):
         return coef * time_to_maturity.pow(2)
 
     torch.manual_seed(42)
-    time_to_maturity = torch.randn(10).exp()  # make it positive
-    coef = torch.randn(10)
+    time_to_maturity = torch.randn(10).to(device).exp()  # make it positive
+    coef = torch.randn(10).to(device)
     result = autogreek.theta(pricer, time_to_maturity=time_to_maturity, coef=coef)
     expect = -2 * coef * time_to_maturity
     assert_close(result, expect)
+
+
+@pytest.mark.gpu
+def test_theta_gpu():
+    test_vega(device="cuda")
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 1cda77e2..dc05b841 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -1,3 +1,5 @@
+import pytest
+
 from pfhedge.instruments import BrownianStock
 from pfhedge.instruments import EuropeanOption
 from pfhedge.nn import BlackScholes
@@ -6,12 +8,12 @@
 from pfhedge.nn import WhalleyWilmott
 
 
-def test_net():
-    derivative = EuropeanOption(BrownianStock(cost=1e-4))
-    model = MultiLayerPerceptron()
+def test_net(device: str = "cpu"):
+    derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(device)
+    model = MultiLayerPerceptron().to(device)
     hedger = Hedger(
         model, ["log_moneyness", "time_to_maturity", "volatility", "prev_hedge"]
-    )
+    ).to(device)
     _ = hedger.fit(derivative, n_paths=100, n_epochs=10)
     _ = hedger.price(derivative)
 
@@ -20,15 +22,30 @@ def test_net():
     )
 
 
-def test_bs():
-    derivative = EuropeanOption(BrownianStock(cost=1e-4))
-    model = BlackScholes(derivative)
-    hedger = Hedger(model, model.inputs())
+@pytest.mark.gpu
+def test_net_gpu():
+    test_net(device="cuda")
+
+
+def test_bs(device: str = "cpu"):
+    derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(device)
+    model = BlackScholes(derivative).to(device)
+    hedger = Hedger(model, model.inputs()).to(device)
     _ = hedger.price(derivative)
 
 
-def test_ww():
-    derivative = EuropeanOption(BrownianStock(cost=1e-4))
-    model = WhalleyWilmott(derivative)
-    hedger = Hedger(model, model.inputs())
+@pytest.mark.gpu
+def test_bs_gpu():
+    test_bs(device="cuda")
+
+
+def test_ww(device: str = "cpu"):
+    derivative = EuropeanOption(BrownianStock(cost=1e-4)).to(device)
+    model = WhalleyWilmott(derivative).to(device)
+    hedger = Hedger(model, model.inputs()).to(device)
     _ = hedger.price(derivative)
+
+
+@pytest.mark.gpu
+def test_ww_gpu():
+    test_ww(device="cuda")