Skip to content

Feature abot dit#22

Open
DmitryMalishev wants to merge 11 commits into
2026-07-03from
feature-abot-dit
Open

Feature abot dit#22
DmitryMalishev wants to merge 11 commits into
2026-07-03from
feature-abot-dit

Conversation

@DmitryMalishev

@DmitryMalishev DmitryMalishev commented Jul 17, 2026

Copy link
Copy Markdown

What

Adds ABot-World support to the engine, in two increments:

  1. Model support (reviewed previously): VERSION_ABOT_WORLD detection (via the act_control_adapter.conv.weight tensor), the ActControlAdapter block (keyboard-action conditioning), Wan2.2-TI2V-5B-family loading, and a guard that rejects ABot models in the batch generate_image()/generate_video() APIs at the shared GenerationRequest::resolve() choke point (ABot is a causal interactive model, not a one-shot generator). Both capability queries report false for ABot.
  2. Interactive walk (this increment): the causal walk core and a public session C API, so ABot can actually be driven:
    • src/abot_world.hpp — walk core using a recompute formulation: each denoise step re-derives attention over [reference tokens | clean history | current block] with a per-row trailing-window mask that reproduces the reference implementation's KV cache exactly (including eviction). Covers the action adapter (PixelUnshuffle'd key planes), zero-masked reference tokens, negative-time RoPE ids, per-frame timestep modulation, the 4-step distilled flow-matching schedule, and taehv pixel decoding (stateless per-block decode with a 3-latent-frame overlap standing in for the reference's streaming decoder cache).
    • include/stable-diffusion.hsd_abot_session_{params_init,new,step,frames_free,free}: a standalone session (own DiT + taehv + scene pack, no sd_ctx_t); step(action_mask) generates the next block under the held keys (bits 0..7 = W,A,S,D,I,J,K,L) and returns its decoded RGB frames.
    • examples/abot-{parity,walk,session} — validation harnesses (static builds only; they link engine internals that the shared library does not export).

Validation (vs the PyTorch reference, F16 GGUF, fixed-scene goldens)

  • Per-step parity across every attention regime — first block, all four timesteps, history recompute, window onset, deep eviction: 7/7 gates at cosine 0.9996–0.9999 (gate 0.999).
  • Full-chain golden-replay walk (4 blocks × 4 steps, replaying the reference's recorded noise): latent cosine 0.9999 → 0.9931 (expected f16-vs-bf16 accumulation in a recursive rollout); decoded frames vs the reference chain: PSNR 31.3–55 dB (mean 38.9) — visually identical, and the W action drives the same forward motion.
  • taehv decode vs the reference streaming decoder on identical latents: mean 46.4 dB, min 33.5 dB.
  • Session C API end-to-end (Q8_0, CPU): one block → 9 RGB frames at 832×480, clean render.

Regression safety

New code is only reachable for VERSION_ABOT_WORLD models or through the new sd_abot_* entry points. Existing paths are untouched except: an optional defaulted attn_mask parameter on WanAttentionBlock::forward, and inline on two free functions in tae.hpp (ODR fix — the header is now included from more than one translation unit). Batch-generation rejection for ABot is unchanged and covered by the monorepo integration test.

olyasir and others added 6 commits July 17, 2026 15:58
In Resample::forward (upsample3d), the first latent-frame chunk skipped
time_conv entirely and never seeded the temporal feat cache, so the second
chunk ran time_conv against zero padding instead of chunk-0 context.

The reference implementation (Wan vae2_2.py, "Rep" cache semantics) runs
time_conv with causal zero padding on chunk 0, doubles the frames, trims
the first one, and stores the input tail in the cache for the next chunk.

Effect on Wan2.2 VAE decode vs the PyTorch reference (5-frame 480x832,
CPU backend, f16 weights): cosine 0.9959 / 27.2 dB PSNR before, cosine
1.000000 / 79.0 dB after. Output was visually near-identical, so this
only shows up in numeric parity testing. Encode and TAEHV paths are
unaffected.
…ction adapter)

ABot-World-0-5B-LF (acvlab, Apache-2.0) is a causal/interactive derivative of
Wan2.2-TI2V-5B with a keyboard-action conditioning adapter. This adds:

- VERSION_ABOT_WORLD: detected via the act_control_adapter.conv.weight tensor;
  helpers sd_version_is_abot_world() / sd_version_is_wan_ti2v_family() (the
  latter now gates the shared TI2V 48ch/16x latent-space branches in
  vae.hpp, tae.hpp and stable-diffusion.cpp instead of == VERSION_WAN2_2_TI2V)
- WAN::ActControlAdapter block (SimpleAdapter in the reference impl:
  conv 2x2/s2 over pixel-unshuffled action planes + one ReLU residual block)
  registered by WAN::Wan when WanParams.abot_world is set, so all 831
  checkpoint tensors (F16/Q8_0 GGUF or safetensors) map and load
- WanRunner: ABot-World-5B config (Wan2.2-TI2V-5B dims + abot_world flag)
- generate_video(): explicit, documented rejection for ABot-World models --
  the causal interactive session (KV cache, per-block actions, 4-step
  distilled schedule) lands in a follow-up; running these weights through the
  batch path executes the wrong recipe by design

No behavior change for any existing model version: every new branch is gated
on VERSION_ABOT_WORLD, and sd_version_is_wan_ti2v_family(v) == (v ==
VERSION_WAN2_2_TI2V) for all previously existing versions.

Verified: ABot Q8_0 GGUF (5.86 GB, 831 tensors incl. adapter) detects as
"ABot-World", instantiates ABot-World-5B, and completes tensor loading;
generation attempt fails fast with the explanatory error.
sd-abot-decode: a native, Python-free example that loads the taehv (taew2_2)
decoder GGUF and a fixed-scene "walk" of precomputed latents (raw f32), decodes
them on the ggml engine, and writes a video. This proves the native taehv
decode path and the S3 -> GPU-runner -> build -> video -> validate CI pipeline
end to end; the DiT causal denoise replaces the "load latents" input in the
follow-up (docs/abot_world_mvp_spec.md).

- examples/abot-decode/{main.cpp,CMakeLists.txt}: standalone decoder (mirrors
  the upscaler.cpp standalone-runner pattern); loads TinyVideoAutoEncoder for
  VERSION_ABOT_WORLD, decodes video latents, writes MJPG-AVI.
- examples/abot-decode/data/walk_latents.{bin,json}: 12-frame golden latent
  walk (from the reference pipeline) as the CI decode input.
- .github/workflows/build.yml: temporary abot-walk-mvp-gpu job on
  qvac-ubuntu2204-x64-gpu — Vulkan build, pull models from S3 (tether-ai-dev via
  OIDC), run the decode, validate the video, upload it as an artifact. Appended
  to the existing workflow so a feature-branch PR can trigger it; to be split
  into abot-walk-mvp.yml at merge.

Builds locally (MinGW, CPU). GPU/S3 path validated on CI.
GPU testing of ABot moves to the diffusion addon (monorepo) via a vcpkg
overlay port, so the engine PR no longer needs the fork-side GPU workflow or
the standalone taehv-decode example:

- revert build.yml to the pristine matrix (drop the abot-walk-mvp-gpu job that
  had no available self-hosted runner in this fork)
- delete .github/workflows/abot-walk-mvp.yml scaffold
- delete examples/abot-decode/ (native taehv decode + committed latent fixture)

This PR is now scoped to engine support only: recognize + load ABot-World
(VERSION_ABOT_WORLD, action-adapter block, TI2V loading) with batch
generate_video() guarded. Docs (docs/abot_world*.md) and the load-validation
script (script/validate_abot_world.sh) are kept.
DmitryMalishev added a commit to tetherto/qvac that referenced this pull request Jul 20, 2026
…test

Builds the diffusion addon against the unmerged sd.cpp PR
tetherto/qvac-ext-stable-diffusion.cpp#22 (ABot-World support) using a vcpkg
overlay port, so ABot can be GPU-tested through the addon's existing
integration CI without merging the engine PR or bumping the shared registry.

- vcpkg/overlay-ports/stable-diffusion-cpp: verbatim copy of the registry port
  (2026-07-03, port-version 5) with REF + SHA512 repinned to PR #22's head
  (da3f6b26). vcpkg-configuration.json gains overlay-ports so it wins over the
  registry port. Temporary — removed when the engine PR merges + the registry
  port is bumped.
- scripts/download-model-abot.sh: fetch the ABot GGUF set (DiT q8_0/f16,
  wan2.2 VAE, taew2_2) from corp S3 (tether-ai-dev, OIDC) with SHA256 check.
- test/integration/abot-world.test.js: skip-guarded. Asserts the ABot set
  loads via the addon and that batch generate is rejected with the documented
  "interactive session not implemented" error (ABot is causal/interactive, not
  one-shot). Auto-discovered by test:integration, so it runs on the existing
  qvac-*-gpu integration workflow. Replaced by a real walk assertion when the
  causal core lands.
Drop the internal implementation-planning spec (belongs with the consuming
project, not this public repo) and remove a reference to non-public tooling
from abot_world.md. No code changes.
Comment thread src/stable-diffusion.cpp Outdated
*num_frames_out = 0;
}
int64_t t0 = ggml_time_ms();
if (sd_version_is_abot_world(sd_ctx->sd->version)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fail-fast guard only covers one of the two doors into generation, and the capability layer disagrees with it.

1. generate_image() is unguarded. sd_version_is_wan() was (correctly) extended to include VERSION_ABOT_WORLD, so ABot travels through all the shared Wan latent/VAE/sampling paths used by both generate_video() and generate_image()/generate_image_internal(). But the rejection was added only here. A caller that loads an ABot GGUF and calls generate_image() runs the bidirectional/one-shot Wan recipe on a causal model with no check at all — silently corrupted output, or a raw GGML_ASSERT abort if it hits a shape assumption that was never true for ABot. That's exactly the hazard this guard exists to prevent.

2. The capability query now lies. sd_version_supports_video_generation() (line 3104) is ... || sd_version_is_wan(version) || ..., so it now reports ABot as video-capable. The example server's /vid_gen route (examples/server/routes_sdcpp.cpp) uses that query to reject unsupported models cleanly before reaching the engine — it will instead wave ABot through to this return false, bypassing the intended clean-rejection path.

Suggested fix: reject in one shared choke point instead of per-entrypoint, and keep the capability query honest:

  • exclude ABot from sd_version_supports_video_generation() (and any image-capability query), e.g. ... && !sd_version_is_abot_world(version);
  • move the actual rejection to a point both entrypoints pass through (GenerationRequest / SamplePlan::resolve()), or reject at new_sd_ctx() load time when the version is VERSION_ABOT_WORLD and no causal-session API was requested.

Loading still works; generation is then refused everywhere with the same message, and the server pre-screen agrees with the engine.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points confirmed and fixed in 23d8fd5 — thanks, this was a real gap.

  • Shared choke point: the rejection moved into GenerationRequest::resolve() (same pattern as validate_ideogram4_uncond_model), so generate_image() and generate_video() are both covered by one check; the per-entrypoint guard here was removed. Message now names both entrypoints.
  • Honest capability queries: sd_version_supports_video_generation() and sd_version_supports_image_generation() both return false for ABot (image could not simply stay !video, or it would have flipped to true).
  • script/validate_abot_world.sh grew an img_gen lane asserting the image path rejects too; verified locally against the ABot Q8_0 GGUF (detection + full load unchanged, both batch paths reject, stock-Wan lane unaffected).

On load-time rejection: kept loading permitted intentionally — downstream (tetherto/qvac#3352) exercises load on GPU CI ahead of the causal session API, and the capability queries now give front-ends the clean pre-screen.

…apability queries

Review feedback (#22): the fail-fast guard covered only generate_video(), while
generate_image() ran the batch recipe on the causal model unchecked, and
sd_version_supports_video_generation() still reported ABot as video-capable,
so front-ends pre-screening on the capability query would wave it through.

- Move the rejection into GenerationRequest::resolve() (the stage both
  generate_image() and generate_video() pass through, same pattern as
  validate_ideogram4_uncond_model): one check now covers every batch door.
  Message updated to name both entrypoints.
- sd_version_supports_video_generation() and
  sd_version_supports_image_generation() both return false for ABot-World
  (image could not simply mirror !video, or it would flip to true).
- Drop the now-redundant guard in generate_video().
- script/validate_abot_world.sh: assert the image path is rejected too;
  docs/abot_world.md wording updated.

Verified locally against the ABot Q8_0 GGUF: detection + full tensor load
unchanged; vid_gen and img_gen both reject with the shared message; stock-Wan
regression lane unchanged.
Implements the actual interactive walk for ABot-World models (the batch
generate_image()/generate_video() APIs keep rejecting them):

- src/abot_world.hpp: causal walk core using the recompute formulation --
  each denoise step re-derives attention over [ref tokens | clean history
  | current block] with a per-row trailing-window mask, reproducing the
  reference KV cache exactly (including eviction). Absolute-counter RoPE
  with negative-time reference ids; per-frame timestep modulation via a
  row-indexed e0 table; PixelUnshuffle'd keyboard-action planes through
  the act_control_adapter. Includes a minimal scene-pack reader
  (safetensors F32), a seed-stable RNG, and AbotWalkSession combining the
  4-step distilled flow-matching block loop with taehv pixel decoding
  (stateless per-block decode with a 3-latent-frame overlap standing in
  for the reference's streaming decoder cache).
- include/stable-diffusion.h, src/stable-diffusion.cpp: public C API --
  sd_abot_session_{params_init,new,step,frames_free,free}. A session is
  standalone (own DiT + taehv + scene pack, no sd_ctx_t); step() takes an
  8-key action mask and returns the next block's decoded RGB frames.
- src/wan.hpp: WanAttentionBlock::forward takes an optional additive
  self-attention mask (defaulted, no behavior change for existing paths).
- src/tae.hpp: patchify/unpatchify marked inline (they now live in a
  header included from more than one translation unit).

Validation vs the PyTorch reference (F16 GGUF, coastal scene goldens):
- Single-step parity across every regime -- first block, all timesteps,
  history recompute, window onset, deep eviction: 7/7 gates at cosine
  0.9996-0.9999 (gate 0.999).
- Native taehv decode vs reference streaming decode on identical latents:
  PSNR mean 46.4 dB, min 33.5 dB.
Three small executables used to prove the causal walk core against the
PyTorch reference and to exercise the public session C API end to end:

- examples/abot-parity: runs one golden denoise step (dumped by the
  reference implementation) through the causal forward and writes the
  block x0 prediction for offline cosine comparison.
- examples/abot-walk: full block-loop walk over a fixed scene; replays
  the reference's recorded noise for deterministic full-chain validation
  against the golden per-block final latents, or free-walks with a seeded
  RNG. Emits raw latents.
- examples/abot-session: drives the public sd_abot_session_* C API for a
  fixed-scene walk, writing decoded frames as PNGs; --mode decode is a
  taehv-only lane for PSNR cross-checks against the reference decoder on
  identical latents.
The abot-parity/walk/session examples link against engine internals
(ModelLoader, SDBackendManager, the runner classes) that are not
exported from the shared library, so -DSD_BUILD_SHARED_LIBS=ON lanes
failed to link them. Gate the three harnesses to static builds; the
public sd_abot_session_* C API itself is exported like every other
sd_* symbol and is unaffected.
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.

3 participants