Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions tests/test_chunked_ce_partial_forward.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2020-2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0

import functools
import inspect
import types
from unittest.mock import MagicMock

import torch
import torch.nn as nn

from trl.trainer.sft_trainer import _patch_chunked_ce_lm_head


class _TinyLM(nn.Module):
def __init__(self):
super().__init__()
self.config = MagicMock()
self.config.text_config = None
# top-level config attrs used when is_vlm=False
self.config.final_logit_softcapping = None
self.config.logit_scale = 1.0
self.config.output_router_logits = False
self.lm_head = nn.Linear(4, 8, bias=False)
self.base_model = MagicMock()

def get_output_embeddings(self):
return self.lm_head

def forward(self, input_ids=None, attention_mask=None, labels=None, **kwargs):
return MagicMock(loss=torch.tensor(0.0), logits=None)


def test_patch_chunked_ce_accepts_partial_forward():
"""Qwen3.5-style models expose forward as functools.partial (#6483)."""
model = _TinyLM()
real = model.forward
model.forward = functools.partial(real) # no __func__

# Should not raise AttributeError: 'partial' object has no attribute '__func__'
_patch_chunked_ce_lm_head(model, chunk_size=2, is_vlm=False)

assert isinstance(model.forward, types.MethodType)
sig = inspect.signature(model.forward)
# Bound method signature should include original kwargs surface
assert "input_ids" in sig.parameters or len(sig.parameters) >= 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Vacuous signature test assertion

Low Severity

The new test claims to check that the patched bound-method signature still exposes the original kwargs surface, but the assertion accepts any non-empty parameter list. A broken unwrap that drops input_ids after MethodType self-stripping would still pass, so the regression this PR guards against is not actually covered.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ca1a551. Configure here.

13 changes: 11 additions & 2 deletions trl/trainer/sft_trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import contextlib
import functools
import inspect
import json
import os
Expand Down Expand Up @@ -373,8 +374,16 @@ def _chunked_ce_forward(

# Keep the original forward signature so `generate`'s `_validate_model_kwargs` still sees the
# model's real inputs (e.g. VLM `pixel_values`, `spatial_shapes`) and doesn't reject them. The
# unbound `__func__` signature makes `MethodType`'s `self`-stripping land correctly.
_chunked_ce_forward.__signature__ = inspect.signature(original_forward.__func__)
# unbound function signature makes `MethodType`'s `self`-stripping land correctly.
#
# Some multimodal models (e.g. Qwen3.5) expose `forward` as a `functools.partial` rather than a
# bound method, so `original_forward.__func__` is unavailable — unwrap partials / fall back to
# the callable itself (see #6483).
forward_for_sig = original_forward
while isinstance(forward_for_sig, functools.partial):
forward_for_sig = forward_for_sig.func
unbound = getattr(forward_for_sig, "__func__", forward_for_sig)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Defensive getattr on forward unwrap

Low Severity

The new signature path uses getattr with a fallback for __func__, which conflicts with the project simplicity guidance to avoid getattr and defensive fallback branches. The bound-method versus plain-callable cases can be distinguished explicitly after partial unwrapping without that pattern.

Fix in Cursor Fix in Web

Triggered by project rule: ../.ai/AGENTS.md

Reviewed by Cursor Bugbot for commit ca1a551. Configure here.

_chunked_ce_forward.__signature__ = inspect.signature(unbound)
model.forward = types.MethodType(_chunked_ce_forward, model)


Expand Down
Loading