ComfyUI получил нативные циклы: ноды Start Loop и End Loop
Ядро ComfyUI теперь зацикливает граф нативно. Соедините Start Loop с End Loop, чтобы повторять граф по числу, диапазону индексов или списку.
utilities/looping.
Как устроен цикл
Настраиваемого контейнера цикла нет. Цикл состоит из пары нод, а телом является всё, что находится между ними: всё, что соединено от Start Loop до End Loop, выполняется один раз за итерацию.
Структура цикла проверяется во время обычной валидации промпта, поэтому некорректный цикл выдаёт ошибку до начала выполнения. Каждый Start Loop должен однозначно сочетаться ровно с одним End Loop, вложенные области должны оставаться согласованными, а ветвь цикла не может выходить за пределы своего End Loop. Start Loop и End Loop объявляют себя границами цикла в схеме своей ноды, и именно это считывает проверка парности.
Во время выполнения исходное тело блокируется, и каждая итерация проецируется как свежие поставленные в очередь ноды графа, включая первую. Итерации остаются обычной работой планировщика, а не обрабатываются отдельным интерпретатором циклов, и прогресс итераций отображается на ноде как Iteration n / total.
Три способа задать число итераций
Вход mode ноды Start Loop определяет, как формируется счётчик итераций, и показываются только входы выбранного режима.
| Режим | По чему итерируется | Собственные входы |
|---|---|---|
simple | Фиксированное число повторов | num_iterations (по умолчанию 4) |
For | Числовой диапазон индексов | start_iteration_index (по умолчанию 0), max_iteration (по умолчанию 4, не включая верхнюю границу), step (по умолчанию 1) |
List | Один проход на каждый элемент прикреплённого списка | list |
step не меньше 1, а значение 0 вызывает ошибку. Если вычисленное число итераций равно нулю, тело цикла не выполняется, а is_last возвращает true.
Что возвращает Start Loop
| Выход | Значение |
|---|---|
iteration_index | Индекс текущей итерации |
is_first | True на первой итерации |
is_last | True на последней итерации |
list_item | Текущий элемент в режиме List, в остальных режимах пусто |
current_iteration_value | Значение, передаваемое через цикл, для этой итерации |
is_first служит обычной точкой входа для отдельного первого прохода, например чтобы инициализировать видео-цикл исходным кадром вместо последнего кадра предыдущего окна.
Минимальный случай: один Start Loop, небольшое тело генерации, один End Loop. Passthrough ноды Save Image подключён к входу termination, поэтому она всё равно выполняется один раз за итерацию.
Замыкание цикла
End Loop закрывает область видимости и определяет, что из неё выходит.
| Вход | Роль |
|---|---|
output_value | Значение, возвращаемое после завершения цикла |
next_iteration_value | Отправляется обратно в Start Loop как current_iteration_value для следующей итерации |
accumulate | Когда включено, возвращается output_value каждой итерации, а не только последней |
входы termination | Динамически расширяемые входы для превью, сохраняющих нод и других побочных эффектов, которые должны выполняться на каждой итерации. Их значения не возвращаются |
У End Loop один выходной сокет, тип которого следует за output_value. При включённом accumulate значения выдаются как обычный список ComfyUI, объединённый в порядке итераций.
Входы termination решают частую неожиданность: нода Preview Image или Save Image внутри тела иначе сработала бы только на последней итерации, поскольку более ранние спроецированные тела перезаписываются. Подключение passthrough этой ноды к входу termination сохраняет её выполнение один раз за итерацию и удерживает её внутри границ цикла.
При включённом accumulate все четыре генерации возвращаются одним списком. Preview Image подключена как termination, поэтому каждая итерация видна по мере завершения.
Изменение работы на каждой итерации
Поскольку iteration_index является обычным целочисленным выходом, он может управлять чем угодно далее по графу: разрешением, числом шагов, вариантами промпта или постепенным изменением силы LoRA.
Режим For подаёт iteration_index в разрешение, а результаты разного размера накапливаются в конце.
Передача состояния между итерациями
initial_iteration_value задаёт начальное передаваемое значение на первой итерации, а next_iteration_value каждой итерации становится current_iteration_value следующей итерации. Это весь канал состояния, и на каждую пару Start и End Loop приходится ровно один такой канал.
Чтобы передавать больше одного значения, поместите их в список. Существующая нода Create List теперь принимает разнородные входы, поэтому одно передаваемое значение может хранить смешанное состояние, например изображение и текстовый промпт, который его создал. Этот приём поддерживают две ноды в категории utilities:
- Create List: собирает передаваемое значение из разнородных входов.
- Get Item From List: новая нода в том же релизе. Она принимает список и индекс и возвращает этот элемент как обычное значение, поэтому компоненты передаваемого состояния можно распаковать внутри следующей итерации.
Генерация Z-Image и vision-language подписи передаются туда-обратно через одно передаваемое значение с изображением и текстом.
Вложенность и кэширование
parent_iteration в Start Loop является force-входом: подключите к нему iteration_index внешнего цикла, чтобы вложить цикл в другой. Вложенность задаётся явно, а не выводится автоматически, и каждому Start Loop по-прежнему нужен ровно один End Loop.
cache_iterations в Start Loop относится к расширенным настройкам и по умолчанию отключён. Если его включить, спроецированные ноды итераций могут переиспользовать кэшированные результаты из более ранних выполнений. Сама нода End Loop является обычной исполняемой нодой, поэтому её финальный выход в любом случае кэшируется обычным образом.
Внешний цикл в режиме List по промптам, внутри которого через parent_iteration вложен цикл генерации.
Примеры для видео и латентов
Механизм создавался с расчётом на расширение видео, где каждое окно зависит от предыдущего. Два опубликованных примера напрямую показывают этот приём.
Видео-цикл из последнего кадра в первый: исходное изображение инициализирует initial_iteration_value, финальный кадр каждого сгенерированного окна становится первым кадром следующего окна, а батчи кадров накапливаются и перегруппировываются в один клип.
Латент передаётся напрямую через цикл при выключенном accumulate, поэтому возвращается только финальный латент, плюс ленивый переключатель для обработки первой итерации и сила LoRA, которая постепенно меняется по мере выборки.
Самая простая отправная точка, цикл генерации на четыре итерации без передаваемого состояния, это одна пара Start и End Loop:
Где находятся ноды
Start Loop и End Loop являются базовыми нодами в категории utilities/looping и поставляются вместе с самим ComfyUI, поэтому пакет узлов не нужен. Create List и Get Item From List находятся в utilities. Поведение зацикливания описано на официальных страницах встроенных нод Start Loop и End Loop.
Существующие рабочие процессы не затрагиваются: циклы включаются по желанию, и граф без пары Start и End Loop ставится в очередь точно так же, как раньше.
Комментарии
Войдите через GitHub, чтобы участвовать в обсуждении.