feat:add hamming_triplet #87

This commit is contained in:
wut0n 2025-12-10 20:22:43 +08:00
parent f989885dde
commit 149d56436b
4 changed files with 375 additions and 0 deletions

View File

@ -0,0 +1,113 @@
import torch
from torch.utils.cpp_extension import load_inline
canberra_triplet_source = """
#include <torch/extension.h>
#include <cuda_runtime.h>
// --- 融合内核计算每个样本的Canberra Triplet Loss ---
__global__ void canberra_triplet_kernel_fused(
const float* __restrict__ anchor,
const float* __restrict__ positive,
const float* __restrict__ negative,
float* __restrict__ per_sample_losses, // 输出每个样本的损失
int batch_size,
int feature_dim,
float margin
) {
int sample_idx = blockIdx.x * blockDim.x + threadIdx.x;
if (sample_idx >= batch_size) return;
int base = sample_idx * feature_dim;
float pos_dist = 0.0f;
float neg_dist = 0.0f;
// --- 融合计算循环同时计算两个距离 ---
for (int i = 0; i < feature_dim; i++) {
float a_val = anchor[base + i];
float p_val = positive[base + i];
float n_val = negative[base + i];
// 计算正样本距离
float pos_diff = fabsf(a_val - p_val);
float pos_denominator = fabsf(a_val) + fabsf(p_val);
if (pos_denominator > 1e-8f) { // 防止除以0
pos_dist += pos_diff / pos_denominator;
}
// 计算负样本距离
float neg_diff = fabsf(a_val - n_val);
float neg_denominator = fabsf(a_val) + fabsf(n_val);
if (neg_denominator > 1e-8f) { // 防止除以0
neg_dist += neg_diff / neg_denominator;
}
}
// --- 计算Triplet Loss ---
float loss = neg_dist - pos_dist + margin;
if (loss < 0.0f) {
loss = 0.0f; // relu
}
// 写入输出张量
per_sample_losses[sample_idx] = loss;
}
// C++包装函数只负责启动内核并返回包含所有样本损失的张量
torch::Tensor canberra_triplet_cuda(torch::Tensor anchor, torch::Tensor positive, torch::Tensor negative, float margin) {
TORCH_CHECK(anchor.scalar_type() == torch::kFloat32, "Anchor must be float32");
TORCH_CHECK(positive.scalar_type() == torch::kFloat32, "Positive must be float32");
TORCH_CHECK(negative.scalar_type() == torch::kFloat32, "Negative must be float32");
auto anchor_contig = anchor.contiguous();
auto positive_contig = positive.contiguous();
auto negative_contig = negative.contiguous();
int batch_size = anchor_contig.size(0);
int feature_dim = anchor_contig.size(1);
// 创建一个输出张量用于存储每个样本的损失
auto losses = torch::empty({batch_size}, anchor.options());
const int block_size = 256;
int num_blocks = (batch_size + block_size - 1) / block_size;
canberra_triplet_kernel_fused<<<num_blocks, block_size>>>(
anchor_contig.data_ptr<float>(),
positive_contig.data_ptr<float>(),
negative_contig.data_ptr<float>(),
losses.data_ptr<float>(),
batch_size,
feature_dim,
margin
);
// 返回包含所有样本损失的张量而不是一个标量
return losses;
}
"""
canberra_triplet_cpp_source = """
torch::Tensor canberra_triplet_cuda(torch::Tensor anchor, torch::Tensor positive, torch::Tensor negative, float margin);
"""
canberra_triplet = load_inline(
name="canberra_triplet_fixed",
cpp_sources=canberra_triplet_cpp_source,
cuda_sources=canberra_triplet_source,
functions=["canberra_triplet_cuda"],
extra_cuda_cflags=["-O3"],
verbose=True
)
class ModelNew(torch.nn.Module):
def __init__(self, margin=1.0):
super(ModelNew, self).__init__()
self.margin = margin
self.canberra_triplet = canberra_triplet
def forward(self, anchor, positive, negative):
# CUDA内核返回每个样本的损失我们在Python中求平均
per_sample_losses = self.canberra_triplet.canberra_triplet_cuda(anchor, positive, negative, self.margin)
return torch.mean(per_sample_losses)

View File

@ -0,0 +1,62 @@
import torch
import torch.nn as nn
class Model(nn.Module):
"""
PyTorch基准实现Canberra Triplet Loss
"""
def __init__(self, margin=1.0):
super(Model, self).__init__()
self.margin = margin
def forward(self, anchor, positive, negative):
"""
计算Canberra Triplet Loss
Args:
anchor (torch.Tensor): 锚点向量 [batch_size, feature_dim]
positive (torch.Tensor): 正样本向量 [batch_size, feature_dim]
negative (torch.Tensor): 负样本向量 [batch_size, feature_dim]
Returns:
torch.Tensor: 平均三元组损失标量值
"""
# --- 第一步计算Canberra距离 ---
# 计算分子 |a - b|
pos_diff = torch.abs(anchor - positive)
neg_diff = torch.abs(anchor - negative)
# 计算分母 |a| + |b|
pos_denominator = torch.abs(anchor) + torch.abs(positive)
neg_denominator = torch.abs(anchor) + torch.abs(negative)
# 防止除以0
pos_denominator = torch.clamp(pos_denominator, min=1e-8)
neg_denominator = torch.clamp(neg_denominator, min=1e-8)
# 计算距离
pos_dist = torch.sum(pos_diff / pos_denominator, dim=1)
neg_dist = torch.sum(neg_diff / neg_denominator, dim=1)
# --- 第二步计算Triplet Loss ---
# Loss = max(0, D(a, n) - D(a, p) + margin)
losses = torch.relu(neg_dist - pos_dist + self.margin)
# --- 第三步:计算平均损失 ---
total_loss = torch.mean(losses)
return total_loss
# --- 测试数据生成函数 ---
batch_size = 128
feature_dim = 256
def get_inputs():
# 生成非零向量以避免分母为0
anchor = torch.randn(batch_size, feature_dim) + 0.1
positive = torch.randn(batch_size, feature_dim) + 0.1
negative = torch.randn(batch_size, feature_dim) + 0.1
return [anchor, positive, negative]
def get_init_inputs():
return [1.0] # margin

