Skip to content

fix(sft): support functools.partial model.forward in chunked CE patch#6486

Open
Solaris-star wants to merge 1 commit into
huggingface:mainfrom
Solaris-star:fix/6483-forward-partial-lm-head-patch
Open

fix(sft): support functools.partial model.forward in chunked CE patch#6486
Solaris-star wants to merge 1 commit into
huggingface:mainfrom
Solaris-star:fix/6483-forward-partial-lm-head-patch

Conversation

@Solaris-star

@Solaris-star Solaris-star commented Jul 21, 2026

Copy link
Copy Markdown

What does this PR do?

_patch_chunked_ce_lm_head clones the original model.forward signature via original_forward.__func__. That assumes a bound method. Multimodal checkpoints such as Qwen3_5ForConditionalGeneration can expose forward as a functools.partial, which has no __func__, so SFTTrainer crashes during init when applying the chunked CE LM-head patch (even for text-only training with the vision tower frozen).

This PR unwraps nested functools.partial objects and falls back to the callable itself when __func__ is missing, then keeps the existing MethodType rebinding.

Fixes #6483

Before submitting

  • This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
  • Did you read the contributor guideline, Pull Request section?
  • Was this discussed/approved via a GitHub issue? Please add a link to it if that's the case.
  • Did you make sure to update the documentation with your changes?
  • Did you write any new necessary tests?

AI writing disclosure

  • No AI usage: the PR was written entirely by a human.
  • AI-assisted: some parts were suggested or improved by AI, but the PR was written and reviewed by a human.
  • AI-generated: the PR was mostly or fully generated by an AI tool.

Who can review?

Anyone in the community is free to review the PR once the tests have passed.


Note

Low Risk
Narrow change to signature extraction during chunked CE patching; behavior for normal bound forward methods is unchanged.

Overview
Fixes SFTTrainer init crashes when loss_type='chunked_nll' is used with models whose forward is a functools.partial (e.g. Qwen3.5-style VLMs), where reading original_forward.__func__ raised AttributeError.

_patch_chunked_ce_lm_head now unwraps nested partials, then uses __func__ when present or the underlying callable for inspect.signature, so the patched forward still exposes the original kwargs for generate validation.

Adds test_patch_chunked_ce_accepts_partial_forward to lock in the behavior (#6483).

Reviewed by Cursor Bugbot for commit ca1a551. Bugbot is set up for automated code reviews on this repo. Configure here.

_patch_chunked_ce_lm_head assumed model.forward was a bound method and
accessed original_forward.__func__ for signature cloning. Multimodal
models such as Qwen3.5 expose forward as a functools.partial, which has
no __func__ and crashed SFTTrainer init under loss_type=chunked_nll.

Unwrap partials and fall back to the callable itself when __func__ is
missing.

Fixes huggingface#6483

Signed-off-by: Solaris-star <820622658@qq.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 using default effort and found 2 potential issues.

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 ca1a551. Configure here.

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.

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.

@Eraly-ml

Copy link
Copy Markdown

Thank you Solaris!

@Eraly-ml

Eraly-ml commented Jul 21, 2026

Copy link
Copy Markdown

Hello @albertvillanova , could you please you review it?

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.

SFTTrainer crashes on Qwen3.5 because forward is functools.partial

2 participants