nnterp · encyclopedia hyperclovax · nnterp.families.hyperclovax

family hyperclovax

HyperCLOVA X

Llama's block with a norm after each sublayer (post_norm1, post_norm2) where use_post_norm is set, and four config multipliers: on the embeddings, on each sublayer's contribution, on the attention scores (in place of head_dim ** -0.5) and on the logits.

The block

01 · the residual stream

One block of the stack. Hover any part for its nnterp name; click to pin the card.

embed_tokenstoken_embeddings
→
layers× 38
→
normfinal_norm_output
→
lm_headlm_head_output
→
logitsscaled
i = 0

identity layers[i].input + self_attn.attention_output + mlp.mlp_output == layer_output

The API

02 · support()

Values, by host

⚠︎ marks a value this checkpoint has only under some load: hover it for the condition.

model HyperCLOVAXForCausalLMlayoutwhat it isread at
logits Logits [batch seq vocab_size] The logits the model returns, after anything it does past lm_head (a softcap, a scale)

the module's own output

token_embeddings Residual [batch seq hidden_size] The embedding module's output; layers[0].input is what enters the first block

the child module embed_tokens's output

final_norm_output Residual [batch seq hidden_size] The residual stream after the final norm, what lm_head reads

the child module norm's output

lm_head_output Logits [batch seq vocab_size] The head's logits before anything the model does past lm_head (a softcap, a scale)

the child module lm_head's output

next_token_probs NextTokenProbs [batch vocab_size] The next-token distribution at the last position; derived, read-only

the module's own output

input_ids Tokens [batch seq] The token ids the model was called with

the module's own input

attention_mask Tokens [batch seq] The attention mask the model was called with; zeros are padding

the module's own input

input_size scalar [batch, seq] of the current call; read-only

the module's own input

model.layers[i] HyperCLOVAXDecoderLayerlayoutwhat it isread at
layer_input Residual [batch seq hidden_size] The residual stream entering the block

the module's own input

attention_norm_output Residual [batch seq hidden_size] The normed stream entering the attention

the child module input_layernorm's output

layer_mid Residual [batch seq hidden_size] The residual stream after the attention add

the child module post_attention_layernorm's input

mlp_norm_output Residual [batch seq hidden_size] The normed stream entering the MLP

the child module post_attention_layernorm's output

mlp_input Residual [batch seq hidden_size] What the MLP is called with

the child module mlp's input

layer_output Residual [batch seq hidden_size] The residual stream leaving the block, a tensor even when the block returns a tuple

the module's own output

model.layers[i].self_attn HyperCLOVAXAttentionlayoutwhat it isread at
⚠︎attention_queries Queries [batch num_heads seq qk_head_dim] The queries entering attention

inside the forward: the arguments of attention_interface_1, element 1

⚠︎attention_keys Keys [batch num_kv_heads seq qk_head_dim] The keys entering attention

inside the forward: the arguments of attention_interface_1, element 2

⚠︎attention_values Values [batch num_kv_heads seq head_dim] The values entering attention

inside the forward: the arguments of attention_interface_1, element 3

⚠︎attention_scores Pattern [batch num_heads query key] The attention scores entering the softmax, masked

inside the forward: the input of nn_functional_softmax_0 inside attention_interface_1

attention_premix HeadOutputs [batch seq num_heads head_dim] Every head's output side by side, as the output projection takes it

the child module o_proj's input

attention_output Residual [batch seq hidden_size] What the attention adds to the residual stream: the post-attention norm's output times residual_multiplier

the sibling module post_norm1's output

⚠︎attention_probabilities Pattern [batch num_heads query key] The attention pattern the values are mixed with

inside the forward: the output of nn_functional_dropout_0 inside attention_interface_1

⚠︎attention_head_outputs HeadOutputs [batch seq num_heads head_dim] The per-head outputs before the output projection

inside the forward: the output of attention_interface_1, element 0

model.layers[i].mlp HyperCLOVAXMLPlayoutwhat it isread at
mlp_activation Neurons [batch seq intermediate_size] The activation function's output

the child module act_fn's output

mlp_neurons Neurons [batch seq intermediate_size] What the down projection receives: act(gate)·up on a gated MLP, the activation itself otherwise

the child module down_proj's input

mlp_output Residual [batch seq hidden_size] What the MLP adds to the residual stream: the post-MLP norm's output times residual_multiplier

