Skip to content

Accept explicit per-token position ids in Transformer.forward - #282

Open
amazloumi wants to merge 5 commits into
mainfrom
feat/explicit-position-ids
Open

amazloumi wants to merge 5 commits into
mainfrom
feat/explicit-position-ids

Conversation

@amazloumi

Copy link
Copy Markdown
Member

Summary

  • Add an optional position_ids input to Transformer.forward: a (batch, seq_len) int64 tensor of per-token RoPE positions. Rows may differ, so documents in a packed sequence can restart at position zero.
  • Gather cos/sin per token from the precomputed RoPE tables; apply_rope accepts the batched (batch, seq_len, head_dim // 2) form alongside the shared one.
  • Validate dtype, rank, shape against the full residual (including modality.prefix_embeds) and the RoPE table range at the top of forward, before any compute. kv_caches and MoT are rejected.
  • No config field, state-dict key, registry string or CLI change. The default path (position_ids=None) is unchanged.

Testing

  • uv run ruff check and uv run ruff format --check pass on the touched files
  • uv run pyright kempnerforge/ passes (0 errors)
  • uv run pytest tests/unit/: 2281 passed, 3 skipped
  • New unit tests: hand-computed rotations at gathered positions, a document packed twice with restarting positions gives identical outputs (and differs without the restart), per-row independence, gradients through the gather, MoMa, and every validation error raised before the embedding runs
  • Default path: contiguous position_ids reproduce position_ids=None bitwise (dense and MoMa), and the state dict gains no entries
  • Re-verified against the codebase design doc: a general model knob entering through the existing forward-input seam

Closes #259

amazloumi and others added 3 commits October 15, 2026 12:41
Optional position_ids runtime input, shape (batch, seq_len) int64: RoPE
cos/sin are gathered per token instead of the contiguous start_pos
slice, and rows may use different positions, so packed documents can
restart positions at zero. apply_rope broadcasts the batched
(batch, seq_len, head_dim // 2) frequency form over heads. Dtype,
shape, and bounds are validated with clear errors before compute;
kv_caches and MoT combinations are rejected. Default None keeps the
contiguous slice bit-exact and no state-dict keys change.

Co-Authored-By: Timothy Ngotiaoco <timothy_ngotiaoco@harvard.edu>
The shape and bounds checks for position_ids ran after the token
embedding, the prefix concat and the RoPE table device transfer, so an
invalid position_ids wasted that compute before failing. All position_ids
validation now runs at the top of forward: dtype, rank, shape against the
residual length (text from tokens or inputs_embeds plus any prefix_embeds),
and bounds against the RoPE table length. Error messages are unchanged and
the default None path is untouched.

Tests assert the token embedding is not called for a bad shape, an
out-of-range id, and text-length ids with prefix_embeds set. They also
cover the inputs_embeds route and a MoMa forward with explicit contiguous
positions.

Co-Authored-By: Timothy Ngotiaoco <timothy_ngotiaoco@harvard.edu>
A model materialized with to_empty() has no RoPE tables until
init_weights_and_freqs() runs. In that state the explicit position_ids
bounds check was skipped, so the token embedding and the prefix concat
ran before the later RoPE assertion fired. The top-of-forward position_ids
validation now rejects the call first, with the same message. The
position_ids=None path is unchanged.

The test builds the model on the meta device, asserts through a forward
pre-hook spy that the token embedding never runs, and checks as a control
that it does once init_weights_and_freqs() has been called.

Co-Authored-By: Timothy Ngotiaoco <timothy_ngotiaoco@harvard.edu>
@amazloumi amazloumi added the core Affecting core label Oct 15, 2026
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
kempnerforge/model/position.py 100.00% <100.00%> (ø)
kempnerforge/model/transformer.py 96.34% <100.00%> (+1.50%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Only the per-token gather is new; the transfer and the contiguous slice
behave exactly as before.

Co-Authored-By: Timothy Ngotiaoco <timothy_ngotiaoco@harvard.edu>
@amazloumi
amazloumi marked this pull request as draft October 16, 2026 14:38
@amazloumi
amazloumi marked this pull request as ready for review October 16, 2026 14:42
@amazloumi
amazloumi requested a review from Naeemkh October 16, 2026 14:45

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Affecting core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accept explicit per-token position ids in Transformer.forward

1 participant