O que 100 nós customizados do ComfyUI fazem em produção, e como são distribuídos
Autora: Anastasiia Butova, engenheira de ComfyUI e modelos de difusão, Belgrado · publicado em 2026-09-23
Tradução da versão em inglês: English
Escrevi 100 nós customizados do ComfyUI em 16 pacotes. O número foi contado em 15 de setembro de 2026, a partir do código de registro de cada pacote, e não lembrado de cabeça. A maioria deles é uma de três coisas: um modelo encapsulado para rodar dentro do grafo, uma decisão que o grafo precisa tomar, ou uma operação de imagem que nenhum pacote existente faz do jeito que nosso pipeline precisa. Abaixo estão exemplos reais de cada uma, e depois a parte que costuma ser pulada: como um nó chega a uma imagem de produção e o que acontece quando o nó errado chega a um servidor.
De onde vem o número
Um script lê os fontes Python de cada pacote e coleta os ids dos nós nas três formas de registro que o ComfyUI aceita. Ele encontrou 104 registros e 100 nós distintos, já que quatro nós utilitários são registrados por dois pacotes; se alguma fonte não puder ser lida, ele sai com código 2 e a contagem não é publicada. Os maiores pacotes são um geral, com 32 nós (roteamento, portões, máscaras, ruído, cor, folhas de contato), um pacote de gemas com SAM 3, com 20, e um de tiling e frequência, com 19. Dez pacotes pequenos têm de um a três nós cada, em sua maioria encapsulamentos de um único modelo. A tabela completa está em /comfyui/.
Os repositórios são privados, e vários pacotes foram escritos dentro de trabalhos de clientes e do empregador (a descrição do repositório do maior ainda diz "by Glam AI"), então eu os descrevo em vez de linká-los. Antes desta contagem, o site dizia "100+ nós, open-source". O número se sustentou; "open-source" não.
Um modelo, encapsulado para rodar dentro do grafo
NAFNet, SwinIR e HYPIR restauram imagens, SAM 3 segmenta peças de carro e gemas, DINOv3 mede carros e classifica, YOLO26 detecta. Como nós, rodam no mesmo job da geração, na mesma GPU, sem nenhuma ida e volta para outro serviço.
Chamar o modelo é a parte fácil de um encapsulamento. O resto é fazê-lo se comportar em hardware que o autor do grafo nunca viu. O nó SwinIR tem perfis de GPU que definem tamanho de tile e batch, e o JSON do workflow carregava o perfil que seu autor escolheu, digamos H200 com 141 GB. No RunPod serverless, o agendador entrega o que o pool do endpoint tiver disponível, de várias variantes de H100 a uma RTX PRO 6000 Blackwell. O perfil errado significava erros de memória insuficiente, novas tentativas e jobs que passavam do timeout de 30 minutos. Desde a versão 1.6.0, o nó detecta a placa sozinho e ignora essa configuração.
Ele também costumava cair para a CPU quando faltava memória, e aí um tile leva cerca de 40 segundos e um job completo leva horas. Tirei esse fallback na 1.5.8: agora o nó falha alto, com um resumo de memória. Um sucesso lento que ninguém percebe é pior do que um erro que alguém conserta.
Decisões que o grafo precisa tomar
Um grafo do ComfyUI não tem if. Os nossos ramificam assim. Um nó roteador coloca a imagem real na saída que escolheu e um placeholder preto de 1×1 em todas as outras. No início de cada ramo fica um portão:
def run(self, image):
from comfy_execution.graph_utils import ExecutionBlocker
if not torch.is_tensor(image):
print(f"[HappyinImageGate] NOT a tensor → BLOCK")
return (ExecutionBlocker(None),)
h, w = image.shape[1], image.shape[2]
if h <= 1 or w <= 1:
print(f"[HappyinImageGate] {w}x{h} → BLOCK")
return (ExecutionBlocker(None),)
print(f"[HappyinImageGate] {w}x{h} → PASS")
return (image,)
ExecutionBlocker diz ao ComfyUI para não rodar nada a jusante dele, então um ramo não escolhido não custa GPU nenhuma. No fim de cada ramo, um nó de placeholder com uma entrada lazy ou pede o resultado do ramo ou, se o ramo não foi escolhido, passa o 1×1 adiante sem pedir nada; um nó de junção então entrega a primeira imagem real. Sem a entrada lazy, o bloqueador chegaria à junção e bloquearia tudo o que vem depois.
Três dos roteadores:
- NSFW e degradação. Uma flag de NSFW mais um veredito de qualidade a partir de métricas de pixel (blocagem de JPEG pela DCT, bandas de cor, ruído de crominância, ringing, desfoque) e uma nota de estética baseada em CLIP. Quatro saídas de imagem, de
nsfw_bad_qualityasafe_good_quality, para que um quadro degradado vá para um ramo de upscale e um limpo para um mais leve. - Close-up. Cobertura do rosto medida de duas formas, segmentação de pele do rosto pelo MediaPipe e a caixa de um detector de rosto; o sinal mais forte vence.
- Roteador de prompt do Florence. Num fluxo de retoque de joias, o Florence-2 legenda o quadro, e se a legenda menciona uma pedra, o prompt de gema é usado.
O roteador do Florence é o que mais me ensinou. Em 30 quadros reais de clientes, o Florence nomeou as pedras em todos os 18 quadros que as tinham, e ainda assim o ramo de pedras só foi escolhido 17 vezes. As três causas estavam no casamento de padrões, nenhuma no modelo. Plurais: diamonds não casa com \bdiamond\b. Negações: "There are 0 gemstones" contém o substantivo. Formas: "diamond-shaped design" é um padrão, não uma pedra. A correção óbvia, um s opcional, foi pior: disparou em todos os 30 quadros, porque as frases negadas também contêm o substantivo. A ordem importa: remover negações, remover usos de forma, e só então casar com plurais. E "cut" não podia entrar na lista de formas, porque "brilliant-cut stone" nomeia uma pedra de verdade.
Sobre um conjunto compartilhado de 22 legendas, a correspondência antiga acertou 13, uma primeira correção proposta acertou 17 e a versão final acertou 22. A parte que eu manteria em qualquer roteador: a correspondência saiu do nó e foi para uma função simples, testada sobre legendas salvas, sem o Florence e sem GPU. Como diz a mensagem do commit, "uma regressão agora custa um segundo em vez de uma rodada de grafo".
Operações de imagem que nenhum pacote faz do nosso jeito
O terceiro tipo é processamento de imagem comum, em que os nós existentes faziam algo parecido, mas não o que o pipeline precisava:
- Ruído. Um estimador (Laplaciano mais MAD sobre o décimo mais liso de patches 64×64, menos um pequeno piso para que uma imagem limpa não pareça ruidosa) e um casador de grão que transporta o grão de uma imagem de referência para o alvo.
- Alinhamento. Alinhamento por fluxo óptico com um fallback de deformação, depois mistura de frequências, para que duas versões de um quadro se alinhem antes de serem misturadas.
- Tamanhos. Um ajuste de tamanho do latente que estica o quadro até o tamanho mais próximo da tabela do modelo escolhido, com todos os lados múltiplos de 16, em vez de cortá-lo.
- Tiling. Divisão e junção de tiles e decomposição de frequência para fotos de produto de 6K–10K pixels; as costuras que vêm com o tiling estão em um post separado.
- Carros. Para o mashinki, um nó que inclina a placa de fundo para casar com o rolamento (roll) da câmera da cabine.
Como um nó chega a uma imagem de produção
Para o OIS.Gold, uma imagem de produção começa de um bundle vendorizado: 21 pacotes, 14 públicos e 7 privados, copiados sem as pastas .git. O build grava o commit do bundle na imagem como .vendor-sha, para que uma imagem possa dizer de qual bundle foi construída. Atualizar é deliberado: atualize os pacotes, rode os workflows contra o novo estado, faça commit, reconstrua. Para um pacote vendorizado, uma mudança na branch principal de outra pessoa não consegue chegar à produção durante a noite.
O bundle foi montado em 23 de abril, depois de uma perda. Quatro dias antes, um host de GPU tinha sido encerrado, e o snapshot do seu contêiner guardava a imagem externa, mas não os nós customizados. Um dos nós, o roteador do Florence acima, foi perdido com ele. Eu o reconstruí em 27 de abril a partir das entradas e saídas registradas no JSON do workflow de produção.
A regra do vendorizado tem uma exceção, e prefiro declará-la a arredondá-la. Pacotes em desenvolvimento ativo, três meus e alguns de terceiros na imagem Blackwell, são clonados de novo sobre o bundle no momento do build, a partir da branch principal, com um pin de commit opcional para quando uma branch regride. Para esses pacotes, a branch principal é produção, inclusive a de outra pessoa. Foi por isso que meu maior pacote ganhou CI em agosto, e o arquivo de workflow diz isso em uma linha:
Whatever lands on master reaches the next worker rebuild, so master has to stay green.
Os testes não instalam o torch de propósito. Eles cobrem o que roda sem Florence e sem GPU, que é exatamente onde estavam os bugs dos roteadores.
Testar não é julgar. Um novo checkpoint ou uma mudança de nó é julgado numa folha de comparação contra a versão anterior (and 'mostra duas folhas desse tipo'), com os mesmos prompts congelados e a mesma semente em cada slot. A página do wowfaceaibot mostra duas planilhas desse tipo, dos meus próprios testes de LoRA de personagem.
Um pacote roda quando é importado
Dois módulos no meu maior pacote nunca foram nós. Não tinham entradas nem registro e existiam só pelos efeitos colaterais na importação: um encapsulava o PromptExecutor.execute do ComfyUI para salvar o PNG e o JSON do workflow de cada novo prompt, o outro corrigia o caminho de salvamento para prefixar o nome do contêiner. Úteis numa máquina de desenvolvimento. Em produção serverless, workers efêmeros gravavam um histórico que ninguém leria, e um encapsulamento com seu próprio try/except ao redor do executor podia esconder um erro real de workflow. Em maio, eles foram para um repositório separado que o build de produção nunca clona, e qualquer efeito colateral sempre ativo passou a viver ali.
O mesmo roteador do Florence ensinou a outra metade. Ele carregava o Florence-2 com trust_remote_code=True, então, num worker novo, a biblioteca transformers baixava o Python do modelo do Hugging Face Hub e o executava. Em maio, esse código parou de funcionar com o transformers instalado. O roteador capturava o erro e devolvia uma legenda vazia, e a cascata terminava com workers entregando um PNG achatado em vez do PSD em camadas com que os retocadores trabalham. A correção foi no nosso próprio nó: uma cópia local carregada com local_files_only, e o transformers fixado. Um HF_HUB_OFFLINE=1 global durou um dia, porque também bloqueava um download legítimo de um nó da comunidade. Em agosto, numa máquina com transformers 5, o nó passou a usar o código do modelo dentro do pacote ComfyUI-Florence2, que nunca segue o caminho de código remoto. Essa segunda falha tinha ficado escondida por dias atrás da mesma legenda vazia.
Um nó customizado é Python com as permissões do servidor
Este é o incidente por trás da linha de segurança em /comfyui/. Em fevereiro de 2026, um contêiner do ComfyUI num servidor com H100 com que eu trabalhava estava minerando Monero com uma variante do XMRig; o host em si estava limpo.
A porta do ComfyUI do contêiner estava exposta à internet sem autenticação. Alguém enviou um workflow usando o SRL Eval do srl-nodes, um pacote público. O nó faz o que sua docstring diz, avalia qualquer Python que você fornecer, e um comentário no código-fonte acrescenta "ComfyUI isn't secure to begin with". Numa máquina fechada, isso é uma conveniência. Numa porta aberta, é um shell remoto.
O código do atacante reescreveu /opt/ComfyUI/execution.py: o arquivo limpo tem cerca de 1.282 linhas, e as cópias infectadas iam de 1.700 a 2.100 e mais. A cada execução de workflow, o código injetado pausava o minerador para que a geração tivesse a CPU, depois o reiniciava, e baixava o binário de novo se ele tivesse sido apagado. Os binários ficavam em cerca de 27 diretórios com nomes que parecem serviços do sistema, diferentes a cada infecção: três ondas em cinco dias, em 13, 17 e 18 de fevereiro. Apagar o pacote não adiantou, porque o ComfyUI-Manager mantinha um snapshot dos nós instalados, percebia ao reiniciar que o srl-nodes tinha sumido e o clonava de volta. A reinfecção vinha em minutos.
Escrevi o runbook de detecção e remoção, com um script de verificação e um script de limpeza. A ordem é o que importa:
- Primeiro bloqueie o acesso externo à porta, na cadeia
DOCKER-USERdo iptables, ou tudo o que vier abaixo é desfeito em minutos. As regras não sobrevivem a um reinício do daemon do Docker, a menos que sejam salvas. - Mate todo processo dentro do contêiner, exceto o próprio ComfyUI.
- Apague o pacote e todo snapshot do Manager que o liste.
- Apague os binários, depois procure qualquer executável que tenha sobrado fora dos diretórios de ferramentas conhecidos.
- Restaure o
execution.pya partir do git, a única forma confiável, depois confira o número de linhas e zero ocorrências dos marcadores do minerador. - Reinicie, confirme que o pacote não voltou, e faça commit da imagem limpa.
O script de verificação olha cinco coisas: strings do minerador no execution.py, processos extras, executáveis inesperados, conexões com o pool e se o pacote voltou. O runbook também lista o que não pode ser apagado: um pacote desconhecido naquele contêiner era, na verdade, gerenciamento legítimo de memória da GPU.
Relendo o script de verificação para este post, encontrei uma falha. Se o SSH não conseguisse conectar, todas as contagens voltavam vazias, todos os testes caíam no OK, e o script imprimia RESULT: CLEAN. Uma verificação que não conseguiu rodar precisa dizer isso, com um terceiro resultado.
Em abril de 2026, a Censys publicou uma análise de uma campanha contra ComfyUI expostos à internet. O SrlEval está na lista de nós explorados dela, e quando nenhum nó vulnerável está instalado, o scanner instala um ele mesmo pelo ComfyUI-Manager. O mesmo nó, e o Manager de novo como instalador.
O que isso significa para escrever e distribuir nós
- As entradas de um nó são uma API. Se um nó executa código, lê um caminho ou baixa algo com base nas suas entradas, quem controla o grafo controla esse comportamento.
- O ComfyUI fica fora da internet pública, atrás de um serviço estreito; como isso funciona para o estúdio de carros deste site está no post companheiro.
- Nós chegam no momento do build, a partir de uma lista no git, e um pin vence uma branch. Código que chega em tempo de execução, um snapshot reinstalando um pacote ou um
trust_remote_codebuscando Python, é código que ninguém distribuiu de propósito. - Quando a produção é construída a partir de uma branch, essa branch ganha CI, com os testes sem GPU dentro do pacote.
Se você precisa de um nó para uma etapa que nenhum pacote existente cobre, ou de um pipeline tornado seguro para distribuir, como eu trabalho está em /comfyui/.