Skip to navigation

nemo_automodel.components.models.deepseek_v4.layers

View as Markdown

DeepSeek V4 Attention Layer.

Architecture (from official inference/model.py):

Q path: x -> wq_a [hidden -> q_lora_rank] -> q_norm (RMSNorm) -> wq_b [q_lora_rank -> n_heads * head_dim] -> reshape [n_heads, head_dim] -> per-head RMSNorm (q_norm applied per-head in official code) -> apply_rotary_emb on last rope_head_dim dims

KV path (K = V, single latent): x -> wkv [hidden -> head_dim] # single KV head, K = V = kv -> kv_norm (RMSNorm on head_dim) -> apply_rotary_emb on last rope_head_dim dims K = V = kv (one latent vector serves both key and value)

Output path (grouped): o [bsz, seq, n_heads, head_dim] -> reshape [bsz, seq, n_groups, n_heads_per_group * head_dim] -> wo_a einsum per group: [n_heads_per_group * head_dim] -> [o_lora_rank] -> reshape [bsz, seq, n_groups * o_lora_rank] -> wo_b [n_groups * o_lora_rank -> hidden]

attn_sink: learnable per-head scalar bias added to attention-sink position score.

HC (Hyper-Connections): Each Block maintains hc_mult=4 copies of the hidden state. hc_pre reduces [bsz, seq, hc_mult, dim] -> [bsz, seq, dim] via Sinkhorn mixing. hc_post expands [bsz, seq, dim] -> [bsz, seq, hc_mult, dim]. See DeepseekV4HyperConnection.compute_weights and optimized_kernels.dsv4_sinkhorn_normalize for the torch reference and optional TileKernels Sinkhorn path.

Compress-ratio attention (Compressor + Indexer) is wired into DeepseekV4Attention.forward for layers with compress_ratio > 0. All layers share the same sliding-window causal mask on the local KV path.

Module Contents

Classes

NameDescription
DeepseekV4AttentionSliding-window attention + Compressor + Indexer + attention sink.
DeepseekV4CompressorHF PR 45616 port. Long-range KV branch. Pools compress_ratio tokens
DeepseekV4FP32ParameterCallable holder for fp32 tensors that need their own FSDP unit.
DeepseekV4GroupedLinearBlock-diagonal grouped linear (HF PR 45616 port).
DeepseekV4HyperConnectionPer-site HyperConnection mixer (attention or FFN). Ported from
DeepseekV4HyperHeadFinal HC-stream collapse before the shared RMSNorm + lm_head.
DeepseekV4IndexerHF PR 45616 port. Picks the top-k compressed positions per query when
DeepseekV4RotaryEmbeddingV4 rotary embedding. Produces (cos, sin) sized to qk_rope_head_dim
DeepseekV4TrainCacheTraining-only cache shim mirroring the three methods DeepseekV4Compressor
_GroupedLinearTokenMajorPer-group matmul that reads and writes token-major activations in place.
_GroupedProjectionContext-

Functions

NameDescription
_apply_partial_ropeSplit x along its last dim into nope (first) and rope (last
_apply_partial_rope_interleavedInterleaved RoPE on the last rope_head_dim dims of x (pairs are
_build_indexer_topk_compressed_maskBuild the additive compressed-position mask for Indexer-selected pool IDs.
_compressed_window_metadata-
_dsv4_kernel_backendUse TileLang DSV4 sparse kernels only when the attention backend requests them.
_dsv4_sinkhorn_backendUse optional TileKernels sinkhorn when available, otherwise torch fallback.
_full_tensor_if_dtensor-
_overlap_transformReshape [B, S, ratio, 2*head_dim] -> [B, S, 2*ratio, head_dim] with the
_overlap_transform_with_cp-
_pool_windowsSoftmax-gated sum-pool over ratio consecutive tokens.
_query_positions-
_rms_norm_last_dimRMS-normalize the last dim without materializing an x.square() tensor.
_rope_pool_positions-
_rotate_halfRotate half the hidden dims of the input (Llama / GPT-NeoX style).
_yarn_correction_dim-
_yarn_correction_range-
_yarn_linear_ramp-
apply_rotary_pos_embPort of transformers.models.llama.modeling_llama.apply_rotary_pos_emb.
build_causal_padding_maskBuild a 4D additive causal+padding (+optional sliding-window) mask
build_packed_causal_padding_maskBuild a 4D additive block-causal mask from packed-sequence lengths.
eager_attention_with_sinkEager attention with per-head sink: appends an extra softmax column
repeat_kvPort of transformers.models.llama.modeling_llama.repeat_kv.

Data

dsv4_indexer_replay

API

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Attention(
layer_idx: int,
)

Bases: Module

Sliding-window attention + Compressor + Indexer + attention sink.

Single-head KV (num_key_value_heads=1), grouped low-rank output via DeepseekV4GroupedLinear. compress_ratio == 0 layers skip the compressor / indexer and run pure SWA.

attention_dropout
backend
= backend or BackendConfig()
compress_ratio
compressor
head_dim
= config.head_dim
kv_norm
num_heads
= config.num_attention_heads
num_key_value_groups
= config.num_attention_heads
q_norm
rope_head_dim
= config.qk_rope_head_dim
scaling
= self.head_dim ** -0.5
sinks
Tensor
sinks_param
sliding_window
= int(getattr(config, 'sliding_window', 128) or 128)
wkv
wo_a
wo_b
wq_a
wq_b
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Attention._project_query(
q_residual: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor
) -> torch.Tensor

Expand, normalize, and partially rotate the low-rank query.

Parameters:

q_residual
torch.Tensor

Tensor of shape [batch, sequence, q_lora_rank].

cos
torch.Tensor

Tensor of shape [batch, sequence, rotary_channels], where rotary_channels is qk_rope_head_dim and the first half contains the unique adjacent-pair frequencies.

sin
torch.Tensor

Tensor of shape [batch, sequence, rotary_channels] with cos’s frequency order.

Returns: torch.Tensor

Tensor of shape [batch, heads, sequence, channels], where channels

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Attention.forward(
hidden_states: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
attention_mask: torch.Tensor | None = None,
position_embeddings_compress: tuple[torch.Tensor, torch.Tensor] | None = None,
rotary_compress: torch.nn.Module | None = None,
start_pos: int = 0,
position_ids: torch.Tensor | None = None,
kwargs: typing.Any = {}
) -> tuple[torch.Tensor, torch.Tensor | None]

Run sliding-window, compressed, and sparse attention for one layer.

Parameters:

hidden_states
torch.Tensor

Tensor of shape [batch, local_sequence, hidden], where local_sequence is this rank’s contiguous query shard under CP.

position_embeddings
tuple[torch.Tensor, torch.Tensor]

Main-rope cosine and sine, each of shape [batch, local_sequence, rotary_channels].

attention_mask
torch.Tensor | NoneDefaults to None

Optional additive mask of shape [batch, 1, local_sequence, keys] over local queries and CP-global keys. None on the canonical unmasked sparse path.

position_embeddings_compress
tuple[torch.Tensor, torch.Tensor] | NoneDefaults to None

Optional compress-rope cosine and sine with the main tables’ shapes; compressed layers use them for queries and keys.

rotary_compress
nn.Module | NoneDefaults to None

Rotary module for compressed keys.

start_pos
intDefaults to 0

First query position.

position_ids
torch.Tensor | NoneDefaults to None

Optional tensor of shape [batch, local_sequence] with each token’s position inside its document.

**kwargs
AnyDefaults to {}

Optional packed_seq_ids and vision_token_types tensors of shape [batch, local_sequence], _dsv4_cp_group, and the model-validated flags _dsv4_all_queries_nonempty (every query sees its diagonal key, so sparse kernels may skip empty-row filtering) and _dsv4_complete_compressor_windows.

Returns: torch.Tensor

Output of shape [batch, local_sequence, hidden], and eager attention

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Attention.init_weights(
buffer_device: torch.device,
init_std: float = 0.02
) -> None
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Attention.setup_cp_attention(
cp_mesh
) -> None

Model-owned context-parallel hook, called by moe.parallelizer.apply_cp.

DSV4 runs Miles-style CP (contiguous query shard + all-gathered K/V), so there is no TE DotProductAttention to configure — we just record the CP process group that forward uses to all-gather K/V across CP ranks.

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Compressor(
compress_ratio: int,
head_dim: int,
)

Bases: Module

HF PR 45616 port. Long-range KV branch. Pools compress_ratio tokens into one compressed KV; when ratio == 4 the Indexer narrows the pool.

ape
Tensor
ape_param
backend
= backend or BackendConfig()
indexer
DeepseekV4Indexer | None
kv_norm
overlap
= compress_ratio == 4
rope_head_dim
= config.qk_rope_head_dim
wgate
wkv
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Compressor._compute_fsdp_group_has_complete_hca_window(
local_has_complete_hca_window: bool,
device: torch.device
) -> bool
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Compressor._set_hca_param_sync_group(
process_group
) -> None
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Compressor.forward(
hidden_states: torch.Tensor,
q_residual: torch.Tensor | None,
rotary: torch.nn.Module,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
layer_idx: int,
start_pos: int,
enable_hca_fsdp_graph_alignment: bool = False,
position_ids: torch.Tensor | None = None,
packed_seq_ids: torch.Tensor | None = None,
cp_group = None
) -> tuple[torch.Tensor, torch.LongTensor | None, torch.Tensor | None, torch.Tensor | None]
class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4FP32Parameter(
value: torch.Tensor
)

Bases: Module

Callable holder for fp32 tensors that need their own FSDP unit.

weight
= nn.Parameter(value.to(torch.float32))
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4FP32Parameter.forward(
reference: torch.Tensor | None = None
) -> torch.Tensor
class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4GroupedLinear(
in_features_per_group: int,
out_features: int,
n_groups: int,
bias: bool = False
)

Bases: Linear

Block-diagonal grouped linear (HF PR 45616 port).

weight parameter has the standard nn.Linear shape [out_features, in_features_per_group] so quantizers keyed on nn.Linear.weight still find it; forward does per-group bmm.

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4GroupedLinear.forward(
x: torch.Tensor
) -> torch.Tensor
class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection(
hc_mult: int,
hidden_size: int,
hc_sinkhorn_iters: int,
hc_eps: float,
rms_norm_eps: float,
sinkhorn_backend: str = 'torch',
compile_hc: bool = False
)

Bases: Module

Per-site HyperConnection mixer (attention or FFN). Ported from transformers/src/transformers/models/deepseek_v4/modular_deepseek_v4.py class DeepseekV4HyperConnection.

Owns fn (packed linear), base (bias), and scale (scalar per-head gains). compute_weights produces three mixer tensors:

  • pre [B, S, H] : sigmoid-gated collapse weights
  • post [B, S, H] : sigmoid-gated expand weights
  • comb [B, S, H, H] : doubly-stochastic combination matrix from Sinkhorn-normalising sigmoid gates

All math runs in fp32 regardless of the outer cast policy; parameters cast themselves via .float() on each forward. HF lists these params in _keep_in_fp32_modules_strict — the KAutomodel adapter does the same via submodule-name matching.

base
fn
scale
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection._expand_fused(
output: torch.Tensor,
residual: torch.Tensor,
post: torch.Tensor,
comb: torch.Tensor
) -> torch.Tensor
staticmethod

Fuse the four-stream reduction without launching tiny batched GEMMs.

Parameters:

output
torch.Tensor

Tensor of shape [batch, sequence, hidden].

residual
torch.Tensor

Tensor of shape [batch, sequence, streams, hidden].

post
torch.Tensor

FP32 coefficients of shape [batch, sequence, streams].

comb
torch.Tensor

FP32 coefficients of shape [batch, sequence, input_streams, output_streams].

Returns: torch.Tensor

Tensor of shape [batch, sequence, streams, hidden] in the residual

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection._project(
hidden_streams: torch.Tensor,
fn: torch.Tensor,
base: torch.Tensor,
scale: torch.Tensor,
hc: int,
norm_eps: float,
hc_eps: float
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]
staticmethod

Predict mixing coefficients before Sinkhorn normalization.

Parameters:

hidden_streams
torch.Tensor

Tensor of shape [batch, sequence, streams, hidden].

fn
torch.Tensor

FP32 projection of shape [mix_components, streams * hidden], where mix_components is streams * (streams + 2).

base
torch.Tensor

FP32 offsets of shape [mix_components], ordered pre, post, combination.

scale
torch.Tensor

FP32 gains of shape [3], ordered pre, post, combination.

hc
int

Number of residual streams.

norm_eps
float

RMS normalization epsilon.

hc_eps
float

Additive epsilon for the pre coefficients.

Returns: torch.Tensor

FP32 pre/post coefficients of shape [batch, sequence, streams] and

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection.collapse(
hidden_streams: torch.Tensor,
pre: torch.Tensor
) -> torch.Tensor
staticmethod

Collapse residual streams in FP32 before restoring the activation dtype.

Parameters:

hidden_streams
torch.Tensor

Tensor of shape [batch, sequence, streams, hidden].

pre
torch.Tensor

FP32 coefficients of shape [batch, sequence, streams].

Returns: torch.Tensor

Tensor of shape [batch, sequence, hidden] in the activation dtype.

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection.compute_weights(
hidden_streams: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]

Predict and normalize the residual mixing coefficients.

Parameters:

hidden_streams
torch.Tensor

Tensor of shape [batch, sequence, streams, hidden].

Returns: torch.Tensor

FP32 pre/post tensors of shape [batch, sequence, streams] and

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection.expand(
output: torch.Tensor,
residual: torch.Tensor,
post: torch.Tensor,
comb: torch.Tensor
) -> torch.Tensor
staticmethod

Mix the residual and sublayer output with the existing BF16 rounding boundaries.

Parameters:

output
torch.Tensor

Tensor of shape [batch, sequence, hidden].

residual
torch.Tensor

Tensor of shape [batch, sequence, streams, hidden].

post
torch.Tensor

FP32 coefficients of shape [batch, sequence, streams].

comb
torch.Tensor

FP32 coefficients of shape [batch, sequence, input_streams, output_streams].

Returns: torch.Tensor

Tensor of shape [batch, sequence, streams, hidden] in the residual dtype.

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection.forward(
hidden_streams: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperConnection.init_weights(
init_std: float
) -> None

Initialize HyperConnection parameters using the DeepSeek-V4 reference scheme.

Parameters:

init_std
float

Standard deviation for the fn weight initialization.

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperHead(
hc_mult: int,
hidden_size: int,
hc_eps: float,
rms_norm_eps: float
)

Bases: Module

Final HC-stream collapse before the shared RMSNorm + lm_head. Ported from modular_deepseek_v4.py class DeepseekV4HyperHead.

Sigmoid-weighted sum over the hc_mult streams (no Sinkhorn). Used once at the end of DeepseekV4Model.forward to go from [B, S, H, D] back to [B, S, D].

hc_base
hc_fn
hc_scale
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperHead.forward(
x: torch.Tensor
) -> torch.Tensor

Collapse residual streams using FP32 mixing coefficients.

Parameters:

x
torch.Tensor

Tensor of shape [batch, sequence, hc_mult, hidden].

Returns: torch.Tensor

Tensor of shape [batch, sequence, hidden] in the input dtype.

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4HyperHead.init_weights(
init_std: float
) -> None

Initialize HyperHead parameters using the DeepSeek-V4 reference scheme.

Parameters:

init_std
float

Standard deviation for the hc_fn weight initialization.

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Indexer(
)

Bases: Module

HF PR 45616 port. Picks the top-k compressed positions per query when compress_ratio == 4. Owns its own pool at index_head_dim plus a query projection + weights_proj head-mixer.

ape
Tensor
ape_param
backend
= backend or BackendConfig()
compress_ratio
= 4
head_dim
= config.index_head_dim
index_topk
= config.index_topk
kv_norm
n_heads
= config.index_n_heads
reuse_selection_on_recompute
= config.reuse_sparse_selection_on_recompute
rope_head_dim
= config.qk_rope_head_dim
softmax_scale
= self.head_dim ** -0.5
weights_proj
wgate
wkv
wq_b
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Indexer._select_keys(
hidden_states: torch.Tensor,
q_residual: torch.Tensor,
rotary: torch.nn.Module,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
layer_idx: int,
start_pos: int,
position_ids: torch.Tensor | None = None,
packed_seq_ids: torch.Tensor | None = None,
cp_group: torch.distributed.ProcessGroup | None = None
) -> torch.LongTensor

Score the compressed pool and select keys; see forward for the contract.

Parameters:

hidden_states
torch.Tensor

Tensor of shape [batch, local_sequence, hidden].

q_residual
torch.Tensor

Tensor of shape [batch, local_sequence, q_lora_rank].

rotary
nn.Module

Rotary module for compressed keys.

position_embeddings
tuple[torch.Tensor, torch.Tensor]

Query cosine and sine, each of shape [batch, local_sequence, rotary_channels].

cache
DeepseekV4TrainCache

Per-attention-call scratch cache.

layer_idx
int

Index of the current transformer layer.

start_pos
int

First query position.

position_ids
torch.Tensor | NoneDefaults to None

Optional tensor of shape [batch, local_sequence].

packed_seq_ids
torch.Tensor | NoneDefaults to None

Optional tensor of shape [batch, local_sequence].

cp_group
dist.ProcessGroup | NoneDefaults to None

Optional context-parallel process group.

Returns: torch.LongTensor

Int64 tensor of shape [batch, local_sequence, selected_keys]

nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4Indexer.forward(
hidden_states: torch.Tensor,
q_residual: torch.Tensor,
rotary: torch.nn.Module,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
layer_idx: int,
start_pos: int,
position_ids: torch.Tensor | None = None,
packed_seq_ids: torch.Tensor | None = None,
cp_group: torch.distributed.ProcessGroup | None = None
) -> torch.LongTensor

Select compressed keys, reusing frozen decisions during checkpoint replay.

The replay owns only integer indices. The main compressor and attention retain their normal differentiable paths. Trainable indexers keep the existing path; each checkpoint invocation owns an independent record.

Parameters:

hidden_states
torch.Tensor

Tensor of shape [batch, local_sequence, hidden], where local_sequence is this rank’s query shard under CP.

q_residual
torch.Tensor

Tensor of shape [batch, local_sequence, q_lora_rank].

rotary
nn.Module

Rotary module for compressed keys.

position_embeddings
tuple[torch.Tensor, torch.Tensor]

Query cosine and sine, each of shape [batch, local_sequence, rotary_channels] with rotary_channels equal to qk_rope_head_dim.

cache
DeepseekV4TrainCache

Per-attention-call scratch cache.

layer_idx
int

Index of the current transformer layer.

start_pos
int

First query position.

position_ids
torch.Tensor | NoneDefaults to None

Optional tensor of shape [batch, local_sequence] with each token’s position inside its document.

packed_seq_ids
torch.Tensor | NoneDefaults to None

Optional tensor of shape [batch, local_sequence] with positive document identities; zero marks padding.

cp_group
dist.ProcessGroup | NoneDefaults to None

Optional context-parallel process group.

Returns: torch.LongTensor

Int64 tensor of shape [batch, local_sequence, selected_keys], with

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4RotaryEmbedding(
rope_theta: float,
head_dim: int,
partial_rotary_factor: float,
attention_scaling: float = 1.0,
device: torch.device | None = None,
rope_scaling: dict | None = None
)

Bases: Module

V4 rotary embedding. Produces (cos, sin) sized to qk_rope_head_dim (via partial_rotary_factor = qk_rope_head_dim / head_dim), matching HF.

YaRN: when rope_scaling is a YaRN-typed dict ({"type": "yarn", "factor": F, "original_max_position_embeddings": L0, "beta_fast": ..., "beta_slow": ...}), modify inv_freq per dsv4flash/inference/model.py:precompute_freqs_cis — frequency interpolation with a smooth linear ramp between beta_fast/beta_slow correction dims. Used by the compress-rope (theta=160000) on layers with compress_ratio > 0. The main rope (theta=10000, used only on sliding-window layers) gets rope_scaling=None because the reference builds it with original_seq_len=0 for those layers.

inv_freq
Tensor
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4RotaryEmbedding.forward(
x: torch.Tensor,
position_ids: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]

Build rotary tables using FP32 position-frequency products.

Parameters:

x
torch.Tensor

Tensor of shape [batch, sequence, hidden], supplying output dtype and device.

position_ids
torch.Tensor

Tensor of shape [batch, sequence] containing absolute token positions.

Returns: torch.Tensor

Cosine and sine tensors of shape [batch, sequence, rotary_dim], where

class nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4TrainCache()

Training-only cache shim mirroring the three methods DeepseekV4Compressor / DeepseekV4Indexer call on DeepseekV4Cache.

KAutomodel training forward is stateless — we never persist KV or compressor windows across calls. Each DeepseekV4Attention.forward creates a fresh cache instance, which holds per-layer scratch dicts for the duration of the call. When a full window hasn’t accumulated yet we return an empty tensor and let the downstream code handle it.

compressor_state
list[dict] = []
indexer_state
list[dict] = []
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4TrainCache._branch_state(
state_key: str,
layer_idx: int
) -> dict
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4TrainCache.accumulate_windows(
kv: torch.Tensor,
gate: torch.Tensor,
layer_idx: int,
state_key: str,
ratio: int,
start_pos: int
) -> tuple[torch.Tensor, torch.Tensor, int]
nemo_automodel.components.models.deepseek_v4.layers.DeepseekV4TrainCache.update_pool(
new_pooled: torch.Tensor,
layer_idx: int,
state_key: str
) -> torch.Tensor
class nemo_automodel.components.models.deepseek_v4.layers._GroupedLinearTokenMajor()

Bases: Function

Per-group matmul that reads and writes token-major activations in place.

torch.bmm wants the group axis first. Permuting a token-major [tokens, groups, d_in] activation is a free view on the input side, but the [groups, tokens, d_out] result had to be permuted back and copied (flatten / reshape by the caller) and, in backward, the permuted input gradient was copied again by the reshape that produced the grouped view (ViewBackward): two 0.5 GB copies per attention layer at the DeepSeek-V4.1 benchmark shape. cuBLAS handles a strided output, so the forward and the input gradient are written straight into token-major buffers through out= and the copies disappear.

nemo_automodel.components.models.deepseek_v4.layers._GroupedLinearTokenMajor.backward(
grad_y: torch.Tensor
) -> tuple[torch.Tensor | None, torch.Tensor | None]
staticmethod

Input gradient written token-major; weight gradient as a grouped bmm over the token axis. Both are computed in the forward’s compute dtype and returned in the dtype of the tensor they belong to (what autograd does for a plain autocast bmm whose operands were cast).

nemo_automodel.components.models.deepseek_v4.layers._GroupedLinearTokenMajor.forward(
x: torch.Tensor,
w: torch.Tensor
) -> torch.Tensor
staticmethod

Args: x [tokens, groups, d_in] (contiguous), w [groups, d_out, d_in]. Returns [tokens, groups, d_out].

torch.bmm(..., out=) is not an autocast op, so the cast autocast would apply to a plain bmm is done here: under autocast both operands and the output take the autocast dtype (an FP32 weight meets BF16 activations in AMP training, and an FP32 input returns BF16 as before); without autocast the dtypes are used as given, so the same-dtype path costs no copy.

class nemo_automodel.components.models.deepseek_v4.layers._GroupedProjectionContext()
Protocol
grad_dtypes
tuple[dtype, dtype]
needs_input_grad
tuple[bool, ...]
saved_tensors
tuple[Tensor, ...]
nemo_automodel.components.models.deepseek_v4.layers._GroupedProjectionContext.save_for_backward(
tensors: torch.Tensor = ()
) -> None
nemo_automodel.components.models.deepseek_v4.layers._apply_partial_rope(
x: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
rope_head_dim: int,
backend: typing.Literal['torch', 'quack', 'triton'] = 'torch'
) -> torch.Tensor

Split x along its last dim into nope (first) and rope (last rope_head_dim) slices, rotate only the rope slice with INTERLEAVED pair-RoPE (pairs (2k, 2k+1)), concat back.

The DSV4-Flash released checkpoint uses interleaved RoPE end-to-end (see dsv4flash/inference/model.py:apply_rotary_emb — complex multiplication on view_as_complex of pairs). HF transformers PR 45616 / PR 45643 ship a Llama-style rotate_half here instead, which pairs (d, d+rd/2). Same algebra but a different dim-to-frequency mapping — the released weights expect the interleaved layout, so the Llama-style helper produces wrong activations on the released checkpoint (verified empirically: kv_post_rope cosine drops from 0.9999 to 0.866 after one block under Llama-style; matches at >0.999 under interleaved).

Parameters:

x
torch.Tensor

Tensor of shape […, sequence, channels] rotated on its last rope_head_dim channels. The eager path accepts any leading dimensions broadcastable with the tables, for example [batch, heads, sequence, channels] or [batch, 1, sequence, channels]; the Triton path requires exactly [batch, heads, sequence, channels].

cos
torch.Tensor

Tensor of shape [batch, sequence, rope_head_dim]; the first half holds the unique adjacent-pair frequencies.

sin
torch.Tensor

Tensor with cos’s shape and frequency order; negate it for the inverse rotation.

rope_head_dim
int

Even number of trailing channels to rotate.

backend
Literal['torch', 'quack', 'triton']Defaults to 'torch'

"triton" uses the fused CUDA kernel when x is on CUDA and the tables do not require gradients; otherwise the eager path runs.

Returns: torch.Tensor

Tensor with x’s shape and dtype. The leading channels - rope_head_dim

nemo_automodel.components.models.deepseek_v4.layers._apply_partial_rope_interleaved(
x: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
rope_head_dim: int
) -> torch.Tensor

Interleaved RoPE on the last rope_head_dim dims of x (pairs are (2k, 2k+1)). Matches the DeepSeek inference reference’s complex-mul formulation in dsv4flash/inference/model.py:apply_rotary_emb: the released DSV4-Flash weights were trained with this layout, NOT the Llama-style rotate_half layout HF transformers PR 45616/45643 still uses (pairs (d, d+rd/2)).

Inverse rotation: pass -sin instead of sin (caller’s responsibility — same as our existing inverse-rope call site).

Parameters:

x
torch.Tensor

[..., rope_head_dim] (or larger trailing dim with rope on the last rope_head_dim slice). Typical attention-layout shapes: [B, H, S, D] for q/k or [B, 1, S, D] for shared-KV.

cos, sin

shape [B, S, rope_head_dim] produced by the Llama-style cat([freqs, freqs], -1) rotary; we take the first half which contains the unique per-pair frequencies (the second half is a duplicate that the Llama-style helper needs and we don’t).

rope_head_dim
int

Must be even.

nemo_automodel.components.models.deepseek_v4.layers._build_indexer_topk_compressed_mask(
attention_mask: torch.Tensor,
indexer_topk: torch.Tensor,
n_pooled: int
) -> torch.Tensor

Build the additive compressed-position mask for Indexer-selected pool IDs.

nemo_automodel.components.models.deepseek_v4.layers._compressed_window_metadata(
seq_ids: torch.Tensor | None,
position_ids: torch.Tensor | None,
ready_len: int,
ratio: int
) -> tuple[torch.Tensor | None, torch.Tensor | None]
nemo_automodel.components.models.deepseek_v4.layers._dsv4_kernel_backend(
) -> str

Use TileLang DSV4 sparse kernels only when the attention backend requests them.

nemo_automodel.components.models.deepseek_v4.layers._dsv4_sinkhorn_backend(
) -> str

Use optional TileKernels sinkhorn when available, otherwise torch fallback.

nemo_automodel.components.models.deepseek_v4.layers._full_tensor_if_dtensor(
tensor: torch.Tensor
) -> torch.Tensor
nemo_automodel.components.models.deepseek_v4.layers._overlap_transform(
tensor: torch.Tensor,
head_dim: int,
fill_value: float,
window_seq_ids: torch.Tensor | None = None
) -> torch.Tensor

Reshape [B, S, ratio, 2*head_dim] -> [B, S, 2*ratio, head_dim] with the cross-window overlap from the DeepSeek inference reference (Compressor.overlap_transform in dsv4flash/inference/model.py:307-314).

Window N consumes:

  • positions [ratio:] of the new tensor: the second half of the feature dim of window N (current block).
  • positions [:ratio] of the new tensor: the first half of the feature dim of window N-1 (previous block, i.e. the overlap into the past).

Window 0 has no previous block, so its [:ratio] slice is left at fill_value (0 for the kv tensor, -inf for the score tensor so softmax masks it out).

nemo_automodel.components.models.deepseek_v4.layers._overlap_transform_with_cp(
tensor: torch.Tensor,
head_dim: int,
fill_value: float,
cp_group,
window_seq_ids: torch.Tensor | None = None
) -> torch.Tensor
nemo_automodel.components.models.deepseek_v4.layers._pool_windows(
kv: torch.Tensor,
gate: torch.Tensor,
ape: torch.Tensor,
ratio: int,
head_dim: int,
overlap: bool = False,
cp_group = None,
window_seq_ids: torch.Tensor | None = None
) -> torch.Tensor

Softmax-gated sum-pool over ratio consecutive tokens.

Non-overlap mode (HF PR 45616 layout, ratio==128 in V4-Flash): Input kv/gate of shape [B, length, head_dim]. Reshape to [B, length/ratio, ratio, head_dim] and pool over the ratio axis.

Overlap mode (DeepSeek inference reference layout, ratio==4 in V4-Flash): Input kv/gate of shape [B, length, 2*head_dim] (wkv/wgate project to 2*head_dim so each window can carry both its own kv and a half-overlap into the next window). Reshape to [B, length/ratio, ratio, 2*head_dim], apply _overlap_transform to remap to [B, length/ratio, 2*ratio, head_dim], then pool over the 2*ratio axis. Each compressed token thus aggregates 2*ratio = 8 raw tokens — the ratio tokens of the current window plus the ratio tokens of the previous window — giving smoother compression boundaries that the released checkpoint was trained under.

HF PR 45616 omits the overlap path entirely; the released DSV4-Flash safetensors have ape/wkv/wgate shapes that only match the overlap layout ([ratio, 2*head_dim] and [2*head_dim, hidden]), so we must support it here to load the released weights.

nemo_automodel.components.models.deepseek_v4.layers._query_positions(
position_ids: torch.Tensor | None,
batch: int,
seq_len: int,
device: torch.device,
cp_group = None
) -> torch.Tensor
nemo_automodel.components.models.deepseek_v4.layers._rms_norm_last_dim(
x: torch.Tensor,
eps: float
) -> torch.Tensor

RMS-normalize the last dim without materializing an x.square() tensor.

nemo_automodel.components.models.deepseek_v4.layers._rope_pool_positions(
pool_length: int,
pool_base: int,
ratio: int,
device: torch.device,
batch: int
) -> torch.Tensor
nemo_automodel.components.models.deepseek_v4.layers._rotate_half(
x: torch.Tensor
) -> torch.Tensor

Rotate half the hidden dims of the input (Llama / GPT-NeoX style).

nemo_automodel.components.models.deepseek_v4.layers._yarn_correction_dim(
num_rotations: float,
dim: int,
base: float,
max_seq_len: int
) -> float
nemo_automodel.components.models.deepseek_v4.layers._yarn_correction_range(
low_rot: float,
high_rot: float,
dim: int,
base: float,
max_seq_len: int
) -> tuple[int, int]
nemo_automodel.components.models.deepseek_v4.layers._yarn_linear_ramp(
min_v: float,
max_v: float,
dim: int,
device = None
) -> torch.Tensor
nemo_automodel.components.models.deepseek_v4.layers.apply_rotary_pos_emb(
q: torch.Tensor,
k: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
position_ids: torch.Tensor | None = None,
unsqueeze_dim: int = 1
) -> tuple[torch.Tensor, torch.Tensor]

Port of transformers.models.llama.modeling_llama.apply_rotary_pos_emb.

nemo_automodel.components.models.deepseek_v4.layers.build_causal_padding_mask(
attention_mask: torch.Tensor | None,
seq_len: int,
dtype: torch.dtype,
device: torch.device,
batch_size: int = 1,
sliding_window: int | None = None
) -> torch.Tensor | None

Build a 4D additive causal+padding (+optional sliding-window) mask compatible with eager_attention_with_sink.

Mirrors HF’s create_sliding_window_causal_mask (used in DeepseekV4Model.forward): each query at position i attends only to keys at positions [max(0, i - sliding_window + 1), i]. The DSV4-Flash weights were trained with this banding on every layer, so dropping it makes the softmax see a different distribution than training and degrades loss.

Inputs

attention_mask: 2D [B, S] tensor with 1=valid, 0=padding (HF convention), or already-4D additive mask, or None. sliding_window: if not None, mask out keys further back than this many positions from the query (in addition to causal masking).

Returns: [B, 1, S, S] additive mask of dtype (0 where keep, large negative where mask).

nemo_automodel.components.models.deepseek_v4.layers.build_packed_causal_padding_mask(
seq_lens: torch.Tensor,
seq_len: int,
dtype: torch.dtype,
device: torch.device,
sliding_window: int | None = None
) -> torch.Tensor

Build a 4D additive block-causal mask from packed-sequence lengths.

nemo_automodel.components.models.deepseek_v4.layers.eager_attention_with_sink(
module: torch.nn.Module,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attention_mask: torch.Tensor | None,
scaling: float,
dropout: float = 0.0,
kwargs = {}
) -> tuple[torch.Tensor, torch.Tensor]

Eager attention with per-head sink: appends an extra softmax column whose logit is module.sinks[h] and whose value-slot is zero. Ported verbatim from HF PR 45616.

nemo_automodel.components.models.deepseek_v4.layers.repeat_kv(
hidden_states: torch.Tensor,
n_rep: int
) -> torch.Tensor

Port of transformers.models.llama.modeling_llama.repeat_kv.

nemo_automodel.components.models.deepseek_v4.layers.dsv4_indexer_replay: RecomputeReplay[Tensor] = RecomputeReplay('DSV4 frozen indexer selection')