Download tiny_gdn/fused_ops.py from KerzgrAI/Monody-Exp: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/fused_ops.py
- Command line
-
hf download hf://KerzgrAI/Monody-Exp/tiny_gdn/fused_ops.py
-
curl -L -o fused_ops.py https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/fused_ops.py
21.5 kB
| """Fused CUDA kernels for memory-bound blocks: SiTU-GLU, the LM loss, token log-probs.""" | |
| from __future__ import annotations | |
| import torch | |
| import torch.nn.functional as F | |
| import triton | |
| import triton.language as tl | |
| from triton.language.extra import libdevice | |
| from tiny_gdn.nn_common import ( | |
| linear_cross_entropy_reference, | |
| linear_token_logprobs_reference, | |
| situ_glu_reference, | |
| ) | |
| BLOCK_SIZE = 1024 | |
| VOCAB_BLOCK_SIZE = 4096 | |
| LOGITS_CHUNK_BYTES = 1 << 30 | |
| def _situ_glu_forward_kernel( | |
| gate_up, | |
| output, | |
| total, | |
| width, | |
| gate_cap, | |
| up_cap, | |
| BLOCK: tl.constexpr, | |
| ): | |
| offsets = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK) | |
| mask = offsets < total | |
| row = offsets // width | |
| gate_offsets = offsets + row * width | |
| gate = tl.load(gate_up + gate_offsets, mask=mask, other=0.0).to(tl.float32) | |
| up = tl.load(gate_up + gate_offsets + width, mask=mask, other=0.0).to(tl.float32) | |
| gated = gate_cap * libdevice.tanh(gate / gate_cap) * tl.sigmoid(gate) | |
| result = gated * up_cap * libdevice.tanh(up / up_cap) | |
| tl.store(output + offsets, result.to(output.dtype.element_ty), mask=mask) | |
| def _situ_glu_backward_kernel( | |
| gate_up, | |
| grad_output, | |
| grad_gate_up, | |
| total, | |
| width, | |
| gate_cap, | |
| up_cap, | |
| BLOCK: tl.constexpr, | |
| ): | |
| offsets = tl.program_id(0).to(tl.int64) * BLOCK + tl.arange(0, BLOCK) | |
| mask = offsets < total | |
| row = offsets // width | |
| gate_offsets = offsets + row * width | |
| gate = tl.load(gate_up + gate_offsets, mask=mask, other=0.0).to(tl.float32) | |
| up = tl.load(gate_up + gate_offsets + width, mask=mask, other=0.0).to(tl.float32) | |
| grad = tl.load(grad_output + offsets, mask=mask, other=0.0).to(tl.float32) | |
| gate_tanh = libdevice.tanh(gate / gate_cap) | |
| up_tanh = libdevice.tanh(up / up_cap) | |
| sigmoid = tl.sigmoid(gate) | |
| gated = gate_cap * gate_tanh * sigmoid | |
| capped_up = up_cap * up_tanh | |
| gate_slope = sigmoid * ((1.0 - gate_tanh * gate_tanh) + gate_cap * gate_tanh * (1.0 - sigmoid)) | |
| grad_gate = grad * capped_up * gate_slope | |
| grad_up = grad * gated * (1.0 - up_tanh * up_tanh) | |
| dtype = grad_gate_up.dtype.element_ty | |
| tl.store(grad_gate_up + gate_offsets, grad_gate.to(dtype), mask=mask) | |
| tl.store(grad_gate_up + gate_offsets + width, grad_up.to(dtype), mask=mask) | |
| def _launch_grid(total: int) -> tuple[int]: | |
| return (triton.cdiv(total, BLOCK_SIZE),) | |
| class _SiTUGLUFunction(torch.autograd.Function): | |
| def forward(ctx, gate_up: torch.Tensor, gate_cap: float, up_cap: float) -> torch.Tensor: | |
| gate_up = gate_up.contiguous() | |
| width = gate_up.shape[-1] // 2 | |
| output = gate_up.new_empty((*gate_up.shape[:-1], width)) | |
| total = output.numel() | |
| if total: | |
| _situ_glu_forward_kernel[_launch_grid(total)]( | |
| gate_up, output, total, width, gate_cap, up_cap, BLOCK=BLOCK_SIZE | |
| ) | |
| ctx.save_for_backward(gate_up) | |
| ctx.caps = (gate_cap, up_cap) | |
| return output | |
| def backward(ctx, grad_output: torch.Tensor) -> tuple[torch.Tensor, None, None]: | |
| (gate_up,) = ctx.saved_tensors | |
| gate_cap, up_cap = ctx.caps | |
| grad_output = grad_output.contiguous() | |
| grad_gate_up = torch.empty_like(gate_up) | |
| width = gate_up.shape[-1] // 2 | |
| total = grad_output.numel() | |
| if total: | |
| _situ_glu_backward_kernel[_launch_grid(total)]( | |
| gate_up, | |
| grad_output, | |
| grad_gate_up, | |
| total, | |
| width, | |
| gate_cap, | |
| up_cap, | |
| BLOCK=BLOCK_SIZE, | |
| ) | |
| return grad_gate_up, None, None | |
| def situ_glu(gate_up: torch.Tensor, gate_cap: float, up_cap: float) -> torch.Tensor: | |
| """``softcap(gate) * sigmoid(gate) * softcap(up)`` over ``[..., gate | up]``. | |
| The CUDA path evaluates in FP32 and rounds once; backward recomputes the | |
| activations from ``gate_up`` instead of saving intermediates. | |
| """ | |
| if gate_up.shape[-1] % 2: | |
| raise ValueError("gate_up must have an even last dimension") | |
| if not gate_up.is_cuda: | |
| return situ_glu_reference(gate_up, gate_cap, up_cap) | |
| return _SiTUGLUFunction.apply(gate_up, gate_cap, up_cap) | |
| def _cross_entropy_rows_kernel( | |
| logits, | |
| targets, | |
| losses, | |
| log_partitions, | |
| valid_count, | |
| row_stride, | |
| vocab_size, | |
| z_loss_coefficient, | |
| ignore_index, | |
| COMPUTE_GRAD: tl.constexpr, | |
| BLOCK: tl.constexpr, | |
| ): | |
| """Per row: logsumexp and NLL; optionally overwrite the row with d(mean loss)/d(logits).""" | |
| row = tl.program_id(0).to(tl.int64) | |
| row_logits = logits + row * row_stride | |
| target = tl.load(targets + row) | |
| columns = tl.arange(0, BLOCK) | |
| if target == ignore_index: | |
| tl.store(losses + row, 0.0) | |
| tl.store(log_partitions + row, 0.0) | |
| if COMPUTE_GRAD: | |
| for start in range(0, vocab_size, BLOCK): | |
| offsets = start + columns | |
| zeros = tl.zeros((BLOCK,), dtype=logits.dtype.element_ty) | |
| tl.store(row_logits + offsets, zeros, mask=offsets < vocab_size) | |
| else: | |
| running_max = tl.full((), float("-inf"), tl.float32) | |
| running_sum = tl.zeros((), dtype=tl.float32) | |
| for start in range(0, vocab_size, BLOCK): | |
| offsets = start + columns | |
| values = tl.load( | |
| row_logits + offsets, mask=offsets < vocab_size, other=float("-inf") | |
| ).to(tl.float32) | |
| new_max = tl.maximum(running_max, tl.max(values, axis=0)) | |
| running_sum = running_sum * tl.exp(running_max - new_max) + tl.sum( | |
| tl.exp(values - new_max), axis=0 | |
| ) | |
| running_max = new_max | |
| log_partition = running_max + tl.log(running_sum) | |
| target_logit = tl.load(row_logits + target).to(tl.float32) | |
| tl.store(losses + row, log_partition - target_logit) | |
| tl.store(log_partitions + row, log_partition) | |
| if COMPUTE_GRAD: | |
| grad_scale = 1.0 / tl.load(valid_count).to(tl.float32) | |
| softmax_scale = grad_scale * (1.0 + 2.0 * z_loss_coefficient * log_partition) | |
| for start in range(0, vocab_size, BLOCK): | |
| offsets = start + columns | |
| in_vocab = offsets < vocab_size | |
| values = tl.load(row_logits + offsets, mask=in_vocab, other=0.0).to(tl.float32) | |
| grad = tl.exp(values - log_partition) * softmax_scale | |
| grad = tl.where(offsets == target, grad - grad_scale, grad) | |
| tl.store(row_logits + offsets, grad.to(logits.dtype.element_ty), mask=in_vocab) | |
| class _LinearCrossEntropyFunction(torch.autograd.Function): | |
| """Chunked ``states @ weight.T`` + cross-entropy + z-loss with gradients precomputed. | |
| Each logits chunk is consumed in place, so peak extra memory is one chunk | |
| instead of ``[tokens, vocab]`` logits plus their softmax for backward. | |
| """ | |
| def forward( | |
| ctx, | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| z_loss_coefficient: float, | |
| ignore_index: int, | |
| compute_grad: bool, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| states = states.contiguous() | |
| targets = targets.contiguous() | |
| rows, vocab_size = states.shape[0], weight.shape[0] | |
| valid = targets.ne(ignore_index) | |
| valid_count = valid.sum(dtype=torch.float32) | |
| losses = states.new_empty(rows, dtype=torch.float32) | |
| log_partitions = states.new_empty(rows, dtype=torch.float32) | |
| grad_states = torch.empty_like(states) if compute_grad else None | |
| grad_weight = ( | |
| torch.zeros(weight.shape, dtype=torch.float32, device=weight.device) | |
| if compute_grad | |
| else None | |
| ) | |
| chunk_rows = max(1, LOGITS_CHUNK_BYTES // (vocab_size * states.element_size())) | |
| for start in range(0, rows, chunk_rows): | |
| end = min(start + chunk_rows, rows) | |
| chunk = states[start:end] | |
| logits = chunk @ weight.t() | |
| _cross_entropy_rows_kernel[(end - start,)]( | |
| logits, | |
| targets[start:end], | |
| losses[start:end], | |
| log_partitions[start:end], | |
| valid_count, | |
| logits.stride(0), | |
| vocab_size, | |
| z_loss_coefficient, | |
| ignore_index, | |
| COMPUTE_GRAD=compute_grad, | |
| BLOCK=VOCAB_BLOCK_SIZE, | |
| num_warps=8, | |
| ) | |
| if compute_grad: | |
| torch.mm(logits, weight, out=grad_states[start:end]) | |
| if chunk.dtype == torch.float32: | |
| grad_weight.addmm_(logits.t(), chunk) | |
| else: | |
| torch.addmm( | |
| grad_weight, logits.t(), chunk, out_dtype=torch.float32, out=grad_weight | |
| ) | |
| cross_entropy = losses.sum() / valid_count | |
| z_loss = log_partitions.square().sum() / valid_count | |
| objective = cross_entropy + z_loss_coefficient * z_loss | |
| if compute_grad: | |
| ctx.save_for_backward(grad_states, grad_weight) | |
| ctx.weight_dtype = weight.dtype | |
| ctx.mark_non_differentiable(cross_entropy, z_loss) | |
| return objective, cross_entropy, z_loss | |
| def backward( | |
| ctx, grad_objective: torch.Tensor, *_: torch.Tensor | |
| ) -> tuple[torch.Tensor, torch.Tensor, None, None, None, None]: | |
| grad_states, grad_weight = ctx.saved_tensors | |
| scale = grad_objective.to(torch.float32) | |
| grad_states = (grad_states * scale).to(grad_states.dtype) | |
| grad_weight = (grad_weight * scale).to(ctx.weight_dtype) | |
| return grad_states, grad_weight, None, None, None, None | |
| class _WeightedLinearCrossEntropyFunction(torch.autograd.Function): | |
| """``Σ_i w_i (CE_i + z log Z_i²)`` over chunked ``states @ weight.T``, gradients precomputed. | |
| The row kernel runs with unit scale and each row's gradient is then | |
| multiplied by its weight, so per-token weights cost one in-place multiply. | |
| """ | |
| def forward( | |
| ctx, | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| token_weights: torch.Tensor, | |
| z_loss_coefficient: float, | |
| compute_grad: bool, | |
| excluded_token_id: int | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| states = states.contiguous() | |
| targets = targets.contiguous() | |
| token_weights = token_weights.float().contiguous() | |
| rows, vocab_size = states.shape[0], weight.shape[0] | |
| unit = torch.ones((), dtype=torch.float32, device=states.device) | |
| losses = states.new_empty(rows, dtype=torch.float32) | |
| log_partitions = states.new_empty(rows, dtype=torch.float32) | |
| grad_states = torch.empty_like(states) if compute_grad else None | |
| grad_weight = ( | |
| torch.zeros(weight.shape, dtype=torch.float32, device=weight.device) | |
| if compute_grad | |
| else None | |
| ) | |
| chunk_rows = max(1, LOGITS_CHUNK_BYTES // (vocab_size * states.element_size())) | |
| for start in range(0, rows, chunk_rows): | |
| end = min(start + chunk_rows, rows) | |
| chunk = states[start:end] | |
| logits = chunk @ weight.t() | |
| if excluded_token_id is not None: | |
| logits[:, excluded_token_id] = float("-inf") | |
| _cross_entropy_rows_kernel[(end - start,)]( | |
| logits, | |
| targets[start:end], | |
| losses[start:end], | |
| log_partitions[start:end], | |
| unit, | |
| logits.stride(0), | |
| vocab_size, | |
| z_loss_coefficient, | |
| -100, | |
| COMPUTE_GRAD=compute_grad, | |
| BLOCK=VOCAB_BLOCK_SIZE, | |
| num_warps=8, | |
| ) | |
| if compute_grad: | |
| logits.mul_(token_weights[start:end, None].to(logits.dtype)) | |
| torch.mm(logits, weight, out=grad_states[start:end]) | |
| if chunk.dtype == torch.float32: | |
| grad_weight.addmm_(logits.t(), chunk) | |
| else: | |
| torch.addmm( | |
| grad_weight, logits.t(), chunk, out_dtype=torch.float32, out=grad_weight | |
| ) | |
| objective = (token_weights * (losses + z_loss_coefficient * log_partitions.square())).sum() | |
| if compute_grad: | |
| ctx.save_for_backward(grad_states, grad_weight) | |
| ctx.weight_dtype = weight.dtype | |
| ctx.mark_non_differentiable(losses, log_partitions) | |
| return objective, losses, log_partitions | |
| def backward( | |
| ctx, grad_objective: torch.Tensor, *_: torch.Tensor | |
| ) -> tuple[torch.Tensor, torch.Tensor, None, None, None, None, None]: | |
| grad_states, grad_weight = ctx.saved_tensors | |
| scale = grad_objective.to(torch.float32) | |
| grad_states = (grad_states * scale).to(grad_states.dtype) | |
| grad_weight = (grad_weight * scale).to(ctx.weight_dtype) | |
| return grad_states, grad_weight, None, None, None, None, None | |
| def weighted_linear_cross_entropy( | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| token_weights: torch.Tensor, | |
| *, | |
| z_loss_coefficient: float = 0.0, | |
| excluded_token_id: int | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """``(Σ_i w_i (CE_i + z log Z_i²), CE [rows], log Z [rows])`` without full logits. | |
| Every row is scored; only the weighted sum carries gradients. | |
| ``excluded_token_id`` gets probability zero (its logit is -inf). | |
| """ | |
| if states.ndim != 2 or targets.shape != states.shape[:1] or token_weights.shape != targets.shape: | |
| raise ValueError("states must be [tokens, hidden], targets and token_weights [tokens]") | |
| if not states.is_cuda: | |
| logits = F.linear(states, weight).float() | |
| if excluded_token_id is not None: | |
| logits[:, excluded_token_id] = float("-inf") | |
| log_partitions = torch.logsumexp(logits, dim=-1) | |
| losses = log_partitions - logits.gather(1, targets[:, None]).squeeze(1) | |
| weights = token_weights.float() | |
| objective = (weights * (losses + z_loss_coefficient * log_partitions.square())).sum() | |
| return objective, losses.detach(), log_partitions.detach() | |
| compute_grad = torch.is_grad_enabled() and (states.requires_grad or weight.requires_grad) | |
| return _WeightedLinearCrossEntropyFunction.apply( | |
| states, weight, targets, token_weights, z_loss_coefficient, compute_grad, excluded_token_id | |
| ) | |
| def linear_cross_entropy( | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| *, | |
| z_loss_coefficient: float = 0.0, | |
| ignore_index: int = -100, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Mean ``cross_entropy(states @ weight.T, targets)`` without full logits. | |
| Returns ``(objective, cross_entropy, z_loss)``. Only ``objective = | |
| cross_entropy + z_loss_coefficient * z_loss`` carries gradients; ``z_loss`` | |
| is the mean squared log-partition over non-ignored targets. | |
| """ | |
| if states.ndim != 2 or targets.shape != states.shape[:1]: | |
| raise ValueError("states must be [tokens, hidden] and targets [tokens]") | |
| if not states.is_cuda: | |
| return linear_cross_entropy_reference( | |
| states, | |
| weight, | |
| targets, | |
| z_loss_coefficient=z_loss_coefficient, | |
| ignore_index=ignore_index, | |
| ) | |
| compute_grad = torch.is_grad_enabled() and (states.requires_grad or weight.requires_grad) | |
| return _LinearCrossEntropyFunction.apply( | |
| states, weight, targets, z_loss_coefficient, ignore_index, compute_grad | |
| ) | |
| def _token_logprob_grad_rows_kernel( | |
| logits, | |
| targets, | |
| log_partitions, | |
| row_grads, | |
| row_stride, | |
| vocab_size, | |
| ignore_index, | |
| BLOCK: tl.constexpr, | |
| ): | |
| """Overwrite each row with ``g * (onehot(target) - softmax)``, the gradient of ``g * log p``.""" | |
| row = tl.program_id(0).to(tl.int64) | |
| row_logits = logits + row * row_stride | |
| target = tl.load(targets + row) | |
| columns = tl.arange(0, BLOCK) | |
| if target == ignore_index: | |
| for start in range(0, vocab_size, BLOCK): | |
| offsets = start + columns | |
| zeros = tl.zeros((BLOCK,), dtype=logits.dtype.element_ty) | |
| tl.store(row_logits + offsets, zeros, mask=offsets < vocab_size) | |
| else: | |
| scale = tl.load(row_grads + row).to(tl.float32) | |
| log_partition = tl.load(log_partitions + row) | |
| for start in range(0, vocab_size, BLOCK): | |
| offsets = start + columns | |
| in_vocab = offsets < vocab_size | |
| values = tl.load(row_logits + offsets, mask=in_vocab, other=0.0).to(tl.float32) | |
| grad = -scale * tl.exp(values - log_partition) | |
| grad = tl.where(offsets == target, grad + scale, grad) | |
| tl.store(row_logits + offsets, grad.to(logits.dtype.element_ty), mask=in_vocab) | |
| class _LinearTokenLogprobFunction(torch.autograd.Function): | |
| """Per-row ``log_softmax(states @ weight.T)[target]`` without keeping logits. | |
| Preference losses weight every sequence differently, so the gradient | |
| cannot be precomputed in forward like the mean LM loss. Forward keeps one | |
| log-partition per row; backward recomputes each logits chunk and turns it | |
| into the scaled softmax gradient in place. | |
| """ | |
| def forward( | |
| ctx, | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| ignore_index: int, | |
| ) -> torch.Tensor: | |
| states = states.contiguous() | |
| targets = targets.contiguous() | |
| rows, vocab_size = states.shape[0], weight.shape[0] | |
| negative_logprobs = states.new_empty(rows, dtype=torch.float32) | |
| log_partitions = states.new_empty(rows, dtype=torch.float32) | |
| unused_count = states.new_ones((), dtype=torch.float32) | |
| chunk_rows = max(1, LOGITS_CHUNK_BYTES // (vocab_size * states.element_size())) | |
| for start in range(0, rows, chunk_rows): | |
| end = min(start + chunk_rows, rows) | |
| logits = states[start:end] @ weight.t() | |
| _cross_entropy_rows_kernel[(end - start,)]( | |
| logits, | |
| targets[start:end], | |
| negative_logprobs[start:end], | |
| log_partitions[start:end], | |
| unused_count, | |
| logits.stride(0), | |
| vocab_size, | |
| 0.0, | |
| ignore_index, | |
| COMPUTE_GRAD=False, | |
| BLOCK=VOCAB_BLOCK_SIZE, | |
| num_warps=8, | |
| ) | |
| ctx.save_for_backward(states, weight, targets, log_partitions) | |
| ctx.ignore_index = ignore_index | |
| ctx.chunk_rows = chunk_rows | |
| return negative_logprobs.neg_() | |
| def backward( | |
| ctx, grad_logprobs: torch.Tensor | |
| ) -> tuple[torch.Tensor | None, torch.Tensor | None, None, None]: | |
| states, weight, targets, log_partitions = ctx.saved_tensors | |
| row_grads = grad_logprobs.to(torch.float32).contiguous() | |
| rows, vocab_size = states.shape[0], weight.shape[0] | |
| grad_states = torch.empty_like(states) if ctx.needs_input_grad[0] else None | |
| grad_weight = ( | |
| torch.zeros(weight.shape, dtype=torch.float32, device=weight.device) | |
| if ctx.needs_input_grad[1] | |
| else None | |
| ) | |
| for start in range(0, rows, ctx.chunk_rows): | |
| end = min(start + ctx.chunk_rows, rows) | |
| chunk = states[start:end] | |
| logits = chunk @ weight.t() | |
| _token_logprob_grad_rows_kernel[(end - start,)]( | |
| logits, | |
| targets[start:end], | |
| log_partitions[start:end], | |
| row_grads[start:end], | |
| logits.stride(0), | |
| vocab_size, | |
| ctx.ignore_index, | |
| BLOCK=VOCAB_BLOCK_SIZE, | |
| num_warps=8, | |
| ) | |
| if grad_states is not None: | |
| torch.mm(logits, weight, out=grad_states[start:end]) | |
| if grad_weight is not None: | |
| if chunk.dtype == torch.float32: | |
| grad_weight.addmm_(logits.t(), chunk) | |
| else: | |
| torch.addmm( | |
| grad_weight, logits.t(), chunk, out_dtype=torch.float32, out=grad_weight | |
| ) | |
| return ( | |
| grad_states, | |
| None if grad_weight is None else grad_weight.to(weight.dtype), | |
| None, | |
| None, | |
| ) | |
| def linear_token_logprobs( | |
| states: torch.Tensor, | |
| weight: torch.Tensor, | |
| targets: torch.Tensor, | |
| *, | |
| ignore_index: int = -100, | |
| ) -> torch.Tensor: | |
| """fp32 ``log_softmax(states @ weight.T)[row, target]`` per row without full logits. | |
| Rows whose target is ``ignore_index`` return 0 and receive no gradient. | |
| """ | |
| if states.ndim != 2 or targets.shape != states.shape[:1]: | |
| raise ValueError("states must be [tokens, hidden] and targets [tokens]") | |
| if not states.is_cuda: | |
| return linear_token_logprobs_reference( | |
| states, weight, targets, ignore_index=ignore_index | |
| ) | |
| return _LinearTokenLogprobFunction.apply(states, weight, targets, ignore_index) | |