126
S1/wut0n_#87/prompt.txt Normal file
View File

@ -0,0 +1,126 @@
You write custom CUDA kernels to replace the pytorch operators in the given architecture to get speedups. You have complete freedom to choose the set of operators you want to replace. You may make the decision to replace some operators with custom CUDA kernels and leave others unchanged. You may replace multiple operators with custom implementations, consider operator fusion opportunities (combining multiple operators into a single kernel, for example, combining matmul+relu), or algorithmic changes (such as online softmax). You are only limited by your imagination.
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:
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, a, b):
return a + b
def get_inputs():
# randomly generate input tensors based on the model architecture
a = torch.randn(1, 128).cuda()
b = torch.randn(1, 128).cuda()
return [a, b]
def get_init_inputs():
# randomly generate tensors required for initialization based on the model architecture
return []
The example new arch with custom CUDA kernels looks like this:
python
import torch
from torch.utils.cpp_extension import load_inline
… CUDA C++ source code for the kernel …
relu_source = “”"
“”"
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)
You are given the following architecture:
python
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Hamming Triplet Loss implementation.
Computes the Triplet Loss using Hamming distance as the metric.
"""
def __init__(self, margin=1.0):
super(Model, self).__init__()
self.margin = margin
def forward(self, anchor, positive, negative):
"""
Compute Hamming Triplet Loss.
Args:
anchor (torch.Tensor): Anchor vectors [batch_size, feature_dim]
positive (torch.Tensor): Positive vectors [batch_size, feature_dim]
negative (torch.Tensor): Negative vectors [batch_size, feature_dim]
Returns:
torch.Tensor: Average Triplet Loss (scalar)
"""
# Compute Hamming distances
pos_dist = torch.sum(anchor != positive, dim=1).float()
neg_dist = torch.sum(anchor != negative, dim=1).float()
# Compute Triplet Loss
losses = torch.relu(neg_dist - pos_dist + self.margin)
# Return average loss
return torch.mean(losses)
batch_size = 128
feature_dim = 256
def get_inputs():
# Generate binary vectors for Hamming distance
anchor = torch.randint(0, 2, (batch_size, feature_dim)).float()
positive = torch.randint(0, 2, (batch_size, feature_dim)).float()
negative = torch.randint(0, 2, (batch_size, feature_dim)).float()
return [anchor, positive, negative]
def get_init_inputs():
return [1.0] # margin
Your task is to write a new file `hamming_triplet_cudacode.py` that defines a new model `ModelNew` which uses a custom CUDA kernel to accelerate the Hamming Triplet Loss calculation. The goal is to achieve a significant speedup while maintaining numerical precision.
**CRITICAL REQUIREMENTS:**
1. **Operator Fusion:** The entire calculation (computing two Hamming distances and the final Triplet Loss for each sample) must be performed within a **single CUDA kernel**. This kernel should output a tensor of per-sample losses.
2. **Parallel Reduction Strategy:** The recommended implementation strategy is to use a parallel reduction pattern:
* Launch one thread block for each sample in the batch (`batch_size` number of blocks).
* Within each block, have multiple threads collaborate to compute the two Hamming distances for that single sample.
* Each thread should iterate over the feature dimension with a stride equal to the block size, accumulating partial sums for both the positive and negative distances.
* Use shared memory to store these partial sums and then perform a standard parallel reduction to get the final two distances for that sample.
* The first thread of the block should compute the final Triplet Loss (`max(0, neg_dist - pos_dist + margin)`) and write the result to the output tensor.
3. **Host-Side Logic:** The host-side C++ function should launch the kernel and then return the tensor of per-sample losses. The `ModelNew`'s `forward` method in Python should then call `torch.mean()` on this tensor to match the PyTorch baseline's output.
4. **Code Structure:** The final output should be a single python file containing the CUDA kernel, the compilation logic via `load_inline`, and the new `ModelNew` class.
5. **Compilation Flags:** Use `extra_cuda_cflags` like `"-O3"` for performance. You may also use `"--use_fast_math"` as the operations are not highly sensitive to precision.

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

@ -0,0 +1,74 @@
###########################################################
# 性能和精度验证程序
###########################################################
import torch
import torch.nn as nn
import time
from hamming_triplet_torchcode import Model, get_inputs, get_init_inputs
from hamming_triplet_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 hamming_triplet 平均执行时间: {torch_time:.6f}")
print(f"自定义 CUDA hamming_triplet 平均执行时间: {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()