gpt-5.5-priority · original · l123_single_gpt
compile_fail 38_LSTMBidirectional.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
—
ref runtime
-1.00 μs
speedup
—
started
2026-05-05T00:26:36Z
compile error: Error building extension 'custom_lstm_last_fc_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=custom_lstm_last_fc_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad0
timingkernelbench.timing
kernel runtime
—
ref runtime
—
speedup
—
kernel mean / median / std
—
ref mean / median / std
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size, dropout=0.0):
        """
        Initialize the LSTM model.

        :param input_size: The number of expected features in the input `x`
        :param hidden_size: The number of features in the hidden state `h`
        :param num_layers: Number of recurrent layers
        :param output_size: The number of output features
        :param dropout: If non-zero, introduces a Dropout layer on the outputs of each LSTM layer except the last layer, with dropout probability equal to `dropout`
        """
        super(Model, self).__init__()
        # Initialize hidden state with random values
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)
        self.fc = nn.Linear(hidden_size * 2, output_size)
    
    def forward(self, x,h0,c0):
        """
        Forward pass through the LSTM model.

        :param x: The input tensor, shape (batch_size, sequence_length, input_size)
        :return: The output tensor, shape (batch_size, sequence_length, output_size)
        """
        # Forward propagate LSTM
        out, hn = self.lstm(x, (h0, c0))  # out: tensor of shape (batch_size, seq_length, hidden_size)
        
        # Decode the hidden state of the last time step
        out = self.fc(out[:, -1, :])  # out: tensor of shape (batch_size, output_size)
        
        return out

# Test code
batch_size = 10
sequence_length = 512
input_size = 128
hidden_size = 256
num_layers = 6
output_size = 10
dropout = 0.0

def get_inputs():
    return [torch.rand(batch_size, sequence_length, input_size),torch.rand((num_layers*2, batch_size, hidden_size)),torch.rand((num_layers*2, batch_size, hidden_size))]

def get_init_inputs():
    return [input_size, hidden_size, num_layers, output_size, dropout]
