diff --git a/.gitmodules b/.gitmodules index 1a5fb675..b3625f46 100644 --- a/.gitmodules +++ b/.gitmodules @@ -70,3 +70,7 @@ url = https://github.com/1bit-MONSTER/ROCmFPX.git shallow = true branch = 1bit/vulkan-rocmi4 +[submodule "third_party/comfyui.cpp"] + path = third_party/comfyui.cpp + url = https://github.com/1bit-MONSTER/comfyui.cpp.git + branch = master diff --git a/CMakeLists.txt b/CMakeLists.txt index e05e2cec..ba645538 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,24 @@ if(ONEBIT_ZINC) endif() endif() +# ── ComfyUI.cpp (docs/comfyui.md) ────────────────────────────────────────── +# Off by default: it needs LibTorch. Builds third_party/comfyui.cpp (GPL-3.0) with +# scripts/build-comfyui.sh as its own program; `1bit comfy ` runs it as a +# child process. The engine never links it, so the engine itself stays Apache-2.0. +option(ONEBIT_COMFYUI "Build ComfyUI.cpp (third_party/comfyui.cpp) for 1bit comfy" OFF) +if(ONEBIT_COMFYUI) + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/third_party/comfyui.cpp/CMakeLists.txt") + message(FATAL_ERROR "ONEBIT_COMFYUI needs third_party/comfyui.cpp: git submodule update --init third_party/comfyui.cpp") + endif() + set(ONEBIT_COMFYUI_BIN "${CMAKE_BINARY_DIR}/comfyui/comfyui_cpp") + add_custom_target(comfyui + COMMAND ${CMAKE_SOURCE_DIR}/scripts/build-comfyui.sh ${CMAKE_BINARY_DIR}/comfyui + BYPRODUCTS ${ONEBIT_COMFYUI_BIN} + USES_TERMINAL) + add_dependencies(onebit comfyui) + target_compile_definitions(onebit PRIVATE ONEBIT_COMFYUI_BIN="${ONEBIT_COMFYUI_BIN}") +endif() + # ── Hugging Face tokenizers (docs/tokenizers.md) ─────────────────────────── # Off by default: it needs cargo. Any model's tokenizer.json, through our C ABI # over third_party/tokenizers (the same release as the tokenizers npm/PyPI diff --git a/NOTICE b/NOTICE index 4a3f5397..736f55a4 100644 --- a/NOTICE +++ b/NOTICE @@ -68,6 +68,16 @@ ZINC (third_party/zinc) Copyright (c) 2025 ZINC Contributors License: MIT +ComfyUI.cpp (third_party/comfyui.cpp) + https://github.com/1bit-MONSTER/comfyui.cpp + A C++ port of ComfyUI (https://github.com/Comfy-Org/ComfyUI, Copyright (C) + Comfy Org and ComfyUI contributors, GPL-3.0). + License: GPL-3.0. ComfyUI.cpp is built as a separate program + (scripts/build-comfyui.sh) that `1bit comfy` runs as a child process; none of + its code is linked into this product. It links LibTorch (BSD-3-Clause) and + vendors stb (MIT / public domain), nlohmann/json (MIT) and the CLIP BPE + vocabulary (MIT). + OpenFlowLM-Next open kernels (third_party/OpenFlowLM-Next; only open_kernels/ is built) https://github.com/bong-water-water-bong/OpenFlowLM-Next (a fork of https://github.com/Atomic-Germ/OpenFlowLM-Next) diff --git a/README.md b/README.md index e40ffa74..9f20c18e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ serves each model behind an OpenAI-compatible API (`1bit serve`), whatever devic - ZINC, which also reaches NVIDIA GPUs (CUDA) and Apple GPUs (Metal) - MLX on Apple Silicon, through lemon-mlx-engine - Laya, which decides where each request runs +- ComfyUI.cpp: ComfyUI workflows (Stable Diffusion 1.5 text-to-image and image-to-image) in C++, matching ComfyUI's output to 50 dB ([docs/comfyui.md](docs/comfyui.md)) - every Hugging Face model architecture, kept current by a daily census > **Status:** the engine runs inside Lemonade through `1bit serve` ([docs/lemonade.md](docs/lemonade.md), diff --git a/app/main.cpp b/app/main.cpp index 3ec1d9b6..ee96e062 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -46,6 +46,9 @@ #include #include +#include +#include + namespace { constexpr const char* kVersion = "0.0.1"; @@ -61,6 +64,7 @@ void usage(FILE* out) { " npu-run [options] generate on the NPU fast lane (npu-run --help)\n" " npu-lax [options] Qwen3.6-35B-A3B on the NPU lax kernels (npu-lax --help)\n" #endif + " comfy run a ComfyUI workflow with ComfyUI.cpp (docs/comfyui.md)\n" " version print the version\n" " help show this help\n"); } @@ -119,6 +123,29 @@ int run_npu(int argc, char** argv) { } #endif +// ComfyUI.cpp is GPL-3.0 and a separate program: exec it, never link it (docs/comfyui.md). +// The binary: $ONEBIT_COMFYUI, then this build's (-DONEBIT_COMFYUI=ON), then comfyui_cpp on PATH. +int run_comfy(int argc, char** argv) { + if (argc < 1 || !std::strcmp(argv[0], "-h") || !std::strcmp(argv[0], "--help")) { + std::printf("usage: 1bit comfy \n" + " runs a ComfyUI API-format workflow (SD1.5 txt2img/img2img, see docs/comfyui.md)\n"); + return argc < 1 ? 2 : 0; + } + std::string bin; + if (const char* e = std::getenv("ONEBIT_COMFYUI"); e && *e) bin = e; +#ifdef ONEBIT_COMFYUI_BIN + if (bin.empty() && std::filesystem::exists(ONEBIT_COMFYUI_BIN)) bin = ONEBIT_COMFYUI_BIN; +#endif + if (bin.empty()) bin = "comfyui_cpp"; + std::vector args{bin.data()}; + for (int i = 0; i < argc; ++i) args.push_back(argv[i]); + args.push_back(nullptr); + ::execvp(args[0], args.data()); + std::fprintf(stderr, "1bit comfy: cannot run %s: %s (build with -DONEBIT_COMFYUI=ON or set ONEBIT_COMFYUI)\n", + bin.c_str(), std::strerror(errno)); + return 127; +} + } // namespace int main(int argc, char** argv) { @@ -161,6 +188,7 @@ int main(int argc, char** argv) { } } #endif + if (cmd == "comfy") return run_comfy(argc - 2, argv + 2); if (cmd == "version" || cmd == "--version") { std::printf("1bit %s\n", kVersion); return 0; diff --git a/docs/comfyui.md b/docs/comfyui.md new file mode 100644 index 00000000..0bca39aa --- /dev/null +++ b/docs/comfyui.md @@ -0,0 +1,72 @@ + + +# ComfyUI.cpp + +ComfyUI.cpp runs ComfyUI workflows in C++. It is a port of ComfyUI's workflow engine and of +the Stable Diffusion 1.5 nodes on LibTorch, pinned as the submodule `third_party/comfyui.cpp` +([1bit-MONSTER/comfyui.cpp](https://github.com/1bit-MONSTER/comfyui.cpp)). + +``` +git submodule update --init third_party/comfyui.cpp +cmake -S . -B build -DONEBIT_COMFYUI=ON +cmake --build build +build/1bit comfy workflow.json +``` + +`1bit comfy` takes a workflow in ComfyUI's API format (the JSON that ComfyUI's "Save (API)" +writes) and runs it. SaveImage writes the PNG to the node's `path` input. + +## License + +ComfyUI is GPL-3.0, and so is ComfyUI.cpp. The engine is Apache-2.0, so it never links +ComfyUI.cpp. `scripts/build-comfyui.sh` builds it as its own program, +`build/comfyui/comfyui_cpp`, and `1bit comfy` runs that program in place of itself. +`1bit comfy` looks for the binary in `$ONEBIT_COMFYUI` first, then this build's, then +`comfyui_cpp` on `PATH`. + +## What runs + +Everything a Stable Diffusion 1.5 txt2img or img2img workflow needs: +- CheckpointLoaderSimple, CLIPTextEncode, EmptyLatentImage +- KSampler with `euler` / `normal`, including denoise below 1 +- VAEEncode, VAEDecode, LoadImage and SaveImage +- the conditioning, latent and image nodes + +It runs on the CPU for now, through LibTorch; the build takes LibTorch from the Python `torch` +package. + +## Parity with ComfyUI + +ComfyUI.cpp pins ComfyUI itself (`third_party/ComfyUI`, Comfy-Org/ComfyUI `1568e6c`). +`tools/parity_sd15.py` in that repository runs the same workflows through both on CPU fp32 and +compares the images. Measured on Strix Halo with `v1-5-pruned-emaonly.safetensors`, 512x512, +20 steps, cfg 7: + +| workflow | PSNR vs ComfyUI | max / mean abs diff (0-255) | pixels off by > 2 | +|---|---|---|---| +| txt2img, "a cat", seed 0 | 50.6 dB | 15 / 0.515 | 0.286% | +| img2img, denoise 0.6, seed 7 | 51.2 dB | 1 / 0.498 | 0.000% | + +The remaining difference is fp32 rounding: ComfyUI batches the cond and uncond passes and uses +PyTorch's attention kernel. Through `1bit comfy`, the txt2img workflow takes 48 s and gives the +same 50.6 dB. ComfyUI takes about 35 s on the same CPU. + +## Next + +- The GPU: LibTorch on ROCm for gfx1151. +- More model families and samplers beyond SD1.5 with Euler. diff --git a/scripts/build-comfyui.sh b/scripts/build-comfyui.sh new file mode 100755 index 00000000..25afd81b --- /dev/null +++ b/scripts/build-comfyui.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright 2026 bong-water-water-bong +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +# +# build-comfyui.sh +# +# Builds ComfyUI.cpp pinned in third_party/comfyui.cpp (1bit-MONSTER/comfyui.cpp, GPL-3.0) +# into /comfyui_cpp. It is a separate program: the engine runs it as a child +# process (`1bit comfy`) and never links it (docs/comfyui.md). Needs LibTorch; the +# build takes torch's CMake config from `python3 -c "import torch"`, or from Torch_DIR. +set -euo pipefail +prefix=${1:?usage: build-comfyui.sh } +root=$(cd "$(dirname "$0")/.." && pwd) +src=$root/third_party/comfyui.cpp +[ -f "$src/CMakeLists.txt" ] || { echo "third_party/comfyui.cpp is empty: git submodule update --init third_party/comfyui.cpp"; exit 1; } +mkdir -p "$prefix" +prefix=$(cd "$prefix" && pwd) +cmake -S "$src" -B "$prefix/build" -DCMAKE_BUILD_TYPE=Release +cmake --build "$prefix/build" -j"$(nproc)" --target comfyui_cpp +cp "$prefix/build/comfyui_cpp" "$prefix/comfyui_cpp" +echo "built $prefix/comfyui_cpp (comfyui.cpp $(git -C "$src" rev-parse --short=12 HEAD 2>/dev/null || echo unknown))" diff --git a/third_party/comfyui.cpp b/third_party/comfyui.cpp new file mode 160000 index 00000000..4a08238c --- /dev/null +++ b/third_party/comfyui.cpp @@ -0,0 +1 @@ +Subproject commit 4a08238cef8bcfc7048d852eefa096651d62785a diff --git a/tools/site.py b/tools/site.py index 1586680f..8a9e66a3 100644 --- a/tools/site.py +++ b/tools/site.py @@ -77,7 +77,7 @@ ("Start", [("serve", "1bit serve"), ("lemonade", "Lemonade")]), ("Devices", [("npu", "NPU"), ("hrx", "HRX + Vulkan"), ("vulkan", "Vulkan (upstream)"), ("lean", "Lean (ROCmFP4, ROCmI4)"), ("zinc", "ZINC"), ("apple", "Apple Silicon")]), - ("Components", [("laya", "Laya router"), ("tokenizers", "Tokenizers"), ("kernel", "Linux kernel")]), + ("Components", [("laya", "Laya router"), ("comfyui", "ComfyUI.cpp"), ("tokenizers", "Tokenizers"), ("kernel", "Linux kernel")]), ("Project", [("PORTING", "Porting map")]), ]