reasoning
reasoning (truncated 54714 chars): Let me analyze this task. I need to implement a custom CUDA kernel for a Conditional Variational Autoencoder (CVAE) forward pass.
The model consists of:
1. Encoder:
- Linear layer from (input_dim + label_dim) to hidden_dim
- ReLU activation
- Two linear layers from hidden_dim to latent_dim for mu and logvar
2. Reparameterization trick:
- std = exp(0.5 * logvar)
- z = mu + std * random_noise
3. Decoder:
- Linear layer from (latent_dim + label_dim) to hidden_dim
- ReLU activation
- Linear layer from hidden_dim to input_dim
4. Loss computation:
- recon_loss = mse_loss(x_recon, x, reduction="sum") / batch_size
- kl_loss = -0.5 * (1 + logvar - mu^2 - exp(logvar)).sum() / batch_size
- loss = recon_loss + kl_loss
Let me design a custom CUDA kernel for this. The operations are:
1. Matrix multiplications (multiple linear layers)
2. ReLU activations
3. Exponential operations
4. Element-wise operations
5. Random sampling (reparameterization)
6. Loss computation (MSE and KL divergence)
For a full CUDA implementation, I need to:
1. Fuse multiple operations together
2. Use cuBLAS for matrix multiplications
3. Use cuRAND for random number generation
4. Fuse the element-wise operations
Let me write the kernel:
```python
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline
import math
# Define the CUDA kernel
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <curand.h>
#include <curand_kernel.h>
__device__ float relu(float x) {
return x > 0.0f ? x : 0.0f;
}
__global__ void encoder_forward_kernel(
const float* __restrict__ input_data,
const float* __restrict__ enc_weight,
const float* __restrict__ enc_bias,
const float* __restrict__ enc_mu_weight,
const float* __restrict__ enc_mu_bias,
const float* __restrict__ enc_logvar_weight,
const float* __restrict__ enc_logvar_bias,
float* __restrict__ hidden,
float* __restrict__ mu,
float* __restrict__ logvar,
int batch_size,
int input_label_dim,
int hidden_dim,
int latent_dim
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < batch_size * hidden_dim) {
int batch_idx = idx / hidden_dim;
int hidden_idx = idx % hidden_dim;
float val = enc_bias[hidden_idx];
for (int i = 0; i < input_label_dim; i++) {
val += input_data[batch_idx * input_label_dim + i] *
enc_weight[hidden_idx * input_label_dim + i];
}
hidden[idx] = relu(val);
}
}
// ... more kernels
"""
# But wait, using cuRAND for random sampling in the middle of computation is complex.
# Let me think about this differently...
# Actually, for best performance, I should:
# 1. Use cuBLAS for matrix multiplications (it's highly optimized)
# 2. Fuse the element-wise operations with custom kernels
# 3. Generate random numbers separately
# Let me design a more practical approach:
# - Use torch's matmul which uses cuBLAS internally
# - Use custom kernels for element-wise operations and fusions
```
Actually, let me think about this more carefully. The model has several matrix multiplications:
1. enc: (B, input_dim + label_dim) @ (input_dim + label_dim, hidden_dim)^T
2. enc_mu: (B, hidden_dim) @ (hidden_dim, latent_dim)^T
3. enc_logvar: (B, hidden_dim) @ (hidden_dim, latent_dim)^T
4. dec[0]: (B, latent_dim + label_dim) @ (latent_dim + label_dim, hidden_dim)^T
5. dec[2]: (B, hidden_dim) @ (hidden_dim, input_dim)^T
Matrix multiplications are best handled by cuBLAS, which PyTorch already uses efficiently.
What I can optimize:
1. Fuse the concatenation operations with the matrix multiplications
2. Fuse the element-wise operations (relu, exp, std calculation, reparameterization)
3. Fuse the loss computation
Let me create a kernel that:
1. Takes the weight matrices directly
2. Performs the entire forward pass in a fused manner
3. Uses cuBLAS-style matrix multiplication internally or calls cublasSgemm
Actually, let me take a simpler approach that should still be faster than PyTorch:
- Fused kernel for encoder + reparameterization
- Fused kernel for decoder
- Fused kernel for loss computation
Or even better - fuse everything into one big kernel if possible, but that might have register pressure issues.
Let me try a practical approach:
```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
# I'll create custom CUDA kernels that:
# 1. Fuse encoder operations (linear + relu + linear for mu/logvar + reparameterization)
# 2. Fuse decoder operations (linear + relu + linear)
# 3. Fuse loss computation
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <curand_kernel.h>
// Fused encoder kernel: compute hidden, mu, logvar, std, z
template <int TILE_SIZE>
__global__ void encoder_fused_kernel(
const float* __restrict__ x, // (B, input_dim)
const float* __restrict__ y, // (B, label_dim)
const float* __restrict__ enc_w, // (hidden_dim, input_dim + label_dim)
const float* __restrict__ enc_b, // (hidden_dim,)
const float* __restrict__ mu_w, // (latent_dim, hidden_dim)
const float* __restrict__ mu_b, // (latent_dim,)
const float* __restrict__ logvar_w, // (latent_dim, hidden_dim)
const float* __restrict__ logvar_b, // (latent_dim,)
float* __restrict__ z, // (B, latent_dim)
float* __restrict__ mu_out, // (B, latent_dim)
float* __restrict__ logvar_out, // (B, latent_dim)
float* __restrict__ hidden, // (B, hidden_dim)
int B, int input_dim, int label_dim, int hidden_dim, int latent_dim,
unsigned long long seed
) {
int batch_idx = blockIdx.x;
int tid = threadIdx.x;
int in_dim = input_dim + label_dim;
// Initialize curand
curandState state;
curand_init(seed, batch_idx * latent_dim + tid, 0, &state);
extern __shared__ float shared_mem[];
float* shared_input = shared_mem; // size: in_dim
// Load x and y into shared memory
for (int i = tid; i < in_dim; i += blockDim.x) {
if (i < input_dim) {
shared_input[i] = x[batch_idx * input_dim + i];
} else {
shared_input[i] = y[batch_idx * label_dim + (i - input_dim)];
}
}
__syncthreads();
// Compute hidden layer
if (tid < hidden_dim) {
float h_val = enc_b[tid];
for (int i = 0; i < in_dim; i++) {
h_val += shared_input[i] * enc_w[tid * in_dim + i];
}
hidden[batch_idx * hidden_dim + tid] = fmaxf(0.0f, h_val);
}
__syncthreads();
// Compute mu and logvar
if (tid < latent_dim) {
float mu_val = mu_b[tid];
float logvar_val = logvar_b[tid];
for (int i = 0; i < hidden_dim; i++) {
float h = hidden[batch_idx * hidden_dim + i];
mu_val += h * mu_w[tid * hidden_dim + i];
logvar_val += h * logvar_w[tid * hidden_dim + i];
}
mu_out[batch_idx * latent_dim + tid] = mu_val;
logvar_out[batch_idx * latent_dim + tid] = logvar_val;
// Reparameterization trick
float std = expf(0.5f * logvar_val);
float rand_val = curand_normal(&state);
z[batch_idx * latent_dim + tid] = mu_val + std * rand_val;
}
}
"""
Actually, this approach with one sample per thread might not be efficient. Let me reconsider.
For the matrix multiplications, using cuBLAS (via torch.matmul or cublasSgemm) is typically the most efficient approach. The optimization opportunities are:
1. Fusing concatenation with the first linear layer
2. Fusing the reparameterization trick with the encoder output
3. Fusing the loss computation
Let me try a more balanced approach:
```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load
...[ omitted 54714 characters ]...
float lv = logvar[batch_idx * latent_dim + tid];
kl_sum = -0.5f * (1.0f + lv - m * m - expf(lv));
}
// Block reduction for KL loss
shared_reduce[tid] = kl_sum;
__syncthreads();
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
shared_reduce[tid] += shared_reduce[tid + s];
}
__syncthreads();
}
float total_kl = shared_reduce[0];
// Atomically add to global loss
if (tid == 0) {
atomicAdd(&loss_out[0], total_recon + total_kl);
atomicAdd(&loss_out[1], total_recon);
atomicAdd(&loss_out[2], total_kl);
}
}
std::vector<torch::Tensor> encoder_cuda(
torch::Tensor x,
torch::Tensor y,
torch::Tensor enc_w,
torch::Tensor enc_b,
torch::Tensor mu_w,
torch::Tensor mu_b,
torch::Tensor logvar_w,
torch::Tensor logvar_b
) {
const int B = x.size(0);
const int input_dim = x.size(1);
const int label_dim = y.size(1);
const int hidden_dim = enc_b.size(0);
const int latent_dim = mu_b.size(0);
auto options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA);
auto mu_out = torch::empty({B, latent_dim}, options);
auto logvar_out = torch::empty({B, latent_dim}, options);
const int shared_mem_size = (input_dim + label_dim + hidden_dim) * sizeof(float);
encoder_kernel<<<B, BLOCK_SIZE, shared_mem_size>>>(
x.data_ptr<float>(),
y.data_ptr<float>(),
enc_w.data_ptr<float>(),
enc_b.data_ptr<float>(),
mu_w.data_ptr<float>(),
mu_b.data_ptr<float>(),
logvar_w.data_ptr<float>(),
logvar_b.data_ptr<float>(),
mu_out.data_ptr<float>(),
logvar_out.data_ptr<float>(),
B, input_dim, label_dim, hidden_dim, latent_dim
);
return {mu_out, logvar_out};
}
torch::Tensor decoder_loss_cuda(
torch::Tensor x,
torch::Tensor y,
torch::Tensor z,
torch::Tensor mu,
torch::Tensor logvar,
torch::Tensor dec1_w,
torch::Tensor dec1_b,
torch::Tensor dec2_w,
torch::Tensor dec2_b
) {
const int B = x.size(0);
const int input_dim = x.size(1);
const int label_dim = y.size(1);
const int hidden_dim = dec1_b.size(0);
const int latent_dim = z.size(1);
auto options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA);
auto loss_out = torch::zeros({3}, options);
const int shared_mem_size = (input_dim + label_dim + latent_dim + hidden_dim + BLOCK_SIZE) * sizeof(float);
decoder_loss_kernel<<<B, BLOCK_SIZE, shared_mem_size>>>(
x.data_ptr<float>(),
y.data_ptr<float>(),
z.data_ptr<float>(),
mu.data_ptr<float>(),
logvar.data_ptr<float>(),
dec1_w.data_ptr<float>(),
dec1_b.data_ptr<float>(),
dec2_w.data_ptr<float>(),
dec2_b.data_ptr<float>(),
loss_out.data_ptr<float>(),
B, input_dim, label_dim, hidden_dim, latent_dim
);
return loss_out;
}
"""
# Compile the CUDA extension
cvae_ops = load_inline(
name='cvae_ops',
cpp_sources='',
cuda_sources=cuda_source,
functions=['encoder_cuda', 'decoder_loss_cuda'],
extra_cuda_cflags=['-O3'],
verbose=False
)
class ModelNew(nn.Module):
def __init__(self, input_dim, label_dim, hidden_dim, latent_dim):
super().__init__()
# Encoder
self.enc = nn.Sequential(
nn.Linear(input_dim + label_dim, hidden_dim),
nn.ReLU(),
)
self.enc_mu = nn.Linear(hidden_dim, latent_dim)
self.enc_logvar = nn.Linear(hidden_dim, latent_dim)
# Decoder
self.dec = nn.Sequential(
nn.Linear(latent_dim + label_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, input_dim),
)
def forward(self, x: torch.Tensor, y: torch.Tensor) -> tuple:
B = x.shape[0]
# Encode using CUDA kernel
mu, logvar = cvae_ops.encoder_cuda(
x, y,
self.enc[0].weight, self.enc[0].bias,
self.enc_mu.weight, self.enc_mu.bias,
self.enc_logvar.weight, self.enc_logvar.bias
)
# Reparameterization trick using PyTorch (to match random behavior)
std = torch.exp(0.5 * logvar)
z = mu + std * torch.randn_like(std)
# Decode and compute loss using CUDA kernel
loss_out = cvae_ops.decoder_loss_cuda(
x, y, z, mu, logvar,
self.dec[0].weight, self.dec[0].bias,
self.dec[2].weight, self.dec[2].bias
)
# Finalize loss computation
loss = loss_out[0] / B
recon_loss = loss_out[1] / B
kl_loss = loss_out[2] / B
return loss, recon_loss, kl_loss
```
Hmm, I think this should work. But let me reconsider the performance.
The current implementation:
1. Two kernel launches
2. Uses shared memory extensively
3. Each block handles one sample
For better performance, I could consider:
1. Using more threads per block to increase occupancy
2. Using warp-level primitives for reductions
3. Fusing more operations
But for correctness, this should work. Let me also add error checking.
Actually, one thing I'm concerned about: the encoder kernel only computes mu and logvar for threads where tid < latent_dim. If latent_dim < BLOCK_SIZE, many threads are idle. This is fine for correctness but could be optimized.
Let me also check if the reduction is correct. In the decoder_loss kernel:
- We accumulate recon_sum across threads in a block
- We use shared memory for reduction
- We start with blockDim.x / 2
If BLOCK_SIZE = 128 and we have fewer active threads, the reduction still works because all threads participate (inactive ones have recon_sum = 0).
For KL loss, only latent_dim threads contribute. The rest have kl_sum = 0. The reduction still works.
One more thing: the shared memory for reduction is BLOCK_SIZE elements. The shared_mem calculation includes:
- input_dim + label_dim + latent_dim + hidden_dim + BLOCK_SIZE
For input_dim=256, label_dim=10, latent_dim=32, hidden_dim=128, BLOCK_SIZE=128:
Total = 256 + 10 + 32 + 128 + 128 = 554 floats = 2216 bytes
This should fit within the shared memory limit (typically 48KB per SM).
I think the implementation is correct. Let me finalize it.
Actually, I just realized there might be a subtle issue. In the reference implementation, the losses are computed as:
```python
recon_loss = F.mse_loss(x_recon, x, reduction="sum") / x.shape[0]
kl_loss = -0.5 * (1 + logvar - mu.pow(2) - logvar.exp()).sum() / x.shape[0]
```
The sum is over all elements (batch and feature dimensions), then divided by batch size.
In my implementation:
- Each block computes sum over feature dimensions for one sample
- The global loss_out accumulates these sums
- At the end, we divide by batch size
This should give the same result. Let me verify:
Reference:
total_recon = sum over batch, feature (x_recon - x)^2
recon_loss = total_recon / batch_size
My implementation:
block i computes: sum over feature (x_recon[i] - x[i])^2
loss_out[1] = sum over i of block_i_sum
recon_loss = loss_out[1] / batch_size
This is the same! Good.
Let me write the final code and submit.
Actually, let me also double-check the weight access. In PyTorch:
- Linear layer weight has shape (out_features, in_features)
- Output = input @ weight.T + bias
So weight[i, j] accesses row i, column j.
In my kernel:
- enc_w has shape (hidden_dim, input_dim + label_dim)
- For output h, I access enc_w[h * in_dim + i] which is enc_w[h, i]
This is correct!
Similarly for mu_w, logvar_w, dec1_w, dec2_w.
One last thing: I need to ensure the tensors are contiguous. Let me add contiguous() calls in the forward function.
Actually, torch.utils.cpp_extension.load_inline handles non-contiguous tensors, but for performance, it's better to ensure contiguity. Let me add checks.
Final code:
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Define CUDA source\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#define BLOCK_SIZE 128\n\n__global__ void encoder_kernel(\n const float* __restrict__ x,\n const float* __restrict__ y,\n const float* __restrict__ enc_w,\n const float* __restrict__ enc_b,\n const float* __restrict__ mu_w,\n const float* __restrict__ mu_b,\n const float* __restrict__ logvar_w,\n const float* __restrict__ logvar_b,\n float* __restrict__ mu_out,\n float* __restrict__ logvar_out,\n const int B, const int input_dim, const int label_dim, \n const int hidden_dim, const int latent_dim\n) {\n extern __shared__ float shared_mem[];\n float* shared_x = shared_mem;\n float* shared_y = shared_mem + input_dim;\n float* shared_hidden = shared_mem + input_dim + label_dim;\n \n const int batch_idx = blockIdx.x;\n const int tid = threadIdx.x;\n const int in_dim = input_dim + label_dim;\n \n // Load x and y into shared memory\n for (int i = tid; i < input_dim; i += blockDim.x) {\n shared_x[i] = x[batch_idx * input_dim + i];\n }\n for (int i = tid; i < label_dim; i += blockDim.x) {\n shared_y[i] = y[batch_idx * label_dim + i];\n }\n __syncthreads();\n \n // Compute encoder hidden layer with ReLU\n for (int h = tid; h < hidden_dim; h += blockDim.x) {\n float val = enc_b[h];\n for (int i = 0; i < input_dim; i++) {\n val += shared_x[i] * enc_w[h * in_dim + i];\n }\n for (int i = 0; i < label_dim; i++) {\n val += shared_y[i] * enc_w[h * in_dim + input_dim + i];\n }\n shared_hidden[h] = fmaxf(0.0f, val);\n }\n __syncthreads();\n \n // Compute mu and logvar\n if (tid < latent_dim) {\n float mu_val = mu_b[tid];\n float logvar_val = logvar_b[tid];\n for (int h = 0; h < hidden_dim; h++) {\n mu_val += shared_hidden[h] * mu_w[tid * hidden_dim + h];\n logvar_val += shared_hidden[h] * logvar_w[tid * hidden_dim + h];\n }\n mu_out[batch_idx * latent_dim + tid] = mu_val;\n logvar_out[batch_idx * latent_dim + tid] = logvar_val;\n }\n}\n\n__global__ void decoder_loss_kernel(\n const float* __restrict__ x,\n const float* __restrict__ y,\n const float* __restrict__ z,\n const float* __restrict__ mu,\n const float* __restrict__ logvar,\n const float* __restrict__ dec1_w,\n const float* __restrict__ dec1_b,\n const float* __restrict__ dec2_w,\n const float* __restrict__ dec2_b,\n float* __restrict__ loss_out,\n const int B, const int input_dim, const int label_dim, \n const int hidden_dim, const int latent_dim\n) {\n extern __shared__ float shared_mem[];\n float* shared_x = shared_mem;\n float* shared_y = shared_mem + input_dim;\n float* shared_z = shared_mem + input_dim + label_dim;\n float* shared_hidden = shared_mem + input_dim + label_dim + latent_dim;\n float* shared_reduce = shared_mem + input_dim + label_dim + latent_dim + hidden_dim;\n \n const int batch_idx = blockIdx.x;\n const int tid = threadIdx.x;\n const int dec_in_dim = latent_dim + label_dim;\n \n // Load x, y, z into shared memory\n for (int i = tid; i < input_dim; i += blockDim.x) {\n shared_x[i] = x[batch_idx * input_dim + i];\n }\n for (int i = tid; i < label_dim; i += blockDim.x) {\n shared_y[i] = y[batch_idx * label_dim + i];\n }\n for (int i = tid; i < latent_dim; i += blockDim.x) {\n shared_z[i] = z[batch_idx * latent_dim + i];\n }\n __syncthreads();\n \n // Compute decoder hidden layer with ReLU\n for (int h = tid; h < hidden_dim; h += blockDim.x) {\n float val = dec1_b[h];\n for (int l = 0; l < latent_dim; l++) {\n val += shared_z[l] * dec1_w[h * dec_in_dim + l];\n }\n for (int i = 0; i < label_dim; i++) {\n val += shared_y[i] * dec1_w[h * dec_in_dim + latent_dim + i];\n }\n shared_hidden[h] = fmaxf(0.0f, val);\n }\n __syncthreads();\n \n // Compute decoder output and reconstruction loss\n float recon_sum = 0.0f;\n for (int o = tid; o < input_dim; o += blockDim.x) {\n float val = dec2_b[o];\n for (int h = 0; h < hidden_dim; h++) {\n val += shared_hidden[h] * dec2_w[o * hidden_dim + h];\n }\n float diff = val - shared_x[o];\n recon_sum += diff * diff;\n }\n \n // Block reduction for reconstruction loss\n shared_reduce[tid] = recon_sum;\n __syncthreads();\n for (int s = blockDim.x / 2; s > 0; s >>= 1) {\n if (tid < s) {\n shared_reduce[tid] += shared_reduce[tid + s];\n }\n __syncthreads();\n }\n float total_recon = shared_reduce[0];\n \n // Compute KL loss\n float kl_sum = 0.0f;\n if (tid < latent_dim) {\n float m = mu[batch_idx * latent_dim + tid];\n float lv = logvar[batch_idx * latent_dim + tid];\n kl_sum = -0.5f * (1.0f + lv - m * m - expf(lv));\n }\n \n // Block reduction for KL loss\n shared_reduce[tid] = kl_sum;\n __syncthreads();\n for (int s = blockDim.x / 2; s > 0; s >>= 1) {\n if (tid < s) {\n shared_reduce[tid] += shared_reduce[tid + s];\n }\n __syncthreads();\n }\n float total_kl = shared_reduce[0];\n \n // Atomically add to global loss\n if (tid == 0) {\n atomicAdd(&loss_out[0], total_recon + total_kl);\n atomicAdd(&loss_out[1], total_recon);\n atomicAdd(&loss_out[2], total_kl);\n }\n}\n\nstd::vector<torch::Tensor> encoder_cuda(\n torch::Tensor x,\n torch::Tensor y,\n torch::Tensor enc_w,\n torch::Tensor enc_b,\n torch::Tensor mu_w,\n torch::Tensor mu_b,\n torch::Tensor logvar_w,\n torch::Tensor logvar_b\n) {\n const int B = x.size(0);\n const int input_dim = x.size(1);\n const int label_dim = y.size(1);\n const int hidden_dim = enc_b.size(0);\n const int latent_dim = mu_b.size(0);\n \n auto options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA);\n auto mu_out = torch::empty({B, latent_dim}, options);\n auto logvar_out = torch::empty({B, latent_dim}, options);\n \n const int shared_mem_size = (input_dim + label_dim + hidden_dim) * sizeof(float);\n \n encoder_kernel<<<B, BLOCK_SIZE, shared_mem_size>>>(\n x.data_ptr<float>(),\n y.data_ptr<float>(),\n enc_w.data_ptr<float>(),\n enc_b.data_ptr<float>(),\n mu_w.data_ptr<float>(),\n mu_b.data_ptr<float>(),\n logvar_w.data_ptr<float>(),\n logvar_b.data_ptr<float>(),\n mu_out.data_ptr<float>(),\n logvar_out.data_ptr<float>(),\n B, input_dim, label_dim, hidden_dim, latent_dim\n );\n \n return {mu_out, logvar_out};\n}\n\ntorch::Tensor decoder_loss_cuda(\n torch::Tensor x,\n torch::Tensor y,\n torch::Tensor z,\n torch::Tensor mu,\n torch::Tensor logvar,\n torch::Tensor dec1_w,\n torch::Tensor dec1_b,\n torch::Tensor dec2_w,\n torch::Tensor dec2_b\n) {\n const int B = x.size(0);\n const int input_dim = x.size(1);\n const int label_dim = y.size(1);\n const int hidden_dim = dec1_b.size(0);\n const int latent_dim = z.size(1);\n \n auto options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA);\n auto loss_out = torch::zeros({3}, options);\n \n const int shared_mem_size = (input_dim + label_dim + latent_dim + hidden_dim + BLOCK_SIZE) * sizeof(float);\n \n decoder_loss_kernel<<<B, BLOCK_SIZE, shared_mem_size>>>(\n x.data_ptr<float>(),\n y.data_ptr<float>(),\n z.data_ptr<float>(),\n mu.data_ptr<float>(),\n logvar.data_ptr<float>(),\n dec1_w.data_ptr<float>(),\n dec1_b.data_ptr<float>(),\n dec2_w.data_ptr<float>(),\n dec2_b.data_ptr<float>(),\n loss_out.data_ptr<float>(),\n B, input_dim, label_dim, hidden_dim, latent_dim\n );\n \n return loss_out;\n}\n\"\"\"\n\n# Compile the CUDA extension\ncvae_ops = load_inline(\n name='cvae_ops',\n cpp_sources='',\n cuda_sources=cuda_source,\n functions=['encoder_cuda', 'decoder_loss_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, input_dim, label_dim, hidden_dim, latent_dim):\n super().__init__()\n # Encoder\n self.enc = nn.Sequential(\n nn.Linear(input_dim + label_dim, hidden_dim),\n nn.ReLU(),\n )\n self.enc_mu = nn.Linear(hidden_dim, latent_dim)\n self.enc_logvar = nn.Linear(hidden_dim, latent_dim)\n \n # Decoder\n self.dec = nn.Sequential(\n nn.Linear(latent_dim + label_dim, hidden_dim),\n nn.ReLU(),\n nn.Linear(hidden_dim, input_dim),\n )\n \n def forward(self, x: torch.Tensor, y: torch.Tensor) -> tuple:\n B = x.shape[0]\n \n # Ensure contiguous memory\n x = x.contiguous()\n y = y.contiguous()\n \n # Encode using CUDA kernel\n mu, logvar = cvae_ops.encoder_cuda(\n x, y,\n self.enc[0].weight.contiguous(),\n self.enc[0].bias.contiguous(),\n self.enc_mu.weight.contiguous(),\n self.enc_mu.bias.contiguous(),\n self.enc_logvar.weight.contiguous(),\n self.enc_logvar.bias.contiguous()\n )\n \n # Reparameterization trick using PyTorch (to match random behavior)\n std = torch.exp(0.5 * logvar)\n z = mu + std * torch.randn_like(std)\n \n # Decode and compute loss using CUDA kernel\n loss_out = cvae_ops.decoder_loss_cuda(\n x, y, z.contiguous(), mu, logvar,\n self.dec[0].weight.contiguous(),\n self.dec[0].bias.contiguous(),\n self.dec[2].weight.contiguous(),\n self.dec[2].bias.contiguous()\n )\n \n # Finalize loss computation\n loss = loss_out[0] / B\n recon_loss = loss_out[1] / B\n kl_loss = loss_out[2] / B\n \n return loss, recon_loss, kl_loss"
}