What Is the Dimension of BERT-base pooler_output?

BERT-base pooler_output has shape (batch_size, 768). It is the hidden state of the [CLS] token passed through a 768×768 dense layer (590,592 parameters) and a tanh activation. The output dimension is fixed at 768, equal to the model’s hidden size, and does not depend on the input sequence length.

Shape at a Glance

last_hidden_state:  (batch_size, seq_len, 768)   # one vector per token
pooler_output:      (batch_size, 768)            # one vector per batch item

The sequence dimension is the key difference. last_hidden_state keeps every token position, so its length varies with the input. pooler_output collapses that dimension to a single 768-vector per example, which is convenient for classification heads that expect a fixed-size representation.

How It Is Computed

The pooler is a single dense layer followed by tanh. It reads only the hidden state at position 0, the [CLS] token:

pooled = dense(last_hidden_state[:, 0, :])   # shape (batch, 768)
pooler_output = tanh(pooled)                  # shape (batch, 768)

With hidden size 768, the dense layer has 768×768 weights plus a 768-vector bias:

weights: 768 * 768  = 589,824
bias:               =     768
total:              = 590,592 parameters

Why It Is Always 768

In BERT-base the hidden size H is 768, and every transformer output vector lives in that space. The pooler is a linear map from 768 → 768, so its output is always 768-dimensional. The number never changes with the number of layers, attention heads, or tokens; it is pinned to the model dimension.

Hidden size (H):  768
Intermediate:     3072   (4 * H)
Attention heads:  12
Layers:           12
pooler_output:    (batch, 768)

Reading the Output Dict

BertModel.forward() returns a BaseModelOutputWithPoolingAndCrossAttentions. The fields you normally use are last_hidden_state, pooler_output, and hidden_states. Only the first element of pooler_output is ever used by the model’s prediction heads:

outputs = model(input_ids)
cls_vec  = outputs.pooler_output        # (batch, 768)
# or the underlying tensor, identical to:
cls_vec  = outputs.last_hidden_state[:, 0]  # then densed + tanh

pooler_output vs .pooler.dense

When you build the model with config.output_hidden_states=True or access submodules directly, you may see model.pooler and model.pooler.dense. These are the same layer that produces pooler_output. The named module is a BertPooler whose single dense member holds the 768×768 + 768 parameters; pooler_output is just its output applied through tanh. Accessing the tensor directly bypasses the tanh:

pre_tanh = model.pooler.dense(last_hidden_state[:, 0])   # no tanh
pooled   = model.pooler(last_hidden_state)               # tanh applied

Disabling the Pooler

If you do not need a sentence-level vector, you can turn the pooler off entirely. Passing add_pooling_layer=False to BertModel removes it, so pooler_output comes back as None and model.pooler is absent:

model = BertModel.from_pretrained("bert-base-uncased",
                                  add_pooling_layer=False)
outputs = model(input_ids)
outputs.pooler_output   # None
outputs.last_hidden_state  # (batch, seq, 768)

With the pooler disabled, build your own pooled vector when you need one, for example a mean over tokens:

# mask-aware mean pooling over last_hidden_state
mask = attention_mask.unsqueeze(-1)          # (batch, seq, 1)
sums = (last_hidden_state * mask).sum(dim=1)
counts = mask.sum(dim=1).clamp(min=1e-9)
mean_pooled = sums / counts                  # (batch, 768)

Contrast With Other BERT Models

Model          | Hidden | pooler_output dim
------------------------------|--------------
BERT-tiny      |  128   | (batch, 128)
BERT-mini      |  256   | (batch, 256)
BERT-small     |  512   | (batch, 512)
BERT-base      |  768   | (batch, 768)
BERT-large     | 1024   | (batch, 1024)

In every BERT variant the pooler output dimension equals the hidden size. BERT-large uses 1024, so its pooler_output is (batch_size, 1024) with a 1024×1024 dense layer and 1,049,600 total pooler parameters.

Try the Parameter Counter

TENSOR PREFLIGHT · DOWNLOADABLE PYTORCH TOOLKIT

Take the next step in your own PyTorch model.

Trace a local forward pass, inspect tensor shapes in an HTML report, and turn a repair into a regression check. Includes eight broken-and-fixed workflows, source code and setup instructions.

Python 3.9+ · PyTorch 2.8+ · Runs locally

Get Tensor Preflight — $29 One-time payment · ZIP download See a sample report
By the same builder: GitHub, theluckystrike BeLikeNative, Grammar AI EarlyThunder, Dev Blog Bug Bounty Reality Zovo, AI Dev Tools