Skip to content

Default generative Llama-Guard scorer crashes on every supported transformers 5.x: apply_chat_template returns a BatchEncoding that is passed to generate() as input_ids (CR-A06-003) #108

Closed

Description

@cemililik

Wave 0 · theme D0-7: Dependency ranges and security floors · highest severity High · 3 finding(s) · effort small

Summary

An operator enables evaluation.safety with the shipped default classifier. Training finishes, the safety pass raises AttributeError on the first probe, and the run exits 2 with an empty error. No safety verdict is produced on any supported transformers version.

Primary finding

CR-A06-003 — Default generative Llama-Guard scorer crashes on every supported transformers 5.x: apply_chat_template returns a BatchEncoding that is passed to generate() as input_ids

severity High · reachability default · intent contract-violation · confidence confirmed · independently confirmed · reproduced end to end

What happens. _generate_guard_verdict (forgelm/safety/_score_generation.py:143-152) calls tokenizer.apply_chat_template(conversation, return_tensors='pt', truncation=True, max_length=4096) and treats the result as a tensor (.to(), model.generate(input_ids=...), .shape[-1]). In transformers>=5.3,<6, return_dict defaults to True, so the call returns a BatchEncoding and generate() raises a bare, message-less AttributeError. AttributeError is missing from the except tuple at :162, and the orchestrator catches only RuntimeError around the generative scorer (_orchestrator.py:251-263). The error escapes run_safety_evaluation. The training run and safety-eval exit 2 with error '', no safety verdict or audit event is written, and final_model/ stays on disk. The unit tests stub apply_chat_template with a plain tensor.

Where.

Evidence.

  • S-G0 (no fixture): exit 2, envelope {"success": false, "error": ""}, audit pipeline.failed {"error": ""}; traceback ends at transformers/generation/utils.py:2498 called from forgelm/safety/_score_generation.py:151. S-G1..S-G8: same, with the call log showing invalid_input_passthrough. chk_act.py: apply_chat_template returns BatchEncoding on 5.3.0, 5.13.0 and 5.18.0; pyproject.toml:53 requires transformers>=5.3.0,<6.0.0.
  • r5/run-main.log (main venv, no fixture): EXIT=2, AttributeError raised from _generate_guard_verdict line 151. r5/run.log (fpy --venv latest): TypeError: SFTConfig.init() got an unexpected keyword argument 'warmup_ratio', EXIT=2.
  • scratch/act_type.py: type BatchEncoding on transformers 5.3.0 (lower), 5.13.0 (main) and 5.18.0 (latest). scratch/guard_verdict.py: _generate_guard_verdict(tiny-qwen) raises AttributeError from transformers/generation/utils.py:2498 via _score_generation.py:151. End-to-end: runs/gen (FIX train_guard_generative.yaml with own output_dir and auto_revert: true) gave EXIT=2 and the envelope {"success": false, "error": ""}. The audit log holds only pipeline.initialized, training.started, evaluation.lo…

Recommended fix. Pass return_dict=False (or take ['input_ids'] from the BatchEncoding) and use the attention mask. Add AttributeError to the handled tuple, or catch the BatchEncoding shape up front. Log type(e).name when str(e) is empty. Add a test that runs the real apply_chat_template.

Other findings with the same root cause

These close with the same fix; update them in the same pull request.

TECH-T05-001 — Evaluation and safety models: Generation-based guard scoring (_generate_guard_verdict) treats apply_chat_template(..., return_tensors='pt') as a tensor; transformers 5 returns BatchEncoding (outdated)

severity Now

Item. Generation-based guard scoring (_generate_guard_verdict) treats apply_chat_template(..., return_tensors='pt') as a tensor; transformers 5 returns BatchEncoding · verdict: outdated · urgency: Now

State in ForgeLM. forgelm/safety/_score_generation.py:143-152 calls tokenizer.apply_chat_template(conversation, return_tensors='pt', truncation=True, max_length=4096), then .to(model.device), then model.generate(input_ids=input_ids, ...), and slices by input_ids.shape[-1]. The per-pair except tuple at :162 catches (RuntimeError, ValueError, TypeError, IndexError, KeyError). run_safety_evaluation catches only RuntimeError around the generative scorer (forgelm/safety/_orchestrator.py:254-257). pyproject.toml:53 pins transformers>=5.3.0,<6.0.0. The unit tests stub apply_chat_template to return a tensor (tests/test_safety_advanced.py:1409, :1437).

