Skip to content

v2.5.9 - Transformers ImageProcessorKwargs "not documented" Docstring Patch

Choose a tag to compare

@ussoewwin ussoewwin released this 27 Aug 04:27
· 76 commits to main since this release
EN 中文

This document is the complete explanation of the fix that removes the
[ERROR] ... but not documented lines printed by Hugging Face transformers
at ComfyUI startup. The fix lives entirely inside the
ComfyUI-QwenImageLoraLoader custom node
— no site-packages files are
touched, no output is filtered, and the patch removes itself automatically
once upstream transformers fixes the underlying defect.

Environment when documented (2026-08-27):

Item Value
transformers 5.15.1
ComfyUI-QwenImageLoraLoader v2.5.8 (commit 8d82442)
Affected TypedDicts DeepseekVLHybridImageProcessorKwargs, Kimi_K25ImageProcessorKwargs, PaddleOCRVLImageProcessorKwargs
Missing fields high_res_size, merge_size, min_pixels, max_pixels

Design constraints

  1. Do not edit transformers in site-packages. The workaround only
    monkey-patches get_args_doc_from_source in-process from this custom node.
  2. Apply only from prestartup_script.py. The patch must be installed
    before any custom node imports the affected transformers modules
    (main.execute_prestartup_script() runs before custom node loading).
  3. Fully automatic upstream auto-disable. Probe upstream on every ComfyUI
    start; install the wrapper only while upstream still emits the [ERROR]
    lines. No user env vars or toggles.

Decision table

Condition Action Log level
Tagged wrapper already on get_args_doc_from_source Return True (idempotent) —
Affected transformers modules already in sys.modules Skip — restart required WARNING
Source dict (ImageProcessorArgs/VideoProcessorArgs) already documents all 4 fields Skip — upstream schema fixed INFO
Subprocess import probe prints CLEAN (no [ERROR] ... not documented) Skip — upstream behavior fixed INFO
Subprocess probe prints ERRORS, or probe cannot run (None) Apply wrapper if prior rows did not skip INFO
transformers.utils.auto_docstring missing / no get_args_doc_from_source Skip DEBUG

Decision flow

flowchart TD
    A[apply_transformers_image_processor_kwargs_patch] --> B{Already applied?}
    B -->|yes| Y[Return True]
    B -->|no| C{Affected modules in sys.modules?}
    C -->|yes| W[WARN skip - restart ComfyUI]
    C -->|no| D{Source dict already has all 4 fields?}
    D -->|yes| Z[Skip - upstream fixed]
    D -->|no| E[Subprocess import probe of 5 modules]
    E -->|CLEAN| Z
    E -->|ERRORS or probe failed| F[Wrap get_args_doc_from_source]
    F --> X[Return True]

1. What was the problem

1.1 Symptom

At ComfyUI startup, after the custom-node initialization banners, transformers
printed 13 [ERROR] lines into the console, for example:

[ERROR] `high_res_size` is part of DeepseekVLHybridImageProcessorKwargs, but not documented. Make sure to add it to the docstring of the function in D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\transformers\models\deepseek_vl_hybrid\image_processing_deepseek_vl_hybrid.py.
[ERROR] `merge_size` is part of Kimi_K25ImageProcessorKwargs, but not documented. Make sure to add it to the docstring of the function in D:\USERFILES\ComfyUI\python_embeded\Lib\site-packages\transformers\models\kimi_k25\image_processing_kimi_k25.py.
[ERROR] `min_pixels` is part of PaddleOCRVLImageProcessorKwargs, but not documented. Make sure to add it to the docstring of the function in ...\image_processing_paddleocr_vl.py.
[ERROR] `max_pixels` is part of PaddleOCRVLImageProcessorKwargs, but not documented. Make sure to add it to the docstring of the function in ...\image_processing_paddleocr_vl.py.

The complete breakdown (13 lines in the observed startup log):

Source file (in transformers\models\...) Missing field(s) Lines printed
deepseek_vl_hybrid\image_processing_deepseek_vl_hybrid.py high_res_size 1
deepseek_vl_hybrid\image_processing_pil_deepseek_vl_hybrid.py high_res_size 2
kimi_k25\image_processing_kimi_k25.py merge_size 2
paddleocr_vl\image_processing_paddleocr_vl.py min_pixels, max_pixels 4
paddleocr_vl\image_processing_pil_paddleocr_vl.py min_pixels, max_pixels 4

1.2 Impact

  • No functional breakage. These lines are validation noise produced while
    transformers auto-generates documentation strings at import time.
    ComfyUI, the image processors, and the models all work normally.
  • Log pollution / alarm. The lines look like real errors, make the startup
    log noisy, and make genuine errors harder to spot.
  • Because the messages originate inside the installed transformers
    package
    , a naive "fix" would edit files under site-packages — which is
    unacceptable (see §2.4): the change would be lost on every pip upgrade and
    would diverge from the upstream package.

2. Root cause

2.1 The validation machinery: transformers.utils.auto_docstring

In transformers 5.x, image-processor classes are decorated with
@auto_docstring. At module import time the decorator inspects the class
and its __call__ signature and rebuilds the docstring. Part of that pipeline
is _process_kwargs_parameters(), which handles **kwargs parameters typed
with a TypedDict such as DeepseekVLHybridImageProcessorKwargs.

The relevant logic (from transformers\utils\auto_docstring.py,
transformers 5.15.1):

# _process_kwargs_parameters()
kwargs_documentation = kwarg_param.annotation.__args__[0].__doc__   # the TypedDict's OWN docstring
if kwargs_documentation is not None:
    documented_kwargs = parse_docstring(kwargs_documentation)[0]    # parse it

for param_name, param_type_annotation in kwarg_param.annotation.__args__[0].__annotations__.items():
    ...
    param_type, optional_string, shape_string, additional_info, description, is_documented = (
        _get_parameter_info(param_name, documented_kwargs, source_args_dict, param_type, optional)
    )
    ...
    else:   # not documented
        undocumented_parameters.append(
            f"[ERROR] `{param_name}` is part of {kwarg_param.annotation.__args__[0].__qualname__}, "
            f"but not documented. Make sure to add it to the docstring of the function in {func.__code__.co_filename}."
        )

and the collected messages are emitted with a plain print():

# _process_parameters_section()
if len(undocumented_parameters) > 0:
    print("\n".join(undocumented_parameters))

Key detail: the error is printed via print(), not through the
transformers logging system — so changing TRANSFORMERS_VERBOSITY or log
levels cannot silence it.

_get_parameter_info() resolves each annotation key against three sources, in
priority order:

if param_name in documented_params:      # 1. the function's own docstring
    ...
elif param_name in source_args_dict:     # 2. the generic args source dict (fallback)
    ...
else:                                    # 3. nothing -> is_documented = False -> [ERROR]
    is_documented = False

The fallback source_args_dict is produced by:

def get_args_doc_from_source(args_classes: object | list[object]) -> dict:
    if isinstance(args_classes, list | tuple):
        return _merge_args_dicts(tuple(args_classes))   # merges cls.__dict__ of each class
    return args_classes.__dict__

For image processors it is called as
get_args_doc_from_source([ImageProcessorArgs, VideoProcessorArgs]).

The docstring parser that decides whether a key is "documented" uses this
pre-compiled regex:

_re_param = re.compile(
    r"^\s{0,0}(\w+)\s*\(\s*([^, \)]*)(\s*.*?)\s*\)\s*:\s*((?:(?!\n^\s{0,0}\w+\s*\().)*)",
    re.DOTALL | re.MULTILINE,
)

^\s{0,0} means: the parameter name must start exactly at column 0 of the
line (after the common indentation has been stripped by set_min_indent).
Any leading whitespace left on the line makes the entry invisible to the
parser.

2.2 Three distinct upstream defects (all in transformers 5.15.1)

Defect A — DeepseekVLHybridImageProcessorKwargs (both backend files):
a stray space hides high_res_size.

The TypedDict's docstring does document high_res_size, but the line was
written with five leading spaces instead of four:

class DeepseekVLHybridImageProcessorKwargs(ImagesKwargs, total=False):
    r"""
    min_size (`int`, *optional*, defaults to 14):
        The minimum allowed size for the resized image. ...
     high_res_size (`dict`, *optional*, defaults to `{"height": 1024, "width": 1024}`):   # <-- 5 spaces!
        Size of the high resolution output image after resizing. ...

set_min_indent() strips the smallest common indent (4 spaces), leaving
high_res_size at 1 space — which ^\s{0,0} cannot match. The field is
therefore treated as undocumented even though it is documented.

Defect B — Kimi_K25ImageProcessorKwargs: name mismatch.

The TypedDict annotation declares merge_size (default 2), but the
docstring documents a non-existent parameter merge_kernel_size:

class Kimi_K25ImageProcessorKwargs(ImagesKwargs, total=False):
    r"""
    max_patches (`int`, *optional*, defaults to `16384`): ...
    patch_size (`int`, *optional*, defaults to 14): ...
    merge_kernel_size (`int`, *optional*, defaults to 2):   # <-- annotation says "merge_size"
    """

The processor code itself uses merge_size (merge_size = images_kwargs.get("merge_size", self.merge_size)),
so the docstring simply has the wrong name.

Defect C — PaddleOCRVLImageProcessorKwargs (both backend files):
min_pixels / max_pixels are never documented.

The TypedDict declares five fields but its docstring documents only three:

class PaddleOCRVLImageProcessorKwargs(ImagesKwargs, total=False):
    r"""
    patch_size (`int`, *optional*, defaults to 14): ...
    temporal_patch_size (`int`, *optional*, defaults to 1): ...
    merge_size (`int`, *optional*, defaults to 2): ...
    """   # <-- min_pixels / max_pixels annotations exist but are missing here

2.3 Why the error text mentions "the function" but the fix is in the TypedDict

The message string uses func.__code__.co_filename (the processor module
file) for the location hint, but the text that is actually parsed is the
TypedDict class's own __doc__
(kwarg_param.annotation.__args__[0].__doc__). That is why the message is
misleading: editing the function's docstring would not help at all — the
TypedDict's docstring (or the fallback source dict) is what must change.

2.4 Why direct site-packages edits were rejected

The first attempt fixed the five files under
...\python_embeded\Lib\site-packages\transformers\models\... directly
(fix the indent, rename the kimi field, add the paddleocr entries). It worked,
but it was not acceptable because:

  • pip upgrades / reinstalls of transformers silently revert the edits;
  • every ComfyUI environment would need the same manual surgery;
  • it forks the local package away from upstream, hiding the upstream bug.

Therefore the direct edits were fully reverted (verified: the exact 13
[ERROR] lines reproduced again), and the fix was re-implemented as a
repo-native, in-process runtime patch described in §3–§5.


3. Files added / modified

File Change Kind
patches/transformers_image_processor_kwargs_patch.py Added — new patch module implementing the wrapper + upstream probes new file
prestartup_script.py Modified — one new path constant + one load/apply block for the new patch edited

Committed and pushed as:

8d82442  fix(patches): suppress transformers ImageProcessorKwargs 'not documented' startup noise

Note: patches/nunchaku_patch.py shows a local modification (one blank
line) that is unrelated to this fix and was intentionally not included
in the commit.


4. Full code

4.1 New file: patches/transformers_image_processor_kwargs_patch.py

# -*- coding: utf-8 -*-
"""
Patch transformers auto_docstring for ImageProcessorKwargs TypedDict fields.

transformers 5.15.x auto_docstring validation parses each *ImageProcessorKwargs
TypedDict's own __doc__ at import time and prints [ERROR] for every annotation
key that the parser cannot find in that docstring. Known upstream breakages
(transformers 5.15.1):

  - DeepseekVLHybridImageProcessorKwargs: the `high_res_size` docstring entry
    has a stray leading space, so the docstring parser never matches it.
  - Kimi_K25ImageProcessorKwargs: the docstring documents `merge_kernel_size`,
    but the TypedDict annotation is `merge_size`.
  - PaddleOCRVLImageProcessorKwargs: `min_pixels` / `max_pixels` annotations
    are not documented at all.

Fix: wrap transformers.utils.auto_docstring.get_args_doc_from_source so the
returned source dict also carries those four fields. _get_parameter_info()
falls back to source_args_dict when a TypedDict key is missing from its own
docstring, which stops the [ERROR] output without touching site-packages.

Fully automatic at ComfyUI prestartup (no user env vars):
  - Probe upstream state before installing any wrapper.
  - If the source dict already documents the fields, skip (no wrapper).
  - If a subprocess import probe shows clean stdout, skip (no wrapper).
  - Otherwise install the wrapper (default when upstream is still broken).

The wrapper is inert once upstream fixes the docstrings: the fields are only
consulted for TypedDict annotations that declare them, and only when they are
missing from that TypedDict's own docstring.
"""

from __future__ import annotations

import importlib
import logging
import subprocess
import sys
from typing import Any, Callable, Dict, Optional

logger = logging.getLogger(__name__)

_PATCH_TAG = "_qwen_lora_loader_image_processor_kwargs_patch"

# Fields missing from the affected TypedDict docstrings, added to the
# ImageProcessorArgs/VideoProcessorArgs source dict as a fallback.
_EXTRA_KWARGS: Dict[str, Dict[str, Any]] = {
    "high_res_size": {
        "type": "dict",
        "shape": None,
        "description": """
    Size of the high resolution output image after resizing. Can be overridden by the `high_res_size` parameter in the `preprocess` method.
    """,
    },
    "merge_size": {
        "type": "int",
        "shape": None,
        "description": """
    The merge size of the vision encoder to llm encoder.
    """,
    },
    "min_pixels": {
        "type": "int",
        "shape": None,
        "description": """
    The minimum allowed number of pixels for the resized image. Ensures the total pixel count does not fall below this value after resizing.
    """,
    },
    "max_pixels": {
        "type": "int",
        "shape": None,
        "description": """
    The maximum allowed number of pixels for the resized image. Ensures the total pixel count does not exceed this value after resizing.
    """,
    },
}

_AFFECTED_MODULES = (
    "transformers.models.deepseek_vl_hybrid.image_processing_deepseek_vl_hybrid",
    "transformers.models.deepseek_vl_hybrid.image_processing_pil_deepseek_vl_hybrid",
    "transformers.models.kimi_k25.image_processing_kimi_k25",
    "transformers.models.paddleocr_vl.image_processing_paddleocr_vl",
    "transformers.models.paddleocr_vl.image_processing_pil_paddleocr_vl",
)

_patch_applied: bool = False
_original_get_args_doc_from_source: Optional[Callable[..., dict]] = None


def _affected_modules_already_imported() -> bool:
    return any(name in sys.modules for name in _AFFECTED_MODULES)


def _upstream_documents_extra_kwargs(auto_docstring_module) -> bool:
    """Probe upstream native state (never via the patched get_args_doc_from_source)."""
    args_classes = []
    for name in ("ImageProcessorArgs", "VideoProcessorArgs"):
        cls = getattr(auto_docstring_module, name, None)
        if cls is not None:
            args_classes.append(cls)
    if not args_classes:
        return False
    try:
        source = auto_docstring_module.get_args_doc_from_source(args_classes)
    except Exception:
        return False
    return all(field in source for field in _EXTRA_KWARGS)


def _image_processor_args_requested(args_classes: Any, image_processor_args_type: type) -> bool:
    if args_classes is image_processor_args_type:
        return True
    if isinstance(args_classes, (list, tuple)):
        return image_processor_args_type in args_classes
    return False


def _make_patched_get_args_doc_from_source(
    auto_docstring_module,
    original: Callable[..., dict],
) -> Callable[..., dict]:
    image_processor_args_type = auto_docstring_module.ImageProcessorArgs

    def patched_get_args_doc_from_source(args_classes: Any) -> dict:
        result = original(args_classes)

        if not _image_processor_args_requested(args_classes, image_processor_args_type):
            return result

        if all(field in result for field in _EXTRA_KWARGS):
            return result

        merged = dict(result)
        for field, entry in _EXTRA_KWARGS.items():
            merged.setdefault(field, dict(entry))
        return merged

    setattr(patched_get_args_doc_from_source, _PATCH_TAG, True)
    return patched_get_args_doc_from_source


def _import_probe_reports_clean() -> Optional[bool]:
    """
    True: affected transformers modules import with no [ERROR] ... not documented lines.
    False: errors still present (patch may be needed).
    None: probe could not run.
    """
    python_exe = sys.executable
    module_list = ", ".join(repr(m) for m in _AFFECTED_MODULES)
    code = (
        "import importlib, io, contextlib\n"
        "buf = io.StringIO()\n"
        "with contextlib.redirect_stdout(buf):\n"
        f"    for m in ({module_list}):\n"
        "        importlib.import_module(m)\n"
        "lines = buf.getvalue().splitlines()\n"
        "errs = [l for l in lines if '[ERROR]' in l and 'but not documented' in l]\n"
        "print('CLEAN' if not errs else 'ERRORS')\n"
    )
    try:
        proc = subprocess.run(
            [python_exe, "-c", code],
            capture_output=True,
            text=True,
            timeout=180,
        )
    except (OSError, subprocess.SubprocessError) as exc:
        logger.debug("ImageProcessorKwargs docstring import probe failed: %s", exc)
        return None

    if proc.returncode != 0:
        logger.debug(
            "ImageProcessorKwargs docstring import probe exit %s stderr=%s",
            proc.returncode,
            proc.stderr[:500] if proc.stderr else "",
        )
        return None

    last_line = (proc.stdout or "").strip().splitlines()
    if not last_line:
        return None
    status = last_line[-1].strip()
    if status == "CLEAN":
        return True
    if status == "ERRORS":
        return False
    return None


def apply_transformers_image_processor_kwargs_patch() -> bool:
    """
    Install get_args_doc_from_source wrapper unless upstream already fixed the issue.

    Returns True if the wrapper is active (or was already applied), False if skipped.
    """
    global _patch_applied, _original_get_args_doc_from_source

    if _patch_applied:
        return True

    if _affected_modules_already_imported():
        logger.warning(
            "ImageProcessorKwargs docstring patch skipped: affected transformers modules "
            "already imported before prestartup; restart ComfyUI"
        )
        return False

    try:
        auto_docstring_module = importlib.import_module("transformers.utils.auto_docstring")
    except ImportError:
        logger.debug("transformers.utils.auto_docstring not available; patch skipped")
        return False

    get_args = getattr(auto_docstring_module, "get_args_doc_from_source", None)
    if get_args is None:
        return False

    if getattr(get_args, _PATCH_TAG, False):
        _patch_applied = True
        return True

    if _upstream_documents_extra_kwargs(auto_docstring_module):
        logger.info(
            "ImageProcessorKwargs docstring patch skipped: transformers source dict "
            "already documents the fields (upstream fixed; patch not installed)"
        )
        return False

    import_probe = _import_probe_reports_clean()
    if import_probe is True:
        logger.info(
            "ImageProcessorKwargs docstring patch skipped: affected transformers modules "
            "import without docstring errors (upstream fixed; patch not installed)"
        )
        return False

    _original_get_args_doc_from_source = get_args
    auto_docstring_module.get_args_doc_from_source = _make_patched_get_args_doc_from_source(
        auto_docstring_module,
        _original_get_args_doc_from_source,
    )
    _patch_applied = True

    logger.info(
        "Patched transformers.utils.auto_docstring.get_args_doc_from_source for "
        "ImageProcessorKwargs fields (high_res_size/merge_size/min_pixels/max_pixels); "
        "removes when upstream fixes the docstrings"
    )
    return True


def is_patch_applied() -> bool:
    return _patch_applied


def is_patch_wrapped() -> bool:
    try:
        auto_docstring_module = importlib.import_module("transformers.utils.auto_docstring")
    except ImportError:
        return False
    get_args = getattr(auto_docstring_module, "get_args_doc_from_source", None)
    return get_args is not None and getattr(get_args, _PATCH_TAG, False)

4.2 Modified file: prestartup_script.py (current full content)

The two additions are marked with [ADDED]; everything else is
pre-existing code.

"""
Inject apply_rotary_emb on comfy.ldm.qwen_image.model before any custom node __init__.

ComfyUI-nunchaku loads before ComfyUI-QwenImageLoraLoader (Windows listdir order), so
__init__.py alone is too late. prestartup_script.py runs from main.execute_prestartup_script().
"""
import importlib.util
import logging
import os

logger = logging.getLogger(__name__)

_PATCH_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "patches")
_NUNCHAKU_PATCH_PATH = os.path.join(_PATCH_DIR, "nunchaku_patch.py")
_DOCSTRING_PATCH_PATH = os.path.join(_PATCH_DIR, "transformers_qwen_vl_docstring_patch.py")
_IP_KWARGS_PATCH_PATH = os.path.join(_PATCH_DIR, "transformers_image_processor_kwargs_patch.py")  # [ADDED]


def _load_patch_module(module_name: str, path: str):
    spec = importlib.util.spec_from_file_location(module_name, path)
    if spec is None or spec.loader is None:
        raise RuntimeError(f"Failed to load patch module spec from {path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


# Load the nunchaku patch module once; reuse it for warning suppression and the
# apply_rotary_emb compat shim.
_patch_module = None
try:
    _patch_module = _load_patch_module(
        "comfyui_qwenimageloraloader_nunchaku_patch_prestartup",
        _NUNCHAKU_PATCH_PATH,
    )
except Exception:
    logger.exception("ComfyUI-QwenImageLoraLoader prestartup: failed to load nunchaku patch module")

# Install the cosmetic 'Torch already imported' warning filter first, before any
# prestartup step imports comfy.ldm (which imports torch) and before main.py logs it.
if _patch_module is not None:
    try:
        if _patch_module.suppress_torch_preimport_warning():
            logger.debug(
                "ComfyUI-QwenImageLoraLoader prestartup: torch pre-import warning suppressed"
            )
    except Exception:
        logger.exception(
            "ComfyUI-QwenImageLoraLoader prestartup: torch warning suppression failed"
        )

try:
    _docstring_patch_module = _load_patch_module(
        "comfyui_qwenimageloraloader_docstring_patch_prestartup",
        _DOCSTRING_PATCH_PATH,
    )
    if _docstring_patch_module.apply_transformers_causal_lm_docstring_patch():
        logger.info("ComfyUI-QwenImageLoraLoader prestartup: CausalLM ModelOutput docstring patch applied")
    else:
        logger.debug(
            "ComfyUI-QwenImageLoraLoader prestartup: CausalLM ModelOutput docstring patch not applied"
        )
except Exception:
    logger.exception("ComfyUI-QwenImageLoraLoader prestartup: CausalLM ModelOutput docstring patch failed")

# ImageProcessorKwargs docstring patch (deepseek_vl_hybrid / kimi_k25 / paddleocr_vl   # [ADDED] block start
# [ERROR] "but not documented" noise): wraps get_args_doc_from_source at prestartup,
# before any custom node imports those transformers modules.
try:
    _ip_kwargs_patch_module = _load_patch_module(
        "comfyui_qwenimageloraloader_image_processor_kwargs_patch_prestartup",
        _IP_KWARGS_PATCH_PATH,
    )
    if _ip_kwargs_patch_module.apply_transformers_image_processor_kwargs_patch():
        logger.info(
            "ComfyUI-QwenImageLoraLoader prestartup: ImageProcessorKwargs docstring patch applied"
        )
    else:
        logger.debug(
            "ComfyUI-QwenImageLoraLoader prestartup: ImageProcessorKwargs docstring patch not applied"
        )
except Exception:
    logger.exception(
        "ComfyUI-QwenImageLoraLoader prestartup: ImageProcessorKwargs docstring patch failed"
    )
# [ADDED] block end

if _patch_module is not None:
    try:
        if _patch_module.apply_qwen_image_apply_rotary_emb_compat():
            logger.info("ComfyUI-QwenImageLoraLoader prestartup: apply_rotary_emb compat applied")
        else:
            logger.debug(
                "ComfyUI-QwenImageLoraLoader prestartup: apply_rotary_emb compat not needed or already present"
            )
    except Exception:
        logger.exception("ComfyUI-QwenImageLoraLoader prestartup: apply_rotary_emb compat failed")

5. What the fix means

5.1 Mechanism: closing the fallback gap

The validation logic in _get_parameter_info() consults, in order:

  1. the TypedDict's own docstring (documented_kwargs) — where the fields are
    wrongly absent (the three upstream defects), then
  2. the generic source dict returned by
    get_args_doc_from_source([ImageProcessorArgs, VideoProcessorArgs]) —
    which we now augment, then
  3. "undocumented" — which previously produced the [ERROR] lines.

The patch wraps step 2's function. Whenever ImageProcessorArgs is among the
requested argument classes, the wrapper adds the four missing fields to the
returned dict (merged.setdefault(...), so it never overwrites real upstream
entries). Now the second branch of _get_parameter_info() resolves
high_res_size, merge_size, min_pixels, and max_pixels, is_documented
becomes True, and no [ERROR] is appended.

Why this is safe and precise:

  • The extra keys are only consulted by TypedDicts whose annotations
    actually declare them (the loop iterates
    kwarg_param.annotation.__args__[0].__annotations__). No other model's
    documentation changes.
  • The wrapper returns the original dict untouched for every non-image-processor
    call (e.g. ModelArgs-only calls) and delegates all actual work to the
    original function.
  • Only the docstring generation input is influenced. No runtime inference
    path, tensor shape, or model behavior is touched — the generated docstrings
    are cosmetic by design.

5.2 Self-disabling behavior

On every ComfyUI start the patch first asks: "is the problem still there?"

  1. Cheap in-process check: does the source dict already contain all four
    fields? If yes, upstream has been fixed at the schema level — skip.
  2. Subprocess probe: import the five affected modules in a clean interpreter
    and look for [ERROR] ... but not documented on stdout. If clean — skip.
  3. Only if both checks fail is the wrapper installed.

So the day Hugging Face fixes the indent, the kimi field name, and the
paddleocr entries, the startup log will simply show a skip line instead of
Patched .... No cleanup task, no uninstall step, and — because the wrapper
only ever adds missing keys — even a stale wrapper would remain inert.

5.3 Why no site-packages edits

The wrapper monkey-patches one function in process memory from the custom
node's own prestartup_script.py. Nothing under
python_embeded\Lib\site-packages is written, so pip upgrades, environment
moves, and other machines keep working without manual surgery. The patch is
versioned with the custom node (commit 8d82442) and travels with it.

5.4 Verification results

Verification was performed with the embedded Python
(D:\USERFILES\ComfyUI\python_embeded\python.exe) in clean subprocesses:

Phase Setup [ERROR] ... not documented lines
A — baseline Package reverted to original; patch not loaded 13 (exactly matching the original startup log — proves the revert restored the pristine state and the errors are reproducible)
B — patched Patch module loaded + apply_transformers_image_processor_kwargs_patch() 0 (WRAPPED=True, merged source dict contains all 4 keys)

Both prestartup_script.py and the new patch module pass py_compile.

5.5 What you should see after restarting ComfyUI

With the wrapper active, the startup log shows:

INFO  ComfyUI-QwenImageLoraLoader prestartup: ImageProcessorKwargs docstring patch applied

and no [ERROR] \...` is part of ...ImageProcessorKwargslines. After an upstreamtransformers` fix, the line changes to a skip message instead:

INFO  ... ImageProcessorKwargs docstring patch skipped: ... (upstream fixed; patch not installed)

5.6 Removing the patch (if ever needed)

The patch is two small, self-contained changes. To remove it:

git revert 8d82442

or delete patches/transformers_image_processor_kwargs_patch.py and the
[ADDED] block plus constant in prestartup_script.py.


Appendix — history of the fix

  1. First attempt (rejected): the five transformer files under
    site-packages were edited directly (indent fix, field rename, added
    entries). It silenced the errors but modified installed package files —
    unacceptable: lost on every pip upgrade and invisible to version control.
  2. Revert: all five files were restored byte-for-byte; Phase A above
    confirmed the original 13 [ERROR] lines return.
  3. Final fix (this document): a repo-native runtime wrapper in
    patches/transformers_image_processor_kwargs_patch.py, wired through
    prestartup_script.py, following the exact design already used by the
    sibling patch TRANSFORMERS_QWEN_VL_CAUSAL_LM_DOCSTRING_PATCH.md.
  4. Committed and pushed: 8d82442 on main.

Related documents

  • md/TRANSFORMERS_QWEN_VL_CAUSAL_LM_DOCSTRING_PATCH.md — the sibling patch
    for Qwen VL CausalLMOutputWithPast loss / logits docstring errors
    (same design: probe, wrap get_args_doc_from_source, auto-disable).
  • md/TORCH_PREIMPORT_WARNING_SUPPRESSION.md — another startup-noise fix in
    the same prestartup pipeline.