Skip to content

fix(dataprep): smart_chunk_text single-chunk path leaks internal tensor type when eos_token_id is None#7151

Merged
Etherll merged 3 commits into
unslothai:mainfrom
chuenchen309:fix/raw-text-single-chunk-eos-none-tensor-leak
Jul 16, 2026
Merged

fix(dataprep): smart_chunk_text single-chunk path leaks internal tensor type when eos_token_id is None#7151
Etherll merged 3 commits into
unslothai:mainfrom
chuenchen309:fix/raw-text-single-chunk-eos-none-tensor-leak

Conversation

@chuenchen309

Copy link
Copy Markdown
Contributor

Problem

RawTextDataLoader.smart_chunk_text() (unsloth/dataprep/raw_text.py) has
two branches for building the input_ids output, and they disagree on
output type:

Single-chunk branch (when the whole text fits in chunk_size):

if return_tokenized:
    eos_token_id = getattr(self.tokenizer, "eos_token_id", None)
    if eos_token_id is not None:
        tokens = tokens.tolist() if hasattr(tokens, "tolist") else list(tokens)
        tokens.append(eos_token_id)

    attention_mask = [1] * len(tokens)
    return [{"input_ids": tokens, "attention_mask": attention_mask}]

The tolist()/list() conversion only happens inside the
if eos_token_id is not None: guard. When the tokenizer has no EOS token
configured, that branch never runs, and tokens — whatever
tensor-like object came out of the tokenizer normalization step a few
lines up — is returned as input_ids verbatim.

Multi-chunk branch (a few lines below), for comparison:

chunk_tokens_list = (
    chunk_tokens.tolist() if hasattr(chunk_tokens, "tolist") else list(chunk_tokens)
)
if end_idx == len(tokens) or len(chunk_tokens_list) == chunk_size:
    eos_token_id = getattr(self.tokenizer, "eos_token_id", None)
    if eos_token_id is not None:
        chunk_tokens_list.append(eos_token_id)

Here the conversion runs unconditionally, before checking
eos_token_id — the correct pattern.

Impact

create_causal_dataset() does labels = [list(ids) for ids in input_ids].
list()-ing a tensor produces a list of 0-d tensor elements rather than
plain Python ints, inconsistent with every multi-chunk sample and liable to
break type inference in Dataset.from_dict() / downstream
tokenization/collation code that expects plain ints.

Repro (bare, before fix — via a minimal mock tokenizer, no torch/GPU needed)

loader = RawTextDataLoader(MockTokenizerWithNoEos(), chunk_size=2048, stride=512)
result = loader.smart_chunk_text("hello world short text", chunk_size=2048, stride=512, return_tokenized=True)
type(result[0]["input_ids"])   # MockTensor (or torch.Tensor in practice), not list

Fix

Move the list conversion out of the eos_token_id guard, matching the
multi-chunk branch's existing pattern (2-line diff, single-chunk branch
only).

Testing

Added test_smart_chunk_text_single_chunk_no_eos_returns_plain_list to
tests/test_raw_text.py (reusing the file's existing MockTensor/mock-
tokenizer pattern, no heavy deps). Confirmed:

  • red against unfixed code (assertion failure: input_ids was the mock
    tensor type, not a list)
  • green after the fix
  • full tests/test_raw_text.py (both test functions) passes:
    python3 tests/test_raw_text.py✅ All tests passed! /
    ✅ test_smart_chunk_text_single_chunk_no_eos_returns_plain_list passed!

ruff check and the repo's scripts/run_ruff_format.py (kwarg-spacing
formatter): clean.

Note: tests/test_raw_text.py doesn't appear to be wired into any
.github/workflows/*.yml job as far as I could find — a pre-existing repo
characteristic, not something this PR changes. Verified locally instead.

Duplicate-work check

Searched merged/closed PRs and issues for smart_chunk_text /
raw_text.py — found #7126 ("guard smart_chunk_text against stride >=
chunk_size", merged), a different bug in the same function (stride
validation, not the EOS-branch type leak this PR fixes). No open PR
touches this file.


Disclosure: I used AI assistance (Claude) to find this bug and draft the
fix and test. I independently traced both branches, reproduced the type
mismatch against unfixed code with a bare mock-tokenizer repro, verified
the fix red→green, and ran the full local test file + ruff before
submitting.

…or type when eos_token_id is None

RawTextDataLoader.smart_chunk_text()'s single-chunk branch only
converts `tokens` to a plain Python list inside the
`if eos_token_id is not None:` guard. When a tokenizer has no
eos_token_id configured, that conversion is skipped entirely and the
function returns whatever internal tensor-like object came out of
the tokenizer normalization step (e.g. a torch.Tensor) as
"input_ids", instead of a list of ints.

The sibling multi-chunk branch a few lines below does the conversion
unconditionally, before checking eos_token_id -- the two branches of
the same method disagree on output type depending purely on whether
the tokenizer has an EOS token. Downstream, create_causal_dataset()
does `labels = [list(ids) for ids in input_ids]`; list()'ing a
tensor produces a list of 0-d tensor elements rather than plain
ints, inconsistent with every multi-chunk sample and liable to break
type inference in Dataset.from_dict()/downstream collation.

Fix: move the list conversion out of the eos_token_id guard,
matching the multi-chunk branch's existing pattern.

Added test_smart_chunk_text_single_chunk_no_eos_returns_plain_list
to tests/test_raw_text.py, confirmed red against unfixed code
(assertion failure: input_ids was a MockTensor, not a list) and
green after the fix. Full tests/test_raw_text.py (both test
functions) passes. ruff check + the repo's ruff-format-with-kwargs
script: clean.

Note: tests/test_raw_text.py does not appear to be wired into any
.github/workflows/*.yml CI job (a pre-existing repo characteristic,
not something introduced by this change) -- verified locally via
`python3 tests/test_raw_text.py`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the smart_chunk_text method in unsloth/dataprep/raw_text.py to ensure that tokens are unconditionally converted to a plain list when returning tokenized single-chunk text, even if the tokenizer does not have an eos_token_id. Additionally, a unit test has been added to tests/test_raw_text.py to verify this behavior. There are no review comments to address, and we have no further feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@Etherll Etherll self-assigned this Jul 16, 2026

@Etherll Etherll left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM!

@Etherll

Etherll commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

@Etherll
Etherll merged commit c2762f7 into unslothai:main Jul 16, 2026
1 check was pending
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