Diffusers documentation

إجراءات الاستدعاء (Callbacks) في خط الأنابيب

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.42.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

إجراءات الاستدعاء (Callbacks) في خط الأنابيب

تُنفَّذ الدالة الاستدعائية (Callback) في نهاية خطوة إزالة الضوضاء، ويمكنها تغيير حالة خط الأنابيب أو المصفوفات (Tensors) للخطوات اللاحقة. استخدمها لتعديل السمات أو متغيرات المصفوفات لتحقيق سلوك جديد دون إعادة كتابة خط الأنابيب.

تعمل هذه الاستدعاءات (callbacks) مع حلقات DiffusionPipeline التقليدية دون أي إعداد مسبق. كما تعمل مع خطوط أنابيب Modular Diffusers بمجرد إدراج كتلة تشغّلها داخل حلقة إزالة الضوضاء؛ راجع خطوط أنابيب Modular.

توفر مكتبة Diffusers عدة استدعاءات مستدعاة (callbacks) في نظرة عامة على خط الأنابيب.

لتفعيل استدعاء، قم بتحديد متى يُنفَّذ الاستدعاء بعد عدد معين من خطوات إزالة الضوضاء باستخدام أحد الحُجج التالية.

  • cutoff_step_ratio يحدد متى يتم تفعيل الاستدعاء كنسبة مئوية من إجمالي خطوات إزالة الضوضاء. استخدمه عندما يجب أن تتناسب نقطة القطع مع num_inference_steps (على سبيل المثال، إسقاط CFG بعد 40% من التشغيل).
  • cutoff_step_index يحدد رقم الخطوة الدقيق الذي يتم فيه تفعيل الاستدعاء. استخدمه عندما يهمك خطوة مطلقة (على سبيل المثال، الخطوة 10 في جدول ثابت من 25 خطوة).

يستخدم المثال أدناه cutoff_step_ratio=0.4، مما يعني أن الاستدعاء الخلفي (callback) يُفعَّل عندما يصل إزالة الضوضاء (denoising) إلى 40% من إجمالي خطوات الاستدلال. يُعطّل SDXLCFGCutoffCallback التوجيه الحر من التصنيف (CFG) بعد عدد معين من الخطوات، مما قد يساعد في توفير الحسابات دون التأثير بشكل كبير على الأداء.

حدد استدعاءً عكسيًا (callback) يحتوي على أحد حُجج cutoff وقم بتمريره إلى معامل callback_on_step_end في خط الأنابيب.

import torch
from diffusers import DPMSolverMultistepScheduler, StableDiffusionXLPipeline
from diffusers.callbacks import SDXLCFGCutoffCallback

callback = SDXLCFGCutoffCallback(cutoff_step_ratio=0.4)
# if using cutoff_step_index
# callback = SDXLCFGCutoffCallback(cutoff_step_ratio=None, cutoff_step_index=10)

pipeline = StableDiffusionXLPipeline.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16,
    device_map="cuda"  # or "mps", "xpu", "cpu"
)
pipeline.scheduler = DPMSolverMultistepScheduler.from_config(pipeline.scheduler.config, use_karras_sigmas=True)

prompt = "a sports car on the road, best quality, high quality, high detail, 8k resolution"
output = pipeline(
    prompt=prompt,
    negative_prompt="",
    guidance_scale=6.5,
    num_inference_steps=25,
    callback_on_step_end=callback,
)

تحدد الاستدعاءات الرسمية المدخلات المتجهية الخاصة بها. أما بالنسبة لدالة مخصصة، فمرّر callback_on_step_end_tensor_inputs كما هو موضح في عرض الصور الوسيطة.

إذا كنت ترغب في إضافة استجابة رسمية جديدة، فلا تتردد في فتح طلب ميزة أو إرسال طلب سحب. وإلا، يمكنك أيضًا إنشاء استجابة خاصة بك كما هو موضح أدناه.

الإيقاف المبكر

