reasoning
Let me analyze this task. I need to create a custom CUDA kernel that replaces the PyTorch model's forward computation.
The model does:
1. `alpha = torch.sigmoid(layout_logits).unsqueeze(-1)` - sigmoid of layout_logits, shape (B, T, 1)
2. `amax = x.detach().abs().max().clamp(min=1e-8)` - find max absolute value in x, clamped
3. `scale = amax / 127.0` - compute scale for int8 quantization
4. `x_q = _fake_quant_symmetric_int8(x, scale)` - fake quantize: round(x/scale) clamped to [-127, 127], then * scale
5. `mixed = alpha * x_q + (1.0 - alpha) * x` - blend quantized and original
6. `return self.out_proj(mixed)` - linear projection
So the fused kernel should:
1. Compute sigmoid of layout_logits
2. Compute amax (reduction across entire x tensor)
3. Compute scale
4. For each element: compute quantized value, blend with original based on alpha
5. Then apply linear projection (matrix multiplication + bias)
The linear projection at the end is a standard GEMM operation. I could either:
- Fuse everything including the linear projection
- Fuse steps 1-4 and use PyTorch for the linear projection
Let me think about fusing everything into one kernel. The linear projection is `(B*T, D) @ (D, D) + bias`, which is a GEMM. This is typically well-optimized by cuBLAS, so fusing it might not give much benefit.
However, fusing the element-wise operations (sigmoid, quantization, blending) into one kernel could save memory bandwidth.
Let me write a kernel that:
1. Computes amax using a reduction kernel
2. Fuses sigmoid, quantization, and blending into one kernel
3. Uses PyTorch for the final linear projection
Actually, for better performance, let me try to fuse as much as possible:
- Compute amax in a reduction pass
- Fuse sigmoid + quantization + blending + output into one kernel, storing results in a temporary buffer
- Apply the linear projection using PyTorch
Wait, I could actually fuse everything into one kernel by storing the mixed values in shared memory and then having each thread apply its portion of the linear projection. But that gets complicated with the D×D matrix multiplication.
A more practical approach: use a reduction for amax, then a single kernel that handles sigmoid, quantization, blending, and the linear projection all together. For the linear projection, each thread could compute a dot product with the weight matrix.
Actually, the cleanest strategy is keeping it simple: one pass to compute amax, another pass that fuses sigmoid + quantization + blending, then leverage cuBLAS for the linear projection since it's already highly optimized for this operation. I'll create a kernel file with the quantization and blending logic, then integrate it with PyTorch's GEMM for the final projection. ], D);
}
}
}
The kernel handles the quantized transformation by computing sigmoid-based mixing coefficients, then blending between quantized and original values before projecting to the output dimension. Each thread processes multiple input elements to maximize throughput, with the mixed values stored in shared memory before the final projection step.
I need to reconsider the approach. Using cuBLAS for the linear layer might be more efficient, and I should verify the quantization logic matches the reference implementation—specifically computing the scale from the global maximum absolute value rather than per-token statistics.
The flow requires computing the scale factor from the maximum across all tokens, then quantizing each value by rounding, clamping to [-127, 127], and scaling back. I'll launch one kernel to extract the global maximum, another to handle the sigmoid computation and blending, then leverage PyTorch's optimized linear layer for the final projection. The scale is a scalar derived from the maximum value across the entire tensor. I'll implement this with a two-kernel approach: first computing the global max via reduction, then applying the fused operations. For the reduction, a simple atomic max approach avoids custom kernel complexity, though I should consider optimization tradeoffs since atomic operations can be costly on large tensors.
An alternative would be using thrust or cub libraries, but that introduces dependencies. Instead, I'll create a reduction kernel that computes the maximum absolute value efficiently by processing data in tiles and maintaining partial maxima in shared memory. The kernel loads chunks of the input tensor, computes absolute values, and threads cooperate to find the maximum across their assigned elements, ultimately writing the result to global memory.
For the second kernel, I apply the sigmoid-based quantization and blending operation. This kernel takes the layout logits, input tensor, and pre-computed scale, then produces the mixed output. The challenge is handling different tensor shapes—the layout logits have dimensions (B, T) while the input x has dimensions (B, T, D), so I need to map the per-token alpha values appropriately across the feature dimension.
The complete implementation combines both kernels: a reduction kernel to compute the maximum absolute value across all elements, followed by a blending kernel that applies the sigmoid transformation and quantization scheme. The host function orchestrates this by launching the first kernel to get the global scale factor, then using that scale in the second kernel to produce the final mixed output. _proj.weight
bias = self.out_proj.bias
mixed = quant_blend_cuda(x, layout_logits, weight, bias)
return mixed
The implementation needs to handle the quantization and blending steps in CUDA while delegating the linear projection to the fused kernel. I'm defining the extension with inline CUDA code that includes a clamp helper and a kernel for computing the maximum absolute value across the input tensor. The reduction kernel processes elements with a stride loop, tracking the maximum absolute value encountered.
For quantization and blending, a second kernel applies sigmoid to layout logits, then uses that to blend between quantized and original values. Each thread handles multiple output elements, computing per-token alpha values from the layout logits and applying the quantization scale before blending with the original input. The blended results are stored back to global memory for the subsequent linear projection.
The main function handles dimension checks and determines grid/block dimensions based on the batch size and sequence length. It then allocates output tensors and launches the quant_blend kernel with the appropriate parameters, using streams to synchronize execution before returning the result.
However, I'm reconsidering whether fusing the linear layer with the quantization operation is actually beneficial. The quantization produces a (B, T, D) tensor, which then gets reshaped to (B*T, D) for the GEMM operation—this is a standard matrix multiplication that cuBLAS already handles efficiently. While fused kernels reduce memory bandwidth for element-wise operations, the GEMM itself is compute-bound rather than memory-bound, so the performance gain from fusion might not be significant. A simpler approach would be to keep the quantization and blending separate from the linear layer, leveraging PyTorch's optimized GEMM implementation instead. _quant_symmetric_int8(x, scale)
mixed = alpha * x_q + (1.0 - alpha) * x
return self.out_proj(mixed)
```
This approach is too slow since it relies on PyTorch operations. I need to fuse the sigmoid, quantization, and blending into a single CUDA kernel for efficiency. The kernel should apply sigmoid to layout_logits, compute quantized values, blend them with the original input, and handle the linear projection in one pass.
Each thread processes multiple elements across the sequence dimension. For each batch index and sequence position, I compute alpha from the sigmoid of layout_logits, then iterate through the embedding dimension to quantize, blend, and apply the linear transformation. The weight matrix and bias are loaded from global memory, and the result is written back to the output tensor. amax = torch.max(torch.abs(x)).clamp(min=1e-8)
scale = amax / 127.0
B, T, D = x.shape
output = torch.empty(B, T, D, dtype=x.dtype, device=x.device)
grid = (B * T,)
block = 256
quant_blend_linear_kernel<<<grid, block>>>(
x.data_ptr<float>(),
layout_logits.data_ptr<float>(),
self.weight.data_ptr<float>(),
self.bias.data_ptr<float>(),
output.data_ptr<float>(),
scale.item(),
B, T, D
);
return output
This approach reduces the quantization scale computation to a single tensor operation, then launches a fused kernel that handles sigmoid, quantization, blending, and the linear projection in one pass. The kernel processes all elements across batch and sequence dimensions, with each thread handling multiple feature dimensions to maximize throughput.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel inline at module load time\n_fused_kernel = load_inline(\n name='fused_quant_blend_linear',\n cpp_sources=r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n__global__ void quant_blend_linear_kernel(\n const float* __restrict__ x,\n const float* __restrict__ layout_logits,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n float* __restrict__ output,\n float scale,\n int B, int T, int D\n) {\n int bt = blockIdx.x;\n int b = bt / T;\n int t = bt % T;\n int tid = threadIdx.x;\n \n if (b >= B || t >= T) return;\n \n // Compute sigmoid for this token\n float alpha = 1.0f / (1.0f + expf(-layout_logits[bt]));\n \n // Pre-compute quantize, blend, and linear for each D element\n // Each thread handles multiple D elements\n for (int d = tid; d < D; d += blockDim.x) {\n float x_val = x[b * T * D + t * D + d];\n \n // Fake quant: round(x/scale) clamped to [-127, 127], times scale\n float q = roundf(x_val / scale);\n q = fmaxf(-127.0f, fminf(127.0f, q));\n float x_q = q * scale;\n \n // Blend\n float mixed = alpha * x_q + (1.0f - alpha) * x_val;\n \n // Linear projection\n float sum = bias[d];\n for (int k = 0; k < D; ++k) {\n float m = (k < d) ? \n ((k < tid) ? x[b * T * D + t * D + k] : __ldg(&x[b * T * D + t * D + k])) :\n __ldg(&x[b * T * D + t * D + k]);\n }\n \n // Actually compute linear: output[d] = sum_k(mixed[k] * weight[k, d]) + bias[d]\n sum = bias[d];\n for (int k = 0; k < D; ++k) {\n float m_k;\n if (k == d && tid == d % blockDim.x) {\n // Use local mixed value\n m_k = mixed;\n } else {\n // Need to compute mixed[k] or reload\n float x_k = __ldg(&x[b * T * D + t * D + k]);\n float q_k = roundf(x_k / scale);\n q_k = fmaxf(-127.0f, fminf(127.0f, q_k));\n float x_q_k = q_k * scale;\n m_k = alpha * x_q_k + (1.0f - alpha) * x_k;\n }\n sum += m_k * __ldg(&weight[k * D + d]);\n }\n output[b * T * D + t * D + d] = sum;\n }\n}\n\n// Optimized version: compute mixed values first in shared memory\n__global__ void quant_blend_linear_kernel_opt(\n const float* __restrict__ x,\n const float* __restrict__ layout_logits,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n float* __restrict__ output,\n float scale,\n int B, int T, int D\n) {\n extern __shared__ float mixed_shared[];\n \n int bt = blockIdx.x;\n int b = bt / T;\n int t = bt % T;\n int tid = threadIdx.x;\n \n if (b >= B || t >= T) return;\n \n // Compute sigmoid for this token (same for all d)\n float alpha = 1.0f / (1.0f + expf(-layout_logits[bt]));\n \n // Load x, quantize, blend, store to shared memory\n for (int d = tid; d < D; d += blockDim.x) {\n float x_val = __ldg(&x[b * T * D + t * D + d]);\n float q = roundf(x_val / scale);\n q = fmaxf(-127.0f, fminf(127.0f, q));\n float x_q = q * scale;\n mixed_shared[d] = alpha * x_q + (1.0f - alpha) * x_val;\n }\n __syncthreads();\n \n // Linear projection: each thread computes one output element\n for (int d_out = tid; d_out < D; d_out += blockDim.x) {\n float sum = bias[d_out];\n for (int k = 0; k < D; ++k) {\n sum += mixed_shared[k] * __ldg(&weight[k * D + d_out]);\n }\n output[b * T * D + t * D + d_out] = sum;\n }\n}\n\ntorch::Tensor fused_quant_blend_linear_cuda(\n torch::Tensor x,\n torch::Tensor layout_logits,\n torch::Tensor weight,\n torch::Tensor bias\n) {\n TORCH_CHECK(x.dim() == 3, \"x must be 3D\");\n TORCH_CHECK(layout_logits.dim() == 2, \"layout_logits must be 2D\");\n \n int B = x.size(0);\n int T = x.size(1);\n int D = x.size(2);\n \n TORCH_CHECK(layout_logits.size(0) == B, \"layout_logits batch mismatch\");\n TORCH_CHECK(layout_logits.size(1) == T, \"layout_logits seq mismatch\");\n \n // Compute global amax and scale on CPU (fast for single value)\n float amax = x.abs().max().item<float>();\n amax = fmaxf(amax, 1e-8f);\n float scale = amax / 127.0f;\n \n auto output = torch::empty({B, T, D}, x.options());\n \n int grid = B * T;\n int block = (D + 31) / 32 * 32; // Round up to warp size\n block = min(block, 1024);\n size_t shared_mem = D * sizeof(float);\n \n quant_blend_linear_kernel_opt<<<grid, block, shared_mem>>>(\n x.data_ptr<float>(),\n layout_logits.data_ptr<float>(),\n weight.data_ptr<float>(),\n bias.data_ptr<float>(),\n output.data_ptr<float>(),\n scale,\n B, T, D\n );\n \n return output;\n}\n''',\n functions=['fused_quant_blend_linear_cuda'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"Fused CUDA kernel for quant-blend-linear operation.\"\"\"\n\n def __init__(self, dim: int):\n super().__init__()\n self.dim = dim\n # Create linear layer with parameters\n self.out_proj = nn.Linear(dim, dim, bias=True)\n\n def forward(self, x: torch.Tensor, layout_logits: torch.Tensor) -> torch.Tensor:\n B, T, D = x.shape\n assert D == self.dim\n assert layout_logits.shape == (B, T)\n \n # Use fused CUDA kernel for the entire computation\n return _fused_kernel.fused_quant_blend_linear_cuda(\n x, layout_logits, self.out_proj.weight, self.out_proj.bias\n )"
}