multimodalart's picture
multimodalart HF Staff
Sync the split MiniMax-H3 Spaces
186aa49 verified
Raw
History Blame Contribute Delete
11.3 kB
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""`diffusers-cli schema` — print the input schema for any pipeline repo.
Tries `DiffusionPipeline.config_name` first (so standard repos get their `__call__` signature introspected); falls back
to `ModularPipelineBlocks.from_pretrained` for modular repos. No weights are downloaded — only the small index file
(and any custom block code if `--trust-remote-code` is set).
"""
from __future__ import annotations
import inspect
import re
from argparse import ArgumentParser, Namespace, _SubParsersAction
from typing import Any
from huggingface_hub.cli._output import OutputFormat, out
from ..utils import logging
from . import BaseDiffusersCLICommand
logger = logging.get_logger("diffusers-cli/schema")
def _schema(args: Namespace) -> None:
"""Print the pipeline's input schema.
Tries `DiffusionPipeline.config_name` (= `model_index.json`) first; if present, introspects the declared pipeline
class's `__call__` signature. Otherwise falls back to `ModularPipelineBlocks.from_pretrained` and reads the
block-declared `inputs`. No weights downloaded either way.
"""
import diffusers
try:
index = diffusers.DiffusionPipeline.load_config(args.model, token=args.token, revision=args.revision)
except OSError:
index = None
if index is not None:
class_name = index.get("_class_name")
if class_name is None:
raise SystemExit(
f"{diffusers.DiffusionPipeline.config_name} for {args.model!r} has no `_class_name` field."
)
pipeline_cls = getattr(diffusers, class_name, None)
if pipeline_cls is None:
raise SystemExit(
f"Pipeline class {class_name!r} declared in {diffusers.DiffusionPipeline.config_name} "
"is not exported by the installed diffusers."
)
sig = inspect.signature(pipeline_cls.__call__)
descriptions = _parse_docstring_args(pipeline_cls.__call__.__doc__) if args.verbose else {}
schema: list[dict[str, Any]] = []
for name, param in sig.parameters.items():
if name == "self":
continue
if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
continue
has_default = param.default is not inspect.Parameter.empty
schema.append(
{
"name": name,
"type_hint": str(param.annotation) if param.annotation is not inspect.Parameter.empty else None,
"default": param.default if has_default else None,
"required": not has_default,
"description": descriptions.get(name, ""),
}
)
else:
kwargs: dict[str, Any] = {"trust_remote_code": args.trust_remote_code}
if args.revision:
kwargs["revision"] = args.revision
if args.token:
kwargs["token"] = args.token
# If the repo declares custom code + external dependencies, surface them upfront so
# the user knows what to install before we hit an ImportError inside from_pretrained.
_warn_custom_block_requirements(args)
try:
blocks = diffusers.ModularPipelineBlocks.from_pretrained(args.model, **kwargs)
except Exception as e:
hint = "\nPass --trust-remote-code if it ships custom block code." if not args.trust_remote_code else ""
raise SystemExit(
f"Could not read schema for {args.model!r}: no {diffusers.DiffusionPipeline.config_name} and "
f"loading as a modular pipeline failed with:\n {type(e).__name__}: {e}{hint}"
) from e
class_name = type(blocks).__name__
schema = [
{
"name": p.name,
"type_hint": str(p.type_hint) if p.type_hint is not None else None,
"default": p.default,
"required": p.required,
"description": p.description,
}
for p in blocks.inputs
]
if out.mode == OutputFormat.json:
out.dict({"task": "schema", "model": args.model, "pipeline_class": class_name, "inputs": schema})
elif out.mode == OutputFormat.agent:
out.table(schema, headers=["name", "required", "type_hint", "default", "description"])
else:
out.text(f"{class_name} ({args.model}) inputs:")
for entry in schema:
tag = "required" if entry["required"] else f"optional, default={entry['default']!r}"
out.text(f" {entry['name']} ({tag})")
if entry["type_hint"]:
out.text(f" type: {entry['type_hint']}")
if entry["description"]:
out.text(f" desc: {entry['description']}")
def _warn_custom_block_requirements(args: Namespace) -> None:
"""Warn upfront when a modular block ships custom code with declared external dependencies.
Reads `modular_config.json` if present; if it has an `auto_map` (custom code) and a non-empty `requirements`
list/dict, prints a heads-up. `from_pretrained` will otherwise fail with an `ImportError` deep in the loader stack
when a listed dep is missing.
"""
import diffusers
try:
config = diffusers.ModularPipelineBlocks.load_config(args.model, token=args.token, revision=args.revision)
except Exception:
return # no modular_config.json or unreachable — nothing to warn about
if not isinstance(config, dict):
return
if not config.get("auto_map"):
return
requirements = config.get("requirements")
if not requirements:
return
# `requirements` may be a dict {name: version} or (older repos) a list of [name, version] pairs.
if isinstance(requirements, dict):
pairs = list(requirements.items())
elif isinstance(requirements, list):
pairs = [(item[0], item[1]) for item in requirements if isinstance(item, (list, tuple)) and len(item) >= 2]
else:
pairs = []
if not pairs:
return
formatted = ", ".join(f"{name}=={version}" for name, version in pairs)
logger.warning(
f"{args.model!r} ships custom block code with external dependencies: {formatted}. "
"You will need to install these in order to determine the pipeline schema."
)
def _parse_docstring_args(docstring: str | None) -> dict[str, str]:
"""Extract per-argument descriptions from a Google-style `Args:` block.
Returns a `{name: description}` mapping. Best-effort — unrecognised formats just yield an empty dict rather than
raising.
"""
if not docstring:
return {}
lines = docstring.expandtabs().splitlines()
start = None
section_indent = 0
for i, line in enumerate(lines):
if line.strip() in ("Args:", "Arguments:", "Parameters:"):
start = i + 1
section_indent = len(line) - len(line.lstrip())
break
if start is None:
return {}
descriptions: dict[str, str] = {}
current_name: str | None = None
current_lines: list[str] = []
arg_indent: int | None = None
name_pattern = re.compile(r"^(\w+)\s*(?:\([^)]*\))?\s*:?\s*(.*)$")
def _flush() -> None:
if current_name and current_lines:
descriptions[current_name] = " ".join(s.strip() for s in current_lines).strip()
for line in lines[start:]:
if not line.strip():
continue
indent = len(line) - len(line.lstrip())
# A new top-level section ends the Args block.
if indent <= section_indent and line.strip().endswith(":"):
break
if arg_indent is None:
arg_indent = indent
if indent == arg_indent:
_flush()
current_lines = []
match = name_pattern.match(line.strip())
if match:
current_name = match.group(1)
tail = match.group(2).strip()
if tail:
current_lines.append(tail)
else:
current_name = None
elif current_name is not None and indent > arg_indent:
current_lines.append(line.strip())
_flush()
return descriptions
class SchemaCommand(BaseDiffusersCLICommand):
task = "schema"
@staticmethod
def register_subcommand(subparsers: _SubParsersAction) -> None:
from argparse import RawDescriptionHelpFormatter
epilog = (
"Examples\n"
" $ diffusers-cli schema -m stabilityai/stable-diffusion-xl-base-1.0\n"
" $ diffusers-cli schema -m black-forest-labs/FLUX.1-dev --verbose\n"
" $ diffusers-cli --format json schema -m stabilityai/stable-diffusion-xl-base-1.0\n"
"\n"
"Learn more\n"
" Use `diffusers-cli <command> --help` for more information about a command.\n"
" Read the documentation at https://huggingface.co/docs/diffusers\n"
)
parser: ArgumentParser = subparsers.add_parser(
"schema",
help="Print the input schema for a diffusers pipeline repo. No weights downloaded.",
usage="\n diffusers-cli schema [options]",
epilog=epilog,
formatter_class=RawDescriptionHelpFormatter,
)
parser._optionals.title = "Options"
parser.add_argument(
"--model",
"-m",
required=True,
help="Model id on the Hugging Face Hub or local path.",
)
parser.add_argument(
"--revision",
default=None,
help="Model revision (branch, tag, or commit SHA).",
)
parser.add_argument(
"--token",
default=None,
help="Hugging Face token for gated/private models.",
)
parser.add_argument(
"--trust-remote-code",
action="store_true",
help="Allow custom code from the Hub (required for modular pipelines that ship block code).",
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help=(
"Also include per-argument descriptions from the pipeline's __call__ docstring. "
"Modular pipelines always include block-declared descriptions; --verbose populates "
"the equivalent field for standard pipelines by parsing the Google-style Args: block."
),
)
parser.set_defaults(func=SchemaCommand)
def __init__(self, args: Namespace):
self.args = args
def run(self) -> None:
_schema(self.args)