Skip to content

engine-candidate: check stable-diffusion.cpp builds (engine=sd-cpp) - #47

Merged
Vect0rM merged 3 commits into
mainfrom
change/sdcpp-engine-candidate
Oct 21, 2026
Merged

Vect0rM merged 3 commits into
mainfrom
change/sdcpp-engine-candidate

Conversation

@Vect0rM

@Vect0rM Vect0rM commented Oct 21, 2026

Copy link
Copy Markdown
Member

Why

The engine update pipeline (atomic-hatchet-pipeline) now updates stable-diffusion.cpp, the image engine (sd-cpp). It reads only results.json from engine-candidate.yml, and needs it for two runs:

  1. the Atomic fork's build of a leejet tag (master-<N>-<sha7> from AtomicBot-ai/stable-diffusion.cpp, core_ref=main);
  2. conf's signed mirror of it (master-<N>-<sha7>-a<rev> in AtomicBot-ai/atomic-chat-conf), on a core release, with the workflow started from that release's ref.

Until now engine-candidate checked llama.cpp builds only.

What changes

.github/engine-candidate/sd-cpp.json (kind: sdcpp) holds what differs, read by the same scripts:

llama.cpp sd.cpp
tags b<N>, prism-b<N>-<sha7> master-<N>-<sha7>(-a<rev>), as SDCPP_TAG_RE
complete mirror whitelist + CPU archives archives conf's mirror-sdcpp.yml requires + the CPU archive of each live platform, each up once with its digest; on conf also a manifest (staging, or the live one when it names the tag) that names the tag, downloads from conf and pins the release's sizes and sha256
candidate.commit the release's the leejet tag's (git ls-remote); results.json adds candidate.upstream_tag, atomic_rev, manifest
pin conf's release list / PrismML manifest conf's backends/sdcpp-manifest.json tag_name (resolveSdcppPins ↔ parseSdcppManifest + sdcppHostChoice), archives from conf (pin_repo)
contract generated from the argv builders test/fixtures/sdcpp/engine-contract.json, hand-written and held to src/diffusion and src/engine-builds by test/contract/sdcpp-engine-contract.test.ts
surface scripts/lib/engine-surface.mjs scripts/lib/sdcpp-surface.mjs, the same report schema: sd-server --help by whole aliases (--vae-format never counts as --vae), --version naming the tag's commit, the install probe's markers in sd-cli --help, archives by backend, leejet's history between the two tags
live llama-server + a GGUF test/live/diffusion.test.ts + new test/live/diffusion-install.test.ts (install through a manifest: pick, download, sha256, unpack, probe; a tampered sha256 refused; --version)
platforms 5 / 3 linux-x64, linux-arm64, macos-arm64 (macos-26: the archive's minos is 26.0), CPU archives only

Live models: FLUX.2 Klein 4B Q4_K_M, Qwen3-4B-Instruct-2507 Q4_K_M as --llm and the FLUX.2 VAE, the files conf's diffusion catalog ships (5.4 GB, pinned by Hugging Face revision and sha256). They go into a cache entry of their own, keyed by their hashes, on a path Linux and macOS share, so one copy fits the 10 GB cache. SD-Turbo, the fork's smoke model, cannot be used: core passes --diffusion-model, and sd.cpp then cannot read the checkpoint's version (get sd version from file failed, checked on master-883).

Core change: ATOMIC_DIFFUSION_BACKEND=cpu starts every diffusion load with --backend cpu (a separate commit). The live run sets it through ATOMIC_LIVE_SD_BACKEND=cpu, because a macOS runner's paravirtual GPU crashes ggml's Metal kernels. Nothing else reads it.

Workflow:

  • sd-cpp in the engine choice.
  • The core suites run on the engine's platforms (core_matrix from resolve, core_runner where it differs). For llama.cpp this is the same seven jobs as before; the pipeline keeps suites=all.
  • The live timeout comes from the config (live_timeout_minutes: 90 for sd.cpp, GitHub's 360 otherwise).
  • The models cache step runs only for an engine with live_models.

Smaller changes: live_env fills the live test env from what the fetch leaves (live.json); required live tests may name platforms; invocations may name a bin; the upstream git cache is one folder per upstream repo. The live test's not.toContain('edit') now applies to Qwen Image 2.1 without its projector only (FLUX.2 Klein, the default family, edits).

Docs: ADR 2026-10-21-check-sdcpp-builds-with-the-engine-candidate, docs/live-tests.md ("stable-diffusion.cpp builds"), test/live/README.md, docs/testing-critical-flows.md.

Checked (macOS arm64)

  • npm run engine:check -- --engine sd-cpp --tag master-883-137f740 → PASS in 9 min:
    • pin master-883-137f740-a36f1b1a 5/5 and candidate 5/5 (the video block is skipped without a video model);
    • surface: 0 break, 4 warn (-M is sd-cli's only; core still emits the deprecated --qwen2vl, --clip-on-cpu, --vae-on-cpu).
  • results.json validates against the pipeline worker's CandidateResults.
  • resolve on conf's mirror (repo=AtomicBot-ai/atomic-chat-conf tag=master-883-137f740-a36f1b1a) → complete, pin the same tag.
  • Regression: --tag b11734 and --engine atomic-prism --tag prism-b10787-73bfe6c → PASS.
  • lint, typecheck, format, test:coverage (floor ok), build, build:bin, test:contract:bun.
  • test:e2e: 212 pass. One fails only on this machine: the desktop app listens on :1337, and owner.test.ts expects no Local API Server.
  • master-975-95b3090 is not checked: neither the fork nor conf has that release yet.

Not covered

  • Metal, CUDA, Vulkan, ROCm and Windows (not_verified).
  • A standard macOS runner has 7 GB of memory for 5.4 GB of weights; if it pages too much, the 90-minute live timeout will show it on the first CI run.
  • Found along the way, not fixed here: the macOS archives (the fork's and leejet's) are built with minos 26.0, so sd.cpp does not run on macOS 14/15; leejet does not print the generating video: line the tracker waits for (reported as text in neither tree).

After merge

A core release is needed: the pipeline re-checks conf's mirror on a core release, from that release's ref, and holds an sd.cpp rollout until a release contains .github/engine-candidate/sd-cpp.json.

🤖 Generated with Claude Code

Vect0rM and others added 3 commits October 21, 2026 14:30
…backend

The engine candidate check runs sd.cpp's live tests on CI runners, and a
macOS runner's GPU is a paravirtual device that ggml's Metal kernels crash
on. With ATOMIC_DIFFUSION_BACKEND=cpu a load starts with the same extraArgs
the automatic CPU recovery uses (`--backend cpu`, cpuBackendExtraArgs); any
other value, or none, changes nothing. A chosen backend is not a recovery:
cpuFallback stays false.

The live test sets it through ATOMIC_LIVE_SD_BACKEND=cpu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine update pipeline (atomic-hatchet-pipeline) now updates the image
engine too and needs results.json for two runs: the Atomic fork's build of
a leejet tag (master-<N>-<sha7>, core_ref=main) and conf's signed mirror of
it (master-<N>-<sha7>-a<rev>) at a core release. What differs from
llama.cpp is in .github/engine-candidate/sd-cpp.json (`kind: sdcpp`):

- resolve: complete once every archive conf's mirror requires and the CPU
  archive of each live platform is up once with its digest; on conf, the
  staging manifest (or the live one, when that names the tag) has to name
  the tag, download from conf and pin the release's sizes and sha256.
  candidate.commit is the leejet tag's (git ls-remote); results.json adds
  candidate.upstream_tag, atomic_rev and manifest. The pin is conf's
  sdcpp-manifest.json tag_name (resolveSdcppPins, held to
  parseSdcppManifest and sdcppHostChoice), fetched from conf (pin_repo).
- surface (scripts/lib/sdcpp-surface.mjs): the same report schema with
  sd.cpp's checks: sd-server --help by whole aliases (--vae-format is not
  --vae), --version naming the tag's commit, the install probe's markers,
  the archives by backend, leejet's history between the two tags (dates of
  leejet's releases, a git cache per upstream repo). Contract:
  test/fixtures/sdcpp/engine-contract.json, held to src/diffusion and
  src/engine-builds by test/contract/sdcpp-engine-contract.test.ts.
- live: test/live/diffusion.test.ts and the new diffusion-install.test.ts
  (a manifest install: pick, download, sha256, unpack, probe; a tampered
  sha256 refused; --version) on linux-x64, linux-arm64 and macos-arm64
  (macos-26: the archive's minos is 26.0), CPU archives, with FLUX.2 Klein
  4B Q4_K_M + Qwen3-4B-Instruct-2507 Q4_K_M + the FLUX.2 VAE (conf's catalog
  files, 5.4 GB, pinned by revision and sha256) in a cache entry of their
  own keyed by their hashes. SD-Turbo cannot be used: core passes
  --diffusion-model, and sd.cpp then fails to read the checkpoint's version.
  live_env fills the test env from what the fetch leaves (live.json);
  required live tests may name platforms.
- workflow: sd-cpp in the engine choice; the core suites run on the
  engine's platforms (core_matrix from resolve; the same seven jobs for
  llama.cpp); the live timeout comes from the config (90 min for sd.cpp).

The live test's `not.toContain('edit')` now applies to Qwen Image 2.1
without its projector only: FLUX.2 Klein, the default family, edits.
ADR 2026-10-21-check-sdcpp-builds-with-the-engine-candidate.

Checked on macOS arm64: `npm run engine:check -- --engine sd-cpp --tag
master-883-137f740` passes (pin master-883-137f740-a36f1b1a and candidate
5/5 each, surface 0 break), 9 min; `--tag b11734` and `--engine atomic-prism
--tag prism-b10787-73bfe6c` still pass; lint, typecheck, format, coverage,
build, contract:bun. e2e: 212 pass, one fails only because the desktop app
listens on :1337 here (owner.test.ts expects no Local API Server).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rators

liveModelPath joins with node:path, so on Windows the path the fetch writes
has backslashes; the test expected a POSIX string and failed on both Windows
runners. The expected path is now joined the same way, still compared whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Vect0rM
Vect0rM merged commit a52cac1 into main Oct 21, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant