LeetGPU | 01_Vector_Addition

1178 字
6 分钟
LeetGPU | 01_Vector_Addition

关注 LeetGPU repository,了解更多内容。

Problem Description#

Difficulty: Easy

LeetGPU Challenge

Write a GPU program that performs element-wise addition of two vectors containing 32-bit floating point numbers. The program should take two input vectors of equal length and produce a single output vector containing their sum.

Implementation Requirements#

  • External libraries are not permitted.
  • The solve function signature must remain unchanged.
  • The final result must be stored in vector C.

Example 1#

Input: A = [1.0, 2.0, 3.0, 4.0]
B = [5.0, 6.0, 7.0, 8.0]
Output: C = [6.0, 8.0, 10.0, 12.0]

Example 2#

Input: A = [1.5, 1.5, 1.5]
B = [2.3, 2.3, 2.3]
Output: C = [3.8, 3.8, 3.8]

Constraints#

  • Input vectors A and B have identical lengths.
  • 1 <= N <= 100,000,000.
  • Performance is measured with N = 25,000,000.

CUDA#

Approach#

CUDA 采用“一元素一线程”的方式,为每个向量元素分配一个 GPU 线程。

  • 线程的全局索引由 blockIdx.xblockDim.xthreadIdx.x 共同计算得出。
  • 每个索引有效的线程分别从 AB 读取一个元素,完成加法后将结果写入 C
  • 每个线程块包含 256 个线程,线程块数量通过向上取整除法确定。
  • idx < N 的边界检查可避免最后一个线程块发生越界访问。
  • 内核使用 CUDA C++ 编写,并通过 nvcc 编译。

Solution#

#include <cuda_runtime.h>
__global__ void vector_add(const float* A, const float* B, float* C, int N) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N) {
C[idx] = A[idx] + B[idx];
}
}
// A, B, C are device pointers (i.e. pointers to memory on the GPU)
extern "C" void solve(const float* A, const float* B, float* C, int N) {
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vector_add<<<blocksPerGrid, threadsPerBlock>>>(A, B, C, N);
}

Test Code#

#include <cuda_runtime.h>
#include <array>
#include <cmath>
#include <iostream>
extern "C" void solve(const float* a, const float* b, float* c, int n);
int main() {
constexpr int n = 4;
const std::array<float, n> input_a = {1.0f, 2.0f, 3.0f, 4.0f};
const std::array<float, n> input_b = {5.0f, 6.0f, 7.0f, 8.0f};
const std::array<float, n> expected = {6.0f, 8.0f, 10.0f, 12.0f};
std::array<float, n> actual = {};
constexpr size_t bytes = n * sizeof(float);
float* device_a = nullptr;
float* device_b = nullptr;
float* device_c = nullptr;
auto check_cuda = [](cudaError_t status, const char* operation) {
if (status == cudaSuccess) {
return true;
}
std::cerr << "Test failed. CUDA error in " << operation << ": "
<< cudaGetErrorString(status) << '\n';
return false;
};
bool success = check_cuda(cudaMalloc(&device_a, bytes), "cudaMalloc(device_a)") &&
check_cuda(cudaMalloc(&device_b, bytes), "cudaMalloc(device_b)") &&
check_cuda(cudaMalloc(&device_c, bytes), "cudaMalloc(device_c)") &&
check_cuda(cudaMemcpy(device_a, input_a.data(), bytes, cudaMemcpyHostToDevice), "copy input_a") &&
check_cuda(cudaMemcpy(device_b, input_b.data(), bytes, cudaMemcpyHostToDevice), "copy input_b");
if (success) {
solve(device_a, device_b, device_c, n);
success = check_cuda(cudaGetLastError(), "solve") &&
check_cuda(cudaDeviceSynchronize(), "synchronize") &&
check_cuda(cudaMemcpy(actual.data(), device_c, bytes, cudaMemcpyDeviceToHost), "copy result");
}
cudaFree(device_a);
cudaFree(device_b);
cudaFree(device_c);
if (!success) {
return 1;
}
for (int index = 0; index < n; ++index) {
if (std::fabs(actual[index] - expected[index]) > 1e-6f) {
std::cerr << "Test failed at index " << index
<< ". Expected: " << expected[index]
<< ", Actual: " << actual[index] << '\n';
return 1;
}
}
std::cout << "Test passed.\n";
return 0;
}

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSN=25000000200.826 ms0.748 ms1.325 ms363.144 GB/s
NVIDIA GeForce RTX 4090PASSN=25000000200.324 ms0.322 ms0.327 ms926.118 GB/s
NVIDIA GeForce RTX 5090PASSN=25000000200.193 ms0.190 ms0.195 ms1557.852 GB/s

Triton#

Approach#

