Final Logit Softcap¶
Summary¶
Final logit softcapping bounds logits smoothly before a vocabulary softmax. The
operator implements c * tanh(x / c) with the cap c supplied at runtime, so
one shared kernel serves every model that needs a different cap.
| Model | Cap | Note |
|---|---|---|
| Gemma-4-31B-it | 30 | Default value of the public interface. |
| Muse-Glimmer-30B | 20 | The model applies output_multiplier before calling this operator; that multiplier stays on the model side. |
The operator acts independently on each element. It does not perform a softmax,
a reduction, or the output_multiplier scaling.
For input \(x_i\), output \(y_i\), upstream gradient \(g_{y_i}\), and cap \(c\):
The cap cancels in the derivative: the outer factor \(c\) and the \(1/c\) from the chain rule multiply to one.
Entry Point¶
import torch
from rl_engine.runtime.registry import kernel_registry
x = torch.randn(2, 3, 262144, device="cuda", dtype=torch.bfloat16, requires_grad=True)
# Registry selection uses the default cap of 30.
op = kernel_registry.get_op("final_logit_softcap", device=x.device)
y = op(x)
# A model that needs another cap constructs the wrapper directly.
from rl_engine.backends.shared.triton.activation import TritonFinalLogitSoftcapOp
op20 = TritonFinalLogitSoftcapOp(softcap=20.0)
y20 = op20(x)
grad_y = torch.randn_like(y)
(grad_x,) = torch.autograd.grad(y, x, grad_outputs=grad_y)
assert grad_x.dtype == x.dtype
The public wrappers are NativeFinalLogitSoftcapOp in
rl_engine.reference.activation and TritonFinalLogitSoftcapOp in
rl_engine.backends.shared.triton.activation. The legacy import paths
rl_engine.kernels.ops.pytorch.activation and
rl_engine.kernels.ops.triton.activation remain available as compatibility
shims.
Both wrappers take softcap as the first constructor argument and default it to
30.0. The value must be finite and positive; anything else raises
ValueError at construction rather than producing NaN inside a kernel.
Backends¶
| Platform | Registry priority | Validation status |
|---|---|---|
| NVIDIA CUDA | Triton → PyTorch native | CUDA correctness and performance validated on H100; SM89 development on RTX 4090 |
| ROCm | Triton → PyTorch native | Kernel uses libdevice.tanh, which maps to HIP; hardware execution pending |
| CPU | PyTorch native | Covered by the reference and integration tests |
| MUSA / NPU | PyTorch native | Registry selection tested; hardware execution not validated |
The Triton implementation has forward and backward kernels; there is no separate CUDA C++ implementation. The registry falls back when a backend cannot be loaded or instantiated. It does not retry a failed kernel launch using another backend. The native implementation computes on the input's device.
Tensor and Precision Contract¶
| Value | Shape | Dtype | Behavior |
|---|---|---|---|
Input x |
Any shape, including scalar and empty | FP16 / BF16 / FP32 | Finite floating-point logits |
| Intermediate values | Elementwise | FP32 | Input converted before division and tanh |
Output y (Triton) |
Same as x |
FP32 | Same device as x |
Output y (forward) |
Same as x |
Input dtype | FP32 math, cast once at the end |
Output y (forward_fp32) |
Same as x |
FP32 | Ground-truth path |
Upstream gradient grad_y |
Same as y |
FP32 | Supplied by autograd |
Input gradient grad_x |
Same as x |
Input dtype | Computed in FP32, then cast once |
The native reference converts with x.to(torch.float32) and uses
c * torch.tanh(x_f / c). The Triton kernels use tl.div_rn and
libdevice.tanh. FP32 arithmetic specifies the working dtype; it does not
promise identical transcendental-function rounding across hardware backends.
softcap is a runtime kernel argument, not a tl.constexpr. A constant would
recompile the kernel once per distinct cap; passing it at launch keeps a single
compiled variant for 20, 30, and any later value. The trade-off is a small loss
of compile-time optimization that does not matter for an elementwise kernel.
The wrappers accept noncontiguous inputs and upstream gradients. Triton makes contiguous copies where necessary, preserving logical shape and values. This may add allocation and copy costs. Inputs and upstream gradients are not modified. Backward saves the contiguous input in its original dtype and recomputes tanh. Empty inputs return empty buffers without launching a kernel.
Both Triton kernels convert the program ID to int64 before multiplying by the
block size, keeping element-offset arithmetic in 64 bits. This removes the
previous \(2^{31}\)-element indexing restriction without changing floating-point
arithmetic or output dtypes. Allocation and launch limits still apply.
Dispatch Behavior¶
kernel_registry.get_op("final_logit_softcap", device=...) resolves through the
OpBackend priority map:
| Platform | Priority |
|---|---|
cuda |
Triton → PyTorch native |
rocm |
Triton → PyTorch native |
musa |
PyTorch native |
cpu |
PyTorch native |
npu |
PyTorch native |
The registry constructs the backend with no arguments, so the public wrappers
must keep a default for softcap. A model that needs a different cap constructs
its wrapper directly or passes a configured instance through its own provider.
Accuracy and Invariance¶
Tests obtain thresholds from the shared WS1 tolerance_contract.json through
resolve_tolerance, using the elementwise operator class. The current accuracy
thresholds for both forward and gradient comparisons are:
| Compared tensor dtype | Absolute tolerance | Relative tolerance |
|---|---|---|
| FP32 | 1e-5 | 1e-5 |
| FP16 | 1e-3 | 1e-3 |
| BF16 | 2e-2 | 1.6e-2 |
Forward results always use the FP32 row, even for BF16 or FP16 inputs. Gradient comparisons use the input dtype. Native calculations are also checked against an FP64 reference whose derivative uses \(1/\cosh^2(x/c)\) and a random upstream gradient.
Every numerical test runs for softcap=20 and softcap=30. Input scales are
rescaled with the cap, so both caps exercise the linear region, the transition
region, and saturation of tanh.
The GPU suite checks bitwise equality for repeated calls, slicing, padding and
reshaping, for both outputs and input gradients. It also compares training-mode
forward output with torch.no_grad() output. These are operator-level checks on
one device; they do not establish full-model train/rollout parity or cross-device
bitwise equality.
Tests¶
python -m pytest tests/ops/activation/test_final_logit_softcap.py -v -rs
python tools/validation/operators/check_operator.py --op final_logit_softcap \
--candidate triton --device cuda --dtype bf16 --batch 2 --seq 3 --vocab 262144 --check-grad
The focused operator suite covers both caps, all three input dtypes, block
boundaries, scalar/empty tensors, multidimensional shapes, saturation,
strided/expanded views, random upstream gradients, registry fallback, the
accuracy harness, and a regression that compares softcap=30 against the
original hard-coded formula bitwise.
CPU-only runs skip GPU cases. On a GPU host, a missing Triton backend fails the GPU tests instead of silently testing the native fallback.
Benchmark¶
Run on a CUDA (NVIDIA) or ROCm (AMD) host with the development dependencies installed:
# Quick execution check before the complete run.
python benchmarks/operators/activation/benchmark_final_logit_softcap.py \
--shapes 1x1x1025 --dtypes fp32 --softcaps 30 --warmup 3 --repeat 5 \
--output-dir artifacts/benchmarks/softcap-smoke
# FP16, BF16 and FP32 at both caps; small tails through a full vocabulary row.
python benchmarks/operators/activation/benchmark_final_logit_softcap.py \
--softcaps 20 30 --output-dir artifacts/benchmarks/softcap-run-1
This standalone operator benchmark reuses the existing PerformanceProfiler
accelerator-event timer (CUDA events on NVIDIA, HIP events on ROCm). It builds
the Native and Triton wrappers on the same GPU with the same cap, checks output
and gradient accuracy before timing, and writes results.json and report.md.
Failure to import or run Triton is an error.
The native baseline is driven through forward_fp32. NativeFinalLogitSoftcapOp.forward
casts its result back to the input dtype while the Triton wrapper always returns
FP32, so timing the two as-is would compare a BF16 or FP16 write path against an
FP32 one and inflate the Triton speedup. Routing the baseline through the FP32
entry keeps the byte volume identical on both sides.
Default shapes are 1x1x1025, 1x1x262144, 1x16x262144 and 1x64x262144.
Default caps are 20 and 30. Inputs are contiguous, with random FP32 upstream
gradients. Noncontiguous layouts are covered by correctness tests, but their copy
overhead is not measured by this benchmark. Use --shapes, --dtypes,
--softcaps, --warmup, --repeat and --device to select another workload.
Run on an otherwise idle GPU.
- Forward: inference under
torch.no_grad(). - Backward:
torch.autograd.gradon a prebuilt, retained graph; excludes forward. - Forward + backward: a fresh forward graph and gradient computation per call.
The report contains median latency, sample standard deviation, incremental peak
allocated memory, and native_ms / triton_ms speedup. Extra peak allocation excludes
existing inputs and any prebuilt backward graph, so it is not total training memory.
Input generation, correctness checks and compilation are outside the timing window.
Public-wrapper allocation and autograd dispatch are included. For small tensors,
host dispatch gaps can dominate accelerator-event measurements.
The report records the backend and its runtime version, the GPU and architecture, compute capability (CUDA only), software versions, commit, tracked changes, input shapes/dtypes/caps, warmup/repetitions, seed, block size and tolerance contract fingerprint.
Reports and validation evidence¶
Write local outputs to artifacts/benchmarks/, which is git-ignored. Reviewed
measurements belong in reports/experiments; keep generated reports out of the
checkout otherwise.
Known Limitations¶
- The cap must be finite and positive; trainable or learned caps are not implemented.
- The Triton autograd wrapper supports first-order gradients only.
- The current Triton configuration uses a fixed block size of 1024, without autotuning.
- FP64, integer and complex inputs are rejected. NaN/Inf input semantics have not been separately validated.
- ROCm and MUSA hardware execution remain pending.
Implementation Files¶
rl_engine/reference/activation/final_logit_softcap.py— PyTorch reference (forward,forward_fp32)rl_engine/backends/shared/triton/activation/final_logit_softcap.py— Triton kernels and autogradrl_engine/kernels/ops/pytorch/activation/final_logit_softcap.py— compatibility shimrl_engine/kernels/ops/triton/activation/final_logit_softcap.py— compatibility shimrl_engine/runtime/registry.py— dispatch registrationrl_engine/runtime/provenance/module_names.json— stable operator pathsrl_engine/validation/operators/operator_specs.py— accuracy harness entrytests/ops/activation/test_final_logit_softcap.py— correctness and invariance testsbenchmarks/operators/activation/benchmark_final_logit_softcap.py— performance comparison