the sibling module post_norm2's output

Printout

What print(model) shows on naver-hyperclovax/HyperCLOVAX-SEED-Think-14B: the native modules, standard names and every standard value with its layout.

HyperCLOVAXForCausalLM(
  (lm_head): Linear(in_features=6144, out_features=110592, bias=False)
  (generator): Generator(
    (streamer): Streamer()
  )
  (embed_tokens): Embedding(110592, 6144, padding_idx=100257)
  (layers): ModuleList(
    (0-37): 38 x HyperCLOVAXDecoderLayer(
      (self_attn): HyperCLOVAXAttention(
        (q_proj): Linear(in_features=6144, out_features=6144, bias=False)
        (k_proj): Linear(in_features=6144, out_features=1024, bias=False)
        (v_proj): Linear(in_features=6144, out_features=1024, bias=False)
        (o_proj): Linear(in_features=6144, out_features=6144, bias=False)
        (attention_output) -> Residual [batch seq hidden_size]: What the attention adds to the residual stream: the post-attention norm's output times residual_multiplier
        (attention_queries) -> Queries [batch num_heads seq qk_head_dim]: The queries entering attention
        (attention_keys) -> Keys [batch num_kv_heads seq qk_head_dim]: The keys entering attention
        (attention_values) -> Values [batch num_kv_heads seq head_dim]: The values entering attention
        (attention_scores) -> Pattern [batch num_heads query key]: The attention scores entering the softmax, masked
        (attention_premix) -> HeadOutputs [batch seq num_heads head_dim]: Every head's output side by side, as the output projection takes it
        (attention_probabilities) -> Pattern [batch num_heads query key]: The attention pattern the values are mixed with
        (attention_head_outputs) -> HeadOutputs [batch seq num_heads head_dim]: The per-head outputs before the output projection
      )
      (mlp): HyperCLOVAXMLP(
        (gate_proj): Linear(in_features=6144, out_features=14336, bias=False)
        (up_proj): Linear(in_features=6144, out_features=14336, bias=False)
        (down_proj): Linear(in_features=14336, out_features=6144, bias=False)
        (act_fn): SiLUActivation()
        (mlp_output) -> Residual [batch seq hidden_size]: What the MLP adds to the residual stream: the post-MLP norm's output times residual_multiplier
        (mlp_activation) -> Neurons [batch seq intermediate_size]: The activation function's output
        (mlp_neurons) -> Neurons [batch seq intermediate_size]: What the down projection receives: act(gate)·up on a gated MLP, the activation itself otherwise
      )
      (input_layernorm): HyperCLOVAXRMSNorm((6144,), eps=1e-05)
      (post_attention_layernorm): HyperCLOVAXRMSNorm((6144,), eps=1e-05)
      (post_norm1): HyperCLOVAXRMSNorm((6144,), eps=1e-05)
      (post_norm2): HyperCLOVAXRMSNorm((6144,), eps=1e-05)
      (layer_input) -> Residual [batch seq hidden_size]: The residual stream entering the block
      (attention_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the attention
      (layer_mid) -> Residual [batch seq hidden_size]: The residual stream after the attention add
      (mlp_norm_output) -> Residual [batch seq hidden_size]: The normed stream entering the MLP
      (mlp_input) -> Residual [batch seq hidden_size]: What the MLP is called with
      (layer_output) -> Residual [batch seq hidden_size]: The residual stream leaving the block, a tensor even when the block returns a tuple
    )
  )
  (norm): HyperCLOVAXRMSNorm((6144,), eps=1e-05)
  (logits) -> Logits [batch seq vocab_size]: The logits the model returns, after anything it does past lm_head (a softcap, a scale)
  (token_embeddings) -> Residual [batch seq hidden_size]: The embedding module's output; layers[0].input is what enters the first block
  (final_norm_output) -> Residual [batch seq hidden_size]: The residual stream after the final norm, what lm_head reads
  (lm_head_output) -> Logits [batch seq vocab_size]: The head's logits before anything the model does past lm_head (a softcap, a scale)
  (next_token_probs) -> NextTokenProbs [batch vocab_size]: The next-token distribution at the last position; derived, read-only
  (input_ids) -> Tokens [batch seq]: The token ids the model was called with
  (attention_mask) -> Tokens [batch seq]: The attention mask the model was called with; zeros are padding
  (input_size): [batch, seq] of the current call; read-only
)

Config

model.num_layers38
model.hidden_size6144
model.num_heads48
model.num_kv_heads8
model.head_dim128
model.qk_head_dim128
model.vocab_size110592
model.intermediate_size14336
architecturesHyperCLOVAXForCausalLM
num_hidden_layers38
hidden_size6144
intermediate_size14336
num_attention_heads48
num_key_value_heads8
head_dim128
vocab_size110592
max_position_embeddings131072
hidden_actsilu
rms_norm_eps1e-05
tie_word_embeddingsFalse
dtypefloat32
embedding_multiplier10.0
residual_multiplier1.0
attention_multiplier0.0078125
logits_scaling0.125
attention_biasFalse

The Notes

03 · what to know before you run

The block, in order

x      = embed_tokens(ids) * embedding_multiplier
h      = x + post_norm1(self_attn(input_layernorm(x))) * residual_multiplier
out    = h + post_norm2(mlp(post_attention_layernorm(h))) * residual_multiplier
logits = lm_head(norm(out)) * logits_scaling

With use_post_norm (Think-14B) a block has four RMSNorms, two per sublayer, and the names are the trap: post_attention_layernorm is the MLP's input norm, as on Llama, and the norms after the sublayers are post_norm1 (after the attention) and post_norm2 (after the MLP). nnterp keeps the native names. Each multiplier is one number for the whole model. On Think-14B they are embedding_multiplier 10, residual_multiplier 1, attention_multiplier 1/128 and logits_scaling 0.125.

The contributions are the post-norms' outputs, times residual_multiplier

attention_output is post_norm1.output * residual_multiplier and mlp_output is post_norm2.output * residual_multiplier: the tensors the block adds. On Think-14B the multiplier is 1.0, so they equal the post-norms' outputs bit for bit. self_attn.output[0] and mlp.output are the tensors entering the post-norms. The identity is the plain sum, exact in float32 and bfloat16:

with model.trace(prompt):
    x = model.layers[1].input.save()
    post = model.layers[1].post_norm1.output.save()
    attn = model.layers[1].self_attn.attention_output.save()
    mlp = model.layers[1].mlp.mlp_output.save()
    out = model.layers[1].layer_output.save()

torch.testing.assert_close(attn, post * model.config.residual_multiplier)
torch.testing.assert_close(x + attn + mlp, out)

With post-norms, scale the contribution, not the module. RMSNorm is scale-invariant: post_norm2(0.5 * y) equals post_norm2(y) up to eps: halving mlp.output leaves the stream as it was, and zeroing it works only because the norm of zero is zero. Partial ablations and steering go through mlp_output / attention_output, which the block adds as they are:

with model.trace(prompt):
    model.layers[1].mlp.mlp_output[:] *= 0.5        # halves what the block adds

with model.trace(prompt):
    model.layers[1].mlp.output[:] *= 0.5            # a no-op: post_norm2 rescales it back

A write to a contribution goes through the multiplier

The contributions are computed copies: a write to attention_output or mlp_output reaches the model by dividing the whole edited copy by residual_multiplier and handing it back as the post-norm's output, and the block multiplies it again. With the multiplier at 1.0, as on Think-14B, both steps are exact: an edit at one position leaves every other position bit-identical, in bfloat16 too. With another multiplier the unedited positions go through the round trip as well and move by rounding in bfloat16. Editing the post-norm's output by the vector over the multiplier reaches the same place and touches nothing else, for any multiplier:

m = model.config.residual_multiplier
with model.trace(prompt):
    model.layers[1].mlp.mlp_output[:, -1] += v            # exact when m is 1.0

with model.trace(prompt):
    model.layers[1].post_norm2.output[:, -1] += v / m     # position -1 only, for any m

A read changes nothing: the forward stays bit-identical.

The query scale is attention_multiplier

The attention multiplies q · kᵀ by config.attention_multiplier in place of head_dim ** -0.5. On Think-14B it is 1/128, 1 / head_dim, a factor of 11.3 below 128 ** -0.5; on Think-32B it is 128 ** -0.5. attention_scores are read after the scale and the causal mask, and softmax(attention_scores) reproduces attention_probabilities. Code that recomputes scores takes the multiplier from the config:

attn = model.layers[1].self_attn
with model.trace(prompt):
    q = attn.attention_queries.save()
    k = attn.attention_keys.save()
    scores = attn.attention_scores.save()

k = k.repeat_interleave(model.num_heads // model.num_kv_heads, dim=1)
qk = q @ k.transpose(-1, -2) * model.config.attention_multiplier    # not head_dim ** -0.5
causal = torch.ones_like(qk[0, 0], dtype=torch.bool).tril()
torch.testing.assert_close(qk[..., causal], scores[..., causal])

Think-14B has 48 query heads over 8 key/value heads: attention_keys and attention_values are [batch, 8, seq, 128], read before repeat_kv, and an edit to key/value head j reaches query heads 6j to 6j + 5. The six interior values need attn_implementation="eager".

Logits are the head's output times logits_scaling

model.logits is model.lm_head.output * logits_scaling exactly (0.125 on Think-14B), and project_on_vocab multiplies too, so a logit lens at the last block equals logits. A softmax over lm_head.output, or over lm_head(norm(hidden)) written by hand, is at temperature 1/8: sharper than the model's own distribution. Take probabilities and KLs from logits or project_on_vocab.

with model.trace(prompt):
    resid = model.layers[-1].layer_output.save()
    raw = model.lm_head.output.save()
    logits = model.logits.save()

torch.testing.assert_close(logits, raw * model.config.logits_scaling)
torch.testing.assert_close(model.project_on_vocab(resid), logits)

lm_head and embed_tokens are separate weights.

token_embeddings is the unscaled lookup

The embedding module returns the plain lookup, and the model multiplies it by embedding_multiplier afterwards: layers[0].input is token_embeddings * 10 on Think-14B. Embeddings passed as inputs_embeds are multiplied too. An edit to token_embeddings reaches block 0 multiplied by embedding_multiplier; to set what block 0 reads, edit layers[0].input.

with model.trace(prompt):
    emb = model.token_embeddings.save()
    first = model.layers[0].input.save()

torch.testing.assert_close(first, emb * model.config.embedding_multiplier)

Loading Think-14B

The checkpoint is stored in float32 (torch_dtype in its config), and a default load keeps it, about 59 GB for its 14.7B parameters; pass dtype=torch.bfloat16 to halve it. The tokenizer prepends no BOS token to a plain prompt; the chat template starts with <|im_start|>.

The 32B vision-language checkpoint

HyperCLOVAX-SEED-Think-32B is a vision-language wrapper (hyperclovax_vision_v2, remote code) whose text model is this family. Its text config turns the post-norms off (use_post_norm false), sets embedding_multiplier, residual_multiplier and logits_scaling to 1.0 and attention_multiplier to 128 ** -0.5: under these names it computes Llama's block. With use_post_norm false, post_norm1 and post_norm2 are identities, and attention_output and mlp_output are the modules' outputs times residual_multiplier.

From the family module

04 · nnterp/families/hyperclovax.py

HyperCLOVA X (HyperCLOVAXForCausalLM, NAVER's HyperCLOVA X SEED Think).

Llama's tree with a sandwich block and Granite's µP multipliers::

h = x + post_norm1(self_attn(input_layernorm(x))) * residual_multiplier
out = h + post_norm2(mlp(post_attention_layernorm(h))) * residual_multiplier

post_norm1 / post_norm2 are RMS norms under use_post_norm and identities otherwise; post_attention_layernorm is the pre-MLP norm (the Llama meaning). What the block adds is each post-norm's output times the multiplier, so attention_output and mlp_output are that product: a computed copy, divided by the multiplier on assignment, with a transform carrying an in-place edit back into the post-norm's output, as on Granite; both modules keep the config, where the multiplier is read. attention_multiplier is the softmax scale passed to the shared interface; embedding_multiplier scales the embedding module's output before the first block (token_embeddings times it is layers[0].input); logits_scaling multiplies the head's output (Granite divides), so the family's project_on_vocab does the same.

HyperCLOVA X Vision V2 (HyperCLOVAXVisionV2ForConditionalGeneration, model type hyperclovax_vision_v2, needs transformers 5.18 or later) wraps this text model at model.language_model. Its tower is the Qwen2.5-VL ViT at model.vision_model, vision (a QwenVision: packed, windowed, its own merger inside), and a linear model.projector after the tower, projector, maps the merger's output onto the text width; vision.image_features is read at the scatter in HyperCLOVAXVisionV2Model's forward (ImageScatter).