CUDA Samples 中的蒙特卡洛亚式期权定价:MC_SingleAsianOptionP 源码级解析
2026/9/16 10:20:30 网站建设 项目流程

CUDA Samples 中的蒙特卡洛亚式期权定价:MC_SingleAsianOptionP 源码级解析

【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples

导读

本文围绕 NVIDIA CUDA Samples 仓库中的 MC_SingleAsianOptionP 示例展开,讲解如何借助 NVIDIA CURAND 随机数生成库,在 GPU 上通过蒙特卡洛(Monte Carlo)方法为单资产亚式期权(Asian Option)定价。读完本文,你将掌握亚式期权定价的数学建模、CUDA 上"路径生成 + 收益估值"的三阶段 Kernel 流水线设计、CURAND 的初始化与采样用法,以及该示例的编译、命令行参数调优与正确性验证方法。

示例概述:用蒙特卡洛为亚式期权定价

MC_SingleAsianOptionP位于 cpp/2_Concepts_and_Techniques/ 目录下,属于"概念与技术"章节中的计算金融示例。根据 README 的说明,该示例使用 Monte Carlo 方法,借助NVIDIA CURAND 库模拟单资产亚式期权(Single Asian Option),属于 Computational Finance 领域的经典应用。

从源码 pricingengine.cu 的注释可以确认,它定价的是欧式行权、算术离散平均价(arithmetic discrete average)的亚式期权——即期末收益由存续期内离散观测的标的资产价格均值决定,而非到期日单点价格决定。

亚式期权与普通欧式期权的核心差异在于收益函数:

  • 普通欧式看涨期权收益:max(S_T - K, 0)
  • 亚式看涨期权收益:max(mean(S_{t1}, ..., S_{tN}) - K, 0),其中均值对标的资产存续期内的离散观测价格取算术平均

由于算术平均没有解析封闭解(对数正态假设下算术平均的分布不可解析表达),蒙特卡洛模拟是最自然的定价手段,而这恰好与 GPU 的大规模并行特性高度契合。

示例代码目录结构

该示例由以下文件组成:

cpp/2_Concepts_and_Techniques/MC_SingleAsianOptionP/ ├── CMakeLists.txt # CMake 构建配置 ├── README.md # 官方文档 ├── inc/ │ ├── asianoption.h # 期权参数与结果结构体 │ ├── cudasharedmem.h # 模板化动态共享内存包装 │ ├── pricingengine.h # 定价引擎类声明 │ └── test.h # 测试驱动器与默认参数宏 └── src/ ├── main.cpp # 命令行解析与入口 ├── pricingengine.cu # 三个 CUDA Kernel 与主机端定价流程 └── test.cpp # 测试用例:设定参数、执行定价、校验结果

金融模型:从几何布朗运动到路径模拟

在 test.cpp 中,测试用例使用如下参数构造了一个亚式看涨期权(asianoption.h 定义了参数结构体):

参数含义
spot40.0标的资产当前价格 S₀
strike35.0行权价 K
r0.03无风险利率
sigma0.20波动率 σ
tenor1.0/3.0期权存续期(约 4 个月)
dt1.0/261时间步长(按每年 261 个交易日)
typeCall看涨期权
golden5.162534参考期望值(用于校验)

其中dt = 1/261意味着按一年 261 个交易日对路径做离散化,存续期内的观测次数numTimesteps = tenor / dt = 87,即对该期权做 87 次算术平均采样。

对数正态路径的离散化

在 pricingengine.cu 的generatePathsKernel 中,标的资产价格路径按照标准几何布朗运动(GBM)的 Euler 离散化生成:

drift = (r - 0.5 * sigma²) * dt diffusion = sigma * sqrt(dt) S_{t+1} = S_t * exp(drift + diffusion * Z) // Z ~ N(0,1)

对应到代码,getPathStep设备函数实现了单步递推:

__device__ inline float getPathStep(float &drift, float &diffusion, curandState &state) { return expf(drift + diffusion * curand_normal(&state)); }

注意两个细节:

  • 单精度使用curand_normal,双精度使用curand_normal_double,这是 CURAND 库为不同精度提供的标准正态采样接口(代码中通过函数重载分别实现 float 与 double 版本);
  • 路径从S₀ = 1开始模拟(归一化处理),最终在估值阶段乘以option->spot还原真实价格,这样可以减少中间计算量。

收益计算与折现

computeValueKernel 对每条路径计算 87 个时间点的算术平均价格avg,再计算收益:

avg = avg * option->spot / numTimesteps; // 还原真实均价 Real payoff = avg - option->strike; // 看涨收益 if (option->type == AsianOption<Real>::Put) { payoff = -payoff; // 看跌则取反 } payoff = max(static_cast<Real>(0), payoff); // max(·, 0)

所有路径的收益在主机端求平均后,再乘以折现因子exp(-r * tenor),即得到期权现值:

option.value = std::accumulate(values.begin(), values.end(), static_cast<Real>(0)); option.value /= m_numSims; option.value *= exp(-option.r * option.tenor);

GPU 定价流水线:三个 Kernel 的职责划分

定价引擎PricingEngine<Real>(模板类,支持float/double)在 pricingengine.cu 中通过三个 Kernel 完成完整流程:

1.initRNG:初始化 CURAND 状态

__global__ void initRNG(curandState *const rngStates, const unsigned int seed) { unsigned int tid = blockIdx.x * blockDim.x + threadIdx.x; curand_init(seed, tid, 0, &rngStates[tid]); }

每个线程持有一个独立的curandState,以seed为种子、线程 ID 为序列号(sequence)调用curand_init完成初始化。这保证每个线程产生独立的随机数流,避免蒙特卡洛模拟中各路径之间的随机数相关性。

2.generatePaths:批量生成标的路径

这是性能核心。网格(grid)中每个线程负责多条路径的生成(通过for (i = tid; i < numSims; i += step)的网格步长循环实现负载均衡),路径以soa(Structure of Arrays)布局存储:output = paths + i,每推进一个时间步指针按numSims步进,即paths[t * numSims + i]存储第 i 条路径在第 t 个时间步的价格。这种列优先布局让同一时间步的所有路径在内存中连续,便于后续computeValue的合并(coalesced)访问。

3.computeValue:并行归约求收益均值

估值 Kernel 对每条路径累加各时间步价格求算术平均,计算收益后,使用块内共享内存树形归约reduce_sum)将同一线程块内的收益求和,再把每块的局部和写入values[bid]。块间归约在主机端完成:

vector<Real> values(grid.x); cudaMemcpy(&values[0], d_values, grid.x * sizeof(Real), cudaMemcpyDeviceToHost); option.value = std::accumulate(values.begin(), values.end(), static_cast<Real>(0));

网格尺寸的自适应策略

在 pricingengine.cu 的主机端代码中,网格大小并非简单按模拟次数/块大小取整,而是做了面向多处理器(SM)数量的自适应收缩

grid.x = (m_numSims + m_threadBlockSize - 1) / m_threadBlockSize; unsigned int blocksPerSM = 10; unsigned int numSMs = deviceProperties.multiProcessorCount; while (grid.x > 2 * blocksPerSM * numSMs) { grid.x >>= 1; // 每块处理的路径数相应翻倍 }

注释说明意图是"让每个 SM 上大约驻留 10 个或更多块"(即总块数约为2 * 10 * numSMs),避免过度庞大的网格造成调度开销,同时保证足够并行度。另外,代码通过cudaFuncGetAttributes检查initRNGgeneratePathscomputeValue三个 Kernel 的maxThreadsPerBlock约束,确保用户指定的块大小合法。

精度支持与设备校验

PricingEngine<Real>通过模板实例化同时支持单精度与双精度(文件末尾显式实例化PricingEngine<float>PricingEngine<double>)。主机端在启动前做了两项关键校验:

  1. 双精度能力校验:在 pricingengine.cu 中,若Realdouble且设备计算能力major*10 + minor < 13(即低于 SM 1.3),抛出 "Device does not have double precision support" 异常;
  2. 块大小校验:块大小必须落在[k_bsize_min, deviceProperties.maxThreadsPerBlock]区间,且必须是 2 的幂(注释说明归约函数reduce_sum要求块大小为 2 的幂,见 main.cpp 中的位运算检查test.threadBlockSize & test.threadBlockSize - 1)。

命令行参数详解

main.cpp 提供了完整的命令行解析,默认值与取值范围定义在 test.h 中:

参数默认值取值范围说明
--device=<N>自动选择(gpuGetMaxGflopsDeviceId0 ~ 设备数-1指定执行用 GPU 设备
--sims=<N>100000(k_sims_def100000 ~ 1000000(k_sims_min/k_sims_max蒙特卡洛模拟路径数
--block-size=<N>128(k_bsize_def32 ~ 设备maxThreadsPerBlock,且须为 2 的幂每块线程数
--seed=<N>1234(k_seed_def非零随机数生成器种子
--precision=<P>singlesingledouble计算精度
--noprompt退出前跳过提示
--help显示帮助信息

典型运行示例:

# 使用默认参数(float 精度,10 万条路径) ./MC_SingleAsianOptionP # 指定设备、模拟次数与精度 ./MC_SingleAsianOptionP --device=0 --sims=500000 --precision=double # 自定义块大小与随机种子 ./MC_SingleAsianOptionP --block-size=256 --seed=42

命令行解析采用helper_cuda.h提供的getCmdLineArgumentString/checkCmdLineFlag辅助函数(位于 Common/helper_cuda.h),参数非法时会抛出std::invalid_argument并给出明确的错误提示后退出。

构建与运行

该示例通过 CMake 构建,CMakeLists.txt 的核心配置包括:

  • 通过find_package(CUDAToolkit REQUIRED)定位 CUDA 工具包;
  • 指定 CUDA 架构集合75 80 86 87 89 90 100 110 120
  • 编译标准要求cxx_std_17cuda_std_17,并开启CUDA_SEPARABLE_COMPILATION
  • 通过include_directories(../../../Common)引入 Common 目录下的辅助头文件。

按照仓库根 README.md 的通用流程,可在仓库根目录构建整个项目,也可以进入该示例的子目录单独构建:

# 在仓库根目录创建构建目录 mkdir build && cd build cmake .. make -j$(nproc)

构建产物默认位于build/bin/x64/linux/release下。若需启用 cuda-gdb 调试,可在 cmake 配置时定义-DENABLE_CUDA_DEBUG=True(此时会关闭-lineinfo、改用-G编译选项,详见该示例的 CMakeLists.txt)。

正确性验证与性能输出

test.cpp 中内置了参考值golden = 5.162534,容差为0.1。运行时会打印如下格式的结果表:

Spot | Strike | r | sigma | tenor | Call/Put | Value | Expected | -----------|------------|------------|------------|------------|------------|------------|------------| 40 | 35 | 0.03 | 0.2 | 0.333333 | Call | ... | 5.162534 |

|value - golden| > 0.1则判定失败并输出 "computed result does not match expected result" 提示。测试同时输出性能指标:

MonteCarloSingleAsianOptionP, Performance = X.XX sims/s, Time = X.XX(ms), NumDevsUsed = 1, Blocksize = 128

代码注释特别指出:该容差仅用于"检测测试是否有严重错误",蒙特卡洛结果的实际精度取决于模拟次数numSims——路径数越多,统计误差越小。

模板化共享内存的小技巧

示例的归约使用了 cudasharedmem.h 中定义的SharedMemory<T>包装结构。其注释解释了动机:CUDA 中动态共享内存数组必须以extern __shared__声明,无法直接在模板函数中按T实例化。该头文件通过宏BUILD_SHAREDMEMORY_TYPEintfloatdouble等 10 种类型生成特化版本,每个特化内部声明不同名称的 extern 数组(s_ints_floats_double...),从而让模板 Kernel 中的共享内存归约代码可以写成:

SharedMemory<Real> sdata; sdata[ltid] = in; cg::sync(cta);

配合 Cooperative Groups 的cg::sync(cta)块同步完成树形归约。这个模式对编写其他需要动态共享内存的模板 Kernel 也有直接借鉴价值。

支持环境概览

根据 README 的声明,该示例:

  • 依赖 CUDA 库:CURAND(随机数生成);
  • 使用到的 CUDA Runtime APIcudaMemcpycudaGetErrorStringcudaFreecudaSetDevicecudaGetDeviceCountcudaMalloccudaFuncGetAttributescudaGetDeviceProperties
  • 支持的 SM 架构:SM 5.0 ~ SM 9.0(含 5.x、6.x、7.x、8.x、9.0 全系列,覆盖 Maxwell 至 Hopper/Blackwell 时代的主流架构);
  • 支持的操作系统:Linux、Windows;
  • 支持的 CPU 架构:x86_64、armv7l。

运行前提是安装与平台匹配的 CUDA Toolkit 并确保 CURAND 依赖可用(仓库根 README.md 的 Dependencies 一节对此有统一说明)。

总结:从示例到生产实践的三个启示

  1. CURAND 的正确用法:先curand_init为每个线程建立独立状态,再用curand_normal/curand_normal_double采样标准正态分布,是 GPU 蒙特卡洛路径模拟的标准范式;
  2. 两阶段 Kernel 划分:把"路径生成"与"收益估值"拆分为独立 Kernel,路径以 SOA 布局存储,估值阶段配合共享内存树形归约,兼顾了访存合并与并行归约效率;
  3. 网格自适应的工程细节:根据multiProcessorCount动态收缩网格规模、用cudaFuncGetAttributes校验启动配置、对块大小强制 2 的幂约束,这些细节让示例在不同规格 GPU 上都能稳定运行——这些模式在 cuda-samples 仓库的其他 Monte Carlo 类示例(如MC_EstimatePiInlineP)中同样可以观察到。

【免费下载链接】cuda-samplesSamples for CUDA Developers which demonstrates features in CUDA Toolkit项目地址: https://gitcode.com/GitHub_Trending/cu/cuda-samples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询