Triton 让每个程序实例负责处理一段连续的向量元素。

  • tl.program_id(0) 用于标识当前程序实例。
  • 每个程序实例最多处理由 tl.arange 生成的 1024 个元素。
  • 内核分别加载两个数据块,逐元素相加后存储结果。
  • 掩码可避免处理最后一个数据块时发生越界加载和存储。
  • 内核使用 Python 编写,并由 Triton 即时编译(JIT)为 GPU 代码。

Solution#

import torch
import triton
import triton.language as tl
@triton.jit
def vector_add_kernel(a, b, c, n_elements, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask=offsets<n_elements
a_block = tl.load(a+offsets, mask=mask)
b_block = tl.load(b+offsets, mask=mask)
c_block = a_block+b_block
tl.store(c+offsets, c_block, mask=mask)
# a, b, c are tensors on the GPU
def solve(a: torch.Tensor, b: torch.Tensor, c: torch.Tensor, N: int):
BLOCK_SIZE = 1024
grid = (triton.cdiv(N, BLOCK_SIZE),)
vector_add_kernel[grid](a, b, c, N, BLOCK_SIZE)

Test Code#

import importlib.util
import sys
from pathlib import Path
import torch
def load_implementation():
source_path = Path(__file__).resolve().parents[2] / "src" / "triton" / "01_Vector_Addition.py"
spec = importlib.util.spec_from_file_location("vector_addition_triton_minimal", source_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load implementation: {source_path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
input_a = torch.tensor([1.0, 2.0, 3.0, 4.0], device="cuda")
input_b = torch.tensor([5.0, 6.0, 7.0, 8.0], device="cuda")
expected = torch.tensor([6.0, 8.0, 10.0, 12.0], device="cuda")
actual = torch.empty_like(input_a)
load_implementation().solve(input_a, input_b, actual, input_a.numel())
torch.cuda.synchronize()
torch.testing.assert_close(actual, expected)
print("Test passed.")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"Test failed: {error}", file=sys.stderr)
raise

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSN=25000000200.809 ms0.764 ms1.085 ms371.021 GB/s
NVIDIA GeForce RTX 4090PASSN=25000000200.341 ms0.338 ms0.350 ms880.608 GB/s
NVIDIA GeForce RTX 5090PASSN=25000000200.223 ms0.216 ms0.251 ms1344.240 GB/s

PyTorch#

Approach#

PyTorch 通过内置的 torch.add 算子完成向量加法。

  • torch.add(A, B, out=C) 将对应位置的元素相加,并把结果直接写入 C
  • PyTorch 会自动选择合适的 GPU 内核并发起执行。
  • 线程调度、索引计算和边界处理均由 PyTorch 在内部完成。
  • 为满足接口要求,函数签名中保留了 N,但该实现不会直接使用它。

Solution#

import torch
# A, B, C are tensors on the GPU
def solve(A: torch.Tensor, B: torch.Tensor, C: torch.Tensor, N: int):
torch.add(A,B,out = C);

Test Code#

import importlib.util
import sys
from pathlib import Path
import torch
def load_implementation():
source_path = Path(__file__).resolve().parents[2] / "src" / "pytorch" / "01_Vector_Addition.py"
spec = importlib.util.spec_from_file_location("vector_addition_pytorch_minimal", source_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Unable to load implementation: {source_path}")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
input_a = torch.tensor([1.0, 2.0, 3.0, 4.0], device="cuda")
input_b = torch.tensor([5.0, 6.0, 7.0, 8.0], device="cuda")
expected = torch.tensor([6.0, 8.0, 10.0, 12.0], device="cuda")
actual = torch.empty_like(input_a)
load_implementation().solve(input_a, input_b, actual, input_a.numel())
torch.cuda.synchronize()
torch.testing.assert_close(actual, expected)
print("Test passed.")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"Test failed: {error}", file=sys.stderr)
raise

Test Result#

PlatformStatusProblem SizeIterationsAverage TimeMinimum TimeMaximum TimePerformance
NVIDIA GeForce RTX 5070 Ti Laptop GPUPASSN=25000000200.817 ms0.731 ms1.077 ms367.031 GB/s
NVIDIA GeForce RTX 4090PASSN=25000000200.330 ms0.329 ms0.334 ms909.717 GB/s
NVIDIA GeForce RTX 5090PASSN=25000000200.207 ms0.204 ms0.216 ms1450.363 GB/s

References#

关注 LeetGPU repository,了解更多内容。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

LeetGPU | 01_Vector_Addition
https://github.com/HaoyangPing0324/LeetGPU
作者
平昊阳
发布于
2026-07-20
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
66
分类
16
标签
93
总字数
477,284
运行时长
0
最后活动
0 天前

文章目录