يُعدّ التوقف المبكر مفيدًا إذا لم تكن راضيًا عن النتائج الوسيطة أثناء التوليد. يحدد هذا الاستدعاء التلقائي نقطة توقف ثابتة من خلال تعيين الخاصية _interrupt إلى True، مما يجعل حلقة إزالة الضوضاء تتخطى الخطوات المتبقية.

import torch
from diffusers import DiffusionPipeline

def interrupt_callback(pipeline, i, t, callback_kwargs):
    stop_idx = 10
    if i == stop_idx:
        pipeline._interrupt = True
    return callback_kwargs

pipeline = DiffusionPipeline.from_pretrained(
    "Qwen/Qwen-Image",
    dtype=torch.bfloat16,
    device_map="cuda",  # or "mps", "xpu", "cpu"
)
pipeline(
    prompt="A photo of a cat",
    num_inference_steps=50,
    callback_on_step_end=interrupt_callback,
)

عرض الصور الوسيطة

تُعدّ تصور الصور الوسيطة مفيدًا لمراقبة التقدم. المعاينة أدناه مخصصة لنموذج SDXL فقط. فهي تُحوّل كامنات SDXL إلى RGB باستخدام تحويل خطي للحصول على نظرة سريعة أثناء إزالة الضوضاء. لا يمكن نقل تلك الأوزان إلى نماذج أخرى. بالنسبة إلى Qwen-Image ونقاط التفتيش المشابهة، فكّك الترميز باستخدام VAE الخاص بالنموذج بدلاً من هذا المُساعد.

تحويل بيانات Stable Diffusion XL الكامنة (4 قنوات) إلى تينسورات RGB (3 قنوات).

import torch
from PIL import Image
from diffusers import AutoPipelineForText2Image

def latents_to_rgb(latents):
    weights = (
        (60, -60, 25, -70),
        (60,  -5, 15, -50),
        (60,  10, -5, -35),
    )

    weights_tensor = torch.t(torch.tensor(weights, dtype=latents.dtype).to(latents.device))
    biases_tensor = torch.tensor((150, 140, 130), dtype=latents.dtype).to(latents.device)
    rgb_tensor = torch.einsum("...lxy,lr -> ...rxy", latents, weights_tensor) + biases_tensor.unsqueeze(-1).unsqueeze(-1)
    image_array = rgb_tensor.clamp(0, 255).byte().cpu().numpy().transpose(1, 2, 0)

    return Image.fromarray(image_array)

استخرج الكمونيات (latents) وحوّل الصورة الأولى في الدفعة إلى RGB. احفظ الصورة كملف PNG مع رقم الخطوة.

def decode_tensors(pipe, step, timestep, callback_kwargs):
    latents = callback_kwargs["latents"]

    image = latents_to_rgb(latents[0])
    image.save(f"{step}.png")

    return callback_kwargs

استخدم callback_on_step_end_tensor_inputs لاختيار المصفوفات التي يستقبلها الاستدعاء العكسي، والتي في هذه الحالة هي الكمونيات.

pipeline = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/stable-diffusion-xl-base-1.0",
    dtype=torch.float16,
    device_map="cuda"  # or "mps", "xpu", "cpu"
)

image = pipeline(
    prompt="A croissant shaped like a cute bear.",
    negative_prompt="Deformed, ugly, bad anatomy",
    callback_on_step_end=decode_tensors,
    callback_on_step_end_tensor_inputs=["latents"],
).images[0]

خطوط أنابيب معيارية

لا تتضمن خطوط الأنابيب المعيارية آلية callback_on_step_end وinterrupt. ستأتي بدلاً من ذلك بدعم أولي للبث: huggingface/diffusers#14159، لم يتم دمجها بعد، تضيف pipe.stream()، والتي تُنتج حالة خط الأنابيب في كل خطوة إزالة ضوضاء وتُنهي التشغيل عندما تتوقف عن التكرار. في الوقت الحالي، لإعادة استخدام دوال الاستدعاء الموجودة لديك، أدخل كتلة في حلقة إزالة الضوضاء: تستقبل BlockState للحلقة مع فهرس الخطوة وخطوة الزمن، ويمكنها قراءة latents أو تحريرها.

