Skip to content

feat(sft): opt-in final stop token in build_training_sample#101

Merged
hubert-marek merged 6 commits into
mainfrom
feat/training-sample-final-stop
Jul 10, 2026
Merged

feat(sft): opt-in final stop token in build_training_sample#101
hubert-marek merged 6 commits into
mainfrom
feat/training-sample-final-stop

Conversation

@hubert-marek

@hubert-marek hubert-marek commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add ensure_final_stop to build_training_sample: when a sample ends with an assistant message whose last trainable token is not a renderer stop token, append the canonical stop as a trainable target
  • no-op for templates whose assistant close is part of the message (ChatML <|im_end|>, Llama <|eot_id|>); restores final-turn stop supervision for GLM-style templates where the turn terminator is the next message's role marker or a sampled <|endoftext|> the template never re-renders
  • matrix-wide test asserting the final assistant turn ends at a trainable stop and that in-message-close renderers stay byte-identical

Validation

  • uv run pytest tests/test_build_helpers.py -q — 101 passed, 10 skipped
  • Ruff format/check (repo defaults) — passed

Made with Cursor

Note

Add ensure_final_stop parameter to build_training_sample to append a trainable stop token

  • Adds an opt-in ensure_final_stop: bool = False keyword argument to build_training_sample in renderers/base.py.
  • When enabled and the last message is an assistant turn, the function checks whether the last trainable token is already a stop token; if not, it appends the renderer's first stop token id and marks it as trainable in the loss mask.
  • Behavior is unchanged when ensure_final_stop=False or when the final trainable token is already a stop token.
  • Risk: multi-modal mm_token_type_ids are sized to the original rendered length and do not include the appended stop token, which may cause a length mismatch in multi-modal samples using this flag.

Changes since #101 opened

  • Updated docstring for ensure_final_stop parameter in build_training_sample function [e036812]
  • Added inline comment in build_training_sample utility function clarifying stop token training behavior [7d362e4]
  • Changed the length argument in _build_mm_token_type_ids call within build_training_sample function from len(rendered.token_ids) to len(token_ids) [807f292]
  • Modified build_training_sample function in renderers.base to require has_sampled_info in addition to ensure_final_stop and the last message being from the assistant before appending a stop token, and updated the docstring to clarify that ensure_final_stop is ignored for renderers without sampled_mask such as DefaultRenderer, with an added inline comment explaining the tightened conditional logic [165a941]
  • Modified build_training_sample function to conditionally append and train the final stop token based on role masking filter [ec1ea8b]

Macroscope summarized fc04fba.

Templates like GLM close assistant turns with the next message's role marker, leaving a final assistant turn with no stop supervision; ensure_final_stop appends the canonical stop as a trainable target while remaining a no-op for in-message closes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hubert-marek hubert-marek marked this pull request as ready for review July 10, 2026 22:16
Comment thread renderers/base.py Outdated
Comment thread renderers/base.py
Comment thread renderers/base.py
)
if last_trainable is None or token_ids[last_trainable] not in stop_ids:
token_ids.append(renderer.get_stop_token_ids()[0])
loss_mask.append(True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loss mask True means training or not training. We should train on eos

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it means we train on it

@macroscopeapp

macroscopeapp Bot commented Jul 10, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

Adds an opt-in parameter (ensure_final_stop=False by default) to build_training_sample — existing behavior is unchanged. The feature is well-documented, tested, and self-contained with no impact on callers that don't explicitly opt in.

You can customize Macroscope's approvability policy. Learn more.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e036812. Configure here.

Comment thread renderers/base.py
hubert-marek and others added 2 commits July 10, 2026 22:20
Co-authored-by: Cursor <cursoragent@cursor.com>
Build the modality flags from the post-append token count so multimodal samples keep aligned per-token arrays.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hubert-marek hubert-marek requested a review from samsja July 10, 2026 22:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc04fbaa8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread renderers/base.py
Comment thread renderers/base.py
Comment thread renderers/base.py
samsja
samsja previously approved these changes Jul 10, 2026

@samsja samsja left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Opaque templates give no reliable way to locate the assistant close, so ensure_final_stop is a no-op for DefaultRenderer instead of corrupting already-terminated turns.

Co-authored-by: Cursor <cursoragent@cursor.com>
A filter that excludes the final assistant message must also exclude its stop token.

Co-authored-by: Cursor <cursoragent@cursor.com>
@hubert-marek hubert-marek merged commit fa04acd into main Jul 10, 2026
11 checks passed
@hubert-marek hubert-marek deleted the feat/training-sample-final-stop branch July 10, 2026 22:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec1ea8ba35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread renderers/base.py
Comment on lines +1714 to +1717
last_trainable = next(
(k for k in range(len(loss_mask) - 1, -1, -1) if loss_mask[k]), None
)
if last_trainable is None or token_ids[last_trainable] not in stop_ids:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check the final assistant span before skipping the stop

With ensure_final_stop=True and no role_to_mask, GLM-style renderers can leave the last assistant unterminated: in a user, assistant, user, empty assistant sample, the second user's role marker is a trainable sampled stop for the previous assistant turn, so this global last_trainable check sees a stop token and skips appending one for the final assistant. The stop check should be scoped to tokens attributed to messages[-1] (or append when that final assistant has no trainable tokens).

Useful? React with 👍 / 👎.

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.

2 participants