feat:add minkowski+leakyrelu #75

This commit is contained in:
wut0n 2025-12-10 16:59:30 +08:00
parent f989885dde
commit 7fcd64b831
4 changed files with 451 additions and 0 deletions

View File

@ -0,0 +1,174 @@
import torch
from torch.utils.cpp_extension import load_inline
minkowski_leakyrelu_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
#include <math.h>
// 通用融合内核 - Minkowski Distance + LeakyReLU
__global__ void minkowski_leakyrelu_kernel_general(
const float* __restrict__ x,
const float* __restrict__ y,
float* __restrict__ output,
int batch_size,
int feature_dim,
float p,
float inv_p,
float negative_slope
) {
int sample_idx = blockIdx.x;
if (sample_idx >= batch_size) return;
int tid = threadIdx.x;
int base = sample_idx * feature_dim;
extern __shared__ float shared_sum[];
shared_sum[tid] = 0.0f;
int stride = blockDim.x;
for (int dim = tid; dim < feature_dim; dim += stride) {
float diff = x[base + dim] - y[base + dim];
float abs_diff = fabsf(diff);
shared_sum[tid] += powf(abs_diff, p);
}
__syncthreads();
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
shared_sum[tid] += shared_sum[tid + s];
}
__syncthreads();
}
if (tid == 0) {
float distance = powf(shared_sum[0], inv_p);
// --- 融合步骤直接计算 LeakyReLU(distance) ---
// LeakyReLU(x) = max(x, negative_slope * x)
output[sample_idx] = fmaxf(distance, negative_slope * distance);
}
}
// 特殊优化融合版本 - p=2 (欧几里得距离)
__global__ void minkowski_leakyrelu_kernel_p2(
const float* __restrict__ x,
const float* __restrict__ y,
float* __restrict__ output,
int batch_size,
int feature_dim,
float negative_slope
) {
int sample_idx = blockIdx.x;
if (sample_idx >= batch_size) return;
int base = sample_idx * feature_dim;
float sum_sq = 0.0f;
for (int dim = 0; dim < feature_dim; dim++) {
float diff = x[base + dim] - y[base + dim];
sum_sq += diff * diff;
}
float distance = sqrtf(sum_sq);
// --- 融合步骤直接计算 LeakyReLU(distance) ---
output[sample_idx] = fmaxf(distance, negative_slope * distance);
}
// 特殊优化融合版本 - p=1 (曼哈顿距离)
__global__ void minkowski_leakyrelu_kernel_p1(
const float* __restrict__ x,
const float* __restrict__ y,
float* __restrict__ output,
int batch_size,
int feature_dim,
float negative_slope
) {
int sample_idx = blockIdx.x;
if (sample_idx >= batch_size) return;
int base = sample_idx * feature_dim;
float sum_abs = 0.0f;
for (int dim = 0; dim < feature_dim; dim++) {
float diff = x[base + dim] - y[base + dim];
sum_abs += fabsf(diff);
}
// --- 融合步骤直接计算 LeakyReLU(distance) ---
output[sample_idx] = fmaxf(sum_abs, negative_slope * sum_abs);
}
torch::Tensor minkowski_leakyrelu_cuda(torch::Tensor x, torch::Tensor y, float p, float negative_slope) {
TORCH_CHECK(x.scalar_type() == torch::kFloat32, "X must be float32");
TORCH_CHECK(y.scalar_type() == torch::kFloat32, "Y must be float32");
TORCH_CHECK(p > 0, "p must be positive");
auto x_contig = x.contiguous();
auto y_contig = y.contiguous();
int batch_size = x_contig.size(0);
int feature_dim = x_contig.size(1);
auto output = torch::zeros({batch_size}, x.options());
if (p == 1.0f) {
minkowski_leakyrelu_kernel_p1<<<batch_size, 1>>>(
x_contig.data_ptr<float>(),
y_contig.data_ptr<float>(),
output.data_ptr<float>(),
batch_size,
feature_dim,
negative_slope
);
} else if (p == 2.0f) {
minkowski_leakyrelu_kernel_p2<<<batch_size, 1>>>(
x_contig.data_ptr<float>(),
y_contig.data_ptr<float>(),
output.data_ptr<float>(),
batch_size,
feature_dim,
negative_slope
);
} else {
float inv_p = 1.0f / p;
const int block_size = 256;
size_t shared_mem = block_size * sizeof(float);
minkowski_leakyrelu_kernel_general<<<batch_size, block_size, shared_mem>>>(
x_contig.data_ptr<float>(),
y_contig.data_ptr<float>(),
output.data_ptr<float>(),
batch_size,
feature_dim,
p,
inv_p,
negative_slope
);
}
return output;
}
"""
minkowski_leakyrelu_cpp_source = """
torch::Tensor minkowski_leakyrelu_cuda(torch::Tensor x, torch::Tensor y, float p, float negative_slope);
"""
minkowski_leakyrelu = load_inline(
name="minkowski_leakyrelu",
cpp_sources=minkowski_leakyrelu_cpp_source,
cuda_sources=minkowski_leakyrelu_source,
functions=["minkowski_leakyrelu_cuda"],
verbose=True
)
class ModelNew(torch.nn.Module):
def __init__(self, p=2, negative_slope=0.01):
super(ModelNew, self).__init__()
self.p = p
self.negative_slope = negative_slope
self.minkowski_leakyrelu = minkowski_leakyrelu
def forward(self, x, y):
return self.minkowski_leakyrelu.minkowski_leakyrelu_cuda(x, y, self.p, self.negative_slope)

View File

@ -0,0 +1,51 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
"""
Minkowski Distance followed by a LeakyReLU activation.
This version uses standard PyTorch operations for a fair baseline.
"""
def __init__(self, p=2, negative_slope=0.01):
super(Model, self).__init__()
self.p = p
self.negative_slope = negative_slope
if p <= 0:
raise ValueError("p must be positive")
def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
"""
Compute the LeakyReLU of the Minkowski distance between x and y.
"""
# Input validation
if x.shape != y.shape:
raise ValueError(f"Input tensors must have the same shape, got {x.shape} and {y.shape}")
if x.dim() != 2:
raise ValueError(f"Input tensors must be 2D, got {x.dim()}D")
# Step 1: Compute Minkowski distance using element-wise operations
abs_diff = torch.abs(x - y)
if self.p == 1:
d = torch.sum(abs_diff, dim=1)
elif self.p == 2:
d = torch.sqrt(torch.sum(abs_diff ** 2, dim=1))
else:
d = torch.pow(torch.sum(torch.pow(abs_diff, self.p), dim=1), 1.0/self.p)
# Step 2: Apply LeakyReLU activation
output = F.leaky_relu(d, negative_slope=self.negative_slope)
return output
batch_size = 256
feature_dim = 512
def get_inputs():
x = torch.randn(batch_size, feature_dim)
y = torch.randn(batch_size, feature_dim)
return [x, y]
def get_init_inputs():
return [2, 0.01] # p value and negative_slope

152
S1/wut0n_#75/prompt.txt Normal file
View File

@ -0,0 +1,152 @@
You are an expert CUDA programmer tasked with accelerating a PyTorch model by replacing its operators with a highly optimized, custom CUDA kernel. You should consider operator fusion and algorithmic optimizations to achieve maximum speedup.
Here's an example to show you the syntax of inline embedding custom CUDA operators in PyTorch. The example given architecture is a simple ReLU:
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
def init(self) -> None:
super().init()
def forward(self, x):
return torch.relu(x)
def get_inputs():
x = torch.randn(1, 128).cuda()
return [x]
def get_init_inputs():
return []
The example new architecture with a custom CUDA kernel looks like this:
python
import torch
from torch.utils.cpp_extension import load_inline
relu_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
global void relu_kernel(const float* x, float* y, int size) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < size) {
y[idx] = fmaxf(x[idx], 0.f);
}
}
torch::Tensor relu_cuda(torch::Tensor x) {
auto size = x.numel();
auto y = torch::empty_like(x);
const int block_size = 256;
int num_blocks = (size + block_size - 1) / block_size;
relu_kernel<<<num_blocks, block_size>>>(x.data_ptr<float>(), y.data_ptr<float>(), size);
return y;
}
"""
relu_cpp_source = """
torch::Tensor relu_cuda(torch::Tensor x);
"""
Compile the inline CUDA code
relu = load_inline(
name=“relu”,
cpp_sources=relu_cpp_source,
cuda_sources=relu_source,
functions=[“relu_cuda”],
verbose=True
)
class ModelNew(torch.nn.Module):
def init(self):
super(ModelNew, self).init()
self.relu = relu # The module containing the kernel
def forward(self, x):
return self.relu.relu_cuda(x)
def get_inputs():
x = torch.randn(1, 128).cuda()
return [x]
def get_init_inputs():
return []
---
Now, you are given the following PyTorch architecture to accelerate. The model computes the Minkowski distance between two tensors and then applies a LeakyReLU activation to the resulting distances. This baseline implementation uses standard element-wise operations to ensure a clear, sample-by-sample calculation.
python
import torch
import torch.nn as nn
import torch.nn.functional as F
class Model(nn.Module):
“”"
Minkowski Distance followed by a LeakyReLU activation.
This version uses standard PyTorch operations for a fair baseline.
“”"
def init(self, p=2, negative_slope=0.01):
super(Model, self).init()
self.p = p
self.negative_slope = negative_slope
if p <= 0:
raise ValueError(“p must be positive”)
def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
"""
Compute the LeakyReLU of the Minkowski distance between x and y.
"""
# Input validation
if x.shape != y.shape:
raise ValueError(f"Input tensors must have the same shape, got {x.shape} and {y.shape}")
if x.dim() != 2:
raise ValueError(f"Input tensors must be 2D, got {x.dim()}D")
# Step 1: Compute Minkowski distance using element-wise operations
abs_diff = torch.abs(x - y)
if self.p == 1:
d = torch.sum(abs_diff, dim=1)
elif self.p == 2:
d = torch.sqrt(torch.sum(abs_diff ** 2, dim=1))
else:
d = torch.pow(torch.sum(torch.pow(abs_diff, self.p), dim=1), 1.0/self.p)
# Step 2: Apply LeakyReLU activation
output = F.leaky_relu(d, negative_slope=self.negative_slope)
return output
batch_size = 256
feature_dim = 512
def get_inputs():
x = torch.randn(batch_size, feature_dim)
y = torch.randn(batch_size, feature_dim)
return [x, y]
def get_init_inputs():
return [2, 0.01] # p value and negative_slope
Your task is to generate the `ModelNew` architecture with a custom CUDA kernel that fuses the Minkowski distance calculation and the LeakyReLU activation into a single kernel launch, thereby eliminating the intermediate distance tensor.
**CRITICAL REQUIREMENTS:**
1. **Operator Fusion:** The entire logic—computing the Minkowski distance for each sample in the batch and then applying the LeakyReLU activation (`max(distance, negative_slope * distance)`)—must be performed inside a **single CUDA kernel**. No intermediate distance tensors should be written to global memory.
2. **Algorithmic Specialization:** The implementation must provide specialized, highly optimized kernels for the most common cases, `p=1` (Manhattan) and `p=2` (Euclidean), in addition to a general kernel for any `p`.
3. **Kernel Logic:**
* Each thread block should be responsible for computing the final output for a single sample in the batch.
* For `p=1` and `p=2`, the kernel should be a simple loop without shared memory reduction for maximum efficiency.
* For the general `p` case, the kernel should use a multi-threaded reduction within a thread block with `extern __shared__`.
4. **Final Calculation:** Inside the kernel, after the distance is computed by the first thread (`tid == 0`), the LeakyReLU activation must be applied immediately using `fmaxf(distance, negative_slope * distance)` before writing the final result to the output tensor.
5. **Performance Optimization:** The host-side function should dispatch to the appropriate specialized kernel based on the value of `p` at runtime.
6. **Code Structure:** Follow the exact structure of the provided example, including `load_inline`, the CUDA source string, the C++ wrapper source string, and the `ModelNew` class. The `get_init_inputs` function must return `[2, 0.01]` to match the baseline.
7. **No Fast Math:** Do not use `--use_fast_math` in the compilation flags to ensure numerical accuracy.