State of the ecosystem on 2026-10-01. In transformers v5 (GA 2026-01-26), apply_chat_template returns a BatchEncoding by default; see the v5 migration guide. The arbiter reproduced the failure independently (tmp/a review run/a1-W04/repro_guard_arb.py, tiny-qwen fixture, snapshot forgelm). On transformers 5.3.0, 5.13.0 and 5.18.0, apply_chat_template returns BatchEncoding and _generate_guard_verdict raises AttributeError from transformers/generation/utils.py generate() via BatchEncoding.getattr. An unrelated project hit the same break on 2026-09-24 (Soup issue #​1238).

Impact. The shipped default safety gate (Llama-Guard-3-8B under auto) cannot score any pair on any transformers version ForgeLM allows. The AttributeError escapes the per-pair tuple and the orchestrator's RuntimeError catch. forgelm safety-eval exits 2 (cli/subcommands/_safety_eval.py:266-271), and an in-run gate fails the training pipeline. This is fail-closed: no false PASS, so threat-model hook 3 does not apply. Still, the advertised default safety gate is unusable, and the mocked tests hide the break. Every other generative guard (LG2, LG4, Qwen3Guard and so on) would hit the same line.

Recommendation. Call apply_chat_template(..., return_dict=True), pass **inputs to generate, and slice by inputs['input_ids'].shape[-1]. Alternatively, request return_dict=False explicitly. Add a non-mocked test that runs _generate_guard_verdict against a real tiny tokenizer and model (the tiny-qwen fixture is enough). Widen the per-pair fail-closed boundary to include AttributeError. Fix docs/usermanuals/*/evaluation/safety.md:37 and :203 until this ships.

Sources.

DOC-D3B08-025 — docs/usermanuals/en/evaluation/safety.md: false claim — The default meta-llama/Llama-Guard-3-8B works out of the box via generation-based scoring

severity High

Where. docs/usermanuals/en/evaluation/safety.md:37; docs/reference/safety_eval_subcommand.md:24,69; docs/reference/configuration.md:208 · type: false claim

The document says. The default meta-llama/Llama-Guard-3-8B works out of the box via generation-based scoring

What the code does. Generation-based scoring raises AttributeError on transformers 5.3-5.18 (apply_chat_template returns BatchEncoding); every run exits 2

Code references. forgelm/safety/_score_generation.py:143-151, forgelm/safety/_score_generation.py:143

User impact. Safety gate with the default classifier never produces a verdict

Turkish mirror. the Turkish page has the same error; fix both in the same PR

Acceptance criteria

  • A regression test reproduces the failure scenario and asserts the corrected behaviour.
  • Documentation listed above is corrected in English and in its Turkish mirror in the same PR.
  • Every finding in this issue is resolved or explicitly closed with a reason.
  • The release note names the contract this fix restores.

Found by the October 2026 ForgeLM code and documentation review against commit cab2947. Code links point at that commit; line numbers may have moved since. Finding IDs (CR-…, DOC-…, TECH-…) are stable references for this backlog.

Activity

  1. added
    bugSomething isn't working
    documentationImprovements or additions to documentation
    dependenciesDependency or ecosystem currency
    severity: highWrong behaviour or a false claim users rely on
    source: review-2026-10Found by the October 2026 code and documentation review
    urgency: nowTechnology currency: act now
    wave: 0Roadmap wave 0: immediate (patch release)
    on Oct 9, 2026
  2. cemililik commented on Oct 15, 2026

    @cemililik
    CollaboratorAuthor

    Where to start

    Thanks for taking a look. Please confirm the issue is unassigned and comment before starting so work does not overlap.

    Start with these files on development:

    _generate_guard_verdict calls tokenizer.apply_chat_template(..., return_tensors="pt") and treats the result as a tensor. On the supported transformers 5.x the call returns a BatchEncoding (return_dict defaults to true), so generate() fails with a bare, message-less AttributeError, which is not in the handled exception tuple, and the default generative Llama-Guard scorer does not work at all.

    Pass return_dict=False (or take ["input_ids"] from the result and use its attention mask), handle AttributeError at that boundary, and log type(e).__name__ when str(e) is empty so the next failure is not silent. Add a test that exercises the real apply_chat_template, not a mock: build a tiny tokenizer offline (for example a PreTrainedTokenizerFast with a minimal chat_template) so no model is downloaded. If that turns out impractical, explain why on the issue before settling for a mock.

    Run the focused tests:

    pytest tests/test_safety_advanced.py --no-cov

    Run these guards/checks as applicable to the changed files:

    ruff format --check forgelm/safety/_score_generation.py tests/test_safety_advanced.py
    ruff check forgelm/safety/_score_generation.py tests/test_safety_advanced.py

    Tests must not need a GPU, network access or model downloads. Add a ### Fixed entry under ## [Unreleased] in CHANGELOG.md naming the finding ID.

    If user-facing documentation changes, update the existing Turkish mirror in the same PR; keep commands, code and terminal output in English and structurally aligned. English-only contributor standards need no new mirror.

    Open the PR against development with Fixes #108 once every finding is resolved or explicitly closed with a reason; use Refs #108 for partial work and list the resolved finding IDs (Resolves: CR-A06-003).

  3. cemililik commented on Oct 17, 2026

    @cemililik
    CollaboratorAuthor

    Update — re-verified on development (0766e94). The defect still reproduces.

    One suggestion on the fix: prefer return_dict=False in the apply_chat_template(...)
    call. It returns a tensor again and keeps the three existing stub tests in
    tests/test_safety_advanced.py valid; with return_dict=True those stubs need rewriting.

    The documentation lines that say the generative guard works out of the box become true
    once the code is fixed, so DOC-D3B08-025 needs no edit of its own: close it with that reason.

  4. pinned this issue on Oct 17, 2026
  5. krisnaparahita commented on Oct 20, 2026

    @krisnaparahita
    Contributor

    I'd like to take this on — will follow the fix guidance above (return_dict=False, handle AttributeError at that boundary, offline PreTrainedTokenizerFast test).

  6. added a commit that references this issue on Oct 20, 2026
    902c03c
  7. unpinned this issue on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area: eval-gatesCode area: eval-gatesbugSomething isn't workingdependenciesDependency or ecosystem currencydocumentationImprovements or additions to documentationgood first issueGood for newcomersseverity: highWrong behaviour or a false claim users rely onsource: review-2026-10Found by the October 2026 code and documentation reviewurgency: nowTechnology currency: act nowwave: 0Roadmap wave 0: immediate (patch release)

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions