Instructions to use mkurman/convgpt-v2-b200-full-synth-20h with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mkurman/convgpt-v2-b200-full-synth-20h with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="mkurman/convgpt-v2-b200-full-synth-20h")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("mkurman/convgpt-v2-b200-full-synth-20h", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use mkurman/convgpt-v2-b200-full-synth-20h with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "mkurman/convgpt-v2-b200-full-synth-20h" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mkurman/convgpt-v2-b200-full-synth-20h", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/mkurman/convgpt-v2-b200-full-synth-20h
- SGLang
How to use mkurman/convgpt-v2-b200-full-synth-20h with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "mkurman/convgpt-v2-b200-full-synth-20h" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mkurman/convgpt-v2-b200-full-synth-20h", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "mkurman/convgpt-v2-b200-full-synth-20h" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "mkurman/convgpt-v2-b200-full-synth-20h", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use mkurman/convgpt-v2-b200-full-synth-20h with Docker Model Runner:
docker model run hf.co/mkurman/convgpt-v2-b200-full-synth-20h
| # coding=utf-8 | |
| """ConvGPT-v2: configurable hybrid causal 1D/2D ConvLM with tiny retrieval routers.""" | |
| from __future__ import annotations | |
| import math | |
| from functools import lru_cache | |
| from dataclasses import dataclass | |
| from typing import Optional, Tuple, Union | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from transformers import GenerationMixin | |
| from transformers.activations import ACT2FN | |
| from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast | |
| from transformers.modeling_utils import PreTrainedModel | |
| from transformers.utils import logging | |
| from .configuration_convgpt_v2 import ConvGPTV2Config | |
| logger = logging.get_logger(__name__) | |
| class ConvGPTV2PrefixCache: | |
| """Generation cache for ConvGPT-v2. | |
| Stores the accepted prefix plus per-layer activations needed to decode the | |
| next token without re-running every layer over the whole prefix. If an old | |
| two-tensor legacy cache is provided, the model falls back to the exact | |
| full-prefix path until a rich cache is produced again. | |
| """ | |
| input_ids: torch.LongTensor | |
| attention_mask: Optional[torch.Tensor] = None | |
| layer_inputs: Optional[Tuple[torch.Tensor, ...]] = None | |
| layer_post_conv: Optional[Tuple[torch.Tensor, ...]] = None | |
| def has_incremental_state(self) -> bool: | |
| return self.layer_inputs is not None and self.layer_post_conv is not None | |
| def device(self) -> torch.device: | |
| return self.input_ids.device | |
| def get_seq_length(self, layer_idx: int = 0) -> int: | |
| return int(self.input_ids.shape[1]) | |
| def get_max_cache_shape(self) -> Optional[int]: | |
| return None | |
| def reorder_cache(self, beam_idx: torch.LongTensor) -> "ConvGPTV2PrefixCache": | |
| self.input_ids = self.input_ids.index_select(0, beam_idx.to(self.input_ids.device)) | |
| if self.attention_mask is not None: | |
| self.attention_mask = self.attention_mask.index_select(0, beam_idx.to(self.attention_mask.device)) | |
| if self.layer_inputs is not None: | |
| self.layer_inputs = tuple( | |
| layer_state.index_select(0, beam_idx.to(layer_state.device)) | |
| for layer_state in self.layer_inputs | |
| ) | |
| if self.layer_post_conv is not None: | |
| self.layer_post_conv = tuple( | |
| layer_state.index_select(0, beam_idx.to(layer_state.device)) | |
| for layer_state in self.layer_post_conv | |
| ) | |
| return self | |
| def to_legacy_cache(self): | |
| return (self.input_ids, self.attention_mask) | |
| def from_legacy_cache(cls, cache) -> "ConvGPTV2PrefixCache": | |
| if isinstance(cache, cls): | |
| return cache | |
| if isinstance(cache, tuple) and len(cache) == 2 and torch.is_tensor(cache[0]): | |
| return cls(input_ids=cache[0], attention_mask=cache[1]) | |
| raise TypeError(f"Unsupported ConvGPT-v2 cache type: {type(cache)!r}") | |
| try: | |
| import triton | |
| import triton.language as tl | |
| _TRITON_AVAILABLE = True | |
| except Exception: | |
| triton = None | |
| tl = None | |
| _TRITON_AVAILABLE = False | |
| if _TRITON_AVAILABLE: | |
| def _depthwise_neighbor_gather_kernel( | |
| x_ptr, | |
| idx_ptr, | |
| mask_ptr, | |
| w_ptr, | |
| out_ptr, | |
| c_stride_x, | |
| p_stride_x, | |
| p_stride_idx, | |
| k_stride_idx, | |
| p_stride_mask, | |
| k_stride_mask, | |
| c_stride_w, | |
| k_stride_w, | |
| c_stride_out, | |
| p_stride_out, | |
| P, | |
| K, | |
| N_P_BLOCKS: tl.constexpr, | |
| BLOCK_P: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| ): | |
| linear_pid = tl.program_id(0) | |
| pid = linear_pid % N_P_BLOCKS | |
| bc = linear_pid // N_P_BLOCKS | |
| offs_p = pid * BLOCK_P + tl.arange(0, BLOCK_P) | |
| offs_k = tl.arange(0, BLOCK_K) | |
| p_mask = offs_p < P | |
| k_mask = offs_k < K | |
| mask_pk = p_mask[:, None] & k_mask[None, :] | |
| idx = tl.load(idx_ptr + offs_p[:, None] * p_stride_idx + offs_k[None, :] * k_stride_idx, mask=mask_pk, other=0) | |
| valid = tl.load(mask_ptr + offs_p[:, None] * p_stride_mask + offs_k[None, :] * k_stride_mask, mask=mask_pk, other=0).to(tl.int1) | |
| x = tl.load(x_ptr + bc * c_stride_x + idx * p_stride_x, mask=mask_pk & valid, other=0.0) | |
| w = tl.load(w_ptr + bc * c_stride_w + offs_k * k_stride_w, mask=k_mask, other=0.0) | |
| acc = tl.sum(x * w[None, :], axis=1) | |
| tl.store(out_ptr + bc * c_stride_out + offs_p * p_stride_out, acc, mask=p_mask) | |
| def _chunk_token_score_forward_kernel( | |
| q_ptr, | |
| k_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| out_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| C: tl.constexpr, | |
| BLOCK_C: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| offs_c = tl.arange(0, BLOCK_C) | |
| c_mask = offs_c < C | |
| b = row // T | |
| t = row - b * T | |
| q_base = (b * T + t) * D | |
| cand_pos = tl.load(pos_ptr + row * C + offs_c, mask=c_mask, other=0) | |
| cand_valid = tl.load(valid_ptr + row * C + offs_c, mask=c_mask, other=0).to(tl.int1) | |
| acc = tl.zeros((BLOCK_C,), tl.float32) | |
| for d0 in range(0, D, BLOCK_D): | |
| offs_d = d0 + tl.arange(0, BLOCK_D) | |
| d_mask = offs_d < D | |
| q = tl.load(q_ptr + q_base + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| k = tl.load( | |
| k_ptr + (b * T + cand_pos[:, None]) * D + offs_d[None, :], | |
| mask=c_mask[:, None] & cand_valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| acc += tl.sum(k * q[None, :], axis=1) | |
| score = acc * tl.rsqrt(tl.full((), D, tl.float32)) | |
| neg_inf = tl.full((BLOCK_C,), -3.4028234663852886e38, tl.float32) | |
| score = tl.where(c_mask & cand_valid, score, neg_inf) | |
| tl.store(out_ptr + row * C + offs_c, score, mask=c_mask) | |
| def _chunk_token_score_grad_q_kernel( | |
| grad_scores_ptr, | |
| k_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| grad_q_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| C: tl.constexpr, | |
| BLOCK_C: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| d_block = tl.program_id(1) | |
| offs_c = tl.arange(0, BLOCK_C) | |
| offs_d = d_block * BLOCK_D + tl.arange(0, BLOCK_D) | |
| c_mask = offs_c < C | |
| d_mask = offs_d < D | |
| b = row // T | |
| cand_pos = tl.load(pos_ptr + row * C + offs_c, mask=c_mask, other=0) | |
| cand_valid = tl.load(valid_ptr + row * C + offs_c, mask=c_mask, other=0).to(tl.int1) | |
| grad_scores = tl.load(grad_scores_ptr + row * C + offs_c, mask=c_mask & cand_valid, other=0.0).to(tl.float32) | |
| k = tl.load( | |
| k_ptr + (b * T + cand_pos[:, None]) * D + offs_d[None, :], | |
| mask=c_mask[:, None] & cand_valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| grad_q = tl.sum(k * grad_scores[:, None], axis=0) * tl.rsqrt(tl.full((), D, tl.float32)) | |
| tl.store(grad_q_ptr + row * D + offs_d, grad_q, mask=d_mask) | |
| def _chunk_token_score_grad_k_kernel( | |
| grad_scores_ptr, | |
| q_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| grad_k_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| C: tl.constexpr, | |
| BLOCK_C: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| c_block = tl.program_id(1) | |
| d_block = tl.program_id(2) | |
| offs_c = c_block * BLOCK_C + tl.arange(0, BLOCK_C) | |
| offs_d = d_block * BLOCK_D + tl.arange(0, BLOCK_D) | |
| c_mask = offs_c < C | |
| d_mask = offs_d < D | |
| b = row // T | |
| cand_pos = tl.load(pos_ptr + row * C + offs_c, mask=c_mask, other=0) | |
| cand_valid = tl.load(valid_ptr + row * C + offs_c, mask=c_mask, other=0).to(tl.int1) | |
| grad_scores = tl.load(grad_scores_ptr + row * C + offs_c, mask=c_mask & cand_valid, other=0.0).to(tl.float32) | |
| q = tl.load(q_ptr + row * D + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| grad = grad_scores[:, None] * q[None, :] * tl.rsqrt(tl.full((), D, tl.float32)) | |
| tl.atomic_add( | |
| grad_k_ptr + (b * T + cand_pos[:, None]) * D + offs_d[None, :], | |
| grad, | |
| sem="relaxed", | |
| mask=c_mask[:, None] & cand_valid[:, None] & d_mask[None, :], | |
| ) | |
| def _weighted_value_forward_kernel( | |
| weights_ptr, | |
| v_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| out_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| K: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| d_block = tl.program_id(1) | |
| offs_k = tl.arange(0, BLOCK_K) | |
| offs_d = d_block * BLOCK_D + tl.arange(0, BLOCK_D) | |
| k_mask = offs_k < K | |
| d_mask = offs_d < D | |
| b = row // T | |
| pos = tl.load(pos_ptr + row * K + offs_k, mask=k_mask, other=0) | |
| valid = tl.load(valid_ptr + row * K + offs_k, mask=k_mask, other=0).to(tl.int1) | |
| weights = tl.load(weights_ptr + row * K + offs_k, mask=k_mask & valid, other=0.0).to(tl.float32) | |
| v = tl.load( | |
| v_ptr + (b * T + pos[:, None]) * D + offs_d[None, :], | |
| mask=k_mask[:, None] & valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| out = tl.sum(v * weights[:, None], axis=0) | |
| tl.store(out_ptr + row * D + offs_d, out, mask=d_mask) | |
| def _weighted_value_grad_v_kernel( | |
| grad_out_ptr, | |
| weights_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| grad_v_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| K: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| k_block = tl.program_id(1) | |
| d_block = tl.program_id(2) | |
| offs_k = k_block * BLOCK_K + tl.arange(0, BLOCK_K) | |
| offs_d = d_block * BLOCK_D + tl.arange(0, BLOCK_D) | |
| k_mask = offs_k < K | |
| d_mask = offs_d < D | |
| b = row // T | |
| pos = tl.load(pos_ptr + row * K + offs_k, mask=k_mask, other=0) | |
| valid = tl.load(valid_ptr + row * K + offs_k, mask=k_mask, other=0).to(tl.int1) | |
| weights = tl.load(weights_ptr + row * K + offs_k, mask=k_mask & valid, other=0.0).to(tl.float32) | |
| grad_out = tl.load(grad_out_ptr + row * D + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| grad = weights[:, None] * grad_out[None, :] | |
| tl.atomic_add( | |
| grad_v_ptr + (b * T + pos[:, None]) * D + offs_d[None, :], | |
| grad, | |
| sem="relaxed", | |
| mask=k_mask[:, None] & valid[:, None] & d_mask[None, :], | |
| ) | |
| def _weighted_value_grad_weights_kernel( | |
| grad_out_ptr, | |
| v_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| grad_weights_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| K: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| offs_k = tl.arange(0, BLOCK_K) | |
| k_mask = offs_k < K | |
| b = row // T | |
| pos = tl.load(pos_ptr + row * K + offs_k, mask=k_mask, other=0) | |
| valid = tl.load(valid_ptr + row * K + offs_k, mask=k_mask, other=0).to(tl.int1) | |
| acc = tl.zeros((BLOCK_K,), tl.float32) | |
| for d0 in range(0, D, BLOCK_D): | |
| offs_d = d0 + tl.arange(0, BLOCK_D) | |
| d_mask = offs_d < D | |
| grad_out = tl.load(grad_out_ptr + row * D + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| v = tl.load( | |
| v_ptr + (b * T + pos[:, None]) * D + offs_d[None, :], | |
| mask=k_mask[:, None] & valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| acc += tl.sum(v * grad_out[None, :], axis=1) | |
| tl.store(grad_weights_ptr + row * K + offs_k, acc, mask=k_mask) | |
| # Experimental fused chunk+token selector retained for future tuning. | |
| # Disabled in router dispatch because the first benchmark was not faster | |
| # than chunk PyTorch top-k + fused token top-k. | |
| def _chunk2_token_select_forward_kernel( | |
| q_ptr, | |
| chunk_k_ptr, | |
| token_k_ptr, | |
| top_scores_ptr, | |
| top_pos_ptr, | |
| top_valid_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| NUM_CHUNKS: tl.constexpr, | |
| CHUNK_SIZE: tl.constexpr, | |
| KTOP: tl.constexpr, | |
| INCLUDE_CURRENT: tl.constexpr, | |
| BLOCK_CHUNKS: tl.constexpr, | |
| BLOCK_CAND: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| b = row // T | |
| t = row - b * T | |
| q_base = (b * T + t) * D | |
| offs_chunks = tl.arange(0, BLOCK_CHUNKS) | |
| chunk_mask = offs_chunks < NUM_CHUNKS | |
| token_chunk = t // CHUNK_SIZE | |
| if INCLUDE_CURRENT: | |
| allowed = offs_chunks <= token_chunk | |
| else: | |
| allowed = offs_chunks < token_chunk | |
| chunk_mask = chunk_mask & allowed & (offs_chunks * CHUNK_SIZE < T) | |
| chunk_scores = tl.zeros((BLOCK_CHUNKS,), tl.float32) | |
| for d0 in range(0, D, BLOCK_D): | |
| offs_d = d0 + tl.arange(0, BLOCK_D) | |
| d_mask = offs_d < D | |
| q = tl.load(q_ptr + q_base + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| ck = tl.load( | |
| chunk_k_ptr + (b * NUM_CHUNKS + offs_chunks[:, None]) * D + offs_d[None, :], | |
| mask=chunk_mask[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| chunk_scores += tl.sum(ck * q[None, :], axis=1) | |
| neg_inf_chunks = tl.full((BLOCK_CHUNKS,), -3.4028234663852886e38, tl.float32) | |
| chunk_scores = tl.where(chunk_mask, chunk_scores * tl.rsqrt(tl.full((), D, tl.float32)), neg_inf_chunks) | |
| chunk0 = tl.argmax(chunk_scores, axis=0, tie_break_left=True) | |
| score0 = tl.max(chunk_scores, axis=0) | |
| chunk_scores = tl.where(offs_chunks == chunk0, neg_inf_chunks, chunk_scores) | |
| chunk1 = tl.argmax(chunk_scores, axis=0, tie_break_left=True) | |
| score1 = tl.max(chunk_scores, axis=0) | |
| valid0 = score0 > -3.0e38 | |
| valid1 = score1 > -3.0e38 | |
| offs_c = tl.arange(0, BLOCK_CAND) | |
| c_mask = offs_c < (CHUNK_SIZE * 2) | |
| in_first = offs_c < CHUNK_SIZE | |
| cand_chunk = tl.where(in_first, chunk0, chunk1) | |
| cand_valid_chunk = tl.where(in_first, valid0, valid1) | |
| cand_offset = tl.where(in_first, offs_c, offs_c - CHUNK_SIZE) | |
| cand_pos = cand_chunk * CHUNK_SIZE + cand_offset | |
| cand_valid = c_mask & cand_valid_chunk & (cand_pos < T) | |
| if not INCLUDE_CURRENT: | |
| cand_valid = cand_valid & (cand_pos < t) | |
| token_scores = tl.zeros((BLOCK_CAND,), tl.float32) | |
| for d0 in range(0, D, BLOCK_D): | |
| offs_d = d0 + tl.arange(0, BLOCK_D) | |
| d_mask = offs_d < D | |
| q = tl.load(q_ptr + q_base + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| tk = tl.load( | |
| token_k_ptr + (b * T + cand_pos[:, None]) * D + offs_d[None, :], | |
| mask=cand_valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| token_scores += tl.sum(tk * q[None, :], axis=1) | |
| neg_inf = tl.full((BLOCK_CAND,), -3.4028234663852886e38, tl.float32) | |
| token_scores = tl.where(cand_valid, token_scores * tl.rsqrt(tl.full((), D, tl.float32)), neg_inf) | |
| for i in tl.static_range(0, KTOP): | |
| max_val = tl.max(token_scores, axis=0) | |
| max_idx = tl.argmax(token_scores, axis=0, tie_break_left=True) | |
| is_valid = max_val > -3.0e38 | |
| selected_pos = tl.sum(tl.where(offs_c == max_idx, cand_pos, 0), axis=0) | |
| selected_pos = tl.where(is_valid, selected_pos, 0) | |
| tl.store(top_scores_ptr + row * KTOP + i, max_val, mask=True) | |
| tl.store(top_pos_ptr + row * KTOP + i, selected_pos, mask=True) | |
| tl.store(top_valid_ptr + row * KTOP + i, is_valid, mask=True) | |
| token_scores = tl.where(offs_c == max_idx, neg_inf, token_scores) | |
| def _chunk_token_select_forward_kernel( | |
| q_ptr, | |
| k_ptr, | |
| pos_ptr, | |
| valid_ptr, | |
| top_scores_ptr, | |
| top_pos_ptr, | |
| top_valid_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| C: tl.constexpr, | |
| KTOP: tl.constexpr, | |
| BLOCK_C: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| offs_c = tl.arange(0, BLOCK_C) | |
| c_mask = offs_c < C | |
| b = row // T | |
| t = row - b * T | |
| q_base = (b * T + t) * D | |
| cand_pos = tl.load(pos_ptr + row * C + offs_c, mask=c_mask, other=0) | |
| cand_valid = tl.load(valid_ptr + row * C + offs_c, mask=c_mask, other=0).to(tl.int1) | |
| scores = tl.zeros((BLOCK_C,), tl.float32) | |
| for d0 in range(0, D, BLOCK_D): | |
| offs_d = d0 + tl.arange(0, BLOCK_D) | |
| d_mask = offs_d < D | |
| q = tl.load(q_ptr + q_base + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| k = tl.load( | |
| k_ptr + (b * T + cand_pos[:, None]) * D + offs_d[None, :], | |
| mask=c_mask[:, None] & cand_valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| scores += tl.sum(k * q[None, :], axis=1) | |
| neg_inf = tl.full((BLOCK_C,), -3.4028234663852886e38, tl.float32) | |
| scores = tl.where(c_mask & cand_valid, scores * tl.rsqrt(tl.full((), D, tl.float32)), neg_inf) | |
| for i in tl.static_range(0, KTOP): | |
| max_val = tl.max(scores, axis=0) | |
| max_idx = tl.argmax(scores, axis=0, tie_break_left=True) | |
| is_valid = max_val > -3.0e38 | |
| selected_pos = tl.load(pos_ptr + row * C + max_idx, mask=is_valid, other=0) | |
| tl.store(top_scores_ptr + row * KTOP + i, max_val, mask=True) | |
| tl.store(top_pos_ptr + row * KTOP + i, selected_pos, mask=True) | |
| tl.store(top_valid_ptr + row * KTOP + i, is_valid, mask=True) | |
| scores = tl.where(offs_c == max_idx, neg_inf, scores) | |
| def _chunk_token_select_grad_kernel( | |
| grad_scores_ptr, | |
| q_ptr, | |
| k_ptr, | |
| top_pos_ptr, | |
| top_valid_ptr, | |
| grad_q_ptr, | |
| grad_k_ptr, | |
| T: tl.constexpr, | |
| D: tl.constexpr, | |
| KTOP: tl.constexpr, | |
| BLOCK_K: tl.constexpr, | |
| BLOCK_D: tl.constexpr, | |
| ): | |
| row = tl.program_id(0) | |
| d_block = tl.program_id(1) | |
| offs_k = tl.arange(0, BLOCK_K) | |
| offs_d = d_block * BLOCK_D + tl.arange(0, BLOCK_D) | |
| k_mask = offs_k < KTOP | |
| d_mask = offs_d < D | |
| b = row // T | |
| pos = tl.load(top_pos_ptr + row * KTOP + offs_k, mask=k_mask, other=0) | |
| valid = tl.load(top_valid_ptr + row * KTOP + offs_k, mask=k_mask, other=0).to(tl.int1) | |
| grad_scores = tl.load(grad_scores_ptr + row * KTOP + offs_k, mask=k_mask & valid, other=0.0).to(tl.float32) | |
| q = tl.load(q_ptr + row * D + offs_d, mask=d_mask, other=0.0).to(tl.float32) | |
| k = tl.load( | |
| k_ptr + (b * T + pos[:, None]) * D + offs_d[None, :], | |
| mask=k_mask[:, None] & valid[:, None] & d_mask[None, :], | |
| other=0.0, | |
| ).to(tl.float32) | |
| scale = tl.rsqrt(tl.full((), D, tl.float32)) | |
| grad_q = tl.sum(k * grad_scores[:, None], axis=0) * scale | |
| tl.store(grad_q_ptr + row * D + offs_d, grad_q, mask=d_mask) | |
| grad_k = grad_scores[:, None] * q[None, :] * scale | |
| tl.atomic_add( | |
| grad_k_ptr + (b * T + pos[:, None]) * D + offs_d[None, :], | |
| grad_k, | |
| sem="relaxed", | |
| mask=k_mask[:, None] & valid[:, None] & d_mask[None, :], | |
| ) | |
| def _ceil_pow2(n: int) -> int: | |
| return 1 << (n - 1).bit_length() | |
| def _rotate_half(x: torch.Tensor) -> torch.Tensor: | |
| x1 = x[..., : x.shape[-1] // 2] | |
| x2 = x[..., x.shape[-1] // 2 :] | |
| return torch.cat((-x2, x1), dim=-1) | |
| class ConvGPTV2RotaryEmbedding(nn.Module): | |
| """Simple 1D RoPE cache for router query/key projections.""" | |
| def __init__(self, dim: int, theta: float = 10000.0): | |
| super().__init__() | |
| if dim % 2 != 0: | |
| dim -= 1 | |
| self.dim = max(0, int(dim)) | |
| self.theta = float(theta) | |
| if self.dim > 0: | |
| inv_freq = 1.0 / (self.theta ** (torch.arange(0, self.dim, 2, dtype=torch.float32) / self.dim)) | |
| else: | |
| inv_freq = torch.empty(0, dtype=torch.float32) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| def forward(self, position_ids: torch.Tensor, dtype: torch.dtype, device: torch.device) -> tuple[torch.Tensor, torch.Tensor]: | |
| if self.dim == 0: | |
| empty = torch.empty((*position_ids.shape, 0), device=device, dtype=dtype) | |
| return empty, empty | |
| inv_freq = self.inv_freq.to(device=device) | |
| pos = position_ids.to(device=device, dtype=torch.float32) | |
| with torch.autocast(device_type=device.type if device.type != "mps" else "cpu", enabled=False): | |
| freqs = torch.einsum("bt,d->btd", pos, inv_freq.float()) | |
| emb = torch.cat((freqs, freqs), dim=-1) | |
| cos = emb.cos() | |
| sin = emb.sin() | |
| return cos.to(dtype=dtype), sin.to(dtype=dtype) | |
| def _apply_rope_to_btd(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| rope_dim = cos.shape[-1] | |
| if rope_dim == 0: | |
| return x | |
| x_rope = x[..., :rope_dim] | |
| x_pass = x[..., rope_dim:] | |
| x_rot = (x_rope * cos) + (_rotate_half(x_rope) * sin) | |
| return torch.cat((x_rot, x_pass), dim=-1) if x_pass.shape[-1] else x_rot | |
| def _token_position_ids( | |
| position_ids: Optional[torch.LongTensor], | |
| bsz: int, | |
| seq_len: int, | |
| device: torch.device, | |
| ) -> torch.LongTensor: | |
| if position_ids is not None: | |
| return position_ids.to(device=device, dtype=torch.long) | |
| return torch.arange(seq_len, device=device, dtype=torch.long).view(1, seq_len).expand(bsz, -1) | |
| def _chunk_start_position_ids(token_pos: torch.LongTensor, chunk_size: int, num_chunks: int) -> torch.LongTensor: | |
| seq_len = token_pos.shape[1] | |
| starts = (torch.arange(num_chunks, device=token_pos.device, dtype=torch.long) * chunk_size).clamp(max=max(seq_len - 1, 0)) | |
| return token_pos.index_select(1, starts) | |
| class _ChunkTokenScoreFunction(torch.autograd.Function): | |
| def forward(ctx, q: torch.Tensor, token_k: torch.Tensor, flat_pos: torch.Tensor, flat_valid: torch.Tensor) -> torch.Tensor: | |
| bsz, seq_len, dim = q.shape | |
| cand = flat_pos.shape[-1] | |
| q_c = q.contiguous() | |
| k_c = token_k.contiguous() | |
| pos_c = flat_pos.contiguous() | |
| valid_c = flat_valid.contiguous() | |
| out = torch.empty((bsz, seq_len, cand), device=q.device, dtype=torch.float32) | |
| block_c = _ceil_pow2(cand) | |
| block_d = 64 | |
| _chunk_token_score_forward_kernel[(bsz * seq_len,)]( | |
| q_c, k_c, pos_c, valid_c, out, | |
| seq_len, dim, cand, | |
| BLOCK_C=block_c, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| ctx.save_for_backward(q_c, k_c, pos_c, valid_c) | |
| return out | |
| def backward(ctx, grad_out: torch.Tensor): | |
| q, token_k, flat_pos, flat_valid = ctx.saved_tensors | |
| bsz, seq_len, dim = q.shape | |
| cand = flat_pos.shape[-1] | |
| grad_scores = grad_out.contiguous() | |
| grad_q = torch.empty(q.shape, device=q.device, dtype=torch.float32) | |
| grad_k = torch.zeros(token_k.shape, device=token_k.device, dtype=torch.float32) | |
| block_c_q = _ceil_pow2(cand) | |
| block_c_k = min(32, _ceil_pow2(cand)) | |
| block_d = 64 | |
| _chunk_token_score_grad_q_kernel[(bsz * seq_len, triton.cdiv(dim, block_d))]( | |
| grad_scores, token_k, flat_pos, flat_valid, grad_q, | |
| seq_len, dim, cand, | |
| BLOCK_C=block_c_q, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| _chunk_token_score_grad_k_kernel[(bsz * seq_len, triton.cdiv(cand, block_c_k), triton.cdiv(dim, block_d))]( | |
| grad_scores, q, flat_pos, flat_valid, grad_k, | |
| seq_len, dim, cand, | |
| BLOCK_C=block_c_k, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| return grad_q, grad_k, None, None | |
| class _Chunk2TokenSelectFunction(torch.autograd.Function): | |
| def forward( | |
| ctx, | |
| q: torch.Tensor, | |
| chunk_k: torch.Tensor, | |
| token_k: torch.Tensor, | |
| k_top: int, | |
| chunk_size: int, | |
| include_current_chunk: bool, | |
| ): | |
| bsz, seq_len, dim = q.shape | |
| num_chunks = chunk_k.shape[1] | |
| q_c = q.contiguous() | |
| chunk_k_c = chunk_k.contiguous() | |
| token_k_c = token_k.contiguous() | |
| top_scores = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.float32) | |
| top_pos = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.long) | |
| top_valid = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.bool) | |
| _chunk2_token_select_forward_kernel[(bsz * seq_len,)]( | |
| q_c, chunk_k_c, token_k_c, top_scores, top_pos, top_valid, | |
| seq_len, dim, num_chunks, chunk_size, int(k_top), bool(include_current_chunk), | |
| BLOCK_CHUNKS=_ceil_pow2(num_chunks), BLOCK_CAND=_ceil_pow2(chunk_size * 2), BLOCK_D=64, | |
| num_warps=4, | |
| ) | |
| ctx.save_for_backward(q_c, token_k_c, top_pos, top_valid) | |
| ctx.k_top = int(k_top) | |
| return top_scores, top_pos, top_valid | |
| def backward(ctx, grad_top_scores: torch.Tensor, grad_top_pos=None, grad_top_valid=None): | |
| q, token_k, top_pos, top_valid = ctx.saved_tensors | |
| bsz, seq_len, dim = q.shape | |
| k_top = ctx.k_top | |
| grad_scores = grad_top_scores.contiguous() | |
| grad_q = torch.empty(q.shape, device=q.device, dtype=torch.float32) | |
| grad_k = torch.zeros(token_k.shape, device=token_k.device, dtype=torch.float32) | |
| _chunk_token_select_grad_kernel[(bsz * seq_len, triton.cdiv(dim, 64))]( | |
| grad_scores, q, token_k, top_pos, top_valid, grad_q, grad_k, | |
| seq_len, dim, k_top, | |
| BLOCK_K=_ceil_pow2(k_top), BLOCK_D=64, | |
| num_warps=4, | |
| ) | |
| return grad_q, None, grad_k, None, None, None | |
| class _ChunkTokenSelectFunction(torch.autograd.Function): | |
| def forward(ctx, q: torch.Tensor, token_k: torch.Tensor, flat_pos: torch.Tensor, flat_valid: torch.Tensor, k_top: int): | |
| bsz, seq_len, dim = q.shape | |
| cand = flat_pos.shape[-1] | |
| q_c = q.contiguous() | |
| k_c = token_k.contiguous() | |
| pos_c = flat_pos.contiguous() | |
| valid_c = flat_valid.contiguous() | |
| top_scores = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.float32) | |
| top_pos = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.long) | |
| top_valid = torch.empty((bsz, seq_len, k_top), device=q.device, dtype=torch.bool) | |
| _chunk_token_select_forward_kernel[(bsz * seq_len,)]( | |
| q_c, k_c, pos_c, valid_c, top_scores, top_pos, top_valid, | |
| seq_len, dim, cand, k_top, | |
| BLOCK_C=_ceil_pow2(cand), BLOCK_D=64, | |
| num_warps=4, | |
| ) | |
| ctx.save_for_backward(q_c, k_c, top_pos, top_valid) | |
| ctx.k_top = k_top | |
| return top_scores, top_pos, top_valid | |
| def backward(ctx, grad_top_scores: torch.Tensor, grad_top_pos=None, grad_top_valid=None): | |
| q, token_k, top_pos, top_valid = ctx.saved_tensors | |
| bsz, seq_len, dim = q.shape | |
| k_top = ctx.k_top | |
| grad_scores = grad_top_scores.contiguous() | |
| grad_q = torch.empty(q.shape, device=q.device, dtype=torch.float32) | |
| grad_k = torch.zeros(token_k.shape, device=token_k.device, dtype=torch.float32) | |
| _chunk_token_select_grad_kernel[(bsz * seq_len, triton.cdiv(dim, 64))]( | |
| grad_scores, q, token_k, top_pos, top_valid, grad_q, grad_k, | |
| seq_len, dim, k_top, | |
| BLOCK_K=_ceil_pow2(k_top), BLOCK_D=64, | |
| num_warps=4, | |
| ) | |
| return grad_q, grad_k, None, None, None | |
| class _WeightedValueFunction(torch.autograd.Function): | |
| def forward(ctx, weights: torch.Tensor, token_v: torch.Tensor, top_pos: torch.Tensor, top_valid: torch.Tensor) -> torch.Tensor: | |
| bsz, seq_len, dim = token_v.shape | |
| k_top = top_pos.shape[-1] | |
| weights_c = weights.contiguous() | |
| v_c = token_v.contiguous() | |
| pos_c = top_pos.contiguous() | |
| valid_c = top_valid.contiguous() | |
| out = torch.empty((bsz, seq_len, dim), device=token_v.device, dtype=token_v.dtype) | |
| block_k = _ceil_pow2(k_top) | |
| block_d = 64 | |
| _weighted_value_forward_kernel[(bsz * seq_len, triton.cdiv(dim, block_d))]( | |
| weights_c, v_c, pos_c, valid_c, out, | |
| seq_len, dim, k_top, | |
| BLOCK_K=block_k, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| ctx.save_for_backward(weights_c, v_c, pos_c, valid_c) | |
| return out | |
| def backward(ctx, grad_out: torch.Tensor): | |
| weights, token_v, top_pos, top_valid = ctx.saved_tensors | |
| bsz, seq_len, dim = token_v.shape | |
| k_top = top_pos.shape[-1] | |
| grad_out_c = grad_out.contiguous() | |
| grad_weights = torch.empty(weights.shape, device=weights.device, dtype=torch.float32) | |
| grad_v = torch.zeros(token_v.shape, device=token_v.device, dtype=torch.float32) | |
| block_k_full = _ceil_pow2(k_top) | |
| block_k_v = min(32, _ceil_pow2(k_top)) | |
| block_d = 64 | |
| _weighted_value_grad_weights_kernel[(bsz * seq_len,)]( | |
| grad_out_c, token_v, top_pos, top_valid, grad_weights, | |
| seq_len, dim, k_top, | |
| BLOCK_K=block_k_full, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| _weighted_value_grad_v_kernel[(bsz * seq_len, triton.cdiv(k_top, block_k_v), triton.cdiv(dim, block_d))]( | |
| grad_out_c, weights, top_pos, top_valid, grad_v, | |
| seq_len, dim, k_top, | |
| BLOCK_K=block_k_v, BLOCK_D=block_d, | |
| num_warps=4, | |
| ) | |
| return grad_weights, grad_v, None, None | |
| def _can_use_chunk_token_triton(q: torch.Tensor, token_k: torch.Tensor, token_v: torch.Tensor, flat_pos: torch.Tensor) -> bool: | |
| if not _TRITON_AVAILABLE or q.device.type != "cuda": | |
| return False | |
| if q.dtype not in {torch.float16, torch.bfloat16, torch.float32}: | |
| return False | |
| if token_k.dtype != q.dtype or token_v.dtype != q.dtype: | |
| return False | |
| dim = q.shape[-1] | |
| cand = flat_pos.shape[-1] | |
| return dim <= 1024 and cand <= 256 | |
| def _chunk_token_scores(q: torch.Tensor, token_k: torch.Tensor, flat_pos: torch.Tensor, flat_valid: torch.Tensor) -> torch.Tensor: | |
| if _can_use_chunk_token_triton(q, token_k, token_k, flat_pos): | |
| return _ChunkTokenScoreFunction.apply(q, token_k, flat_pos, flat_valid) | |
| dim = q.shape[-1] | |
| bsz, seq_len, _ = q.shape | |
| cand = flat_pos.shape[-1] | |
| gather_idx = flat_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_k = token_k.gather(1, gather_idx).view(bsz, seq_len, cand, dim) | |
| token_scores = (q.unsqueeze(-2) * cand_k).sum(dim=-1) / math.sqrt(dim) | |
| return token_scores.masked_fill(~flat_valid, torch.finfo(token_scores.dtype).min) | |
| def _select_chunk2_token_scores( | |
| q: torch.Tensor, | |
| chunk_k: torch.Tensor, | |
| token_k: torch.Tensor, | |
| k_top: int, | |
| chunk_size: int, | |
| include_current_chunk: bool, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| if ( | |
| _TRITON_AVAILABLE | |
| and q.device.type == "cuda" | |
| and q.dtype in {torch.float16, torch.bfloat16, torch.float32} | |
| and chunk_k.dtype == q.dtype | |
| and token_k.dtype == q.dtype | |
| and q.shape[-1] <= 1024 | |
| and chunk_size * 2 <= 256 | |
| ): | |
| return _Chunk2TokenSelectFunction.apply(q, chunk_k, token_k, int(k_top), int(chunk_size), bool(include_current_chunk)) | |
| raise RuntimeError("chunk2 token Triton selector called outside supported envelope") | |
| def _select_chunk_token_scores( | |
| q: torch.Tensor, | |
| token_k: torch.Tensor, | |
| flat_pos: torch.Tensor, | |
| flat_valid: torch.Tensor, | |
| k_top: int, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| if _can_use_chunk_token_triton(q, token_k, token_k, flat_pos): | |
| return _ChunkTokenSelectFunction.apply(q, token_k, flat_pos, flat_valid, int(k_top)) | |
| token_scores = _chunk_token_scores(q, token_k, flat_pos, flat_valid) | |
| safe_token_scores = torch.where(flat_valid.any(dim=-1, keepdim=True), token_scores, torch.zeros_like(token_scores)) | |
| top_scores, top_idx = safe_token_scores.topk(k_top, dim=-1) | |
| top_valid = flat_valid.gather(-1, top_idx) | |
| top_pos = flat_pos.gather(-1, top_idx) | |
| top_scores = top_scores.masked_fill(~top_valid, torch.finfo(top_scores.dtype).min) | |
| return top_scores, top_pos, top_valid | |
| def _weighted_token_values(weights: torch.Tensor, token_v: torch.Tensor, top_pos: torch.Tensor, top_valid: torch.Tensor) -> torch.Tensor: | |
| if _can_use_chunk_token_triton(token_v, token_v, token_v, top_pos): | |
| return _WeightedValueFunction.apply(weights, token_v, top_pos, top_valid) | |
| dim = token_v.shape[-1] | |
| bsz, seq_len, _ = token_v.shape | |
| k_top = top_pos.shape[-1] | |
| gather_idx = top_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_v = token_v.gather(1, gather_idx).view(bsz, seq_len, k_top, dim) | |
| return (weights.unsqueeze(-1) * cand_v).sum(dim=-2) | |
| def _part1by1(n: int) -> int: | |
| n &= 0x0000FFFF | |
| n = (n | (n << 8)) & 0x00FF00FF | |
| n = (n | (n << 4)) & 0x0F0F0F0F | |
| n = (n | (n << 2)) & 0x33333333 | |
| n = (n | (n << 1)) & 0x55555555 | |
| return n | |
| def _morton_code(row: int, col: int) -> int: | |
| # Interleave col as x bits and row as y bits. | |
| return _part1by1(col) | (_part1by1(row) << 1) | |
| def _hilbert_d2xy(order: int, d: int) -> Tuple[int, int]: | |
| """Convert Hilbert distance to x/y for a power-of-two square.""" | |
| x = y = 0 | |
| t = d | |
| s = 1 | |
| while s < order: | |
| rx = 1 & (t // 2) | |
| ry = 1 & (t ^ rx) | |
| if ry == 0: | |
| if rx == 1: | |
| x = s - 1 - x | |
| y = s - 1 - y | |
| x, y = y, x | |
| x += s * rx | |
| y += s * ry | |
| t //= 4 | |
| s *= 2 | |
| return x, y | |
| def _build_curve_indices_cached(grid_size: int, packing: str, pack_order: str = "sequence_to_curve") -> Tuple[torch.Tensor, torch.Tensor]: | |
| """ | |
| Return (seq_to_grid, grid_to_seq) flattened indices for a square grid. | |
| seq_to_grid[pos] is the flat grid cell where token position `pos` should be | |
| placed. grid_to_seq[cell] is the source token position for that flat grid | |
| cell. These are inverse permutations. | |
| """ | |
| packing = packing.lower().replace("-", "_") | |
| if packing in {"z_order", "zorder"}: | |
| packing = "morton" | |
| n = grid_size | |
| total = n * n | |
| if packing == "row_major": | |
| cells = list(range(total)) | |
| elif packing == "snake": | |
| cells = [] | |
| for r in range(n): | |
| cols = range(n) if r % 2 == 0 else range(n - 1, -1, -1) | |
| cells.extend(r * n + c for c in cols) | |
| elif packing == "morton": | |
| cells = sorted(range(total), key=lambda idx: _morton_code(idx // n, idx % n)) | |
| elif packing == "hilbert": | |
| order = _ceil_pow2(n) | |
| cells = [] | |
| # For non-power-of-two grid sizes, walk a power-of-two Hilbert curve and | |
| # keep only coordinates inside the requested square. | |
| for d in range(order * order): | |
| x, y = _hilbert_d2xy(order, d) | |
| if x < n and y < n: | |
| cells.append(y * n + x) | |
| if len(cells) == total: | |
| break | |
| else: | |
| raise ValueError(f"Unsupported packing: {packing}") | |
| if pack_order == "curve_to_sequence": | |
| # Interpret the curve as an ordering over grid cells, but fill normal | |
| # row-major cells with sequence positions from that curve. This is useful | |
| # as an ablation for whether curve locality or physical placement matters. | |
| grid_to_seq = torch.tensor(cells, dtype=torch.long) | |
| seq_to_grid = torch.empty_like(grid_to_seq) | |
| seq_to_grid[grid_to_seq] = torch.arange(total, dtype=torch.long) | |
| else: | |
| seq_to_grid = torch.tensor(cells, dtype=torch.long) | |
| grid_to_seq = torch.empty_like(seq_to_grid) | |
| grid_to_seq[seq_to_grid] = torch.arange(total, dtype=torch.long) | |
| return seq_to_grid, grid_to_seq | |
| def build_curve_indices(grid_size: int, packing: str, pack_order: str = "sequence_to_curve") -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Return cloned curve-index tensors, backed by a process-local cache. | |
| A 32-layer 256x256 Hilbert ConvGPT-v2 previously rebuilt the same Hilbert | |
| permutation dozens of times during model construction. Cache the immutable | |
| CPU tensors and hand modules clones so buffer registration stays isolated. | |
| """ | |
| seq_to_grid, grid_to_seq = _build_curve_indices_cached(grid_size, packing, pack_order) | |
| return seq_to_grid.clone(), grid_to_seq.clone() | |
| def _build_conv2d_causal_tables_cached( | |
| grid_size: int, | |
| kernel_size: int, | |
| dilation: int, | |
| packing: str, | |
| pack_order: str, | |
| ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Vectorized cached causal 2D lookup tables. | |
| Model construction used to rebuild these tables with Python loops once per | |
| ConvGPT-v2 layer. A 256x256, 32-layer checkpoint therefore spent ~2 minutes | |
| before generation even started. The tables only depend on geometry, so build | |
| them once per `(grid, kernel, dilation, packing)` and clone on registration. | |
| """ | |
| n = int(grid_size) | |
| k = int(kernel_size) | |
| d = int(dilation) | |
| p = n * n | |
| center = k // 2 | |
| _seq_to_grid, grid_to_seq = _build_curve_indices_cached(n, packing, pack_order) | |
| cells = torch.arange(p, dtype=torch.long) | |
| rows = torch.div(cells, n, rounding_mode="floor").view(p, 1) | |
| cols = (cells % n).view(p, 1) | |
| offsets = [] | |
| for kr in range(k): | |
| for kc in range(k): | |
| offsets.append(((kr - center) * d, (kc - center) * d)) | |
| delta_r = torch.tensor([x[0] for x in offsets], dtype=torch.long).view(1, k * k) | |
| delta_c = torch.tensor([x[1] for x in offsets], dtype=torch.long).view(1, k * k) | |
| nr = rows + delta_r | |
| nc = cols + delta_c | |
| in_bounds = (nr >= 0) & (nr < n) & (nc >= 0) & (nc < n) | |
| neighbor_cells = (nr.clamp(0, n - 1) * n + nc.clamp(0, n - 1)).to(torch.long) | |
| center_seq = grid_to_seq.index_select(0, cells).view(p, 1) | |
| neighbor_seq = grid_to_seq.index_select(0, neighbor_cells.reshape(-1)).view(p, k * k) | |
| neighbor_mask = in_bounds & (neighbor_seq <= center_seq) | |
| neighbor_indices = torch.where(neighbor_mask, neighbor_cells, torch.zeros_like(neighbor_cells)).to(torch.long) | |
| patch_mask = neighbor_mask.transpose(0, 1).contiguous().view(1, 1, k * k, p) | |
| return patch_mask, neighbor_indices.contiguous(), neighbor_mask.contiguous() | |
| class ConvGPTV2RMSNorm(nn.Module): | |
| def __init__(self, hidden_size: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.ones(hidden_size)) | |
| self.eps = eps | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| input_dtype = hidden_states.dtype | |
| hidden_states = hidden_states.float() | |
| variance = hidden_states.pow(2).mean(dim=-1, keepdim=True) | |
| hidden_states = hidden_states * torch.rsqrt(variance + self.eps) | |
| return self.weight * hidden_states.to(input_dtype) | |
| class ConvGPTV2RMSNorm2d(nn.Module): | |
| def __init__(self, hidden_size: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.ones(hidden_size)) | |
| self.eps = eps | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| input_dtype = x.dtype | |
| variance = x.float().pow(2).mean(dim=1, keepdim=True) | |
| x = x * torch.rsqrt(variance + self.eps).to(dtype=x.dtype) | |
| return self.weight.view(1, -1, 1, 1).to(dtype=input_dtype) * x | |
| class ConvGPTV2MLP(nn.Module): | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__() | |
| self.gate_up_proj = nn.Linear(config.hidden_size, config.intermediate_size * 2, bias=False) | |
| self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False) | |
| self.act = ACT2FN[config.hidden_act] | |
| self.dropout = nn.Dropout(config.dropout) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| gate, up = self.gate_up_proj(hidden_states).chunk(2, dim=-1) | |
| return self.dropout(self.down_proj(self.act(gate) * up)) | |
| class CausalDepthwiseConv1d(nn.Module): | |
| def __init__(self, hidden_size: int, kernel_size: int, dilation: int): | |
| super().__init__() | |
| self.left_padding = (kernel_size - 1) * dilation | |
| self.conv = nn.Conv1d( | |
| hidden_size, | |
| hidden_size, | |
| kernel_size=kernel_size, | |
| dilation=dilation, | |
| groups=hidden_size, | |
| bias=False, | |
| ) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| # hidden_states: [B, T, D] | |
| x = hidden_states.transpose(1, 2) | |
| x = F.pad(x, (self.left_padding, 0)) | |
| x = self.conv(x) | |
| return x.transpose(1, 2) | |
| class CausalConv1dBranch(nn.Module): | |
| def __init__(self, config: ConvGPTV2Config, dilation: int): | |
| super().__init__() | |
| d = config.hidden_size | |
| self.norm = ConvGPTV2RMSNorm(d, config.rms_norm_eps) | |
| self.dwconv = CausalDepthwiseConv1d(d, config.conv1d_kernel_size, dilation) | |
| self.pw = nn.Linear(d, d * 2, bias=False) | |
| self.out = nn.Linear(d, d, bias=False) | |
| self.act = ACT2FN[config.hidden_act] | |
| self.dropout = nn.Dropout(config.dropout) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| y = self.norm(hidden_states) | |
| y = self.dwconv(y) | |
| gate, value = self.pw(y).chunk(2, dim=-1) | |
| y = self.out(self.act(gate) * value) | |
| return self.dropout(y) | |
| def forward_token(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| """Exact causal 1D branch output for the newest token only.""" | |
| y = self.norm(hidden_states) | |
| kernel = self.dwconv.conv.kernel_size[0] | |
| dilation = self.dwconv.conv.dilation[0] | |
| seq_len = y.shape[1] | |
| offsets = torch.arange(kernel, device=y.device) | |
| positions = seq_len - 1 - (kernel - 1 - offsets) * dilation | |
| valid = positions >= 0 | |
| positions = positions.clamp(min=0).to(torch.long) | |
| gathered = y.index_select(1, positions) * valid.to(dtype=y.dtype).view(1, -1, 1) | |
| weight = self.dwconv.conv.weight.view(y.shape[-1], kernel).to(dtype=y.dtype) | |
| y_token = (gathered * weight.transpose(0, 1).view(1, kernel, y.shape[-1])).sum(dim=1, keepdim=True) | |
| if self.dwconv.conv.bias is not None: | |
| y_token = y_token + self.dwconv.conv.bias.view(1, 1, -1) | |
| gate, value = self.pw(y_token).chunk(2, dim=-1) | |
| y_token = self.out(self.act(gate) * value) | |
| return self.dropout(y_token) | |
| class SpaceFillingPacker(nn.Module): | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__() | |
| self.grid_size = config.grid_size | |
| self.max_grid_tokens = config.grid_size * config.grid_size | |
| self.hidden_size = config.hidden_size | |
| seq_to_grid, grid_to_seq = build_curve_indices( | |
| config.grid_size, config.packing, config.pack_order | |
| ) | |
| self.register_buffer("seq_to_grid", seq_to_grid, persistent=False) | |
| self.register_buffer("grid_to_seq", grid_to_seq, persistent=False) | |
| def seq_to_2d(self, hidden_states: torch.Tensor) -> Tuple[torch.Tensor, int]: | |
| bsz, seq_len, dim = hidden_states.shape | |
| if seq_len > self.max_grid_tokens: | |
| raise ValueError( | |
| f"Sequence length {seq_len} exceeds configured grid capacity {self.max_grid_tokens}. " | |
| "Increase config.grid_size or truncate inputs." | |
| ) | |
| grid = hidden_states.new_zeros(bsz, self.max_grid_tokens, dim) | |
| grid_positions = self.seq_to_grid[:seq_len].to(hidden_states.device) | |
| expanded_positions = grid_positions.view(1, -1, 1).expand(bsz, -1, dim) | |
| grid.scatter_(1, expanded_positions, hidden_states) | |
| grid = grid.view(bsz, self.grid_size, self.grid_size, dim).permute(0, 3, 1, 2).contiguous() | |
| return grid, seq_len | |
| def d2_to_seq(self, grid: torch.Tensor, seq_len: int) -> torch.Tensor: | |
| bsz, dim, _, _ = grid.shape | |
| flat = grid.permute(0, 2, 3, 1).contiguous().view(bsz, self.max_grid_tokens, dim) | |
| gather_positions = self.seq_to_grid[:seq_len].to(grid.device) | |
| return flat.index_select(1, gather_positions) | |
| class CausalConv2dBranch(nn.Module): | |
| def __init__(self, config: ConvGPTV2Config, dilation: int): | |
| super().__init__() | |
| d = config.hidden_size | |
| k = config.conv2d_kernel_size | |
| self.grid_size = config.grid_size | |
| self.kernel_size = k | |
| self.dilation = dilation | |
| self.padding = dilation * (k // 2) | |
| self.use_depthwise = config.use_2d_depthwise | |
| self.backend = config.conv2d_backend | |
| self.chunk_size = config.conv2d_chunk_size | |
| self.norm = ConvGPTV2RMSNorm2d(d, config.rms_norm_eps) | |
| if self.use_depthwise: | |
| self.spatial = nn.Conv2d( | |
| d, | |
| d, | |
| kernel_size=k, | |
| dilation=dilation, | |
| padding=0, | |
| groups=d, | |
| bias=False, | |
| ) | |
| self.pointwise = nn.Conv2d(d, d * 2, kernel_size=1, bias=False) | |
| else: | |
| inner = d * config.conv_expand | |
| self.spatial = nn.Conv2d( | |
| d, | |
| inner, | |
| kernel_size=k, | |
| dilation=dilation, | |
| padding=0, | |
| bias=False, | |
| ) | |
| self.pointwise = nn.Conv2d(inner, d * 2, kernel_size=1, bias=False) | |
| patch_mask, neighbor_idx, neighbor_mask = _build_conv2d_causal_tables_cached( | |
| config.grid_size, | |
| self.kernel_size, | |
| self.dilation, | |
| config.packing, | |
| config.pack_order, | |
| ) | |
| self.register_buffer("patch_causal_mask", patch_mask, persistent=False) | |
| self.register_buffer("neighbor_indices", neighbor_idx, persistent=False) | |
| self.register_buffer("neighbor_mask", neighbor_mask, persistent=False) | |
| self.register_buffer("seq_to_grid", self._seq_to_grid(config), persistent=False) | |
| self.register_buffer("grid_to_seq", self._grid_to_seq(config), persistent=False) | |
| static_mask = self._build_static_row_major_mask(config) | |
| self.register_buffer("static_causal_mask", static_mask, persistent=False) | |
| self.out = nn.Conv2d(d, d, kernel_size=1, bias=False) | |
| self.act = ACT2FN[config.hidden_act] | |
| self.dropout = nn.Dropout2d(config.dropout) | |
| def _grid_to_seq(self, config: ConvGPTV2Config) -> torch.Tensor: | |
| _, grid_to_seq = build_curve_indices(config.grid_size, config.packing, config.pack_order) | |
| return grid_to_seq | |
| def _seq_to_grid(self, config: ConvGPTV2Config) -> torch.Tensor: | |
| seq_to_grid, _ = build_curve_indices(config.grid_size, config.packing, config.pack_order) | |
| return seq_to_grid | |
| def _build_patch_causal_mask(self, config: ConvGPTV2Config) -> torch.Tensor: | |
| """Per-cell exact causal mask for unfold backend.""" | |
| n = config.grid_size | |
| k = self.kernel_size | |
| center = k // 2 | |
| grid_to_seq = self._grid_to_seq(config) | |
| mask = torch.zeros(k * k, n * n, dtype=torch.bool) | |
| for r in range(n): | |
| for c in range(n): | |
| cell = r * n + c | |
| center_seq = int(grid_to_seq[cell]) | |
| offset_idx = 0 | |
| for kr in range(k): | |
| for kc in range(k): | |
| nr = r + (kr - center) * self.dilation | |
| nc = c + (kc - center) * self.dilation | |
| if 0 <= nr < n and 0 <= nc < n: | |
| neighbor_cell = nr * n + nc | |
| neighbor_seq = int(grid_to_seq[neighbor_cell]) | |
| mask[offset_idx, cell] = neighbor_seq <= center_seq | |
| offset_idx += 1 | |
| return mask.view(1, 1, k * k, n * n) | |
| def _build_neighbor_table(self, config: ConvGPTV2Config) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Precompute exact causal neighbor indices for chunked gather backend.""" | |
| n = config.grid_size | |
| k = self.kernel_size | |
| center = k // 2 | |
| p = n * n | |
| grid_to_seq = self._grid_to_seq(config) | |
| indices = torch.zeros(p, k * k, dtype=torch.long) | |
| mask = torch.zeros(p, k * k, dtype=torch.bool) | |
| for r in range(n): | |
| for c in range(n): | |
| cell = r * n + c | |
| center_seq = int(grid_to_seq[cell]) | |
| offset_idx = 0 | |
| for kr in range(k): | |
| for kc in range(k): | |
| nr = r + (kr - center) * self.dilation | |
| nc = c + (kc - center) * self.dilation | |
| if 0 <= nr < n and 0 <= nc < n: | |
| neighbor_cell = nr * n + nc | |
| neighbor_seq = int(grid_to_seq[neighbor_cell]) | |
| if neighbor_seq <= center_seq: | |
| indices[cell, offset_idx] = neighbor_cell | |
| mask[cell, offset_idx] = True | |
| offset_idx += 1 | |
| return indices, mask | |
| def _build_static_row_major_mask(self, config: ConvGPTV2Config) -> torch.Tensor: | |
| """Static PixelCNN mask, exact only for row_major packing.""" | |
| k = self.kernel_size | |
| center = k // 2 | |
| mask = torch.ones(k, k) | |
| mask[center, center + 1 :] = 0 | |
| mask[center + 1 :, :] = 0 | |
| return mask.view(1, 1, k, k) | |
| def _depthwise_from_patches(self, patches: torch.Tensor, channels: int) -> torch.Tensor: | |
| weight = self.spatial.weight.view(channels, self.kernel_size * self.kernel_size) | |
| return torch.einsum("bcmp,cm->bcp", patches, weight) | |
| def _dense_from_patches(self, patches: torch.Tensor) -> torch.Tensor: | |
| weight = self.spatial.weight.view( | |
| self.spatial.out_channels, patches.shape[1], self.kernel_size * self.kernel_size | |
| ) | |
| return torch.einsum("bcmp,ocm->bop", patches, weight) | |
| def _forward_unfold(self, y: torch.Tensor) -> torch.Tensor: | |
| bsz, channels, height, width = y.shape | |
| patches = F.unfold( | |
| y, | |
| kernel_size=self.kernel_size, | |
| dilation=self.dilation, | |
| padding=self.padding, | |
| stride=1, | |
| ) | |
| patches = patches.view(bsz, channels, self.kernel_size * self.kernel_size, height * width) | |
| patches = patches * self.patch_causal_mask.to(device=patches.device, dtype=patches.dtype) | |
| if self.use_depthwise: | |
| out = self._depthwise_from_patches(patches, channels) | |
| else: | |
| out = self._dense_from_patches(patches) | |
| return out | |
| def _forward_chunked_gather(self, y: torch.Tensor) -> torch.Tensor: | |
| bsz, channels, height, width = y.shape | |
| p = height * width | |
| x_flat = y.view(bsz, channels, p) | |
| out = y.new_empty(bsz, self.spatial.out_channels, p) | |
| idx_all = self.neighbor_indices.to(y.device) | |
| mask_all = self.neighbor_mask.to(device=y.device, dtype=y.dtype) | |
| for p0 in range(0, p, self.chunk_size): | |
| p1 = min(p0 + self.chunk_size, p) | |
| idx = idx_all[p0:p1] # [chunk, K*K] | |
| # Advanced indexing returns [B, C, chunk, K*K]. This is the same | |
| # logical tensor as unfold but only materialized for one chunk. | |
| patches = x_flat[:, :, idx] | |
| patches = patches * mask_all[p0:p1].view(1, 1, p1 - p0, self.kernel_size * self.kernel_size) | |
| patches = patches.permute(0, 1, 3, 2).contiguous() # [B, C, K*K, chunk] | |
| if self.use_depthwise: | |
| out[:, :, p0:p1] = self._depthwise_from_patches(patches, channels) | |
| else: | |
| out[:, :, p0:p1] = self._dense_from_patches(patches) | |
| return out | |
| def _forward_triton_gather(self, y: torch.Tensor) -> torch.Tensor: | |
| if not _TRITON_AVAILABLE: | |
| return self._forward_chunked_gather(y) | |
| if not self.use_depthwise: | |
| return self._forward_chunked_gather(y) | |
| if y.device.type != "cuda": | |
| return self._forward_chunked_gather(y) | |
| bsz, channels, height, width = y.shape | |
| p = height * width | |
| k2 = self.kernel_size * self.kernel_size | |
| x_flat = y.contiguous().view(bsz * channels, p) | |
| idx = self.neighbor_indices.to(y.device).contiguous() | |
| mask = self.neighbor_mask.to(device=y.device, dtype=torch.bool).contiguous() | |
| weight = self.spatial.weight.view(channels, k2).to(y.dtype).contiguous() | |
| weight = weight.repeat(bsz, 1) | |
| out = torch.empty((bsz * channels, p), device=y.device, dtype=y.dtype) | |
| n_p_blocks = triton.cdiv(p, 128) | |
| grid = (n_p_blocks * bsz * channels,) | |
| _depthwise_neighbor_gather_kernel[grid]( | |
| x_flat, | |
| idx, | |
| mask, | |
| weight, | |
| out, | |
| x_flat.stride(0), | |
| x_flat.stride(1), | |
| idx.stride(0), | |
| idx.stride(1), | |
| mask.stride(0), | |
| mask.stride(1), | |
| weight.stride(0), | |
| weight.stride(1), | |
| out.stride(0), | |
| out.stride(1), | |
| p, | |
| k2, | |
| N_P_BLOCKS=n_p_blocks, | |
| BLOCK_P=128, | |
| BLOCK_K=16, | |
| ) | |
| return out.view(bsz, channels, p) | |
| def _forward_triton_sequence_gather( | |
| self, | |
| y_seq: torch.Tensor, | |
| neighbor_seq_indices: torch.Tensor, | |
| neighbor_seq_mask: torch.Tensor, | |
| ) -> torch.Tensor: | |
| """Depthwise causal 2D gather over active sequence cells only. | |
| y_seq is [B, C, T]. neighbor_seq_indices/mask are [T, K*K] in | |
| sequence coordinates, not full-grid coordinates. This is the fast path | |
| for large grids with shorter active sequences, e.g. 256x256 grid and | |
| seq_len=2048. It avoids doing work over all grid_size^2 cells. | |
| """ | |
| if not _TRITON_AVAILABLE or y_seq.device.type != "cuda" or not self.use_depthwise: | |
| raise RuntimeError("active Triton sequence gather requires CUDA Triton depthwise mode") | |
| bsz, channels, seq_len = y_seq.shape | |
| k2 = self.kernel_size * self.kernel_size | |
| x_flat = y_seq.contiguous().view(bsz * channels, seq_len) | |
| idx = neighbor_seq_indices.to(y_seq.device).contiguous() | |
| mask = neighbor_seq_mask.to(device=y_seq.device, dtype=torch.bool).contiguous() | |
| weight = self.spatial.weight.view(channels, k2).to(y_seq.dtype).contiguous().repeat(bsz, 1) | |
| out = torch.empty((bsz * channels, seq_len), device=y_seq.device, dtype=y_seq.dtype) | |
| n_p_blocks = triton.cdiv(seq_len, 128) | |
| grid = (n_p_blocks * bsz * channels,) | |
| _depthwise_neighbor_gather_kernel[grid]( | |
| x_flat, | |
| idx, | |
| mask, | |
| weight, | |
| out, | |
| x_flat.stride(0), | |
| x_flat.stride(1), | |
| idx.stride(0), | |
| idx.stride(1), | |
| mask.stride(0), | |
| mask.stride(1), | |
| weight.stride(0), | |
| weight.stride(1), | |
| out.stride(0), | |
| out.stride(1), | |
| seq_len, | |
| k2, | |
| N_P_BLOCKS=n_p_blocks, | |
| BLOCK_P=128, | |
| BLOCK_K=16, | |
| ) | |
| return out.view(bsz, channels, seq_len) | |
| def forward_sequence(self, hidden_states: torch.Tensor, active_cells: torch.Tensor) -> Optional[torch.Tensor]: | |
| """Fast active-cell path for triton_gather. | |
| Returns [B, T, D] when the active Triton path is available, otherwise | |
| None so callers can fall back to the full-grid backend. This avoids | |
| processing grid_size^2 cells when only the first T sequence positions | |
| are active. | |
| """ | |
| if self.backend != "triton_gather" or not _TRITON_AVAILABLE or hidden_states.device.type != "cuda" or not self.use_depthwise: | |
| return None | |
| bsz, seq_len, channels = hidden_states.shape | |
| input_dtype = hidden_states.dtype | |
| y = hidden_states.float() | |
| variance = y.pow(2).mean(dim=-1, keepdim=True) | |
| y = hidden_states * torch.rsqrt(variance + self.norm.eps).to(dtype=hidden_states.dtype) | |
| y = y * self.norm.weight.view(1, 1, -1).to(device=hidden_states.device, dtype=input_dtype) | |
| y = y.transpose(1, 2).contiguous() # [B, C, T] | |
| active_cells = active_cells[:seq_len].to(hidden_states.device, dtype=torch.long) | |
| # Safe physical indexing for CUDA/Triton. Invalid entries remain masked | |
| # out, but every tensor passed into index_select is clamped to a valid | |
| # physical range. This avoids device-side asserts on newer CUDA/Triton | |
| # stacks while preserving the Triton sequence-gather kernel below. | |
| max_cells = int(self.neighbor_indices.shape[0]) | |
| active_valid = (active_cells >= 0) & (active_cells < max_cells) | |
| active_cells_safe = active_cells.clamp(min=0, max=max(max_cells - 1, 0)) | |
| neighbor_indices = self.neighbor_indices.to(hidden_states.device) | |
| base_neighbor_mask = self.neighbor_mask.to(hidden_states.device) | |
| neighbor_grid = neighbor_indices.index_select(0, active_cells_safe) | |
| neighbor_mask = base_neighbor_mask.index_select(0, active_cells_safe) | |
| neighbor_mask = neighbor_mask & active_valid.view(-1, 1) | |
| grid_to_seq = self.grid_to_seq.to(hidden_states.device) | |
| max_grid = int(grid_to_seq.shape[0]) | |
| neighbor_grid_valid = (neighbor_grid >= 0) & (neighbor_grid < max_grid) | |
| neighbor_grid_safe = neighbor_grid.clamp(min=0, max=max(max_grid - 1, 0)) | |
| neighbor_seq = grid_to_seq.index_select(0, neighbor_grid_safe.reshape(-1)).view_as(neighbor_grid_safe) | |
| neighbor_mask = neighbor_mask & neighbor_grid_valid & (neighbor_seq >= 0) & (neighbor_seq < seq_len) | |
| neighbor_seq = neighbor_seq.clamp(min=0, max=max(seq_len - 1, 0)).to(torch.long).contiguous() | |
| neighbor_mask = neighbor_mask.to(torch.bool).contiguous() | |
| y = self._forward_triton_sequence_gather(y, neighbor_seq, neighbor_mask) | |
| if self.spatial.bias is not None: | |
| y = y + self.spatial.bias.view(1, -1, 1) | |
| # 1x1 convs over active cells only; equivalent to Conv2d(kernel=1). | |
| y_tokens = y.transpose(1, 2).contiguous() # [B, T, C] | |
| pointwise_weight = self.pointwise.weight.view(self.pointwise.out_channels, self.pointwise.in_channels) | |
| pointwise_bias = self.pointwise.bias | |
| gate, value = F.linear(y_tokens, pointwise_weight, pointwise_bias).chunk(2, dim=-1) | |
| out_weight = self.out.weight.view(self.out.out_channels, self.out.in_channels) | |
| out_bias = self.out.bias | |
| y_tokens = F.linear(self.act(gate) * value, out_weight, out_bias) | |
| return F.dropout(y_tokens, p=self.dropout.p, training=self.training) | |
| def forward_token(self, hidden_states: torch.Tensor, position_idx: int) -> torch.Tensor: | |
| """Exact causal 2D branch output for the last token only. | |
| `hidden_states` contains the complete prefix at this layer. The 2D | |
| branch is depthwise-spatial + pointwise 1x1, so the newest token's | |
| output only needs its causal spatial neighbours, not all grid cells. | |
| """ | |
| if not self.use_depthwise: | |
| raise NotImplementedError("incremental 2D branch currently requires depthwise spatial conv") | |
| bsz, seq_len, channels = hidden_states.shape | |
| if position_idx < 0 or position_idx >= seq_len: | |
| raise ValueError(f"position_idx {position_idx} is out of range for seq_len {seq_len}") | |
| input_dtype = hidden_states.dtype | |
| y = hidden_states.float() | |
| variance = y.pow(2).mean(dim=-1, keepdim=True) | |
| y = hidden_states * torch.rsqrt(variance + self.norm.eps).to(dtype=hidden_states.dtype) | |
| y = y * self.norm.weight.view(1, 1, -1).to(device=hidden_states.device, dtype=input_dtype) | |
| seq_to_grid = self.seq_to_grid.to(hidden_states.device) | |
| max_cells = int(self.neighbor_indices.shape[0]) | |
| active_cell = seq_to_grid.narrow(0, position_idx, 1).to(dtype=torch.long) | |
| active_valid = (active_cell >= 0) & (active_cell < max_cells) | |
| active_cell_safe = active_cell.clamp(min=0, max=max(max_cells - 1, 0)) | |
| neighbor_grid = self.neighbor_indices.to(hidden_states.device).index_select(0, active_cell_safe).squeeze(0) | |
| neighbor_mask = self.neighbor_mask.to(hidden_states.device).index_select(0, active_cell_safe).squeeze(0) | |
| neighbor_mask = neighbor_mask & bool(active_valid.item()) | |
| grid_to_seq = self.grid_to_seq.to(hidden_states.device) | |
| max_grid = int(grid_to_seq.shape[0]) | |
| neighbor_grid_valid = (neighbor_grid >= 0) & (neighbor_grid < max_grid) | |
| neighbor_grid_safe = neighbor_grid.clamp(min=0, max=max(max_grid - 1, 0)) | |
| neighbor_seq = grid_to_seq.index_select(0, neighbor_grid_safe) | |
| neighbor_mask = neighbor_mask & neighbor_grid_valid & (neighbor_seq >= 0) & (neighbor_seq < seq_len) | |
| neighbor_seq = neighbor_seq.clamp(min=0, max=max(seq_len - 1, 0)).to(torch.long) | |
| # [B, K*K, C] | |
| gathered = y.index_select(1, neighbor_seq) | |
| gathered = gathered * neighbor_mask.to(dtype=y.dtype).view(1, -1, 1) | |
| weight = self.spatial.weight.view(channels, self.kernel_size * self.kernel_size).to(dtype=y.dtype) | |
| y_token = (gathered * weight.transpose(0, 1).view(1, -1, channels)).sum(dim=1, keepdim=True) | |
| if self.spatial.bias is not None: | |
| y_token = y_token + self.spatial.bias.view(1, 1, -1) | |
| pointwise_weight = self.pointwise.weight.view(self.pointwise.out_channels, self.pointwise.in_channels) | |
| gate, value = F.linear(y_token, pointwise_weight, self.pointwise.bias).chunk(2, dim=-1) | |
| out_weight = self.out.weight.view(self.out.out_channels, self.out.in_channels) | |
| y_token = F.linear(self.act(gate) * value, out_weight, self.out.bias) | |
| return F.dropout(y_token, p=self.dropout.p, training=self.training) | |
| def _forward_masked_conv2d(self, y: torch.Tensor) -> torch.Tensor: | |
| # Fast exact path for row_major packing only. Config validation prevents | |
| # using this backend with arbitrary layouts. | |
| pad = (self.kernel_size - 1) * self.dilation | |
| y = F.pad(y, (pad // 2, pad - pad // 2, pad, 0)) | |
| weight = self.spatial.weight * self.static_causal_mask.to( | |
| device=self.spatial.weight.device, dtype=self.spatial.weight.dtype | |
| ) | |
| return F.conv2d( | |
| y, | |
| weight, | |
| bias=self.spatial.bias, | |
| stride=self.spatial.stride, | |
| padding=0, | |
| dilation=self.spatial.dilation, | |
| groups=self.spatial.groups, | |
| ).view(y.shape[0], self.spatial.out_channels, -1) | |
| def forward(self, grid: torch.Tensor) -> torch.Tensor: | |
| bsz, channels, height, width = grid.shape | |
| if height != self.grid_size or width != self.grid_size: | |
| raise ValueError( | |
| f"CausalConv2dBranch expected {self.grid_size}x{self.grid_size} grid, got {height}x{width}" | |
| ) | |
| y = self.norm(grid) | |
| if self.backend == "unfold": | |
| y = self._forward_unfold(y) | |
| elif self.backend == "chunked_gather": | |
| y = self._forward_chunked_gather(y) | |
| elif self.backend == "triton_gather": | |
| y = self._forward_triton_gather(y) | |
| elif self.backend == "masked_conv2d": | |
| y = self._forward_masked_conv2d(y) | |
| else: | |
| raise ValueError(f"Unknown conv2d_backend: {self.backend}") | |
| if self.spatial.bias is not None: | |
| y = y + self.spatial.bias.view(1, -1, 1) | |
| y = y.view(bsz, self.spatial.out_channels, height, width) | |
| gate, value = self.pointwise(y).chunk(2, dim=1) | |
| y = self.out(self.act(gate) * value) | |
| return self.dropout(y) | |
| class TinyRetrievalRouter(nn.Module): | |
| """Small top-k learned-memory router for cheap content-based long-range mixing.""" | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__() | |
| d = config.hidden_size | |
| self.num_slots = config.retrieval_num_slots | |
| self.top_k = max(1, min(config.retrieval_top_k, self.num_slots)) | |
| self.norm = ConvGPTV2RMSNorm(d, config.rms_norm_eps) | |
| self.query = nn.Linear(d, d, bias=False) | |
| self.key = nn.Parameter(torch.empty(self.num_slots, d)) | |
| self.value = nn.Parameter(torch.empty(self.num_slots, d)) | |
| self.out = nn.Linear(d, d, bias=False) | |
| self.dropout = nn.Dropout(config.retrieval_dropout) | |
| nn.init.normal_(self.key, mean=0.0, std=config.initializer_range) | |
| nn.init.normal_(self.value, mean=0.0, std=config.initializer_range) | |
| def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: | |
| q = self.query(self.norm(hidden_states)) | |
| scores = torch.matmul(q, self.key.t()) / math.sqrt(q.shape[-1]) | |
| if self.top_k < self.num_slots: | |
| top_values, top_indices = scores.topk(self.top_k, dim=-1) | |
| weights = F.softmax(top_values, dim=-1).to(hidden_states.dtype) | |
| memory = self.value.index_select(0, top_indices.reshape(-1)).view( | |
| *top_indices.shape, hidden_states.shape[-1] | |
| ) | |
| routed = (weights.unsqueeze(-1) * memory).sum(dim=-2) | |
| else: | |
| weights = F.softmax(scores, dim=-1).to(hidden_states.dtype) | |
| routed = torch.matmul(weights, self.value.to(hidden_states.dtype)) | |
| return self.dropout(self.out(routed)) | |
| class CausalChunkMemoryRouter(nn.Module): | |
| """Causal top-k retrieval over prefix chunk summaries. | |
| This is a non-quadratic attention substitute: tokens query pooled chunk | |
| summaries rather than every previous token. With chunk size C, sequence | |
| length T, and top-k K, scoring cost is O(T * ceil(T/C)) and retrieval cost | |
| is O(T * K). The default mask excludes the token's current chunk so future | |
| tokens in the same chunk cannot leak backward. | |
| """ | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__() | |
| d = config.hidden_size | |
| self.chunk_size = int(config.chunk_memory_size) | |
| self.top_k = int(config.chunk_memory_top_k) | |
| self.include_current_chunk = bool(config.chunk_memory_include_current_chunk) | |
| self.norm = ConvGPTV2RMSNorm(d, config.rms_norm_eps) | |
| self.query = nn.Linear(d, d, bias=False) | |
| self.key = nn.Linear(d, d, bias=False) | |
| self.value = nn.Linear(d, d, bias=False) | |
| self.out = nn.Linear(d, d, bias=False) | |
| self.gate = nn.Parameter(torch.tensor(float(config.chunk_memory_gate_init))) | |
| rope_dim = int(d * float(getattr(config, "router_rope_fraction", 1.0))) | |
| rope_dim = min(d, rope_dim - (rope_dim % 2)) | |
| self.rotary_emb = ConvGPTV2RotaryEmbedding(rope_dim, getattr(config, "rope_theta", 10000.0)) if getattr(config, "position_embedding_type", "learned") == "rope_nope" else None | |
| self.dropout = nn.Dropout(config.retrieval_dropout) | |
| def _chunk_summaries(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| bsz, seq_len, dim = hidden_states.shape | |
| num_chunks = math.ceil(seq_len / self.chunk_size) | |
| pad_len = num_chunks * self.chunk_size - seq_len | |
| if pad_len: | |
| padded = F.pad(hidden_states, (0, 0, 0, pad_len)) | |
| else: | |
| padded = hidden_states | |
| chunks = padded.view(bsz, num_chunks, self.chunk_size, dim) | |
| valid = torch.arange(num_chunks * self.chunk_size, device=hidden_states.device).view(1, num_chunks, self.chunk_size) < seq_len | |
| weights = valid.to(hidden_states.dtype).unsqueeze(-1) | |
| denom = weights.sum(dim=2).clamp_min(1.0) | |
| summaries = (chunks * weights).sum(dim=2) / denom | |
| return summaries, valid.any(dim=2) | |
| def forward(self, hidden_states: torch.Tensor, position_ids: Optional[torch.LongTensor] = None) -> torch.Tensor: | |
| bsz, seq_len, dim = hidden_states.shape | |
| if seq_len <= self.chunk_size and not self.include_current_chunk: | |
| return hidden_states.new_zeros(hidden_states.shape) | |
| normed = self.norm(hidden_states) | |
| summaries, valid_chunks = self._chunk_summaries(normed) | |
| num_chunks = summaries.shape[1] | |
| valid_chunks = valid_chunks.expand(bsz, -1) | |
| q = self.query(normed) | |
| k = self.key(summaries) | |
| if self.rotary_emb is not None: | |
| token_pos = _token_position_ids(position_ids, bsz, seq_len, hidden_states.device) | |
| chunk_pos = _chunk_start_position_ids(token_pos, self.chunk_size, num_chunks) | |
| q_cos, q_sin = self.rotary_emb(token_pos, q.dtype, q.device) | |
| k_cos, k_sin = self.rotary_emb(chunk_pos, k.dtype, k.device) | |
| q = _apply_rope_to_btd(q, q_cos, q_sin) | |
| k = _apply_rope_to_btd(k, k_cos, k_sin) | |
| v = self.value(summaries) | |
| scores = torch.matmul(q, k.transpose(-1, -2)) / math.sqrt(dim) | |
| token_chunks = torch.div(torch.arange(seq_len, device=hidden_states.device), self.chunk_size, rounding_mode="floor") | |
| chunk_ids = torch.arange(num_chunks, device=hidden_states.device).view(1, 1, num_chunks) | |
| max_allowed = token_chunks if self.include_current_chunk else token_chunks - 1 | |
| causal_mask = chunk_ids <= max_allowed.view(1, seq_len, 1) | |
| causal_mask = causal_mask & valid_chunks.view(bsz, 1, num_chunks) | |
| scores = scores.masked_fill(~causal_mask, torch.finfo(scores.dtype).min) | |
| any_allowed = causal_mask.any(dim=-1, keepdim=True) | |
| safe_scores = torch.where(any_allowed, scores, torch.zeros_like(scores)) | |
| k_top = max(1, min(self.top_k, num_chunks)) | |
| top_scores, top_idx = safe_scores.topk(k_top, dim=-1) | |
| top_mask = causal_mask.gather(-1, top_idx) | |
| top_scores = top_scores.masked_fill(~top_mask, torch.finfo(top_scores.dtype).min) | |
| weights = F.softmax(top_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(top_mask.any(dim=-1, keepdim=True), weights, torch.zeros_like(weights)) | |
| gathered = v.gather(1, top_idx.reshape(bsz, -1, 1).expand(-1, -1, dim)).view(bsz, seq_len, k_top, dim) | |
| routed = (weights.unsqueeze(-1) * gathered).sum(dim=-2) | |
| return torch.sigmoid(self.gate).to(hidden_states.dtype) * self.dropout(self.out(routed)) | |
| def forward_token(self, hidden_states: torch.Tensor, position_ids: Optional[torch.LongTensor] = None) -> torch.Tensor: | |
| """Exact router output for the newest token only.""" | |
| bsz, seq_len, dim = hidden_states.shape | |
| if seq_len <= self.chunk_size and not self.include_current_chunk: | |
| return hidden_states.new_zeros((bsz, 1, dim)) | |
| normed = self.norm(hidden_states) | |
| summaries, valid_chunks = self._chunk_summaries(normed) | |
| num_chunks = summaries.shape[1] | |
| q = self.query(normed[:, -1:, :]) | |
| k = self.key(summaries) | |
| if self.rotary_emb is not None: | |
| token_pos = _token_position_ids(position_ids, bsz, seq_len, hidden_states.device) | |
| chunk_pos = _chunk_start_position_ids(token_pos, self.chunk_size, num_chunks) | |
| q_cos, q_sin = self.rotary_emb(token_pos[:, -1:], q.dtype, q.device) | |
| k_cos, k_sin = self.rotary_emb(chunk_pos, k.dtype, k.device) | |
| q = _apply_rope_to_btd(q, q_cos, q_sin) | |
| k = _apply_rope_to_btd(k, k_cos, k_sin) | |
| v = self.value(summaries) | |
| scores = torch.matmul(q, k.transpose(-1, -2)) / math.sqrt(dim) | |
| token_chunk = (seq_len - 1) // self.chunk_size | |
| chunk_ids = torch.arange(num_chunks, device=hidden_states.device).view(1, 1, num_chunks) | |
| max_allowed = token_chunk if self.include_current_chunk else token_chunk - 1 | |
| causal_mask = chunk_ids <= max_allowed | |
| causal_mask = causal_mask & valid_chunks.expand(bsz, -1).view(bsz, 1, num_chunks) | |
| scores = scores.masked_fill(~causal_mask, torch.finfo(scores.dtype).min) | |
| any_allowed = causal_mask.any(dim=-1, keepdim=True) | |
| safe_scores = torch.where(any_allowed, scores, torch.zeros_like(scores)) | |
| k_top = max(1, min(self.top_k, num_chunks)) | |
| top_scores, top_idx = safe_scores.topk(k_top, dim=-1) | |
| top_mask = causal_mask.gather(-1, top_idx) | |
| top_scores = top_scores.masked_fill(~top_mask, torch.finfo(top_scores.dtype).min) | |
| weights = F.softmax(top_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(top_mask.any(dim=-1, keepdim=True), weights, torch.zeros_like(weights)) | |
| gathered = v.gather(1, top_idx.reshape(bsz, -1, 1).expand(-1, -1, dim)).view(bsz, 1, k_top, dim) | |
| routed = (weights.unsqueeze(-1) * gathered).sum(dim=-2) | |
| return torch.sigmoid(self.gate).to(hidden_states.dtype) * self.dropout(self.out(routed)) | |
| class CausalChunkTokenMemoryRouter(nn.Module): | |
| """Two-stage causal sparse token memory. | |
| Stage 1 selects top-k previous chunks using pooled summaries. Stage 2 lets | |
| each token attend to the actual tokens inside selected chunks, preserving a | |
| high-bandwidth copy path without full quadratic token-token attention. | |
| """ | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__() | |
| d = config.hidden_size | |
| self.chunk_size = int(config.chunk_memory_size) | |
| self.chunk_top_k = int(config.chunk_memory_top_k) | |
| self.token_top_k = int(config.chunk_memory_token_top_k) | |
| self.include_current_chunk = bool(config.chunk_memory_include_current_chunk) | |
| self.use_triton = bool(getattr(config, "chunk_memory_use_triton", True)) | |
| self.norm = ConvGPTV2RMSNorm(d, config.rms_norm_eps) | |
| self.query = nn.Linear(d, d, bias=False) | |
| self.chunk_key = nn.Linear(d, d, bias=False) | |
| self.token_key = nn.Linear(d, d, bias=False) | |
| self.token_value = nn.Linear(d, d, bias=False) | |
| self.out = nn.Linear(d, d, bias=False) | |
| self.gate = nn.Parameter(torch.tensor(float(config.chunk_memory_gate_init))) | |
| rope_dim = int(d * float(getattr(config, "router_rope_fraction", 1.0))) | |
| rope_dim = min(d, rope_dim - (rope_dim % 2)) | |
| self.rotary_emb = ConvGPTV2RotaryEmbedding(rope_dim, getattr(config, "rope_theta", 10000.0)) if getattr(config, "position_embedding_type", "learned") == "rope_nope" else None | |
| self.dropout = nn.Dropout(config.retrieval_dropout) | |
| def _chunk_summaries(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| bsz, seq_len, dim = hidden_states.shape | |
| num_chunks = math.ceil(seq_len / self.chunk_size) | |
| pad_len = num_chunks * self.chunk_size - seq_len | |
| padded = F.pad(hidden_states, (0, 0, 0, pad_len)) if pad_len else hidden_states | |
| chunks = padded.view(bsz, num_chunks, self.chunk_size, dim) | |
| valid = torch.arange(num_chunks * self.chunk_size, device=hidden_states.device).view(1, num_chunks, self.chunk_size) < seq_len | |
| weights = valid.to(hidden_states.dtype).unsqueeze(-1) | |
| summaries = (chunks * weights).sum(dim=2) / weights.sum(dim=2).clamp_min(1.0) | |
| return summaries, valid.expand(bsz, -1, -1) | |
| def forward(self, hidden_states: torch.Tensor, position_ids: Optional[torch.LongTensor] = None) -> torch.Tensor: | |
| bsz, seq_len, dim = hidden_states.shape | |
| if seq_len <= self.chunk_size and not self.include_current_chunk: | |
| return hidden_states.new_zeros(hidden_states.shape) | |
| normed = self.norm(hidden_states) | |
| summaries, valid_token_chunks = self._chunk_summaries(normed) | |
| num_chunks = summaries.shape[1] | |
| q = self.query(normed) | |
| chunk_k = self.chunk_key(summaries) | |
| token_k = self.token_key(normed) | |
| if self.rotary_emb is not None: | |
| token_pos = _token_position_ids(position_ids, bsz, seq_len, hidden_states.device) | |
| chunk_pos = _chunk_start_position_ids(token_pos, self.chunk_size, num_chunks) | |
| q_cos, q_sin = self.rotary_emb(token_pos, q.dtype, q.device) | |
| tk_cos, tk_sin = self.rotary_emb(token_pos, token_k.dtype, token_k.device) | |
| ck_cos, ck_sin = self.rotary_emb(chunk_pos, chunk_k.dtype, chunk_k.device) | |
| q = _apply_rope_to_btd(q, q_cos, q_sin) | |
| token_k = _apply_rope_to_btd(token_k, tk_cos, tk_sin) | |
| chunk_k = _apply_rope_to_btd(chunk_k, ck_cos, ck_sin) | |
| token_v = self.token_value(normed) | |
| top_scores = top_pos = top_valid = None | |
| flat_pos = flat_valid = None | |
| k_chunks = max(1, min(self.chunk_top_k, num_chunks)) | |
| can_use_chunk2_triton = ( | |
| False # experimental path currently benchmark-neutral/slower | |
| and self.use_triton | |
| and _TRITON_AVAILABLE | |
| and q.device.type == "cuda" | |
| and q.dtype in {torch.float16, torch.bfloat16, torch.float32} | |
| and chunk_k.dtype == q.dtype | |
| and token_k.dtype == q.dtype | |
| and token_v.dtype == q.dtype | |
| and self.chunk_top_k == 2 | |
| and k_chunks == 2 | |
| and self.token_top_k > 0 | |
| and self.token_top_k < self.chunk_top_k * self.chunk_size | |
| and self.chunk_top_k * self.chunk_size <= 256 | |
| and dim <= 1024 | |
| ) | |
| if can_use_chunk2_triton: | |
| top_scores, top_pos, top_valid = _select_chunk2_token_scores( | |
| q, | |
| chunk_k, | |
| token_k, | |
| self.token_top_k, | |
| self.chunk_size, | |
| self.include_current_chunk, | |
| ) | |
| else: | |
| chunk_scores = torch.matmul(q, chunk_k.transpose(-1, -2)) / math.sqrt(dim) | |
| token_pos = torch.arange(seq_len, device=hidden_states.device) | |
| token_chunks = torch.div(token_pos, self.chunk_size, rounding_mode="floor") | |
| chunk_ids = torch.arange(num_chunks, device=hidden_states.device).view(1, 1, num_chunks) | |
| max_allowed = token_chunks if self.include_current_chunk else token_chunks - 1 | |
| chunk_mask = chunk_ids <= max_allowed.view(1, seq_len, 1) | |
| chunk_mask = chunk_mask & valid_token_chunks.any(dim=-1).view(bsz, 1, num_chunks) | |
| chunk_scores = chunk_scores.masked_fill(~chunk_mask, torch.finfo(chunk_scores.dtype).min) | |
| any_chunk = chunk_mask.any(dim=-1, keepdim=True) | |
| safe_chunk_scores = torch.where(any_chunk, chunk_scores, torch.zeros_like(chunk_scores)) | |
| _top_chunk_scores, top_chunk_idx = safe_chunk_scores.topk(k_chunks, dim=-1) | |
| top_chunk_mask = chunk_mask.gather(-1, top_chunk_idx) | |
| offsets = torch.arange(self.chunk_size, device=hidden_states.device).view(1, 1, 1, self.chunk_size) | |
| candidate_pos = top_chunk_idx.unsqueeze(-1) * self.chunk_size + offsets | |
| candidate_valid = candidate_pos < seq_len | |
| candidate_valid = candidate_valid & top_chunk_mask.unsqueeze(-1) | |
| if not self.include_current_chunk: | |
| candidate_valid = candidate_valid & (candidate_pos < token_pos.view(1, seq_len, 1, 1)) | |
| candidate_pos = candidate_pos.clamp(max=max(seq_len - 1, 0)).to(torch.long) | |
| flat_pos = candidate_pos.reshape(bsz, seq_len, k_chunks * self.chunk_size) | |
| flat_valid = candidate_valid.reshape(bsz, seq_len, k_chunks * self.chunk_size) | |
| if top_scores is None: | |
| use_triton = self.use_triton and _can_use_chunk_token_triton(q, token_k, token_v, flat_pos) | |
| any_token = flat_valid.any(dim=-1, keepdim=True) | |
| if self.token_top_k > 0 and self.token_top_k < flat_pos.shape[-1]: | |
| k_tokens = self.token_top_k | |
| if use_triton: | |
| top_scores, top_pos, top_valid = _select_chunk_token_scores(q, token_k, flat_pos, flat_valid, k_tokens) | |
| top_scores = top_scores.masked_fill(~top_valid, torch.finfo(top_scores.dtype).min) | |
| else: | |
| gather_idx = flat_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_k = token_k.gather(1, gather_idx).view(bsz, seq_len, flat_pos.shape[-1], dim) | |
| token_scores = (q.unsqueeze(-2) * cand_k).sum(dim=-1) / math.sqrt(dim) | |
| token_scores = token_scores.masked_fill(~flat_valid, torch.finfo(token_scores.dtype).min) | |
| safe_token_scores = torch.where(any_token, token_scores, torch.zeros_like(token_scores)) | |
| top_scores, top_idx = safe_token_scores.topk(k_tokens, dim=-1) | |
| top_valid = flat_valid.gather(-1, top_idx) | |
| top_pos = flat_pos.gather(-1, top_idx) | |
| top_scores = top_scores.masked_fill(~top_valid, torch.finfo(top_scores.dtype).min) | |
| weights = F.softmax(top_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(top_valid.any(dim=-1, keepdim=True), weights, torch.zeros_like(weights)) | |
| else: | |
| if use_triton: | |
| token_scores = _chunk_token_scores(q, token_k, flat_pos, flat_valid) | |
| else: | |
| gather_idx = flat_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_k = token_k.gather(1, gather_idx).view(bsz, seq_len, flat_pos.shape[-1], dim) | |
| token_scores = (q.unsqueeze(-2) * cand_k).sum(dim=-1) / math.sqrt(dim) | |
| token_scores = token_scores.masked_fill(~flat_valid, torch.finfo(token_scores.dtype).min) | |
| top_valid = flat_valid | |
| top_pos = flat_pos | |
| weights = F.softmax(token_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(any_token, weights, torch.zeros_like(weights)) | |
| else: | |
| top_scores = top_scores.masked_fill(~top_valid, torch.finfo(top_scores.dtype).min) | |
| weights = F.softmax(top_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(top_valid.any(dim=-1, keepdim=True), weights, torch.zeros_like(weights)) | |
| use_triton_value = self.use_triton and _can_use_chunk_token_triton(token_v, token_v, token_v, top_pos) | |
| if use_triton_value: | |
| routed = _weighted_token_values(weights, token_v, top_pos, top_valid) | |
| else: | |
| gather_idx = top_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_v = token_v.gather(1, gather_idx).view(bsz, seq_len, top_pos.shape[-1], dim) | |
| routed = (weights.unsqueeze(-1) * cand_v).sum(dim=-2) | |
| return torch.sigmoid(self.gate).to(hidden_states.dtype) * self.dropout(self.out(routed)) | |
| def forward_token(self, hidden_states: torch.Tensor, position_ids: Optional[torch.LongTensor] = None) -> torch.Tensor: | |
| """Exact sparse token-memory router output for the newest token only.""" | |
| bsz, seq_len, dim = hidden_states.shape | |
| if seq_len <= self.chunk_size and not self.include_current_chunk: | |
| return hidden_states.new_zeros((bsz, 1, dim)) | |
| normed = self.norm(hidden_states) | |
| summaries, valid_token_chunks = self._chunk_summaries(normed) | |
| num_chunks = summaries.shape[1] | |
| q = self.query(normed[:, -1:, :]) | |
| chunk_k = self.chunk_key(summaries) | |
| token_k = self.token_key(normed) | |
| if self.rotary_emb is not None: | |
| token_pos_ids = _token_position_ids(position_ids, bsz, seq_len, hidden_states.device) | |
| chunk_pos = _chunk_start_position_ids(token_pos_ids, self.chunk_size, num_chunks) | |
| q_cos, q_sin = self.rotary_emb(token_pos_ids[:, -1:], q.dtype, q.device) | |
| tk_cos, tk_sin = self.rotary_emb(token_pos_ids, token_k.dtype, token_k.device) | |
| ck_cos, ck_sin = self.rotary_emb(chunk_pos, chunk_k.dtype, chunk_k.device) | |
| q = _apply_rope_to_btd(q, q_cos, q_sin) | |
| token_k = _apply_rope_to_btd(token_k, tk_cos, tk_sin) | |
| chunk_k = _apply_rope_to_btd(chunk_k, ck_cos, ck_sin) | |
| token_v = self.token_value(normed) | |
| k_chunks = max(1, min(self.chunk_top_k, num_chunks)) | |
| chunk_scores = torch.matmul(q, chunk_k.transpose(-1, -2)) / math.sqrt(dim) | |
| token_idx = seq_len - 1 | |
| token_chunk = token_idx // self.chunk_size | |
| chunk_ids = torch.arange(num_chunks, device=hidden_states.device).view(1, 1, num_chunks) | |
| max_allowed = token_chunk if self.include_current_chunk else token_chunk - 1 | |
| chunk_mask = chunk_ids <= max_allowed | |
| chunk_mask = chunk_mask & valid_token_chunks.any(dim=-1).view(bsz, 1, num_chunks) | |
| chunk_scores = chunk_scores.masked_fill(~chunk_mask, torch.finfo(chunk_scores.dtype).min) | |
| any_chunk = chunk_mask.any(dim=-1, keepdim=True) | |
| safe_chunk_scores = torch.where(any_chunk, chunk_scores, torch.zeros_like(chunk_scores)) | |
| _top_chunk_scores, top_chunk_idx = safe_chunk_scores.topk(k_chunks, dim=-1) | |
| top_chunk_mask = chunk_mask.gather(-1, top_chunk_idx) | |
| offsets = torch.arange(self.chunk_size, device=hidden_states.device).view(1, 1, 1, self.chunk_size) | |
| candidate_pos = top_chunk_idx.unsqueeze(-1) * self.chunk_size + offsets | |
| candidate_valid = candidate_pos < seq_len | |
| candidate_valid = candidate_valid & top_chunk_mask.unsqueeze(-1) | |
| if not self.include_current_chunk: | |
| candidate_valid = candidate_valid & (candidate_pos < token_idx) | |
| candidate_pos = candidate_pos.clamp(max=max(seq_len - 1, 0)).to(torch.long) | |
| flat_pos = candidate_pos.reshape(bsz, 1, k_chunks * self.chunk_size) | |
| flat_valid = candidate_valid.reshape(bsz, 1, k_chunks * self.chunk_size) | |
| any_token = flat_valid.any(dim=-1, keepdim=True) | |
| gather_idx = flat_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_k = token_k.gather(1, gather_idx).view(bsz, 1, flat_pos.shape[-1], dim) | |
| token_scores = (q.unsqueeze(-2) * cand_k).sum(dim=-1) / math.sqrt(dim) | |
| token_scores = token_scores.masked_fill(~flat_valid, torch.finfo(token_scores.dtype).min) | |
| if self.token_top_k > 0 and self.token_top_k < flat_pos.shape[-1]: | |
| k_tokens = self.token_top_k | |
| safe_token_scores = torch.where(any_token, token_scores, torch.zeros_like(token_scores)) | |
| top_scores, top_idx = safe_token_scores.topk(k_tokens, dim=-1) | |
| top_valid = flat_valid.gather(-1, top_idx) | |
| top_pos = flat_pos.gather(-1, top_idx) | |
| top_scores = top_scores.masked_fill(~top_valid, torch.finfo(top_scores.dtype).min) | |
| weights = F.softmax(top_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(top_valid.any(dim=-1, keepdim=True), weights, torch.zeros_like(weights)) | |
| else: | |
| top_valid = flat_valid | |
| top_pos = flat_pos | |
| weights = F.softmax(token_scores, dim=-1).to(hidden_states.dtype) | |
| weights = torch.where(any_token, weights, torch.zeros_like(weights)) | |
| gather_idx = top_pos.reshape(bsz, -1, 1).expand(-1, -1, dim) | |
| cand_v = token_v.gather(1, gather_idx).view(bsz, 1, top_pos.shape[-1], dim) | |
| routed = (weights.unsqueeze(-1) * cand_v).sum(dim=-2) | |
| return torch.sigmoid(self.gate).to(hidden_states.dtype) * self.dropout(self.out(routed)) | |
| class HybridConvGPTV2Block(nn.Module): | |
| def __init__(self, config: ConvGPTV2Config, layer_idx: int): | |
| super().__init__() | |
| self.use_1d_branch = config.use_1d_branch | |
| self.use_2d_branch = ( | |
| config.use_2d_branch | |
| and layer_idx >= config.two_d_start_layer | |
| and ((layer_idx - config.two_d_start_layer) % config.two_d_every == 0) | |
| ) | |
| if not self.use_1d_branch and not self.use_2d_branch: | |
| raise ValueError("At least one active branch is required; check use_1d_branch/use_2d_branch/two_d_every") | |
| d1 = config.conv1d_dilations or [1, 2, 4, 8, 16, 32] | |
| d2 = config.conv2d_dilations or [1, 2, 4, 8, 16] | |
| self.branch_1d = CausalConv1dBranch(config, d1[layer_idx % len(d1)]) if self.use_1d_branch else None | |
| self.branch_2d = CausalConv2dBranch(config, d2[layer_idx % len(d2)]) if self.use_2d_branch else None | |
| self.packer = SpaceFillingPacker(config) if self.use_2d_branch else None | |
| self.fusion = config.fusion | |
| branch_count = int(self.use_1d_branch) + int(self.use_2d_branch) | |
| if self.fusion == "concat" and branch_count > 1: | |
| self.fuse_proj = nn.Linear(config.hidden_size * branch_count, config.hidden_size, bias=False) | |
| elif self.fusion == "gated" and branch_count > 1: | |
| self.fuse_gate = nn.Linear(config.hidden_size, branch_count, bias=True) | |
| else: | |
| self.fuse_proj = None | |
| self.fuse_gate = None | |
| self.branch_dropout = nn.Dropout(config.branch_dropout) | |
| self.gate_1d = nn.Parameter(torch.tensor(float(config.conv1d_residual_gate_init))) if self.use_1d_branch else None | |
| self.gate_2d = nn.Parameter(torch.tensor(float(config.conv2d_residual_gate_init))) if self.use_2d_branch else None | |
| self.post_conv_norm = ConvGPTV2RMSNorm(config.hidden_size, config.rms_norm_eps) | |
| self.mlp = ConvGPTV2MLP(config) | |
| self.use_retrieval = ( | |
| config.router_type != "none" | |
| and config.retrieval_every is not None | |
| and config.retrieval_every > 0 | |
| and (layer_idx + 1) % config.retrieval_every == 0 | |
| ) | |
| if self.use_retrieval and config.router_type == "topk_memory": | |
| self.router = TinyRetrievalRouter(config) | |
| elif self.use_retrieval and config.router_type == "chunk_memory": | |
| self.router = CausalChunkMemoryRouter(config) | |
| elif self.use_retrieval and config.router_type == "chunk_token_memory": | |
| self.router = CausalChunkTokenMemoryRouter(config) | |
| else: | |
| self.router = None | |
| def _fuse(self, hidden_states: torch.Tensor, named_branches: list[tuple[str, torch.Tensor]]) -> torch.Tensor: | |
| if len(named_branches) == 1: | |
| name, branch = named_branches[0] | |
| gate = self.gate_1d if name == "1d" else self.gate_2d | |
| return torch.sigmoid(gate).to(branch.dtype) * branch | |
| branches = [branch for _, branch in named_branches] | |
| if self.fusion == "sum": | |
| mixed = torch.stack(branches, dim=0).sum(dim=0) | |
| elif self.fusion == "concat": | |
| mixed = self.fuse_proj(torch.cat(branches, dim=-1)) | |
| else: | |
| gates = F.softmax(self.fuse_gate(hidden_states), dim=-1).to(hidden_states.dtype) | |
| mixed = hidden_states.new_zeros(hidden_states.shape) | |
| for idx, branch in enumerate(branches): | |
| mixed = mixed + gates[..., idx : idx + 1] * branch | |
| residual_scale = hidden_states.new_tensor(0.0) | |
| for name, _branch in named_branches: | |
| gate = self.gate_1d if name == "1d" else self.gate_2d | |
| residual_scale = residual_scale + torch.sigmoid(gate).to(hidden_states.dtype) | |
| residual_scale = residual_scale / len(named_branches) | |
| return residual_scale * mixed | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| return_post_conv: bool = False, | |
| ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: | |
| branches = [] | |
| if self.branch_1d is not None: | |
| branches.append(("1d", self.branch_1d(hidden_states))) | |
| if self.branch_2d is not None: | |
| active_cells = self.packer.seq_to_grid[: hidden_states.shape[1]] | |
| seq_delta = self.branch_2d.forward_sequence(hidden_states, active_cells) | |
| if seq_delta is not None: | |
| branches.append(("2d", seq_delta)) | |
| else: | |
| grid, seq_len = self.packer.seq_to_2d(hidden_states) | |
| grid_delta = self.branch_2d(grid) | |
| branches.append(("2d", self.packer.d2_to_seq(grid_delta, seq_len))) | |
| hidden_states = hidden_states + self.branch_dropout(self._fuse(hidden_states, branches)) | |
| post_conv_states = hidden_states | |
| if self.router is not None: | |
| hidden_states = hidden_states + self.router(hidden_states, position_ids=position_ids) | |
| hidden_states = hidden_states + self.mlp(self.post_conv_norm(hidden_states)) | |
| if return_post_conv: | |
| return hidden_states, post_conv_states | |
| return hidden_states | |
| def forward_token( | |
| self, | |
| hidden_states: torch.Tensor, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| post_conv_prefix: Optional[torch.Tensor] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Exact block output for the newest token only. | |
| Returns `(new_hidden_state, post_conv_hidden_state)` for the last token. | |
| The caller owns appending these one-token states to the cache. | |
| """ | |
| branches = [] | |
| if self.branch_1d is not None: | |
| branches.append(("1d", self.branch_1d.forward_token(hidden_states))) | |
| if self.branch_2d is not None: | |
| branches.append(("2d", self.branch_2d.forward_token(hidden_states, hidden_states.shape[1] - 1))) | |
| last_hidden = hidden_states[:, -1:, :] | |
| token_states = last_hidden + self.branch_dropout(self._fuse(last_hidden, branches)) | |
| post_conv_states = token_states | |
| if self.router is not None: | |
| router_input = token_states if post_conv_prefix is None else torch.cat( | |
| [post_conv_prefix.to(token_states.device), token_states], dim=1 | |
| ) | |
| token_states = token_states + self.router.forward_token(router_input, position_ids=position_ids) | |
| token_states = token_states + self.mlp(self.post_conv_norm(token_states)) | |
| return token_states, post_conv_states | |
| class ConvGPTV2PreTrainedModel(PreTrainedModel): | |
| config_class = ConvGPTV2Config | |
| base_model_prefix = "model" | |
| supports_gradient_checkpointing = True | |
| _no_split_modules = ["HybridConvGPTV2Block"] | |
| def _init_weights(self, module: nn.Module): | |
| std = self.config.initializer_range | |
| if isinstance(module, (nn.Linear, nn.Conv1d, nn.Conv2d)): | |
| module.weight.data.normal_(mean=0.0, std=std) | |
| if getattr(module, "bias", None) is not None: | |
| module.bias.data.zero_() | |
| elif isinstance(module, nn.Embedding): | |
| module.weight.data.normal_(mean=0.0, std=std) | |
| if module.padding_idx is not None: | |
| module.weight.data[module.padding_idx].zero_() | |
| class ConvGPTV2Model(ConvGPTV2PreTrainedModel): | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__(config) | |
| self.padding_idx = config.pad_token_id | |
| self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx) | |
| self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size) if config.position_embedding_type == "learned" else None | |
| if config.use_row_col_embeddings and config.position_embedding_type == "learned": | |
| self.row_embeddings = nn.Embedding(config.grid_size, config.hidden_size) | |
| self.col_embeddings = nn.Embedding(config.grid_size, config.hidden_size) | |
| seq_to_grid, _ = build_curve_indices(config.grid_size, config.packing, config.pack_order) | |
| row_ids = torch.div(seq_to_grid[: config.max_position_embeddings], config.grid_size, rounding_mode="floor") | |
| col_ids = seq_to_grid[: config.max_position_embeddings] % config.grid_size | |
| self.register_buffer("position_row_ids", row_ids, persistent=False) | |
| self.register_buffer("position_col_ids", col_ids, persistent=False) | |
| else: | |
| self.row_embeddings = None | |
| self.col_embeddings = None | |
| self.dropout = nn.Dropout(config.dropout) | |
| self.layers = nn.ModuleList( | |
| [HybridConvGPTV2Block(config, layer_idx) for layer_idx in range(config.num_hidden_layers)] | |
| ) | |
| self.norm = ConvGPTV2RMSNorm(config.hidden_size, config.rms_norm_eps) | |
| self.gradient_checkpointing = False | |
| self.post_init() | |
| def _embed_inputs( | |
| self, | |
| input_ids: Optional[torch.LongTensor], | |
| inputs_embeds: Optional[torch.FloatTensor], | |
| position_ids: Optional[torch.LongTensor], | |
| ) -> Tuple[torch.Tensor, torch.LongTensor]: | |
| if input_ids is not None and inputs_embeds is not None: | |
| raise ValueError("You cannot specify both input_ids and inputs_embeds") | |
| if input_ids is None and inputs_embeds is None: | |
| raise ValueError("You must specify input_ids or inputs_embeds") | |
| if inputs_embeds is None: | |
| inputs_embeds = self.embed_tokens(input_ids) | |
| bsz, seq_len, _ = inputs_embeds.shape | |
| if seq_len > self.config.max_position_embeddings: | |
| raise ValueError( | |
| f"Sequence length {seq_len} exceeds max_position_embeddings {self.config.max_position_embeddings}." | |
| ) | |
| if position_ids is None: | |
| position_ids = torch.arange(seq_len, device=inputs_embeds.device).unsqueeze(0).expand(bsz, -1) | |
| hidden_states = inputs_embeds | |
| if self.position_embeddings is not None: | |
| hidden_states = hidden_states + self.position_embeddings(position_ids) | |
| if self.row_embeddings is not None: | |
| rows = self.position_row_ids.to(inputs_embeds.device).index_select(0, position_ids.reshape(-1)).view_as(position_ids) | |
| cols = self.position_col_ids.to(inputs_embeds.device).index_select(0, position_ids.reshape(-1)).view_as(position_ids) | |
| hidden_states = hidden_states + self.row_embeddings(rows) + self.col_embeddings(cols) | |
| return self.dropout(hidden_states), position_ids | |
| def get_input_embeddings(self): | |
| return self.embed_tokens | |
| def set_input_embeddings(self, value): | |
| self.embed_tokens = value | |
| def forward( | |
| self, | |
| input_ids: Optional[torch.LongTensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| past_key_values: Optional[Tuple[torch.Tensor]] = None, | |
| inputs_embeds: Optional[torch.FloatTensor] = None, | |
| use_cache: Optional[bool] = None, | |
| output_attentions: Optional[bool] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| cache_position: Optional[torch.LongTensor] = None, | |
| ) -> Union[Tuple, BaseModelOutputWithPast]: | |
| output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions | |
| output_hidden_states = output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states | |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict | |
| use_cache = use_cache if use_cache is not None else self.config.use_cache | |
| hidden_states, position_ids = self._embed_inputs(input_ids, inputs_embeds, position_ids) | |
| all_hidden_states = () if output_hidden_states else None | |
| layer_inputs = [] if use_cache and not self.training else None | |
| layer_post_conv = [] if use_cache and not self.training else None | |
| for layer in self.layers: | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (hidden_states,) | |
| if layer_inputs is not None: | |
| layer_inputs.append(hidden_states.detach()) | |
| if self.gradient_checkpointing and self.training: | |
| hidden_states = self._gradient_checkpointing_func(layer.__call__, hidden_states) | |
| else: | |
| if layer_post_conv is not None: | |
| hidden_states, post_conv_states = layer(hidden_states, position_ids=position_ids, return_post_conv=True) | |
| layer_post_conv.append(post_conv_states.detach()) | |
| else: | |
| hidden_states = layer(hidden_states, position_ids=position_ids) | |
| if attention_mask is not None: | |
| hidden_states = hidden_states * attention_mask.to(hidden_states.dtype).unsqueeze(-1) | |
| hidden_states = self.norm(hidden_states) | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (hidden_states,) | |
| if not return_dict: | |
| return tuple(v for v in [hidden_states, None, all_hidden_states, None] if v is not None) | |
| return BaseModelOutputWithPast( | |
| last_hidden_state=hidden_states, | |
| past_key_values=ConvGPTV2PrefixCache( | |
| input_ids=input_ids.detach(), | |
| attention_mask=attention_mask.detach() if attention_mask is not None else None, | |
| layer_inputs=tuple(layer_inputs), | |
| layer_post_conv=tuple(layer_post_conv), | |
| ) if layer_inputs is not None and input_ids is not None else None, | |
| hidden_states=all_hidden_states, | |
| attentions=None if output_attentions else None, | |
| ) | |
| def incremental_forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| cache: ConvGPTV2PrefixCache, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| output_hidden_states: bool = False, | |
| output_attentions: bool = False, | |
| return_dict: bool = True, | |
| ) -> Union[Tuple, BaseModelOutputWithPast]: | |
| if input_ids.shape[1] != 1: | |
| raise ValueError("ConvGPT-v2 incremental_forward expects exactly one new token") | |
| if not cache.has_incremental_state: | |
| raise ValueError("ConvGPT-v2 incremental_forward requires per-layer cache state") | |
| if cache.layer_inputs is None or cache.layer_post_conv is None: | |
| raise ValueError("ConvGPT-v2 incremental_forward requires complete cache state") | |
| if len(cache.layer_inputs) != len(self.layers) or len(cache.layer_post_conv) != len(self.layers): | |
| raise ValueError("ConvGPT-v2 cache layer count does not match model layer count") | |
| bsz = input_ids.shape[0] | |
| prefix_len = cache.input_ids.shape[1] | |
| full_len = prefix_len + 1 | |
| if full_len > self.config.max_position_embeddings: | |
| raise ValueError( | |
| f"Sequence length {full_len} exceeds max_position_embeddings {self.config.max_position_embeddings}." | |
| ) | |
| if position_ids is None: | |
| position_ids = torch.full((bsz, 1), prefix_len, device=input_ids.device, dtype=torch.long) | |
| token_states, _ = self._embed_inputs(input_ids, None, position_ids) | |
| new_layer_inputs = [] | |
| new_layer_post_conv = [] | |
| all_hidden_states = () if output_hidden_states else None | |
| for layer_idx, layer in enumerate(self.layers): | |
| layer_input_full = torch.cat([cache.layer_inputs[layer_idx].to(token_states.device), token_states], dim=1) | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (layer_input_full,) | |
| token_states, post_conv_token = layer.forward_token( | |
| layer_input_full, | |
| position_ids=None, | |
| post_conv_prefix=cache.layer_post_conv[layer_idx], | |
| ) | |
| if attention_mask is not None: | |
| token_mask = attention_mask[:, -1:].to(token_states.device) | |
| token_states = token_states * token_mask.to(token_states.dtype).unsqueeze(-1) | |
| post_conv_token = post_conv_token * token_mask.to(post_conv_token.dtype).unsqueeze(-1) | |
| new_layer_inputs.append(layer_input_full.detach()) | |
| new_layer_post_conv.append(torch.cat([cache.layer_post_conv[layer_idx].to(post_conv_token.device), post_conv_token], dim=1).detach()) | |
| hidden_states = self.norm(token_states) | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (hidden_states,) | |
| next_cache = ConvGPTV2PrefixCache( | |
| input_ids=torch.cat([cache.input_ids.to(input_ids.device), input_ids], dim=1).detach(), | |
| attention_mask=attention_mask.detach() if attention_mask is not None else None, | |
| layer_inputs=tuple(new_layer_inputs), | |
| layer_post_conv=tuple(new_layer_post_conv), | |
| ) | |
| if not return_dict: | |
| return tuple(v for v in [hidden_states, next_cache, all_hidden_states, None] if v is not None) | |
| return BaseModelOutputWithPast( | |
| last_hidden_state=hidden_states, | |
| past_key_values=next_cache, | |
| hidden_states=all_hidden_states, | |
| attentions=None if output_attentions else None, | |
| ) | |
| def build_prefix_cache( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| ) -> ConvGPTV2PrefixCache: | |
| hidden_states, position_ids = self._embed_inputs(input_ids, None, position_ids) | |
| layer_inputs = [] | |
| layer_post_conv = [] | |
| for layer in self.layers: | |
| layer_inputs.append(hidden_states.detach()) | |
| branches = [] | |
| if layer.branch_1d is not None: | |
| branches.append(("1d", layer.branch_1d(hidden_states))) | |
| if layer.branch_2d is not None: | |
| active_cells = layer.packer.seq_to_grid[: hidden_states.shape[1]] | |
| seq_delta = layer.branch_2d.forward_sequence(hidden_states, active_cells) | |
| if seq_delta is not None: | |
| branches.append(("2d", seq_delta)) | |
| else: | |
| grid, seq_len = layer.packer.seq_to_2d(hidden_states) | |
| grid_delta = layer.branch_2d(grid) | |
| branches.append(("2d", layer.packer.d2_to_seq(grid_delta, seq_len))) | |
| hidden_states = hidden_states + layer.branch_dropout(layer._fuse(hidden_states, branches)) | |
| layer_post_conv.append(hidden_states.detach()) | |
| if layer.router is not None: | |
| hidden_states = hidden_states + layer.router(hidden_states, position_ids=position_ids) | |
| hidden_states = hidden_states + layer.mlp(layer.post_conv_norm(hidden_states)) | |
| if attention_mask is not None: | |
| hidden_states = hidden_states * attention_mask.to(hidden_states.dtype).unsqueeze(-1) | |
| return ConvGPTV2PrefixCache( | |
| input_ids=input_ids.detach(), | |
| attention_mask=attention_mask.detach() if attention_mask is not None else None, | |
| layer_inputs=tuple(layer_inputs), | |
| layer_post_conv=tuple(layer_post_conv), | |
| ) | |
| class ConvGPTV2ForCausalLM(ConvGPTV2PreTrainedModel, GenerationMixin): | |
| _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"} | |
| _supports_cache_class = False | |
| def __init__(self, config: ConvGPTV2Config): | |
| super().__init__(config) | |
| self.model = ConvGPTV2Model(config) | |
| self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) | |
| self.post_init() | |
| def get_input_embeddings(self): | |
| return self.model.get_input_embeddings() | |
| def set_input_embeddings(self, value): | |
| self.model.set_input_embeddings(value) | |
| def get_output_embeddings(self): | |
| return self.lm_head | |
| def set_output_embeddings(self, new_embeddings): | |
| self.lm_head = new_embeddings | |
| def set_decoder(self, decoder): | |
| self.model = decoder | |
| def get_decoder(self): | |
| return self.model | |
| def forward( | |
| self, | |
| input_ids: Optional[torch.LongTensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| past_key_values: Optional[Tuple[torch.Tensor]] = None, | |
| inputs_embeds: Optional[torch.FloatTensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| use_cache: Optional[bool] = None, | |
| output_attentions: Optional[bool] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| cache_position: Optional[torch.LongTensor] = None, | |
| logits_to_keep: Union[int, torch.Tensor] = 0, | |
| **loss_kwargs, | |
| ) -> Union[Tuple, CausalLMOutputWithPast]: | |
| return_dict = return_dict if return_dict is not None else self.config.use_return_dict | |
| use_cache = use_cache if use_cache is not None else self.config.use_cache | |
| incoming_cache = past_key_values | |
| ran_incremental = False | |
| if use_cache and incoming_cache is not None and input_ids is not None: | |
| if not isinstance(incoming_cache, ConvGPTV2PrefixCache): | |
| incoming_cache = ConvGPTV2PrefixCache.from_legacy_cache(incoming_cache) | |
| if attention_mask is None and incoming_cache.attention_mask is not None: | |
| attention_mask = incoming_cache.attention_mask.to(input_ids.device) | |
| if input_ids.shape[1] == 1 and incoming_cache.has_incremental_state and inputs_embeds is None and labels is None and not self.training: | |
| outputs = self.model.incremental_forward( | |
| input_ids=input_ids, | |
| cache=incoming_cache, | |
| attention_mask=attention_mask, | |
| position_ids=position_ids, | |
| output_hidden_states=bool(output_hidden_states), | |
| output_attentions=bool(output_attentions), | |
| return_dict=return_dict, | |
| ) | |
| ran_incremental = True | |
| else: | |
| prefix_ids = incoming_cache.input_ids.to(input_ids.device) | |
| if input_ids.shape[1] > 0: | |
| # Fallback path for legacy/unsupported cache states: rebuild | |
| # the exact full prefix. | |
| if input_ids.shape[1] == 1 or not torch.equal(input_ids[:, : prefix_ids.shape[1]], prefix_ids): | |
| input_ids = torch.cat([prefix_ids, input_ids], dim=1) | |
| if not ran_incremental: | |
| outputs = self.model( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| position_ids=position_ids, | |
| past_key_values=past_key_values, | |
| inputs_embeds=inputs_embeds, | |
| use_cache=use_cache, | |
| output_attentions=output_attentions, | |
| output_hidden_states=output_hidden_states, | |
| return_dict=return_dict, | |
| cache_position=cache_position, | |
| ) | |
| hidden_states = outputs[0] | |
| if isinstance(logits_to_keep, int) and logits_to_keep > 0: | |
| hidden_states_for_logits = hidden_states[:, -logits_to_keep:, :] | |
| elif torch.is_tensor(logits_to_keep): | |
| hidden_states_for_logits = hidden_states[:, logits_to_keep, :] | |
| else: | |
| hidden_states_for_logits = hidden_states | |
| logits = self.lm_head(hidden_states_for_logits) | |
| loss = None | |
| if labels is not None: | |
| if logits.shape[1] != labels.shape[1]: | |
| # logits_to_keep is an inference optimization; for training labels | |
| # we need full logits to align next-token loss. | |
| logits = self.lm_head(hidden_states) | |
| loss = self.loss_function( | |
| logits=logits, | |
| labels=labels, | |
| vocab_size=self.config.vocab_size, | |
| **loss_kwargs, | |
| ) | |
| num_items = loss_kwargs.get("num_items_in_batch") | |
| if num_items is not None: | |
| loss = loss / num_items.to(loss.device) | |
| next_cache = None | |
| if use_cache: | |
| if ran_incremental: | |
| next_cache = outputs.past_key_values if return_dict else outputs[1] | |
| elif input_ids is None: | |
| raise ValueError("ConvGPT-v2 cache generation requires input_ids; inputs_embeds-only caching is unsupported.") | |
| elif not self.training: | |
| model_cache = outputs.past_key_values if return_dict and hasattr(outputs, "past_key_values") else None | |
| if isinstance(model_cache, ConvGPTV2PrefixCache): | |
| next_cache = model_cache | |
| else: | |
| next_cache = self.model.build_prefix_cache(input_ids, attention_mask=attention_mask, position_ids=position_ids) | |
| else: | |
| next_cache = ConvGPTV2PrefixCache(input_ids=input_ids.detach(), attention_mask=attention_mask.detach() if attention_mask is not None else None) | |
| if not return_dict: | |
| output = (logits,) + outputs[1:] | |
| if use_cache: | |
| output = output[:1] + (next_cache,) + output[1:] | |
| return ((loss,) + output) if loss is not None else output | |
| return CausalLMOutputWithPast( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=next_cache, | |
| hidden_states=outputs.hidden_states, | |
| attentions=outputs.attentions, | |
| ) | |
| def _supports_default_dynamic_cache(cls) -> bool: | |
| return False | |
| def prepare_inputs_for_generation( | |
| self, | |
| input_ids, | |
| past_key_values=None, | |
| attention_mask=None, | |
| inputs_embeds=None, | |
| cache_position=None, | |
| **kwargs, | |
| ): | |
| # Cache stores the accepted prefix plus layer states. After prefill, | |
| # pass only the newest token; forward uses the incremental layer cache | |
| # when available and falls back to exact full-prefix recompute otherwise. | |
| if past_key_values is not None and input_ids is not None: | |
| model_inputs = {"input_ids": input_ids[:, -1:]} | |
| else: | |
| model_inputs = {"input_ids": input_ids} | |
| if inputs_embeds is not None and past_key_values is None: | |
| model_inputs = {"inputs_embeds": inputs_embeds} | |
| model_inputs.update( | |
| { | |
| "past_key_values": past_key_values, | |
| "use_cache": kwargs.get("use_cache", self.config.use_cache), | |
| "attention_mask": attention_mask, | |
| } | |
| ) | |
| return model_inputs | |
| def _reorder_cache(self, past_key_values, beam_idx): | |
| if past_key_values is None: | |
| return None | |
| if isinstance(past_key_values, ConvGPTV2PrefixCache): | |
| return past_key_values.reorder_cache(beam_idx) | |
| return ConvGPTV2PrefixCache.from_legacy_cache(past_key_values).reorder_cache(beam_idx) | |
| __all__ = [ | |
| "ConvGPTV2Config", | |
| "ConvGPTV2Model", | |
| "ConvGPTV2ForCausalLM", | |
| "ConvGPTV2PreTrainedModel", | |
| "build_curve_indices", | |
| ] | |