GPUCodeForces/S1/44/prompt.txt

66 lines
4.4 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Write a custom CUDA kernel to optimize `torch.nn.MultiLabelMarginLoss`.
The original operation is defined by the formula:
`loss(x, y) = sum_{j,i} max(0, 1 - (x[y[j]] - x[i])) / (x.size(0) * x.size(1))`
where `y[j]` are the positive class indices for a sample and `i` are the negative class indices. This is computed per sample and then reduced.
**Problem Analysis:**
`MultiLabelMarginLoss` is notoriously difficult to vectorize efficiently in PyTorch. The performance bottlenecks are severe:
1. **Irregular Data Access**: Each sample has a variable number of positive labels defined in `y`, which are padded with -1. Identifying the set of positive and negative classes for each sample requires complex, non-vectorized logic (e.g., masks, loops, or boolean indexing), which is slow.
2. **Massive Intermediate Tensors**: A naive vectorized approach would require gathering scores for positive classes and broadcasting them for subtraction against scores of negative classes. This would create huge intermediate tensors and is highly memory-inefficient.
3. **Complex Nested Loop Logic**: The core formula is a nested loop (`for each positive class`, `for each negative class`) for every sample, which is antithetical to efficient GPU execution without a custom kernel.
**Optimization Strategy: Fused Block-Level Parallelism with Shared Memory Caching**
The strategy is to implement the entire complex logic within a single CUDA kernel, using a block-per-sample parallelization model.
1. **Parallelization Model**: A grid of `N` blocks is launched, where `N` is the batch size. Each thread block is assigned to compute the total loss for one sample.
2. **Shared Memory Caching**: For each sample (block), the kernel first collaboratively reads the list of positive class indices from the `target` tensor. These indices (and their count) are cached in **shared memory**. This makes the critical metadata for the sample instantly accessible to all threads in the block.
3. **Fused Computation Loop**: The threads within a block then work together to iterate through all `C` possible classes. For each class `i`, a thread checks if it's a positive or negative class using the cached shared memory data.
* If `i` is a negative class, the thread then iterates through the *positive class indices cached in shared memory*.
* For each positive-negative pair, it calculates the hinge loss term `max(0, 1 - (x_pos - x_neg))` and accumulates it into a thread-local register. This fuses the nested loops, indexing, subtraction, and `max` operations.
4. **Efficient Intra-Block Reduction**: Once all classes are processed, a fast parallel reduction is performed using shared memory to sum the partial results from all threads within the block into a single total loss for that sample.
5. **Finalization**: The first thread of each block performs the final division and writes the result to the output tensor. The kernel directly produces the per-sample losses (`reduction='none'`). The final batch reduction (`'mean'` or `'sum'`) is efficiently handled by a single PyTorch call on the small 1D output tensor.
This approach transforms the complex, memory-bound, and hard-to-vectorize PyTorch operation into a single, efficient, compute-focused CUDA kernel.
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 numpy as np
BATCH_SIZE = 512
NUM_CLASSES = 1024
REDUCTION = 'mean'
# 每个样本的正类标签数量范围
MIN_LABELS = 1
MAX_LABELS = 10
class Model(nn.Module):
def __init__(self, reduction='mean'):
super(Model, self).__init__()
self.loss_fn = nn.MultiLabelMarginLoss(reduction=reduction)
def forward(self, input_tensor: torch.Tensor, target_tensor: torch.Tensor) -> torch.Tensor:
return self.loss_fn(input_tensor, target_tensor)
def get_inputs():
input_tensor = torch.randn(BATCH_SIZE, NUM_CLASSES, dtype=torch.float32)
# target每行包含正类索引并用 -1 填充
target_np = np.full((BATCH_SIZE, NUM_CLASSES), -1, dtype=np.int64)
for i in range(BATCH_SIZE):
num_labels = np.random.randint(MIN_LABELS, MAX_LABELS + 1)
labels = np.random.choice(NUM_CLASSES, num_labels, replace=False)
target_np[i, :num_labels] = labels
target_tensor = torch.from_numpy(target_np)
return [input_tensor.contiguous(), target_tensor.contiguous()]
def get_init_inputs():
return [REDUCTION]