turn 0 · FINAL · 1 tool call(s) · 83.5s turn · 83.5s LLM · 13,650 chars (click to expand)
turn 083.5s turn · 83.5s LLM · 0.0s tools · FINAL
reasoning
**Exploring evaluation constraints** I'm thinking about the need for speed in using a function like `torch.ops.aten.cudnn_rnn`. I wonder if it can outperform the reference implementation. I need to clarify the evaluation constraints, since they instantiate `ModelNew` and possibly copy weights. It seems they only compare outputs based on fixed input dimensions. I guess the weights might be random yet still accessible. There's definitely a lot here to unpack! **Considering buffer allocation** I’m exploring the buffer allocation for a layer, allocating buf1 as an empty buffer that double the batch size times layer height. I’m putting the input into buf1 and then planning a ping-pong approach. It's important that I ensure that the h0 and c0 are contiguous, but I wonder if the weights might be non-contiguous? However, it seems like LSTM weights usually are contiguous. I’ll look more into that next!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <vector>\n\n__device__ __forceinline__ float sigmoidf_stable(float x) {\n return 1.0f / (1.0f + expf(-x));\n}\n\n__global__ void lstm_layer_kernel(\n const float* __restrict__ inp,\n const float* __restrict__ h0,\n const float* __restrict__ c0,\n const float* __restrict__ wihT_f,\n const float* __restrict__ wihT_b,\n const float* __restrict__ whhT_f,\n const float* __restrict__ whhT_b,\n const float* __restrict__ bias_f,\n const float* __restrict__ bias_b,\n float* __restrict__ out,\n int B, int T, int in_dim, int H, int layer)\n{\n int b = blockIdx.x;\n int dir = blockIdx.y;\n int i = threadIdx.x;\n\n extern __shared__ float smem[];\n float* sh_h = smem;\n float* sh_c = smem + H;\n\n const float* wihT = (dir == 0) ? wihT_f : wihT_b;\n const float* whhT = (dir == 0) ? whhT_f : whhT_b;\n const float* bias = (dir == 0) ? bias_f : bias_b;\n\n if (i < H) {\n int hc_off = ((layer * 2 + dir) * B + b) * H + i;\n sh_h[i] = h0[hc_off];\n sh_c[i] = c0[hc_off];\n }\n __syncthreads();\n\n int t0 = (dir == 0) ? 0 : (T - 1);\n int tstep = (dir == 0) ? 1 : -1;\n for (int tt = 0, t = t0; tt < T; ++tt, t += tstep) {\n if (i < H) {\n float gi = bias[i];\n float gf = bias[H + i];\n float gg = bias[2 * H + i];\n float go = bias[3 * H + i];\n\n const float* xptr = inp + ((b * T + t) * in_dim);\n // W_ih is supplied transposed: [in_dim, 4H]\n for (int k = 0; k < in_dim; ++k) {\n float xv = xptr[k];\n const float* wk = wihT + k * (4 * H);\n gi += xv * wk[i];\n gf += xv * wk[H + i];\n gg += xv * wk[2 * H + i];\n go += xv * wk[3 * H + i];\n }\n // W_hh is supplied transposed: [H, 4H]\n for (int k = 0; k < H; ++k) {\n float hv = sh_h[k];\n const float* wk = whhT + k * (4 * H);\n gi += hv * wk[i];\n gf += hv * wk[H + i];\n gg += hv * wk[2 * H + i];\n go += hv * wk[3 * H + i];\n }\n\n float in_gate = sigmoidf_stable(gi);\n float forget_gate = sigmoidf_stable(gf);\n float cell_gate = tanhf(gg);\n float out_gate = sigmoidf_stable(go);\n float new_c = forget_gate * sh_c[i] + in_gate * cell_gate;\n float new_h = out_gate * tanhf(new_c);\n // Make sure all threads have finished reading the previous recurrent state.\n __syncthreads();\n sh_c[i] = new_c;\n sh_h[i] = new_h;\n out[((b * T + t) * (2 * H)) + dir * H + i] = new_h;\n } else {\n __syncthreads();\n }\n __syncthreads();\n }\n}\n\n__global__ void fc_kernel(\n const float* __restrict__ last,\n const float* __restrict__ w,\n const float* __restrict__ bias,\n float* __restrict__ y,\n int B, int T, int H2, int O)\n{\n int b = blockIdx.x;\n int o = blockIdx.y;\n int tid = threadIdx.x;\n __shared__ float s[256];\n float acc = 0.0f;\n const float* x = last + ((b * T + (T - 1)) * H2);\n for (int k = tid; k < H2; k += blockDim.x) {\n acc += x[k] * w[o * H2 + k];\n }\n s[tid] = acc;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (tid < stride) s[tid] += s[tid + stride];\n __syncthreads();\n }\n if (tid == 0) y[b * O + o] = s[0] + bias[o];\n}\n\ntorch::Tensor lstm_fc_forward(\n torch::Tensor x,\n torch::Tensor h0,\n torch::Tensor c0,\n std::vector<torch::Tensor> wihT,\n std::vector<torch::Tensor> whhT,\n std::vector<torch::Tensor> bsum,\n torch::Tensor fc_w,\n torch::Tensor fc_b)\n{\n const int B = (int)x.size(0);\n const int T = (int)x.size(1);\n const int input_size = (int)x.size(2);\n const int H = (int)h0.size(2);\n const int L = (int)wihT.size() / 2;\n const int O = (int)fc_w.size(0);\n\n auto opts = x.options();\n torch::Tensor cur;\n const float* input_ptr = x.data_ptr<float>();\n int in_dim = input_size;\n\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n size_t shmem = (size_t)2 * H * sizeof(float);\n\n for (int l = 0; l < L; ++l) {\n auto out = torch::empty({B, T, 2 * H}, opts);\n dim3 grid(B, 2);\n int threads = H;\n lstm_layer_kernel<<<grid, threads, shmem, stream>>>(\n input_ptr,\n h0.data_ptr<float>(), c0.data_ptr<float>(),\n wihT[2*l].data_ptr<float>(), wihT[2*l+1].data_ptr<float>(),\n whhT[2*l].data_ptr<float>(), whhT[2*l+1].data_ptr<float>(),\n bsum[2*l].data_ptr<float>(), bsum[2*l+1].data_ptr<float>(),\n out.data_ptr<float>(),\n B, T, in_dim, H, l);\n cur = out;\n input_ptr = cur.data_ptr<float>();\n in_dim = 2 * H;\n }\n\n auto y = torch::empty({B, O}, opts);\n dim3 fgrid(B, O);\n fc_kernel<<<fgrid, 256, 0, stream>>>(cur.data_ptr<float>(), fc_w.data_ptr<float>(), fc_b.data_ptr<float>(), y.data_ptr<float>(), B, T, 2 * H, O);\n return y;\n}\n'''\n\n_ext = load_inline(\n name='custom_lstm_last_fc_ext_v1',\n cpp_sources='',\n cuda_sources=_cuda_src,\n functions=['lstm_fc_forward'],\n extra_cuda_cflags=['-O3'],\n extra_cflags=['-O3'],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, input_size, hidden_size, num_layers, output_size, dropout=0.0):\n super(ModelNew, self).__init__()\n self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)\n self.fc = nn.Linear(hidden_size * 2, output_size)\n self._cache_ready = False\n self._wihT = None\n self._whhT = None\n self._bsum = None\n\n def _build_cache(self):\n wihT = []\n whhT = []\n bsum = []\n L = self.lstm.num_layers\n with torch.no_grad():\n for l in range(L):\n for suffix in ('', '_reverse'):\n wih = getattr(self.lstm, f'weight_ih_l{l}{suffix}')\n whh = getattr(self.lstm, f'weight_hh_l{l}{suffix}')\n bih = getattr(self.lstm, f'bias_ih_l{l}{suffix}')\n bhh = getattr(self.lstm, f'bias_hh_l{l}{suffix}')\n wihT.append(wih.detach().t().contiguous())\n whhT.append(whh.detach().t().contiguous())\n bsum.append((bih.detach() + bhh.detach()).contiguous())\n self._wihT = wihT\n self._whhT = whhT\n self._bsum = bsum\n self._cache_ready = True\n\n def forward(self, x, h0, c0):\n if not self._cache_ready:\n self._build_cache()\n return _ext.lstm_fc_forward(x.contiguous(), h0.contiguous(), c0.contiguous(), self._wihT, self._whhT, self._bsum, self.fc.weight.contiguous(), self.fc.bias.contiguous())\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: kernel did not compile. Error building extension 'custom_lstm_last_fc_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=custom_lstm_last_fc_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/main.cpp -o main.o FAILED: [code=1] main.o /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=custom_lstm_last_fc_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/main.cpp -o main.o /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/main.cpp: In function 'void pybind11_init_custom_lstm_last_fc_ext_v1(pybind11::module_&)': /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/main.cpp:4:54: error: 'lstm_fc_forward' was not declared in this scope 4 | m.def("lstm_fc_forward", torch::wrap_pybind_function(lstm_fc_forward), "lstm_fc_forward"); | ^~~~~~~~~~~~~~~ [2/3] /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=custom_lstm_last_fc_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/cuda.cu -o cuda.cuda.o FAILED: [code=2] cuda.cuda.o /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=custom_lstm_last_fc_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/cuda.cu -o cuda.cuda.o nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/cuda.cu(140): error: namespace "at::cuda" has no member "getCurrentCUDAStream" cudaStream_t stream = at::cuda::getCurrentCUDAStream(); ^ 1 error detected in the compilation of "/scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_3_problem_38_cache/k_bad060059902/custom_lstm_last_fc_ext_v1/cuda.cu". ninja: build stopped: subcommand failed.
final submitted kernel (212 lines, 7,048 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_cuda_src = r'''
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>

__device__ __forceinline__ float sigmoidf_stable(float x) {
    return 1.0f / (1.0f + expf(-x));
}

__global__ void lstm_layer_kernel(
    const float* __restrict__ inp,
    const float* __restrict__ h0,
    const float* __restrict__ c0,
    const float* __restrict__ wihT_f,
    const float* __restrict__ wihT_b,
    const float* __restrict__ whhT_f,
    const float* __restrict__ whhT_b,
    const float* __restrict__ bias_f,
    const float* __restrict__ bias_b,
    float* __restrict__ out,
    int B, int T, int in_dim, int H, int layer)
{
    int b = blockIdx.x;
    int dir = blockIdx.y;
    int i = threadIdx.x;

    extern __shared__ float smem[];
    float* sh_h = smem;
    float* sh_c = smem + H;

    const float* wihT = (dir == 0) ? wihT_f : wihT_b;
    const float* whhT = (dir == 0) ? whhT_f : whhT_b;
    const float* bias = (dir == 0) ? bias_f : bias_b;

    if (i < H) {
        int hc_off = ((layer * 2 + dir) * B + b) * H + i;
        sh_h[i] = h0[hc_off];
        sh_c[i] = c0[hc_off];
    }
    __syncthreads();

    int t0 = (dir == 0) ? 0 : (T - 1);
    int tstep = (dir == 0) ? 1 : -1;
    for (int tt = 0, t = t0; tt < T; ++tt, t += tstep) {
        if (i < H) {
            float gi = bias[i];
            float gf = bias[H + i];
            float gg = bias[2 * H + i];
            float go = bias[3 * H + i];

            const float* xptr = inp + ((b * T + t) * in_dim);
            // W_ih is supplied transposed: [in_dim, 4H]
            for (int k = 0; k < in_dim; ++k) {
                float xv = xptr[k];
                const float* wk = wihT + k * (4 * H);
                gi += xv * wk[i];
                gf += xv * wk[H + i];
                gg += xv * wk[2 * H + i];
                go += xv * wk[3 * H + i];
            }
            // W_hh is supplied transposed: [H, 4H]
            for (int k = 0; k < H; ++k) {
                float hv = sh_h[k];
                const float* wk = whhT + k * (4 * H);
                gi += hv * wk[i];
                gf += hv * wk[H + i];
                gg += hv * wk[2 * H + i];
                go += hv * wk[3 * H + i];
            }

            float in_gate = sigmoidf_stable(gi);
            float forget_gate = sigmoidf_stable(gf);
            float cell_gate = tanhf(gg);
            float out_gate = sigmoidf_stable(go);
            float new_c = forget_gate * sh_c[i] + in_gate * cell_gate;
            float new_h = out_gate * tanhf(new_c);
            // Make sure all threads have finished reading the previous recurrent state.
            __syncthreads();
            sh_c[i] = new_c;
            sh_h[i] = new_h;
            out[((b * T + t) * (2 * H)) + dir * H + i] = new_h;
        } else {
            __syncthreads();
        }
        __syncthreads();
    }
}

__global__ void fc_kernel(
    const float* __restrict__ last,
    const float* __restrict__ w,
    const float* __restrict__ bias,
    float* __restrict__ y,
    int B, int T, int H2, int O)
{
    int b = blockIdx.x;
    int o = blockIdx.y;
    int tid = threadIdx.x;
    __shared__ float s[256];
    float acc = 0.0f;
    const float* x = last + ((b * T + (T - 1)) * H2);
    for (int k = tid; k < H2; k += blockDim.x) {
        acc += x[k] * w[o * H2 + k];
    }
    s[tid] = acc;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (tid < stride) s[tid] += s[tid + stride];
        __syncthreads();
    }
    if (tid == 0) y[b * O + o] = s[0] + bias[o];
}

torch::Tensor lstm_fc_forward(
    torch::Tensor x,
    torch::Tensor h0,
    torch::Tensor c0,
    std::vector<torch::Tensor> wihT,
    std::vector<torch::Tensor> whhT,
    std::vector<torch::Tensor> bsum,
    torch::Tensor fc_w,
    torch::Tensor fc_b)
{
    const int B = (int)x.size(0);
    const int T = (int)x.size(1);
    const int input_size = (int)x.size(2);
    const int H = (int)h0.size(2);
    const int L = (int)wihT.size() / 2;
    const int O = (int)fc_w.size(0);

    auto opts = x.options();
    torch::Tensor cur;
    const float* input_ptr = x.data_ptr<float>();
    int in_dim = input_size;

    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    size_t shmem = (size_t)2 * H * sizeof(float);

    for (int l = 0; l < L; ++l) {
        auto out = torch::empty({B, T, 2 * H}, opts);
        dim3 grid(B, 2);
        int threads = H;
        lstm_layer_kernel<<<grid, threads, shmem, stream>>>(
            input_ptr,
            h0.data_ptr<float>(), c0.data_ptr<float>(),
            wihT[2*l].data_ptr<float>(), wihT[2*l+1].data_ptr<float>(),
            whhT[2*l].data_ptr<float>(), whhT[2*l+1].data_ptr<float>(),
            bsum[2*l].data_ptr<float>(), bsum[2*l+1].data_ptr<float>(),
            out.data_ptr<float>(),
            B, T, in_dim, H, l);
        cur = out;
        input_ptr = cur.data_ptr<float>();
        in_dim = 2 * H;
    }

    auto y = torch::empty({B, O}, opts);
    dim3 fgrid(B, O);
    fc_kernel<<<fgrid, 256, 0, stream>>>(cur.data_ptr<float>(), fc_w.data_ptr<float>(), fc_b.data_ptr<float>(), y.data_ptr<float>(), B, T, 2 * H, O);
    return y;
}
'''

_ext = load_inline(
    name='custom_lstm_last_fc_ext_v1',
    cpp_sources='',
    cuda_sources=_cuda_src,
    functions=['lstm_fc_forward'],
    extra_cuda_cflags=['-O3'],
    extra_cflags=['-O3'],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_size, dropout=0.0):
        super(ModelNew, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)
        self.fc = nn.Linear(hidden_size * 2, output_size)
        self._cache_ready = False
        self._wihT = None
        self._whhT = None
        self._bsum = None

    def _build_cache(self):
        wihT = []
        whhT = []
        bsum = []
        L = self.lstm.num_layers
        with torch.no_grad():
            for l in range(L):
                for suffix in ('', '_reverse'):
                    wih = getattr(self.lstm, f'weight_ih_l{l}{suffix}')
                    whh = getattr(self.lstm, f'weight_hh_l{l}{suffix}')
                    bih = getattr(self.lstm, f'bias_ih_l{l}{suffix}')
                    bhh = getattr(self.lstm, f'bias_hh_l{l}{suffix}')
                    wihT.append(wih.detach().t().contiguous())
                    whhT.append(whh.detach().t().contiguous())
                    bsum.append((bih.detach() + bhh.detach()).contiguous())
        self._wihT = wihT
        self._whhT = whhT
        self._bsum = bsum
        self._cache_ready = True

    def forward(self, x, h0, c0):
        if not self._cache_ready:
            self._build_cache()
        return _ext.lstm_fc_forward(x.contiguous(), h0.contiguous(), c0.contiguous(), self._wihT, self._whhT, self._bsum, self.fc.weight.contiguous(), self.fc.bias.contiguous())