حرّر الكتل قبل init_pipeline(): يعيد pipe.blocks نسخة، لذا فإن تغييره بعد وجود خط الأنابيب لا يُحدث أي أثر.

يستقبل الكود أدناه الدالة الاستدعائية (callback) كمدخل للخطّ (pipeline) ويستدعيها بنفس التوقيع المذكور أعلاه: الخطّ، فهرس الخطوة، خطوة الزمن (timestep)، وقاموس (dict) من المصفوفات (tensors)، حيث تُكتب القيم المُعادّة مرة أخرى. يكتشف walk_denoise_loops كل حلقة إزالة الضوضاء (denoising loop) في الكتل، بغض النظر عن سير العمل (workflow) الذي تنتمي إليه، لذا يعمل المقطع نفسه مع أي خطّ (pipeline) معياري.

from typing import Callable

import torch
from diffusers import ModularPipeline
from diffusers.modular_pipelines import InputParam, LoopSequentialPipelineBlocks, ModularPipelineBlocks


class CallbackOnStepEnd(ModularPipelineBlocks):
    @property
    def inputs(self):
        return [
            InputParam(
                "callback_on_step_end",
                type_hint=Callable,
                description="Called after each scheduler step as `callback(pipe, i, t, {'latents': ...})`; the dict it returns is written back.",
            ),
            InputParam("latents", required=True),
        ]

    def __call__(self, components, block_state, i, t):
        if block_state.callback_on_step_end is not None:
            outputs = block_state.callback_on_step_end(components, i, t, {"latents": block_state.latents})
            block_state.latents = outputs.get("latents", block_state.latents)
        return components, block_state


def walk_denoise_loops(block):
    """Every denoising loop in the blocks, whichever workflow it belongs to."""
    if isinstance(block, LoopSequentialPipelineBlocks) and "denoiser" in block.sub_blocks:
        yield block
    for sub in getattr(block, "sub_blocks", {}).values():
        yield from walk_denoise_loops(sub)


repo_id = "stabilityai/stable-diffusion-xl-base-1.0"
blocks = ModularPipeline.from_pretrained(repo_id).blocks
for loop in walk_denoise_loops(blocks):
    loop.sub_blocks["callback"] = CallbackOnStepEnd()  # appended after the last step of the loop
pipe = blocks.init_pipeline(repo_id)
pipe.load_components(dtype=torch.float16)
pipe.to("cuda")

أصبح callback_on_step_end الآن مدخلاً للخط الأنابيب، ويعمل أي مستدعى كُتب لـDiffusionPipeline، مثل decode_tensors أعلاه، كما هو:

image = pipe(prompt="A croissant shaped like a cute bear.", callback_on_step_end=decode_tensors, output="images")[0]

يعمل التوقف المبكر بشكل مختلف: لا يمكن للكتلة تخطي الحلقة التي تعمل فيها، لذا فإن تعيين _interrupt لا يُحدث أي تأثير. بدلاً من ذلك، ارمِ الاستثناء من الاستدعاء العكسي: ينتشر الاستثناء خارج pipe(...) (لا يتم فك ترميز أي صورة)، وتبقى خط الأنابيب قابلاً للاستخدام بعد ذلك.

يعمل هذا مع كل خط أنابيب معياري باستثناء الحلقة الداخلية للحلقات المتداخلة (حيث تُزيل نماذج الفيديو التوليدية الذاتية مثل Helios وWan-Animate-2 الضوضاء عن كل جزء داخل كتلة من الحلقة الخارجية). هناك، أدخل الكتلة في الحلقة الخارجية بدلاً من ذلك: فهي تعمل مرة واحدة لكل جزء وتستقبل فهرس الجزء k بدلاً من i وt.

Update on GitHub