Skip to content

feat(xtoken): replace DP token alignment with offset-based cluster alignment#3286

Open
avenkateshha wants to merge 3 commits into
mainfrom
avenkateshha/xtoken-offset-alignment
Open

feat(xtoken): replace DP token alignment with offset-based cluster alignment#3286
avenkateshha wants to merge 3 commits into
mainfrom
avenkateshha/xtoken-offset-alignment

Conversation

@avenkateshha

@avenkateshha avenkateshha commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What

Moves cross-tokenizer alignment from Needleman-Wunsch DP to an offset-based cluster aligner, and extends it to chat / instruct (and "thinking") data.

Alignment pairs tokens by their character-span offsets (return_offsets_mapping): consecutive same-span tokens collapse into a cluster, a strict char-end walker pairs clusters covering the same character range, and special tokens (offset (0,0)) pair by role. is_correct is recomputed from canonicalized text, so pair_is_correct and the gold-loss exact partition keep their meaning.

Scope / status

  • Part 1 — offset-cluster alignment for raw-text KD (replaces the DP aligner)
  • Part 2 — chat / native-template per-assistant-message alignment

Part 1 — offset alignment (done)

  • nemo_rl/algorithms/x_token/token_aligner.py — DP kernel, anchor segmentation, post-processing, and byte/mojibake canonicalization removed; the offset-cluster kernel (align_by_offsets_cluster) added. canonical_token (still used by tools/x_token and the is_correct mask) kept.
  • nemo_rl/data/cross_tokenizer_collate.py_tokenize_batch emits offset_mapping; align() receives student/teacher char offsets.
  • Teststest_token_aligner.py rewritten for the offset path; the collator's fake tokenizer emits char offsets.

Part 2 — chat / native-template alignment (this PR)

Each model renders a conversation with its own chat template, so the two rendered texts — and their offsets — differ, and the full sequence can't be aligned directly. This part aligns per assistant message: isolate each side's assistant content, rebase offsets to a shared coordinate system, align the slice with the same align_by_offsets_cluster kernel, then map positions back to the full sequence. "Thinking" data is split into reasoning / close / answer regions (+ an EOT marker).

Adds:

  • token_aligner.pyalign_one_offset_per_asst() (per-turn slice → rebase → align → map-back) and align_one_offset() (canonical + NFC decode-fix is_correct).
  • cross_tokenizer_collate.py — a chat mode (__call___call_text / _call_chat) with render / assistant-mask / native-region / lockstep-packing helpers and new constructor knobs.
  • chat_templates.pyfind_rendered_message_content_span().
  • processors.pychat_kd_processor() (reads messages / conversation).
  • Configcollator_mode, include_thinking_in_loss, native_thinking_alignment, drop_first_assistant_chunk_kl, kd_alignment_regions, num_packed_rows.

Testing

  • Part 1: 16/16 x_token unit tests pass (training container); 100-step multi-teacher GPU smoke (Qwen3-4B cross-tok + Llama-3.2-3B same-tok → Llama-3.2-1B, 2 nodes, default dataset) completed and saved a checkpoint. Per-step throughput: offset ~7.5 s/step vs DP ~25 s/step (~3.3× faster) — DP's Needleman-Wunsch alignment stalls the dataloader; the O(n) offset walk keeps it ahead.
  • Part 2: chat-mode unit + smoke coverage to be added with the chat commits.

🤖 Generated with Claude Code

…ignment

Cross-tokenizer alignment now pairs student and teacher tokens by the character spans they cover (return_offsets_mapping) instead of a Needleman-Wunsch DP over canonicalized token strings. Consecutive same-span tokens collapse into a cluster and a strict char-end walker pairs clusters covering the same character range; special tokens (offset (0,0)) pair by role. is_correct is still recomputed from canonicalized text, so pair_is_correct and the gold-loss exact partition keep their meaning.

The collator emits per-token char offsets and threads them into TokenAligner.align. The DP kernel, anchor segmentation, post-processing, and byte/mojibake canonicalization helpers are removed; canonical_token (still used by tools/x_token and the is_correct mask) stays.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Adithya Hanasoge <avenkateshha@nvidia.com>
@avenkateshha
avenkateshha requested review from a team as code owners July 19, 2026 18:56
@copy-pr-bot

copy-pr-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

avenkateshha and others added 2 commits July 19, 2026 12:29
Adds the chat/instruct alignment path to TokenAligner (Part 2 of the offset-alignment PR): align_one_offset_per_asst aligns each assistant message independently (isolate content, rebase offsets to a shared coordinate system, run the offset-cluster kernel on the slice, map positions back to the full sequence), with native reasoning/close/answer regions, a synthetic EOT pair, drop_first_content_pair, and included_regions. align_one_offset adds the single-sample offset path with an NFC decode-fix is_correct mask, and the constructor gains alignment_method (offset_cluster_decode_fix | same_tokenizer_identity). Returns raw 7-tuples; the chat collator (next commit) converts to AlignmentPair.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Adithya Hanasoge <avenkateshha@nvidia.com>
Adds a chat mode to CrossTokenizerCollator (Part 2 of the offset-alignment PR). In chat mode the collator renders each side's chat template, tokenizes with char offsets, derives the per-token assistant mask + each assistant message's char span, and aligns each assistant message independently via TokenAligner.align_one_offset_per_asst (multi-teacher). Loss fires only on assistant content (token_mask = attention_mask * assistant_mask). find_rendered_message_content_span (chat_templates.py) locates message content in the rendered string. Config knobs (collator_mode, drop_first_assistant_chunk_kl, include_thinking_in_loss, native_thinking_alignment, kd_alignment_regions, num_packed_rows) are wired from data_config and default to the raw-text path.

Scope: whole-message chat alignment. native_thinking_alignment (native reasoning/close/answer parsing) and num_packed_rows>1 (lockstep packing) raise NotImplementedError as clearly-marked follow-ups. Validated by unit tests (char-level fake chat tokenizer); a GPU chat smoke on real instruct data is a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Adithya Hanasoge <avenkateshha@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant