| """ |
| Fused Hierarchical Scan for the Tiered Memory HRM. |
| |
| This module implements the algorithm proposed in Section 4.2 of the project proposal: |
| 'The Tiered Memory Hierarchical Scan'. |
| |
| It replaces the Python-level `for` loop over L_cycles with a single OpenAI Triton |
| kernel invocation. The kernel loads the slow state (z_H) once into SRAM, executes |
| the entire fast state (z_L) recurrence for T steps completely within SRAM (registers/shared memory), |
| and only writes the final states back to HBM (DRAM) at the cycle boundary. |
| |
| Algorithm Steps Implemented: |
| 1. Block-wise Loading: Sequence is chunked by T (L_cycles). |
| 2. DRAM -> SRAM Fetch: z_H and inputs loaded once. |
| 3. SRAM-Resident Recurrence: T steps computed without global memory access. |
| 4. Boundary Sync: High-level state updated at the end. |
| 5. SRAM -> DRAM Write: Only final states are materialized. |
| """ |
|
|
| import torch |
| import triton |
| import triton.language as tl |
| import math |
|
|
|
|
| @triton.jit |
| def _fused_hierarchical_scan_kernel( |
| |
| X_ptr, |
| Z_L_in_ptr, |
| Z_H_in_ptr, |
| Z_L_out_ptr, |
| Z_H_out_ptr, |
| |
| W_L_ptr, |
| W_H_ptr, |
| |
| stride_batch_x, stride_seq_x, stride_dim_x, |
| T: tl.constexpr, |
| HIDDEN_SIZE: tl.constexpr, |
| BLOCK_DIM: tl.constexpr, |
| ): |
| """ |
| SRAM-Resident Hierarchical Scan Kernel. |
| |
| This kernel is designed so that the inner loop (t = 0...T) operates entirely |
| on registers (`z_L` and `z_H` variables inside the kernel). |
| """ |
| batch_idx = tl.program_id(0) |
| |
| |
| dim_offsets = tl.arange(0, BLOCK_DIM) |
| mask = dim_offsets < HIDDEN_SIZE |
|
|
| |
| |
| |
| |
| z_L = tl.load(Z_L_in_ptr + batch_idx * HIDDEN_SIZE + dim_offsets, mask=mask, other=0.0) |
| z_H = tl.load(Z_H_in_ptr + batch_idx * HIDDEN_SIZE + dim_offsets, mask=mask, other=0.0) |
|
|
| |
| |
| |
| |
| |
| w_L = tl.load(W_L_ptr + dim_offsets, mask=mask, other=0.0) |
| w_H = tl.load(W_H_ptr + dim_offsets, mask=mask, other=0.0) |
|
|
| |
| |
| |
| |
| for t in range(T): |
| |
| x_t_ptr = X_ptr + batch_idx * stride_batch_x + t * stride_seq_x + dim_offsets * stride_dim_x |
| x_t = tl.load(x_t_ptr, mask=mask, other=0.0) |
|
|
| |
| |
| |
| pre_act = (w_L * z_L) + z_H + x_t |
| |
| |
| z_L = pre_act * tl.sigmoid(pre_act) |
|
|
| |
| |
| |
| |
| |
| pre_act_H = (w_H * z_H) + z_L |
| z_H_new = pre_act_H * tl.sigmoid(pre_act_H) |
|
|
| |
| |
| |
| |
| tl.store(Z_L_out_ptr + batch_idx * HIDDEN_SIZE + dim_offsets, z_L, mask=mask) |
| tl.store(Z_H_out_ptr + batch_idx * HIDDEN_SIZE + dim_offsets, z_H_new, mask=mask) |
|
|
|
|
| |
| |
| |
|
|
| def next_power_of_2(n: int) -> int: |
| """Returns the next power of 2 greater than or equal to n.""" |
| return 1 if n == 0 else 2**(n - 1).bit_length() |
|
|
| class FusedHierarchicalScanLayer(torch.nn.Module): |
| """ |
| PyTorch module that encapsulates the Tiered Memory Hierarchical Scan. |
| |
| This replaces the Python loop over T steps with the Triton kernel, |
| achieving the memory I/O reduction outlined in the paper. |
| """ |
| def __init__(self, hidden_size: int, T_steps: int): |
| super().__init__() |
| self.hidden_size = hidden_size |
| self.T = T_steps |
| |
| |
| self.w_L = torch.nn.Parameter(torch.randn(hidden_size) / math.sqrt(hidden_size)) |
| self.w_H = torch.nn.Parameter(torch.randn(hidden_size) / math.sqrt(hidden_size)) |
|
|
| def forward(self, x_chunk: torch.Tensor, z_L: torch.Tensor, z_H: torch.Tensor): |
| """ |
| Args: |
| x_chunk: Tensor of shape (batch, T, hidden_size) containing the inputs for this chunk. |
| z_L: Tensor of shape (batch, hidden_size) containing the initial L-state. |
| z_H: Tensor of shape (batch, hidden_size) containing the initial H-state. |
| |
| Returns: |
| z_L_new, z_H_new: The updated states after T steps. |
| """ |
| batch_size, seq_len, dim = x_chunk.shape |
| |
| assert seq_len == self.T, f"Expected chunk of size T={self.T}, got {seq_len}" |
| assert dim == self.hidden_size, "Dimension mismatch" |
| assert z_L.shape == (batch_size, self.hidden_size) |
| assert z_H.shape == (batch_size, self.hidden_size) |
| assert x_chunk.is_contiguous() |
|
|
| |
| z_L_out = torch.empty_like(z_L) |
| z_H_out = torch.empty_like(z_H) |
|
|
| |
| BLOCK_DIM = next_power_of_2(self.hidden_size) |
|
|
| |
| grid = (batch_size,) |
|
|
| |
| _fused_hierarchical_scan_kernel[grid]( |
| x_chunk, z_L, z_H, z_L_out, z_H_out, |
| self.w_L, self.w_H, |
| x_chunk.stride(0), x_chunk.stride(1), x_chunk.stride(2), |
| T=self.T, |
| HIDDEN_SIZE=self.hidden_size, |
| BLOCK_DIM=BLOCK_DIM |
| ) |
|
|
| return z_L_out, z_H_out |
|
|
| |
| if __name__ == "__main__": |
| batch = 32 |
| T = 8 |
| dim = 256 |
| |
| |
| scanner = FusedHierarchicalScanLayer(hidden_size=dim, T_steps=T).cuda() |
| |
| |
| x_chunk = torch.randn(batch, T, dim, device='cuda') |
| z_L_init = torch.randn(batch, dim, device='cuda') |
| z_H_init = torch.randn(batch, dim, device='cuda') |
| |
| |
| z_L_final, z_H_final = scanner(x_chunk, z_L_init, z_H_init) |
| |
| print(f"Successfully executed Tiered Memory Hierarchical Scan.") |
| print(f"z_L moved from {z_L_init.shape} -> {z_L_final.shape}") |
| print(f"z_H moved from {z_H_init.shape} -> {z_H_final.shape}") |
|
|