Skip to content

Commit c9a152a

Browse files
committed
wip
1 parent 2e98a83 commit c9a152a

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

benchmarks/cupy/examples/finance/__init__.py

Whitespace-only changes.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from benchmarks.numpy.common import Benchmark
2+
from benchmarks.utils import sync
3+
from benchmarks.utils.helper import parameterize
4+
5+
import cupy
6+
7+
8+
black_scholes_kernel = cupy.ElementwiseKernel(
9+
'T s, T x, T t, T r, T v', # Inputs
10+
'T call, T put', # Outputs
11+
'''
12+
const T sqrt_t = sqrt(t);
13+
const T d1 = (log(s / x) + (r + v * v / 2) * t) / (v * sqrt_t);
14+
const T d2 = d1 - v * sqrt_t;
15+
const T cnd_d1 = get_cumulative_normal_distribution(d1);
16+
const T cnd_d2 = get_cumulative_normal_distribution(d2);
17+
const T exp_rt = exp(- r * t);
18+
call = s * cnd_d1 - x * exp_rt * cnd_d2;
19+
put = x * exp_rt * (1 - cnd_d2) - s * (1 - cnd_d1);
20+
''',
21+
'black_scholes_kernel',
22+
preamble='''
23+
__device__
24+
inline T get_cumulative_normal_distribution(T x) {
25+
const T A1 = 0.31938153;
26+
const T A2 = -0.356563782;
27+
const T A3 = 1.781477937;
28+
const T A4 = -1.821255978;
29+
const T A5 = 1.330274429;
30+
const T RSQRT2PI = 0.39894228040143267793994605993438;
31+
const T W = 0.2316419;
32+
const T k = 1 / (1 + W * abs(x));
33+
T cnd = RSQRT2PI * exp(- x * x / 2) *
34+
(k * (A1 + k * (A2 + k * (A3 + k * (A4 + k * A5)))));
35+
if (x > 0) {
36+
cnd = 1 - cnd;
37+
}
38+
return cnd;
39+
}
40+
''',
41+
)
42+
43+
44+
@sync
45+
@parameterize([('n_options', [1, 10000000])])
46+
class BlackScholes(Benchmark):
47+
def setup(self, n_options):
48+
def rand_range(m, M):
49+
samples = cupy.random.rand(n_options)
50+
return (m + (M - m) * samples).astype(cupy.float64)
51+
self.stock_price = rand_range(5, 30)
52+
self.option_strike = rand_range(1, 100)
53+
self.option_years = rand_range(0.25, 10)
54+
self.risk_free = 0.02
55+
self.volatility = 0.3
56+
57+
def time_black_scholes(self, n_options):
58+
s, x, t = self.stock_price, self.option_strike, self.option_years
59+
r, v = self.risk_free, self.volatility
60+
61+
sqrt_t = cupy.sqrt(t)
62+
d1 = (cupy.log(s / x) + (r + v * v / 2) * t) / (v * sqrt_t)
63+
d2 = d1 - v * sqrt_t
64+
65+
def get_cumulative_normal_distribution(x):
66+
A1 = 0.31938153
67+
A2 = -0.356563782
68+
A3 = 1.781477937
69+
A4 = -1.821255978
70+
A5 = 1.330274429
71+
RSQRT2PI = 0.39894228040143267793994605993438
72+
W = 0.2316419
73+
74+
k = 1 / (1 + W * cupy.abs(x))
75+
cnd = RSQRT2PI * cupy.exp(-x * x / 2) * (
76+
k * (A1 + k * (A2 + k * (A3 + k * (A4 + k * A5)))))
77+
cnd = cupy.where(x > 0, 1 - cnd, cnd)
78+
return cnd
79+
80+
cnd_d1 = get_cumulative_normal_distribution(d1)
81+
cnd_d2 = get_cumulative_normal_distribution(d2)
82+
83+
exp_rt = cupy.exp(- r * t)
84+
call = s * cnd_d1 - x * exp_rt * cnd_d2
85+
put = x * exp_rt * (1 - cnd_d2) - s * (1 - cnd_d1)
86+
return call, put
87+
88+
def time_black_scholes_kernel(self, n_options):
89+
black_scholes_kernel(
90+
self.stock_price, self.option_strike, self.option_years,
91+
self.risk_free, self.volatility)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from benchmarks.numpy.common import Benchmark
2+
from benchmarks.utils import sync
3+
from benchmarks.utils.helper import parameterize
4+
5+
import cupy
6+
7+
8+
monte_carlo_kernel = cupy.ElementwiseKernel(
9+
'T s, T x, T t, T r, T v, int32 n_samples, int32 seed', 'T call',
10+
'''
11+
// We can use special variables i and _ind to get the index of the thread.
12+
// In this case, we used an index as a seed of random sequence.
13+
uint64_t rand_state[2];
14+
init_state(rand_state, i, seed);
15+
16+
T call_sum = 0;
17+
const T v_by_sqrt_t = v * sqrt(t);
18+
const T mu_by_t = (r - v * v / 2) * t;
19+
20+
// compute the price of the call option with Monte Carlo method
21+
for (int i = 0; i < n_samples; ++i) {
22+
const T p = sample_normal(rand_state);
23+
call_sum += get_call_value(s, x, p, mu_by_t, v_by_sqrt_t);
24+
}
25+
// convert the future value of the call option to the present value
26+
const T discount_factor = exp(- r * t);
27+
call = discount_factor * call_sum / n_samples;
28+
''',
29+
preamble='''
30+
typedef unsigned long long uint64_t;
31+
32+
__device__
33+
inline T get_call_value(T s, T x, T p, T mu_by_t, T v_by_sqrt_t) {
34+
const T call_value = s * exp(mu_by_t + v_by_sqrt_t * p) - x;
35+
return (call_value > 0) ? call_value : 0;
36+
}
37+
38+
// Initialize state
39+
__device__ inline void init_state(uint64_t* a, int i, int seed) {
40+
a[0] = i + 1;
41+
a[1] = 0x5c721fd808f616b6 + seed;
42+
}
43+
44+
__device__ inline uint64_t xorshift128plus(uint64_t* x) {
45+
uint64_t s1 = x[0];
46+
uint64_t s0 = x[1];
47+
x[0] = s0;
48+
s1 = s1 ^ (s1 << 23);
49+
s1 = s1 ^ (s1 >> 17);
50+
s1 = s1 ^ s0;
51+
s1 = s1 ^ (s0 >> 26);
52+
x[1] = s1;
53+
return s0 + s1;
54+
}
55+
56+
// Draw a sample from an uniform distribution in a range of [0, 1]
57+
__device__ inline T sample_uniform(uint64_t* state) {
58+
const uint64_t x = xorshift128plus(state);
59+
// 18446744073709551615 = 2^64 - 1
60+
return T(x) / T(18446744073709551615);
61+
}
62+
63+
// Draw a sample from a normal distribution with N(0, 1)
64+
__device__ inline T sample_normal(uint64_t* state) {
65+
T x = sample_uniform(state);
66+
T s = T(-1.4142135623730950488016887242097); // = -sqrt(2)
67+
if (x > 0.5) {
68+
x = 1 - x;
69+
s = -s;
70+
}
71+
T p = x + T(0.5);
72+
return s * erfcinv(2 * p);
73+
}
74+
''',
75+
)
76+
77+
78+
@sync
79+
@parameterize([('n_options', [1, 1000]),
80+
('n_samples_per_thread', [1, 1000]),
81+
('n_threads_per_option', [1, 100000])])
82+
class MonteCarlo(Benchmark):
83+
def setup(self, n_options, n_samples_per_thread, n_threads_per_option):
84+
def rand_range(m, M):
85+
samples = cupy.random.rand(n_options)
86+
return (m + (M - m) * samples).astype(cupy.float64)
87+
self.stock_price = rand_range(5, 30)
88+
self.option_strike = rand_range(1, 100)
89+
self.option_years = rand_range(0.25, 10)
90+
self.risk_free = 0.02
91+
self.volatility = 0.3
92+
self.seed = 0
93+
94+
def time_compute_option_prices(
95+
self, n_options, n_samples_per_thread, n_threads_per_option):
96+
97+
call_prices = cupy.empty(
98+
(n_options, n_threads_per_option), dtype=cupy.float64)
99+
monte_carlo_kernel(
100+
self.stock_price[:, None], self.option_strike[:, None],
101+
self.option_years[:, None], self.risk_free, self.volatility,
102+
n_samples_per_thread, self.seed, call_prices)
103+
return call_prices.mean(axis=1)

0 commit comments

Comments
 (0)