74
S1/wut0n_#75/run_code.py Normal file
View File

@ -0,0 +1,74 @@
###########################################################
# 性能和精度验证程序
###########################################################
import torch
import torch.nn as nn
import time
from minkowski_leakyrelu_torchcode import Model, get_inputs, get_init_inputs
from minkowski_leakyrelu_cudacode import ModelNew
def run_benchmark():
# 检查 CUDA 是否可用
if not torch.cuda.is_available():
print("CUDA 不可用,请确保您有可用的 NVIDIA GPU 并已正确安装 PyTorch CUDA 版本。")
return
else:
device = torch.device("cuda")
# 初始化模型
init_inputs = get_init_inputs()
init_inputs = [
x.cuda(device=device) if isinstance(x, torch.Tensor) else x for x in init_inputs
]
inputs = get_inputs()
inputs = [
x.cuda(device=device) if isinstance(x, torch.Tensor) else x for x in inputs
]
torch_model = Model(*init_inputs).cuda()
cuda_model = ModelNew(*init_inputs).cuda()
torch_model.eval()
cuda_model.eval()
print("-------------------- 精度对齐验证 --------------------")
with torch.no_grad():
output_torch = torch_model( *inputs)
output_cuda = cuda_model(*inputs)
precision_flag = torch.allclose(output_torch, output_cuda,rtol=1e-03)
if precision_flag:
print("✅ 精度对齐:两个模型的输出结果非常接近。")
else:
print("❌ 精度不一致!")
print("\n-------------------- 性能加速比测试 --------------------")
num_iterations = 100
# PyTorch 模型计时
torch.cuda.synchronize()
start_time = time.time()
for _ in range(num_iterations):
_ = torch_model(*inputs)
torch.cuda.synchronize()
torch_time = (time.time() - start_time) / num_iterations
# 自定义 CUDA 内核计时
torch.cuda.synchronize()
start_time = time.time()
for _ in range(num_iterations):
_ = cuda_model(*inputs)
torch.cuda.synchronize()
cuda_time = (time.time() - start_time) / num_iterations
print(f"PyTorch minkowski_leakyrelu 平均执行时间: {torch_time:.6f}")
print(f"自定义 CUDA minkowski_leakyrelu 平均执行时间: {cuda_time:.6f}")
speedup = 0
if cuda_time > 0:
speedup = torch_time / cuda_time
print(f"加速比 (Speedup): {speedup:.2f}x")
else:
print("CUDA 内核执行时间为0无法计算加速比。")
return precision_flag,speedup
if __name__ == "__main__":
precision_flag,speedup = run_benchmark()