CoolFace
Modelpublic

Premchan369/Q-TensorFormer

sourceHugging Faceapache-2.0updated 9d agoView on Hugging Face
2likes185downloads
tensor_layers.py301 linesDownload Raw Back to src
1"""2Tensor-Train decomposed linear layers with Nested-Core Dynamic Execution.3 4Architecture:5  Replaces dense linear matrix W with d TT-cores:6    W_{i1...ik, o1...ok} = G^(1) · G^(2) ··· G^(d)7 8Key Innovations:9  1. Nested-Core Dynamic Execution:10     Allocates cores up to max_rank (default 8). At runtime, dynamically11     slices cores to active rank r in {1, 2, 4, 8} with ZERO runtime SVD overhead!12  2. Memory Traffic Instrumentation:13     Tracks actual bytes read (weights + inputs) and bytes written (activations)14     per forward pass for true hardware bandwidth optimization.15  3. Batched Vectorized Contraction:16     Hardware-efficient einsum/bmm contraction path.17"""18 19import torch20import torch.nn as nn21import torch.nn.functional as F22import math23from typing import Tuple, Optional, Dict, List24 25 26def factorize_dim(dim: int, max_factors: int = 4) -> Tuple[int, ...]:27    """28    Factorize a dimension into factors >= 2 to avoid trivial padding cores.29    """30    if dim <= 1:31        return (1,)32    factors = []33    remaining = dim34    for p in [2, 2, 3, 2, 5, 2, 3, 7]:35        while remaining % p == 0 and len(factors) < max_factors - 1:36            factors.append(p)37            remaining //= p38        if remaining == 1:39            break40    if remaining > 1 and len(factors) < max_factors:41        factors.append(remaining)42    while len(factors) < 2:43        val = factors[0] if factors else dim44        root = int(math.isqrt(val))45        for d in range(root, 1, -1):46            if val % d == 0:47                factors = [d, val // d]48                break49        else:50            factors = [1, val]51    return tuple(factors[:max_factors])52 53 54class TTLinear(nn.Module):55    """56    Tensor-Train decomposed linear layer with nested rank slicing.57    """58 59    def __init__(60        self,61        in_features: int,62        out_features: int,63        max_rank: int = 8,64        bias: bool = True,65        rank: Optional[int] = None,66        **kwargs,67    ):68        super().__init__()69        if rank is not None:70            effective_max_rank = max(max_rank, rank, 8)71            init_active = rank72        else:73            effective_max_rank = max(max_rank, 8)74            init_active = max_rank75 76        self.in_features = in_features77        self.out_features = out_features78        self.max_rank = effective_max_rank79        self.active_rank = init_active80 81        # Factorize input and output dimensions82        in_factors = list(factorize_dim(in_features))83        out_factors = list(factorize_dim(out_features))84        self.ndim = max(len(in_factors), len(out_factors))85 86        while len(in_factors) < self.ndim:87            in_factors.append(1)88        while len(out_factors) < self.ndim:89            out_factors.append(1)90 91        self.in_shape = tuple(in_factors)92        self.out_shape = tuple(out_factors)93 94        # Allocate nested TT-cores: G^(k) of shape (r_k, out_k, in_k, r_{k+1})95        self.cores = nn.ParameterList()96        for k in range(self.ndim):97            r_left = 1 if k == 0 else max_rank98            r_right = 1 if k == self.ndim - 1 else max_rank99            core = torch.empty(r_left, out_factors[k], in_factors[k], r_right)100            fan = max(1, r_left * in_factors[k] + r_right * out_factors[k])101            bound = math.sqrt(6.0 / fan)102            nn.init.uniform_(core, -bound, bound)103            self.cores.append(core)104 105        self.bias = nn.Parameter(torch.zeros(out_features)) if bias else None106 107        # Statistics108        self.dense_params = in_features * out_features109        self._last_bytes_read = 0110        self._last_bytes_written = 0111 112    @property113    def rank(self) -> int:114        return self.active_rank115 116    @rank.setter117    def rank(self, value: int):118        self.set_rank(value)119 120    def set_rank(self, rank: int):121        """Set active rank for nested core slicing via zero-copy tensor stride extraction."""122        self.active_rank = max(1, min(self.max_rank, int(rank)))123 124    def _slice_cores_for_rank(self, r: int) -> List[torch.Tensor]:125        """Slice nested cores according to specified rank."""126        sliced_cores = []127        for k, core in enumerate(self.cores):128            r_left = 1 if k == 0 else min(r, core.shape[0])129            r_right = 1 if k == self.ndim - 1 else min(r, core.shape[3])130            sliced_cores.append(core[:r_left, :, :, :r_right])131        return sliced_cores132 133    def get_active_cores(self) -> List[torch.Tensor]:134        """Slice nested cores according to current active rank."""135        return self._slice_cores_for_rank(self.active_rank)136 137    def measure_slicing_overhead_us(self, n_runs: int = 1000) -> float:138        """Empirically measure microsecond overhead of core slice extraction."""139        import time140        t0 = time.perf_counter()141        for _ in range(n_runs):142            _ = self._slice_cores_for_rank(4)143        return ((time.perf_counter() - t0) / n_runs) * 1e6144 145    @property146    def active_param_count(self) -> int:147        """Parameters actively used under current rank."""148        cores = self.get_active_cores()149        total = sum(c.numel() for c in cores)150        if self.bias is not None:151            total += self.bias.numel()152        return total153 154    @property155    def compression_ratio(self) -> float:156        """Effective compression ratio vs dense linear layer."""157        return self.dense_params / max(1, self.active_param_count)158 159    def _contract_with_cores(self, x_flat: torch.Tensor, cores: List[torch.Tensor]) -> torch.Tensor:160        """Contract 2D input (N, in_features) through given sliced cores."""161        N = x_flat.shape[0]162        state = x_flat.reshape(N, *self.in_shape)163        for k in range(self.ndim):164            core = cores[k]165            r_k, o_k, i_k, r_kp1 = core.shape166            if k == 0:167                rest = math.prod(self.in_shape[1:]) if self.ndim > 1 else 1168                s = state.reshape(N, i_k, rest)169                cm = core.squeeze(0).permute(1, 0, 2).reshape(i_k, o_k * r_kp1)170                s = torch.bmm(s.transpose(1, 2), cm.unsqueeze(0).expand(N, -1, -1))171                s = s.reshape(N, rest, o_k, r_kp1).permute(0, 3, 2, 1)172                state = s.reshape(N, r_kp1, -1)173            elif k == self.ndim - 1:174                prev_os = math.prod(self.out_shape[:k]) if k > 0 else 1175                s = state.reshape(N, r_k, prev_os, i_k)176                cm = core.squeeze(-1)177                s = torch.einsum("brpi,roi->bpo", s, cm)178                state = s.reshape(N, prev_os * o_k)179            else:180                prev_os = math.prod(self.out_shape[:k]) if k > 0 else 1181                rest_in = math.prod(self.in_shape[k + 1:])182                s = state.reshape(N, r_k, prev_os, i_k, rest_in)183                s = torch.einsum("brpix,roiq->bpoqx", s, core)184                s = s.permute(0, 3, 1, 2, 4)185                state = s.reshape(N, r_kp1, prev_os * o_k * rest_in)186        out = state.reshape(N, self.out_features)187        if self.bias is not None:188            out = out + self.bias189        return out190 191    def to_matrix(self, rank: Optional[int] = None) -> torch.Tensor:192        """Reconstruct the effective dense weight matrix (out_features, in_features) for rank r."""193        r = self.active_rank if rank is None else rank194        cores = self._slice_cores_for_rank(r)195        device = self.cores[0].device196        dtype = self.cores[0].dtype197        eye = torch.eye(self.in_features, device=device, dtype=dtype)198        bias_backup = self.bias199        self.bias = None200        try:201            wt = self._contract_with_cores(eye, cores)202        finally:203            self.bias = bias_backup204        return wt.t()205 206 207    def forward(self, x: torch.Tensor, token_ranks: Optional[torch.Tensor] = None) -> torch.Tensor:208        """209        Forward pass with nested core contraction. Supports token-grouped dynamic rank execution.210        """211        batch_shape = x.shape[:-1]212        B = math.prod(batch_shape) if batch_shape else 1213        x_flat = x.reshape(B, self.in_features)214 215        if token_ranks is not None and token_ranks.numel() == B:216            flat_ranks = token_ranks.reshape(-1)217            out = torch.empty(B, self.out_features, device=x.device, dtype=x.dtype)218            total_read = 0219            for r in flat_ranks.unique():220                r_int = int(r.item())221                idx = (flat_ranks == r).nonzero(as_tuple=True)[0]222                cores_r = self._slice_cores_for_rank(r_int)223                out[idx] = self._contract_with_cores(x_flat[idx], cores_r)224                total_read += sum(c.numel() * c.element_size() for c in cores_r) + x_flat[idx].numel() * x_flat.element_size()225            self._last_bytes_read = total_read226            self._last_bytes_written = out.numel() * out.element_size()227            return out.reshape(*batch_shape, self.out_features)228        else:229            cores = self.get_active_cores()230            self._last_bytes_read = sum(c.numel() * c.element_size() for c in cores) + x_flat.numel() * x_flat.element_size()231            out = self._contract_with_cores(x_flat, cores)232            self._last_bytes_written = out.numel() * out.element_size()233            return out.reshape(*batch_shape, self.out_features)234 235    def flops(self, batch_size: int = 1, seq_len: int = 1) -> int:236        """Compute active FLOPs for current rank."""237        r = self.active_rank238        avg_dim = (sum(self.in_shape) + sum(self.out_shape)) / (2 * self.ndim)239        return int(2 * r**2 * self.ndim * avg_dim * batch_size * seq_len)240 241    def get_memory_traffic(self) -> Dict[str, int]:242        """Return bytes read and written in last forward pass."""243        return {244            "bytes_read": self._last_bytes_read,245            "bytes_written": self._last_bytes_written,246            "total_bytes": self._last_bytes_read + self._last_bytes_written,247        }248 249 250class TTFeedForward(nn.Module):251    """252    Tensor-Train Feed-Forward Network with nested dynamic ranks.253    """254 255    def __init__(256        self,257        hidden_dim: int,258        ff_multiplier: int = 4,259        rank: int = 8,260        activation=F.gelu,261    ):262        super().__init__()263        self.hidden_dim = hidden_dim264        self.expanded_dim = hidden_dim * ff_multiplier265        self.max_rank = rank266 267        self.up_proj = TTLinear(hidden_dim, self.expanded_dim, max_rank=rank, bias=True)268        self.down_proj = TTLinear(self.expanded_dim, hidden_dim, max_rank=rank, bias=True)269        self.activation = activation270 271    def forward(self, x: torch.Tensor, token_ranks: Optional[torch.Tensor] = None) -> torch.Tensor:272        return self.down_proj(self.activation(self.up_proj(x, token_ranks=token_ranks)), token_ranks=token_ranks)273 274    def set_rank(self, rank: int):275        self.up_proj.set_rank(rank)276        self.down_proj.set_rank(rank)277 278    @property279    def rank(self) -> int:280        return self.up_proj.active_rank281 282    @property283    def total_params(self) -> int:284        return sum(p.numel() for p in self.parameters())285 286    @property287    def active_params(self) -> int:288        return self.up_proj.active_param_count + self.down_proj.active_param_count289 290    def flops(self, batch_size: int = 1, seq_len: int = 1) -> int:291        return self.up_proj.flops(batch_size, seq_len) + self.down_proj.flops(batch_size, seq_len)292 293    def get_memory_traffic(self) -> Dict[str, int]:294        up = self.up_proj.get_memory_traffic()295        down = self.down_proj.get_memory_traffic()296        return {297            "bytes_read": up["bytes_read"] + down["bytes_read"],298            "bytes_written": up["bytes_written"] + down["bytes_written"],299            "total_bytes": up["total_bytes"] + down["total_bytes"],300        }301