reasoning
reasoning (truncated 48738 chars): This is a complex task - I need to implement a particle filter in CUDA. Let me analyze the reference implementation:
1. **Predict step**: `pred = (particles @ self.A.t()) + self.process_noise * torch.randn_like(particles)`
- Matrix multiplication: (B, P, D) @ (D, D).T = (B, P, D)
- Add Gaussian noise
2. **Weight step**:
- `obs_pred = pred @ self.C.t()` - (B, P, D) @ (D, obs_dim).T = (B, P, obs_dim)
- `diff = observation.unsqueeze(1) - obs_pred` - (B, 1, obs_dim) - (B, P, obs_dim) = (B, P, obs_dim)
- `log_lik = -0.5 * (diff ** 2).sum(-1) / (self.obs_noise ** 2)` - (B, P)
- `new_log_w = log_weights + log_lik`
- Normalize using logsumexp
3. **Resample step**:
- `weights = torch.exp(new_log_w)` - (B, P)
- `cumsum = torch.cumsum(weights, dim=1)` - (B, P)
- `u = (torch.arange(P) + torch.rand(B, 1)) / P` - (B, P)
- `indices = torch.searchsorted(cumsum, u)` - (B, P)
- Resample particles based on indices
This is quite complex to implement in CUDA. Let me think about how to approach this:
1. The random number generation for process noise and resampling needs to be handled carefully
2. The matrix multiplications can be done with cuBLAS or custom kernels
3. The cumulative sum needs a parallel scan algorithm
4. The searchsorted operation needs binary search
Let me start by writing a complete CUDA implementation:
For the forward pass:
1. Generate random noise for prediction
2. Matrix multiply particles by A
3. Add noise to get predictions
4. Matrix multiply predictions by C
5. Compute log likelihood
6. Update log weights
7. Normalize log weights (logsumexp)
8. Exp to get weights
9. Cumulative sum
10. Generate uniform random numbers
11. Binary search to get indices
12. Gather resampled particles
This is quite complex. Let me implement it step by step in a single kernel where possible.
Actually, for matrix operations, it might be more efficient to use PyTorch's built-in operations for the matmul parts and custom CUDA for the more complex parts like resampling.
But the goal is to be faster, so I should try to optimize everything in CUDA.
Let me think about the structure:
- Use cuBLAS for matrix multiplications (or write custom kernels for small matrices)
- Custom kernels for element-wise operations
- Custom parallel scan for cumsum
- Custom binary search for searchsorted
For the given sizes:
- state_dim = 4
- obs_dim = 2
- num_particles = 1024
- batch_size = 16
The matrices are small (4x4 and 2x4), so custom kernels might be better than cuBLAS for these.
Let me write a comprehensive CUDA implementation:
```cpp
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <curand_kernel.h>
#include <cfloat>
// Kernel for prediction step
__global__ void predict_kernel(const float* particles, const float* A,
float* pred, int B, int P, int D, float process_noise,
unsigned long long seed) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total = B * P * D;
if (idx < total) {
int d = idx % D;
int p = (idx / D) % P;
int b = idx / (P * D);
// Initialize curand state
curandState state;
curand_init(seed, idx, 0, &state);
// Compute dot product for this element
float sum = 0.0f;
for (int k = 0; k < D; k++) {
sum += particles[b * P * D + p * D + k] * A[d * D + k];
}
// Add noise
float noise = curand_normal(&state) * process_noise;
pred[idx] = sum + noise;
}
}
```
Actually, this approach of using curand for each thread might be slow. Let me think about a better approach.
For efficiency, I should:
1. Batch similar operations
2. Use shared memory where possible
3. Minimize random number generation overhead
Let me write a more optimized version:
Actually, let me reconsider. The reference implementation uses:
- `torch.randn_like(particles)` for process noise
- `torch.rand(B, 1)` for resampling
These are separate RNG calls. In CUDA, I need to handle the RNG state carefully.
For a practical implementation, let me break this into multiple kernels:
1. Prediction kernel (matmul + add noise)
2. Observation prediction kernel (matmul)
3. Log likelihood kernel
4. Normalize log weights kernel (logsumexp + subtract)
5. Exp kernel
6. Cumulative sum kernel
7. Resample kernel (generate uniform, searchsorted, gather)
Let me write this step by step.
Actually, for simplicity and correctness, I'll use a combination of:
1. Custom CUDA kernels for the core computations
2. PyTorch operations for some parts where CUDA doesn't provide significant speedup
Wait, but I need to implement everything in CUDA for this to be a "custom CUDA kernel" replacement.
Let me write a comprehensive solution:
```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <curand_kernel.h>
#include <cfloat>
// ... kernels ...
"""
# Load the extension
particle_filter_cuda = load_inline(
name='particle_filter_cuda',
cpp_sources='',
cuda_sources=cuda_source,
functions=['particle_filter_forward'],
extra_cuda_cflags=['-O3', '--use_fast_math'],
verbose=True
)
class ModelNew(nn.Module):
def __init__(self, state_dim, obs_dim, num_particles):
super().__init__()
self.state_dim = state_dim
self.obs_dim = obs_dim
self.num_particles = num_particles
self.register_buffer("A", 0.9 * torch.eye(state_dim))
self.register_buffer("C", torch.randn(obs_dim, state_dim) * 0.5)
self.process_noise = 0.1
self.obs_noise = 0.5
def forward(self, particles, log_weights, observation):
# Call the CUDA kernel
return particle_filter_cuda.particle_filter_forward(
particles, log_weights, observation,
self.A, self.C, self.process_noise, self.obs_noise
)
```
Let me now write the complete CUDA kernels:
For small matrices (state_dim=4, obs_dim=2), we can do the matrix multiplication efficiently in a single kernel by having each thread compute one output element.
For the cumsum, I'll implement a parallel scan (blelloch scan).
For searchsorted, I'll use binary search.
Let me write the complete implementation:
```cpp
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <curand_kernel.h>
#include <cfloat>
#include <thrust/device_ptr.h>
#include <thrust/scan.h>
template <typename scalar_t>
__global__ void predict_kernel(
const scalar_t* __restrict__ particles,
const scalar_t* __restrict__ A,
scalar_t* __restrict__ pred,
const int B, const int P, const int D,
const scalar_t process_noise,
unsigned long long seed) {
const int idx = blockIdx.x * blockDim.x + threadIdx.x;
const int total = B * P;
if (idx < total) {
const int b = idx / P;
const int p = idx % P;
// Initialize RNG
curandState state;
curand_init(seed, idx, 0, &state);
// For each output dimension
for (int d = 0; d < D; d++) {
scalar_t sum = 0;
for (int k = 0; k < D; k++) {
sum += particles[b * P * D + p * D + k] * A[d * D + k];
}
// Add Gaussian noise
scalar_t noise = curand_normal(&state) * process_noise;
pred[b * P * D + p * D + d] = sum + noise;
}
}
}
// ... more kernels ...
```
Actually, I realize this is getting complex. Let me simplify by using a more straightforward approach that's easier to debug and verify.
Let me write a solution that:
1. Does the prediction (matmul + noise) in one kernel
2. Does observation prediction (matmul) in another kernel
3. Does log likelihood computation in another kernel
4. Does logsumexp normalization
5. Does exp, cumsum, an
...[ omitted 48738 characters ]...
ilter_forward(
torch::Tensor particles,
torch::Tensor log_weights,
torch::Tensor observation,
torch::Tensor A,
torch::Tensor C,
torch::Tensor process_noise_samples,
torch::Tensor uniform_samples,
double obs_noise) {
const int B = particles.size(0);
const int P = particles.size(1);
const int D = particles.size(2);
const int obs_dim = observation.size(1);
auto options = particles.options();
auto pred = torch::empty_like(particles);
auto obs_pred = torch::empty({B, P, obs_dim}, options);
auto new_log_w = torch::empty_like(log_weights);
auto max_vals = torch::empty({B}, options);
auto sum_exp = torch::empty({B}, options);
auto cumsum = torch::empty_like(log_weights);
auto resampled = torch::empty_like(particles);
auto uniform_log_w = torch::empty_like(log_weights);
float obs_noise_sq = static_cast<float>(obs_noise * obs_noise);
const int threads = 256;
// Prediction
int blocks = (B * P * D + threads - 1) / threads;
predict_kernel<<<blocks, threads>>>(
particles.data_ptr<float>(),
A.data_ptr<float>(),
process_noise_samples.data_ptr<float>(),
pred.data_ptr<float>(),
B, P, D);
// Observation prediction
blocks = (B * P * obs_dim + threads - 1) / threads;
obs_pred_kernel<<<blocks, threads>>>(
pred.data_ptr<float>(),
C.data_ptr<float>(),
obs_pred.data_ptr<float>(),
B, P, D, obs_dim);
// Log likelihood
blocks = (B * P + threads - 1) / threads;
log_lik_kernel<<<blocks, threads>>>(
obs_pred.data_ptr<float>(),
observation.data_ptr<float>(),
log_weights.data_ptr<float>(),
new_log_w.data_ptr<float>(),
B, P, obs_dim, obs_noise_sq);
// Logsumexp
logsumexp_kernel<<<B, threads, 2 * threads * sizeof(float)>>>(
new_log_w.data_ptr<float>(),
max_vals.data_ptr<float>(),
sum_exp.data_ptr<float>(),
B, P);
// Normalize and cumsum
normalize_cumsum_kernel<<<B, P, P * sizeof(float)>>>(
new_log_w.data_ptr<float>(),
max_vals.data_ptr<float>(),
sum_exp.data_ptr<float>(),
cumsum.data_ptr<float>(),
B, P);
// Resample
blocks = (B * P + threads - 1) / threads;
resample_kernel<<<blocks, threads>>>(
pred.data_ptr<float>(),
cumsum.data_ptr<float>(),
uniform_samples.data_ptr<float>(),
resampled.data_ptr<float>(),
uniform_log_w.data_ptr<float>(),
B, P, D);
return {resampled, uniform_log_w};
}
"""
# Compile the extension
particle_filter_cuda = load_inline(
name='particle_filter_cuda',
cpp_sources='',
cuda_sources=cuda_source,
functions=['particle_filter_forward'],
extra_cuda_cflags=['-O3', '--use_fast_math'],
verbose=True
)
class ModelNew(nn.Module):
def __init__(self, state_dim, obs_dim, num_particles):
super().__init__()
self.state_dim = state_dim
self.obs_dim = obs_dim
self.num_particles = num_particles
self.register_buffer("A", 0.9 * torch.eye(state_dim))
self.register_buffer("C", torch.randn(obs_dim, state_dim) * 0.5)
self.process_noise = 0.1
self.obs_noise = 0.5
def forward(self, particles, log_weights, observation):
# Generate random samples using PyTorch for numerical equivalence
process_noise_samples = torch.randn_like(particles) * self.process_noise
uniform_samples = torch.rand(particles.size(0), 1, device=particles.device, dtype=particles.dtype)
# Ensure tensors are contiguous
particles = particles.contiguous()
log_weights = log_weights.contiguous()
observation = observation.contiguous()
A = self.A.contiguous()
C = self.C.contiguous()
process_noise_samples = process_noise_samples.contiguous()
uniform_samples = uniform_samples.contiguous()
resampled, uniform_log_w = particle_filter_cuda.particle_filter_forward(
particles, log_weights, observation,
A, C, process_noise_samples, uniform_samples,
self.obs_noise
)
return resampled, uniform_log_w
```
Wait, I need to reconsider the problem. The reference implementation has `self.C = torch.randn(obs_dim, state_dim) * 0.5`, which initializes C with random values. Since my ModelNew also has this line, the C matrix will be different from the reference!
For the test to work correctly, the model parameters (A and C) should be the same. Let me check the get_init_inputs function:
```python
def get_init_inputs():
return [state_dim, obs_dim, num_particles]
```
This suggests that both Model and ModelNew will be initialized with the same arguments, but they will have different random C matrices because `torch.randn` is called during initialization.
Wait, this is a problem. If C is different, then even if my kernel is correct, the outputs won't match because the C matrix is different.
Let me re-read the reference implementation...
Actually, I think the test framework might be comparing outputs by:
1. Creating Model and ModelNew with the same init arguments
2. Setting the same buffers (A and C) for both models
3. Running both models with the same inputs
4. Comparing outputs
Or maybe the framework creates both models, copies the parameters from one to the other, and then runs the test.
In any case, my ModelNew should work correctly if the parameters are the same. Let me assume that the framework handles this correctly.
But wait, I also need to handle the random number generation. The reference generates random numbers during forward pass:
1. `torch.randn_like(particles)` for process noise
2. `torch.rand(B, 1)` for resampling
My implementation generates these in Python before calling the CUDA kernel. This should give the same random numbers as the reference.
Let me finalize the solution. One thing I want to double-check is whether the cumsum needs to be of the weights or something else.
Reference:
```python
weights = torch.exp(new_log_w)
cumsum = torch.cumsum(weights, dim=1)
```
So `cumsum` is the cumulative sum of the exp of the normalized log weights. My kernel does this correctly.
One more thing: the reference uses `torch.logsumexp(new_log_w, dim=1, keepdim=True)` for normalization. This is equivalent to:
```python
max_val = new_log_w.max(dim=1, keepdim=True)[0]
sum_exp = (new_log_w - max_val).exp().sum(dim=1, keepdim=True)
logsumexp = max_val + sum_exp.log()
```
My `logsumexp_kernel` computes `max_vals[b]` and `sum_exp[b]` for each batch, which are the max and sum of exp. The `normalize_cumsum_kernel` then normalizes by `max_vals[b] + logf(sum_exp[b])`.
This should be correct.
Let me also verify the uniform log weights:
```python
uniform_log_w = torch.full_like(new_log_w, -torch.log(torch.tensor(float(P))))
```
This is `log(1/P) = -log(P)`. My kernel computes `uniform_log_w[idx] = -logf(float(P))`, which is the same.
I think the solution is correct. Let me clean it up and submit.
Actually, I want to reconsider the performance aspect. The reference uses many PyTorch operations that might already be GPU-accelerated. For my custom kernel to be faster, I need to:
1. Reduce kernel launch overhead by combining operations
2. Optimize memory access patterns
3. Use shared memory effectively
Currently, I have 6 kernel launches:
1. predict_kernel
2. obs_pred_kernel
3. log_lik_kernel
4. logsumexp_kernel
5. normalize_cumsum_kernel
6. resample_kernel
I could potentially combine some of these to reduce launch overhead, but for correctness, let me first submit this version and see the results.
One optimization I should consider is using vectorized loads/stores for the prediction and observation prediction kernels, but since D and obs_dim are small (4 and 2), this might not be worth it.
Let me submit the solution now.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n#include <algorithm>\n\n// Prediction: pred = particles @ A.T + noise\n__global__ void predict_kernel(\n const float* __restrict__ particles,\n const float* __restrict__ A,\n const float* __restrict__ noise,\n float* __restrict__ pred,\n const int B, const int P, const int D) {\n \n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total = B * P * D;\n \n if (idx < total) {\n const int d = idx % D;\n const int p = (idx / D) % P;\n const int b = idx / (P * D);\n \n float sum = 0.0f;\n for (int k = 0; k < D; k++) {\n sum += particles[b * P * D + p * D + k] * A[d * D + k];\n }\n pred[idx] = sum + noise[idx];\n }\n}\n\n// Observation prediction: obs_pred = pred @ C.T\n__global__ void obs_pred_kernel(\n const float* __restrict__ pred,\n const float* __restrict__ C,\n float* __restrict__ obs_pred,\n const int B, const int P, const int D, const int obs_dim) {\n \n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total = B * P * obs_dim;\n \n if (idx < total) {\n const int o = idx % obs_dim;\n const int p = (idx / obs_dim) % P;\n const int b = idx / (P * obs_dim);\n \n float sum = 0.0f;\n for (int k = 0; k < D; k++) {\n sum += pred[b * P * D + p * D + k] * C[o * D + k];\n }\n obs_pred[idx] = sum;\n }\n}\n\n// Log likelihood and weight update\n__global__ void log_lik_kernel(\n const float* __restrict__ obs_pred,\n const float* __restrict__ observation,\n const float* __restrict__ log_weights,\n float* __restrict__ new_log_w,\n const int B, const int P, const int obs_dim,\n const float obs_noise_sq) {\n \n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total = B * P;\n \n if (idx < total) {\n const int b = idx / P;\n const int p = idx % P;\n \n float log_lik = 0.0f;\n for (int o = 0; o < obs_dim; o++) {\n float diff = observation[b * obs_dim + o] - obs_pred[b * P * obs_dim + p * obs_dim + o];\n log_lik += diff * diff;\n }\n new_log_w[idx] = log_weights[idx] - 0.5f * log_lik / obs_noise_sq;\n }\n}\n\n// Logsumexp reduction per batch\n__global__ void logsumexp_kernel(\n const float* __restrict__ new_log_w,\n float* __restrict__ max_vals,\n float* __restrict__ sum_exp,\n const int B, const int P) {\n \n extern __shared__ float s_mem[];\n float* s_max = s_mem;\n float* s_sum = s_mem + blockDim.x;\n \n const int b = blockIdx.x;\n const int tid = threadIdx.x;\n const int blockDimX = blockDim.x;\n \n // Find max in this batch\n float my_max = -INFINITY;\n for (int i = tid; i < P; i += blockDimX) {\n float val = new_log_w[b * P + i];\n if (val > my_max) my_max = val;\n }\n s_max[tid] = my_max;\n __syncthreads();\n \n for (int s = blockDimX >> 1; s > 0; s >>= 1) {\n if (tid < s && s_max[tid + s] > s_max[tid]) {\n s_max[tid] = s_max[tid + s];\n }\n __syncthreads();\n }\n float block_max = s_max[0];\n \n // Compute sum of exp\n float my_sum = 0.0f;\n for (int i = tid; i < P; i += blockDimX) {\n my_sum += expf(new_log_w[b * P + i] - block_max);\n }\n s_sum[tid] = my_sum;\n __syncthreads();\n \n for (int s = blockDimX >> 1; s > 0; s >>= 1) {\n if (tid < s) {\n s_sum[tid] += s_sum[tid + s];\n }\n __syncthreads();\n }\n \n if (tid == 0) {\n max_vals[b] = block_max;\n sum_exp[b] = s_sum[0];\n }\n}\n\n// Normalize, exp, and cumsum in one kernel\n__global__ void normalize_cumsum_kernel(\n const float* __restrict__ new_log_w,\n const float* __restrict__ max_vals,\n const float* __restrict__ sum_exp,\n float* __restrict__ cumsum,\n const int B, const int P) {\n \n const int b = blockIdx.x;\n const int tid = threadIdx.x;\n \n extern __shared__ float s_data[];\n \n float log_scale = max_vals[b] + logf(sum_exp[b]);\n \n // Normalize and exp\n if (tid < P) {\n s_data[tid] = expf(new_log_w[b * P + tid] - log_scale);\n }\n __syncthreads();\n \n // Hillis-Steele inclusive scan\n for (int s = 1; s < P; s <<= 1) {\n float val = 0.0f;\n if (tid >= s && tid < P) {\n val = s_data[tid - s];\n }\n __syncthreads();\n if (tid < P) {\n s_data[tid] += val;\n }\n __syncthreads();\n }\n \n if (tid < P) {\n cumsum[b * P + tid] = s_data[tid];\n }\n}\n\n// Resample with binary search\n__global__ void resample_kernel(\n const float* __restrict__ pred,\n const float* __restrict__ cumsum,\n const float* __restrict__ uniform_samples,\n float* __restrict__ resampled,\n float* __restrict__ uniform_log_w,\n const int B, const int P, const int D) {\n \n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total = B * P;\n \n if (idx < total) {\n const int b = idx / P;\n const int p = idx % P;\n \n float u = (float(p) + uniform_samples[b]) / float(P);\n \n // Binary search for lower bound\n int lo = 0, hi = P;\n while (lo < hi) {\n int mid = (lo + hi) >> 1;\n if (cumsum[b * P + mid] < u) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n int resample_idx = min(lo, P - 1);\n \n // Gather particle\n for (int d = 0; d < D; d++) {\n resampled[b * P * D + p * D + d] = pred[b * P * D + resample_idx * D + d];\n }\n \n uniform_log_w[idx] = -logf(float(P));\n }\n}\n\nstd::vector<torch::Tensor> particle_filter_forward(\n torch::Tensor particles,\n torch::Tensor log_weights,\n torch::Tensor observation,\n torch::Tensor A,\n torch::Tensor C,\n torch::Tensor process_noise_samples,\n torch::Tensor uniform_samples,\n double obs_noise) {\n \n const int B = particles.size(0);\n const int P = particles.size(1);\n const int D = particles.size(2);\n const int obs_dim = observation.size(1);\n \n auto options = particles.options();\n \n auto pred = torch::empty_like(particles);\n auto obs_pred = torch::empty({B, P, obs_dim}, options);\n auto new_log_w = torch::empty_like(log_weights);\n auto max_vals = torch::empty({B}, options);\n auto sum_exp = torch::empty({B}, options);\n auto cumsum = torch::empty_like(log_weights);\n auto resampled = torch::empty_like(particles);\n auto uniform_log_w = torch::empty_like(log_weights);\n \n float obs_noise_sq = static_cast<float>(obs_noise * obs_noise);\n \n const int threads = 256;\n \n // Prediction\n int blocks = (B * P * D + threads - 1) / threads;\n predict_kernel<<<blocks, threads>>>(\n particles.data_ptr<float>(),\n A.data_ptr<float>(),\n process_noise_samples.data_ptr<float>(),\n pred.data_ptr<float>(),\n B, P, D);\n \n // Observation prediction\n blocks = (B * P * obs_dim + threads - 1) / threads;\n obs_pred_kernel<<<blocks, threads>>>(\n pred.data_ptr<float>(),\n C.data_ptr<float>(),\n obs_pred.data_ptr<float>(),\n B, P, D, obs_dim);\n \n // Log likelihood\n blocks = (B * P + threads - 1) / threads;\n log_lik_kernel<<<blocks, threads>>>(\n obs_pred.data_ptr<float>(),\n observation.data_ptr<float>(),\n log_weights.data_ptr<float>(),\n new_log_w.data_ptr<float>(),\n B, P, obs_dim, obs_noise_sq);\n \n // Logsumexp\n logsumexp_kernel<<<B, threads, 2 * threads * sizeof(float)>>>(\n new_log_w.data_ptr<float>(),\n max_vals.data_ptr<float>(),\n sum_exp.data_ptr<float>(),\n B, P);\n \n // Normalize and cumsum\n normalize_cumsum_kernel<<<B, P, P * sizeof(float)>>>(\n new_log_w.data_ptr<float>(),\n max_vals.data_ptr<float>(),\n sum_exp.data_ptr<float>(),\n cumsum.data_ptr<float>(),\n B, P);\n \n // Resample\n blocks = (B * P + threads - 1) / threads;\n resample_kernel<<<blocks, threads>>>(\n pred.data_ptr<float>(),\n cumsum.data_ptr<float>(),\n uniform_samples.data_ptr<float>(),\n resampled.data_ptr<float>(),\n uniform_log_w.data_ptr<float>(),\n B, P, D);\n \n return {resampled, uniform_log_w};\n}\n\"\"\"\n\n# Compile the extension\nparticle_filter_cuda = load_inline(\n name='particle_filter_cuda',\n cpp_sources='',\n cuda_sources=cuda_source,\n functions=['particle_filter_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=True\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, state_dim, obs_dim, num_particles):\n super().__init__()\n self.state_dim = state_dim\n self.obs_dim = obs_dim\n self.num_particles = num_particles\n self.register_buffer(\"A\", 0.9 * torch.eye(state_dim))\n self.register_buffer(\"C\", torch.randn(obs_dim, state_dim) * 0.5)\n self.process_noise = 0.1\n self.obs_noise = 0.5\n \n def forward(self, particles, log_weights, observation):\n # Generate random samples using PyTorch for numerical equivalence\n process_noise_samples = torch.randn_like(particles) * self.process_noise\n uniform_samples = torch.rand(particles.size(0), 1, device=particles.device, dtype=particles.dtype)\n \n # Ensure tensors are contiguous\n particles = particles.contiguous()\n log_weights = log_weights.contiguous()\n observation = observation.contiguous()\n A = self.A.contiguous()\n C = self.C.contiguous()\n process_noise_samples = process_noise_samples.contiguous()\n uniform_samples = uniform_samples.contiguous()\n \n resampled, uniform_log_w = particle_filter_cuda.particle_filter_forward(\n particles, log_weights, observation,\n A, C, process_noise_samples, uniform_samples,\n self.obs_noise\n )\n \n return resampled, uniform_log_w"
}