Skip to content

SeedVR2 native PR regression tests (consolidates #21, #22, #24 onto issue_101_pi)#30

Merged
pollockjj merged 17 commits into
issue_101_pifrom
issue_101_pi_tests
May 1, 2026
Merged

SeedVR2 native PR regression tests (consolidates #21, #22, #24 onto issue_101_pi)#30
pollockjj merged 17 commits into
issue_101_pifrom
issue_101_pi_tests

Conversation

@pollockjj
Copy link
Copy Markdown
Owner

Summary

Consolidates three previously-open SeedVR2 regression-test PRs (#21, #22, #24) into a single PR against the clean `issue_101_pi` base. The original PRs all targeted `issue_101`, which has been hard-reset to match `issue_101_pi` (clean upstream-master pin + yousef-rafat/seedvr2 PR Comfy-Org#11294 + 3 SeedVR2 native fixes only). Their head branches were forked from the polluted ancestor of `issue_101` (which carried the entire pyisolate stack + canonical custom node + an older upstream master pin) and against the new base presented as 5,800-line spurious diffs.

Test files added (3, additive only)

File Lines From Purpose
`tests-unit/comfy_extras_test/test_seedvr_node_signature.py` +87 PR #24 (issue 114) Locks SeedVR2InputProcessing schema input-id order vs `execute()` positional parameter order
`tests-unit/comfy_test/test_seedvr_rope_delegation.py` +179 PR #21 (issue 120) Locks `comfy.ldm.seedvr.model.apply_rotary_emb` wrapper -> `apply_rope1` delegation invariant; 7 CPU + 1 CUDA-guarded parametrized cases; `assert_close(rtol=0, atol=0)`
`tests-unit/comfy_test/test_seedvr_vae_tiled_args_no_mutate.py` +8 PR #22 (issue 116) Locks SeedVR2 VAE non-mutation of `tiled_args` (guards against re-introducing `tiled_args.pop(...)`)

Total: 3 files changed, 274 insertions(+), 0 deletions(-).

Commit history

16 commits cherry-picked in chronological order (initial test add + Copilot/codex review-address commits per file). Per-commit history preserved. All from 2026-04-27.

Supersedes

pollockjj added 16 commits May 1, 2026 11:10
…p print noise

Follow-up to PR #20 review feedback and the new mydevelopment#114 plan
(Production-Code Shape Independence rule):

- stub comfy.model_management via unittest.mock.patch.dict instead of
  importing the real module, so test collection no longer triggers
  torch.cuda.is_available() or any GPU/server-side initialization.
  Live introspection confirmed comfy.model_management is the only
  heavy module transitively imported by comfy_extras.nodes_seedvr;
  nodes and server are not, so they are not stubbed.
- pop and re-import comfy_extras.nodes_seedvr inside the patch context
  via importlib.import_module so the stubbed sys.modules entry is the
  one observed during attribute resolution.
- replace the bare assert + print() pair with a single assert carrying
  a descriptive f-string failure message that includes both lists, so
  pytest's standard error report is the only failure surface and the
  passing-run stdout is quiet.
…rtol=0/atol=0, local Generator, non-default start_index/scale coverage, drop autouse params print
Adopt PR #20 Copilot review on e042ff2 verbatim: wrap the stubbed import
in try/finally so the mocked comfy.model_management cannot leak into
later tests via the cached comfy_extras.nodes_seedvr module.

- Bind the MagicMock to a local mock_model_management so the finally
  block can identity-check before deleting.
- Pop comfy_extras.nodes_seedvr from sys.modules in finally so the
  next test that imports it gets a fresh load with the real
  comfy.model_management.
- If the comfy package object itself acquired model_management as an
  attribute pointing at the test mock, delete that attribute so the
  package state matches the unstubbed condition.

The assertion logic is unchanged; the test still passes on issue_114
HEAD and still fails on the spatial_overlap/temporal_tile_size swap.
Two stylistic suggestions from Copilot's review of 5eb9257:

1. Reword the module docstring so it no longer claims the stub prevents
   GPU init at *test collection*; the stub/import happens inside the
   test body, so the wording now scopes the guarantee to the import
   performed inside the test. Adopted verbatim from the review.

2. Iterate inspect.signature(...).parameters.keys() instead of
   .parameters so it reads as 'list of names' rather than 'list of
   Parameter objects'. The runtime semantics are identical because
   .parameters yields keys when iterated, but .keys() makes the
   reader's job easier.

Behaviour, assertion text, and stubbing strategy are unchanged. The
test still passes on issue_114 HEAD and still fails on the
spatial_overlap/temporal_tile_size swap.
Text-level pytest that reads comfy/ldm/seedvr/vae.py and asserts
.tiled_args.pop( is absent and .tiled_args.get( is present. Catches
drift back to dict-mutation semantics per upstream fix in
Comfy-Org#11294 commit 3b418da.

Refs: pollockjj/mydevelopment#116
…5/3150100250 — apply_rope1 spy, freqs-longer-than-seq case, exact-equality wording, eager msg string
When this test imports comfy_extras.nodes_seedvr under a stubbed
comfy.model_management, Python caches the imported module on the
comfy_extras package object as a 'nodes_seedvr' attribute as well as in
sys.modules. The existing finally block only popped sys.modules, so a
later 'from comfy_extras import nodes_seedvr' in the same process could
resolve through the stale parent-package attribute and receive the
mock-loaded module instead of forcing a fresh import.

Add a parallel cleanup step in the finally block: when comfy_extras is
in sys.modules and carries a nodes_seedvr attribute, delete it. This
mirrors the existing comfy.model_management attribute cleanup so both
halves of the cached-import path are cleared.

References: #24 (comment)
            codex review round 1 (issue 114) [P2] finding
…management and comfy_extras.nodes_seedvr package attrs

Capture the pre-test value of comfy.model_management on the comfy
package and nodes_seedvr on the comfy_extras package before importing
comfy_extras.nodes_seedvr under the mock, then in finally restore the
attribute to its pre-test value if it had one, or delete it if it did
not. This addresses the case where another test in the same pytest
process had already legitimately imported comfy.model_management (or
comfy_extras.nodes_seedvr) prior to this test running: the previous
delete-on-mock cleanup left those package attributes unset even though
the real modules were still in sys.modules, which would AttributeError
on a subsequent comfy.model_management or comfy_extras.nodes_seedvr
attribute access without re-import.

The pattern is applied symmetrically to both attributes since the
correctness concern Copilot flagged on comfy.model_management is the
same correctness concern that motivated the codex P2 cleanup of
comfy_extras.nodes_seedvr — both are package attributes set as a
side-effect of import that we must restore rather than blindly delete.

References:
  #24 (comment)
  Copilot review id 4184963495 (round 1, head e96447f, ComfyUI#24)
…odules['comfy_extras.nodes_seedvr']

Both reviewers on round 2 of the autonomous review loop independently
flagged the same concern: the previous cleanup restored the
comfy_extras.nodes_seedvr package attribute but did not explicitly
restore the sys.modules['comfy_extras.nodes_seedvr'] entry. While
patch.dict's clear-and-restore semantics would actually undo the
finally-block pop on with-exit, that behavior is not obvious to a
reader and the intent is better expressed as an explicit
capture-before-patch / restore-in-finally pair, exactly mirroring the
existing handling of the comfy.model_management and
comfy_extras.nodes_seedvr package attributes.

Capture prior_comfy_extras_seedvr_module = sys.modules.get(...) before
the patch.dict block. In finally, if the prior value was the sentinel
(absent before the test) pop the entry; otherwise restore the prior
module object directly into sys.modules. The sys.modules pop inside
finally is replaced by this explicit conditional restore; the
sys.modules.pop before the try stays — that pop is functionally
required so importlib.import_module re-imports under the mock instead
of returning a cached real module.

References:
  #24 (comment) (codex round 2 [P2])
  #24 (comment) (Copilot id 3150906079)
…testing', replace per-comment-id docstring refs with stable issue link
… importing comfy.ldm.* on CPU-only hosts so import-time get_torch_device() does not probe a missing CUDA device (matches tests-unit/comfy_quant/test_mixed_precision.py pattern)
…nvention to test-local choice; cite image_stitch_test.py as a counter-example of stub usage in this repo
…h pytest.mark.skipif(not _cpu_trig_supported(dtype)) so PyTorch CPU wheels that don't implement torch.cos/sin for those dtypes skip cleanly instead of failing CI; cases preserved per plan Comfy-Org#120 Slice 2 AC-2
…se both:' -> 'each parametrized case does both:'
@qa-agent-seveneves
Copy link
Copy Markdown

Codex Review -- Round 1 -- KICKOFF

Runner: scripts/run_codex_review.py
PR: #30
Reviewer model: codex review (configured codex CLI default)
Kickoff timestamp (UTC): 2026-05-01T16:12:24Z

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Awaiting codex output. The result will be posted as a separate comment on this PR when codex exits.

@qa-agent-seveneves
Copy link
Copy Markdown

Codex Review -- Round 1 -- RESULT

PR: #30
Reviewer model: codex review (configured codex CLI default)
Result timestamp (UTC): 2026-05-01T16:14:38Z
Codex exit code: 0
Duration (s): 132.5
Detected verdict: UNKNOWN

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Verbatim codex review output

This PR was reviewed using codex review with the prompt above and produced the following output:


No merge-blocking or actionable P3 issues were found in the added regression tests. The targeted tests pass locally with the available torch environment.


Codex runner stderr (non-evidence)

OpenAI Codex v0.128.0 (research preview)
--------
workdir: /home/johnj/dev_cuda_0/mydevelopment
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, /home/johnj/.codex/memories]
reasoning effort: high
reasoning summaries: none
session id: 019de450-13cb-7870-8150-d84712a79ada
--------
user
Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line either 'APPROVE' (no P0/P1/P2 found) or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.
mcp: codex_apps/github_fetch_pr started
mcp: codex_apps/github_fetch_pr (completed)
exec
/bin/bash -lc pwd in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
/home/johnj/dev_cuda_0/mydevelopment

exec
/bin/bash -lc 'git -C /home/johnj/dev_cuda_0/ComfyUI status --short --branch' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
## issue_101_pi_tests...origin/issue_101_pi_tests

exec
/bin/bash -lc 'git -C /home/johnj/dev_cuda_0/ComfyUI diff --stat origin/issue_101_pi...HEAD' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
 .../test_seedvr_node_signature.py                  |  87 ++++++++++
 .../comfy_test/test_seedvr_rope_delegation.py      | 179 +++++++++++++++++++++
 .../test_seedvr_vae_tiled_args_no_mutate.py        |   8 +
 3 files changed, 274 insertions(+)

exec
/bin/bash -lc 'grep -R "def apply_rotary_emb" -n /home/johnj/dev_cuda_0/ComfyUI/comfy/ldm/seedvr/model.py' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
471:def apply_rotary_emb(

exec
/bin/bash -lc "sed -n '471,540p' /home/johnj/dev_cuda_0/ComfyUI/comfy/ldm/seedvr/model.py" in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
def apply_rotary_emb(
    freqs,
    t,
    start_index = 0,
    scale = 1.,
    seq_dim = -2,
    freqs_seq_dim = None
):
    dtype = t.dtype
    if not exists(freqs_seq_dim):
        if freqs.ndim == 2 or t.ndim == 3:
            freqs_seq_dim = 0

    if t.ndim == 3 or exists(freqs_seq_dim):
        seq_len = t.shape[seq_dim]
        freqs = slice_at_dim(freqs, slice(-seq_len, None), dim = freqs_seq_dim)

    rot_feats = freqs.shape[-1]
    end_index = start_index + rot_feats

    t_left = t[..., :start_index]
    t_middle = t[..., start_index:end_index]
    t_right = t[..., end_index:]

    angles = freqs.to(t_middle.device)[..., ::2]
    cos = torch.cos(angles) * scale
    sin = torch.sin(angles) * scale

    col0 = torch.stack([cos, sin], dim=-1)
    col1 = torch.stack([-sin, cos], dim=-1)
    freqs_mat = torch.stack([col0, col1], dim=-1)

    t_middle_out = apply_rope1(t_middle, freqs_mat)
    out = torch.cat((t_left, t_middle_out, t_right), dim=-1)
    return out.type(dtype)

class NaMMRotaryEmbedding3d(MMRotaryEmbeddingBase):
    def __init__(self, dim: int):
        super().__init__(dim, rope_dim=3)

    def forward(
        self,
        vid_q: torch.FloatTensor,  # L h d
        vid_k: torch.FloatTensor,  # L h d
        vid_shape: torch.LongTensor,  # B 3
        txt_q: torch.FloatTensor,  # L h d
        txt_k: torch.FloatTensor,  # L h d
        txt_shape: torch.LongTensor,  # B 1
        cache: Cache,
    ) -> Tuple[
        torch.FloatTensor,
        torch.FloatTensor,
        torch.FloatTensor,
        torch.FloatTensor,
    ]:
        vid_freqs, txt_freqs = cache(
            "mmrope_freqs_3d",
            lambda: self.get_freqs(vid_shape, txt_shape),
        )
        target_device = vid_q.device
        if vid_freqs.device != target_device:
            vid_freqs = vid_freqs.to(target_device)
        if txt_freqs.device != target_device:
            txt_freqs = txt_freqs.to(target_device)
        vid_q = rearrange(vid_q, "L h d -> h L d")
        vid_k = rearrange(vid_k, "L h d -> h L d")
        vid_q = apply_rotary_emb(vid_freqs, vid_q.float()).to(vid_q.dtype)
        vid_k = apply_rotary_emb(vid_freqs, vid_k.float()).to(vid_k.dtype)
        vid_q = rearrange(vid_q, "h L d -> L h d")
        vid_k = rearrange(vid_k, "h L d -> L h d")

exec
/bin/bash -lc 'grep -R "class SeedVR2InputProcessing" -n /home/johnj/dev_cuda_0/ComfyUI/comfy_extras/nodes_seedvr.py' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
123:class SeedVR2InputProcessing(io.ComfyNode):

exec
/bin/bash -lc "sed -n '123,230p' /home/johnj/dev_cuda_0/ComfyUI/comfy_extras/nodes_seedvr.py" in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
class SeedVR2InputProcessing(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id = "SeedVR2InputProcessing",
            category="image/video",
            inputs = [
                io.Image.Input("images"),
                io.Vae.Input("vae"),
                io.Int.Input("resolution", default = 1280, min = 120), # just non-zero value
                io.Int.Input("spatial_tile_size", default = 512, min = 1),
                io.Int.Input("spatial_overlap", default = 64, min = 1),
                io.Int.Input("temporal_tile_size", default=5, min=1, max=16384, step=4),
                io.Boolean.Input("enable_tiling", default=False),
            ],
            outputs = [
                io.Latent.Output("vae_conditioning")
            ]
        )

    @classmethod
    def execute(cls, images, vae, resolution, spatial_tile_size, spatial_overlap, temporal_tile_size, enable_tiling):

        comfy.model_management.load_models_gpu([vae.patcher])
        vae_model = vae.first_stage_model
        scale = 0.9152
        shift = 0
        if images.dim() != 5: # add the t dim
            images = images.unsqueeze(0)
        images = images.permute(0, 1, 4, 2, 3)

        b, t, c, h, w = images.shape
        images = images.reshape(b * t, c, h, w)

        clip = Lambda(lambda x: torch.clamp(x, 0.0, 1.0))
        normalize = Normalize(0.5, 0.5)
        images = side_resize(images, resolution)

        images = clip(images)
        o_h, o_w = images.shape[-2:]
        images = div_pad(images, (16, 16))
        images = normalize(images)
        _, _, new_h, new_w = images.shape

        images = images.reshape(b, t, c, new_h, new_w)
        images = cut_videos(images)
        images_bcthw = rearrange(images, "b t c h w -> b c t h w")
        images_bthwc = rearrange(images, "b t c h w -> b t h w c")

        # in case users a non-compatiable number for tiling
        def make_divisible(val, divisor):
            return max(divisor, round(val / divisor) * divisor)

        spatial_tile_size = make_divisible(spatial_tile_size, 32)
        spatial_overlap = make_divisible(spatial_overlap, 32)

        if spatial_overlap >= spatial_tile_size:
            spatial_overlap = max(0, spatial_tile_size - 8)

        args = {"tile_size": (spatial_tile_size, spatial_tile_size), "tile_overlap": (spatial_overlap, spatial_overlap),
                "temporal_size":temporal_tile_size}
        if enable_tiling:
            vae_model.img_dims = [o_h, o_w]
            vae_model.original_image_video = images_bcthw
            latent = vae.encode_tiled(
                images_bthwc,
                tile_x=spatial_tile_size,
                tile_y=spatial_tile_size,
                overlap=spatial_overlap,
                tile_t=temporal_tile_size,
            )
        else:
            vae_model.img_dims = [o_h, o_w]
            vae_model.original_image_video = images_bcthw
            latent = vae.encode(images_bthwc)

        clear_vae_memory(vae_model)
        #images = images.to(offload_device)
        #vae_model = vae_model.to(offload_device)

        vae_model.img_dims = [o_h, o_w]
        args["enable_tiling"] = enable_tiling
        vae_model.tiled_args = args
        vae_model.original_image_video = images_bcthw

        latent = latent.unsqueeze(2) if latent.ndim == 4 else latent
        latent = rearrange(latent, "b c ... -> b ... c")

        latent = (latent - shift) * scale

        return io.NodeOutput({"samples": latent})



...truncated 17791 bytes (full stderr in round1_codex_stderr.txt)...

n [(m.torch.float32,(1,8,16),(8,16),0,1.0),(m.torch.float32,(2,16,32),(16,32),0,1.0),(m.torch.float32,(1,8,24),(8,16),4,1.0),(m.torch.float32,(1,8,16),(8,16),0,0.5),(m.torch.float32,(1,8,16),(12,16),0,1.0)]]; print('ok')\"" in /home/johnj/dev_cuda_0/ComfyUI
 exited 1 in 0ms:
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<frozen importlib._bootstrap_external>", line 995, in exec_module
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "/home/johnj/dev_cuda_0/ComfyUI/tests-unit/comfy_test/test_seedvr_rope_delegation.py", line 39, in <module>
    import pytest
ModuleNotFoundError: No module named 'pytest'

exec
/bin/bash -lc '/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python -c "import torch; print(torch.cos(torch.zeros(1, dtype=torch.float16))); print(torch.cos(torch.zeros(1, dtype=torch.bfloat16)))"' in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 984ms:
tensor([1.], dtype=torch.float16)
tensor([1.], dtype=torch.bfloat16)

exec
/bin/bash -lc "/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python -c \"from comfy.cli_args import args; import torch; args.cpu=True; import comfy.ldm.seedvr.model as seedvr_model; from comfy.ldm.flux.math import apply_rope1; from comfy.ldm.seedvr.model import apply_rotary_emb; dtype=torch.float16; generator=torch.Generator(device='cpu').manual_seed(0); t=torch.randn(1,8,16,dtype=dtype,device='cpu',generator=generator); freqs=torch.randn(8,16,dtype=dtype,device='cpu',generator=generator); out=apply_rotary_emb(freqs,t); angles=freqs[...,::2]; cos=torch.cos(angles); sin=torch.sin(angles); freqs_mat=torch.stack([torch.stack([cos,sin],dim=-1),torch.stack([-sin,cos],dim=-1)],dim=-1); direct=apply_rope1(t,freqs_mat).type(t.dtype); torch.testing.assert_close(out,direct,rtol=0,atol=0); print('ok')\"" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1788ms:
ok

exec
/bin/bash -lc "/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python -c \"from comfy.cli_args import args; import torch; args.cpu=True; from comfy.ldm.flux.math import apply_rope1; from comfy.ldm.seedvr.model import apply_rotary_emb; dtype=torch.bfloat16; generator=torch.Generator(device='cpu').manual_seed(0); t=torch.randn(1,8,16,dtype=dtype,device='cpu',generator=generator); freqs=torch.randn(8,16,dtype=dtype,device='cpu',generator=generator); out=apply_rotary_emb(freqs,t); angles=freqs[...,::2]; cos=torch.cos(angles); sin=torch.sin(angles); freqs_mat=torch.stack([torch.stack([cos,sin],dim=-1),torch.stack([-sin,cos],dim=-1)],dim=-1); direct=apply_rope1(t,freqs_mat).type(t.dtype); torch.testing.assert_close(out,direct,rtol=0,atol=0); print('ok')\"" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1794ms:
ok

exec
/bin/bash -lc 'tail -n 80 /home/johnj/dev_cuda_0/ComfyUI/comfy_extras/nodes_seedvr.py' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
        vae_model.tiled_args = args
        vae_model.original_image_video = images_bcthw

        latent = latent.unsqueeze(2) if latent.ndim == 4 else latent
        latent = rearrange(latent, "b c ... -> b ... c")

        latent = (latent - shift) * scale

        return io.NodeOutput({"samples": latent})

class SeedVR2Conditioning(io.ComfyNode):
    @classmethod
    def define_schema(cls):
        return io.Schema(
            node_id="SeedVR2Conditioning",
            category="image/video",
            inputs=[
                io.Latent.Input("vae_conditioning"),
                io.Model.Input("model"),
                io.Float.Input("latent_noise_scale", default=0.0, step=0.001)
            ],
            outputs=[io.Conditioning.Output(display_name = "positive"),
                     io.Conditioning.Output(display_name = "negative"),
                     io.Latent.Output(display_name = "latent")],
        )

    @classmethod
    def execute(cls, vae_conditioning, model, latent_noise_scale) -> io.NodeOutput:

        vae_conditioning = vae_conditioning["samples"]
        device = vae_conditioning.device
        model = model.model.diffusion_model
        pos_cond = model.positive_conditioning
        neg_cond = model.negative_conditioning

        for module in model.modules():
            if hasattr(module, 'rope') and hasattr(module.rope, 'freqs'):
                module.rope.freqs.data = module.rope.freqs.data.to(torch.float32)

        noises = torch.randn_like(vae_conditioning, dtype=vae_conditioning.dtype).to(device)
        aug_noises =  torch.randn_like(vae_conditioning, dtype=vae_conditioning.dtype).to(device)
        aug_noises = noises * 0.1 + aug_noises * 0.05
        cond_noise_scale = latent_noise_scale
        t = (
            torch.tensor([1000.0])
            * cond_noise_scale
        ).to(device)
        shape = torch.tensor(vae_conditioning.shape[1:]).to(device)[None] # avoid batch dim
        t = timestep_transform(t, shape)
        cond = inter(vae_conditioning, aug_noises, t)
        condition = torch.stack([get_conditions(noise, c) for noise, c in zip(noises, cond)])
        condition = condition.movedim(-1, 1)
        noises = noises.movedim(-1, 1)

        pos_shape = pos_cond.shape[0]
        neg_shape = neg_cond.shape[0]
        diff = abs(pos_shape - neg_shape)
        if pos_shape > neg_shape:
            neg_cond = F.pad(neg_cond, (0, 0, 0, diff))
        else:
            pos_cond = F.pad(pos_cond, (0, 0, 0, diff))

        noises = rearrange(noises, "b c t h w -> b (c t) h w")
        condition = rearrange(condition, "b c t h w -> b (c t) h w")

        negative = [[neg_cond.unsqueeze(0), {"condition": condition}]]
        positive = [[pos_cond.unsqueeze(0), {"condition": condition}]]

        return io.NodeOutput(positive, negative, {"samples": noises})

class SeedVRExtension(ComfyExtension):
    @override
    async def get_node_list(self) -> list[type[io.ComfyNode]]:
        return [
            SeedVR2Conditioning,
            SeedVR2InputProcessing
        ]

async def comfy_entrypoint() -> SeedVRExtension:
    return SeedVRExtension()

exec
/bin/bash -lc 'cat /home/johnj/dev_cuda_0/ComfyUI/pytest.ini' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
[pytest]
markers = 
  inference: mark as inference test (deselect with '-m "not inference"')
  execution: mark as execution test (deselect with '-m "not execution"')
testpaths =
  tests
  tests-unit
addopts = -s
pythonpath = .

exec
/bin/bash -lc 'PYTHONPATH=/home/johnj/dev_cuda_0/ComfyUI/.venv/lib/python3.12/site-packages python -c "import torch; import pytest; print(torch.__version__, pytest.__version__)"' in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1020ms:
2.11.0+cu130 9.0.2

exec
/bin/bash -lc 'PYTHONPATH=/home/johnj/dev_cuda_0/ComfyUI/.venv/lib/python3.12/site-packages python -m pytest tests-unit/comfy_extras_test/test_seedvr_node_signature.py tests-unit/comfy_test/test_seedvr_rope_delegation.py tests-unit/comfy_test/test_seedvr_vae_tiled_args_no_mutate.py' in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 2639ms:
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
rootdir: /home/johnj/dev_cuda_0/ComfyUI
configfile: pytest.ini
plugins: anyio-4.13.0
collected 10 items

tests-unit/comfy_extras_test/test_seedvr_node_signature.py .
tests-unit/comfy_test/test_seedvr_rope_delegation.py .......s
tests-unit/comfy_test/test_seedvr_vae_tiled_args_no_mutate.py .

=============================== warnings summary ===============================
../../.local/lib/python3.12/site-packages/_pytest/cacheprovider.py:475
  /home/johnj/.local/lib/python3.12/site-packages/_pytest/cacheprovider.py:475: PytestCacheWarning: cache could not write path /home/johnj/dev_cuda_0/ComfyUI/.pytest_cache/v/cache/nodeids: [Errno 30] Read-only file system: '/home/johnj/dev_cuda_0/ComfyUI/.pytest_cache/v/cache/nodeids'
    config.cache.set("cache/nodeids", sorted(self.cached_nodeids))

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=================== 9 passed, 1 skipped, 1 warning in 1.76s ====================

codex
No merge-blocking or actionable P3 issues were found in the added regression tests. The targeted tests pass locally with the available torch environment.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates three SeedVR2-focused regression tests onto the issue_101_pi base to prevent future behavioral drift in key integration points (node schema/signature alignment, RoPE wrapper delegation, and VAE tiling arg handling).

Changes:

  • Added a regression test asserting SeedVR2InputProcessing.define_schema().inputs[*].id order matches execute() positional parameter order.
  • Added a parametrized regression test locking comfy.ldm.seedvr.model.apply_rotary_emb delegation to apply_rope1 and exact output equivalence vs a direct reproduction.
  • Added a regression test that reads comfy/ldm/seedvr/vae.py and asserts tiled_args is accessed via .get(...) (not .pop(...)) to avoid mutation across calls.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests-unit/comfy_test/test_seedvr_vae_tiled_args_no_mutate.py Source-level regression check preventing reintroduction of mutating tiled_args.pop(...) usage in SeedVR2 VAE decode path.
tests-unit/comfy_test/test_seedvr_rope_delegation.py Parametrized regression coverage for SeedVR2 RoPE wrapper delegation and exact output matching against a direct reproduction.
tests-unit/comfy_extras_test/test_seedvr_node_signature.py Ensures SeedVR2 node schema input-id order cannot drift from execute() positional parameter order.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +149 to +156
# Patch the apply_rope1 symbol as imported into seedvr.model with a wraps
# spy: a future change that inlines the math and stops calling the
# imported apply_rope1 makes spy.call_count == 0 and fails the test.
with patch.object(
seedvr_model, "apply_rope1", wraps=seedvr_model.apply_rope1
) as spy:
wrapper_out = apply_rotary_emb(
freqs, t, start_index=start_index, scale=scale
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

The delegation spy is attached to seedvr_model.apply_rope1, but the test’s stated invariant is delegation to comfy.ldm.flux.math.apply_rope1. If apply_rotary_emb is later refactored to call flux.math.apply_rope1 via a different reference (e.g., re-importing it or qualifying it differently), this test would fail even though the delegation invariant still holds. To align the test with the invariant, consider spying on comfy.ldm.flux.math.apply_rope1 (or patch both references) and update the assertion message accordingly.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

REJECT — patching at comfy.ldm.seedvr.model.apply_rope1 is the correct Python mocking idiom: you patch the name where it is looked up, not where it is defined. comfy.ldm.seedvr.model does from comfy.ldm.flux.math import apply_rope1 at import time, which binds apply_rope1 in seedvr_model's namespace. Patching comfy.ldm.flux.math.apply_rope1 after that import has no effect on the lookup inside seedvr_model.apply_rotary_emb — the spy would never fire and the test would silently always pass.

The stated invariant is apply_rotary_emb delegates to the apply_rope1 it imported from comfy.ldm.flux.math. The current spy on seedvr_model.apply_rope1 is exactly the assertion that catches the regression where a refactor either (a) inlines the math, (b) re-imports under a different name, or (c) stops binding apply_rope1 in seedvr_model at all. Patching the source-side reference would mask all three.

Reference: unittest.mock docs, Where to patch — patch where the object is looked up.
Closing this thread; test stays as written.

Comment on lines +1 to +8
from pathlib import Path


def test_seedvr_vae_tiled_args_uses_get_not_pop():
path = Path(__file__).resolve().parents[2] / "comfy" / "ldm" / "seedvr" / "vae.py"
src = path.read_text(encoding="utf-8")
assert ".tiled_args.pop(" not in src, f"VideoAutoencoderKLWrapper.decode contains tiled_args.pop(...) which mutates self.tiled_args across calls; expected tiled_args.get(...) per the upstream fix in Comfy-Org/ComfyUI#11294 commit 3b418da. Source path: {path}"
assert ".tiled_args.get(" in src, f"VideoAutoencoderKLWrapper.decode does not read tiled_args via .get(); expected exactly one self.tiled_args.get(\"enable_tiling\", False) call per Slice 1 baseline. Source path: {path}"
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

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

The string checks are narrower than the stated intent and can miss a regression: this only asserts absence of the substring ".tiled_args.pop(", so a mutating call like tiled_args = self.tiled_args; tiled_args.pop(...) would not be detected. Consider checking for "tiled_args.pop(" (no leading dot) or using a regex/AST-based check that catches both self.tiled_args.pop and tiled_args.pop patterns. Also, the failure messages mention VideoAutoencoderKLWrapper.decode and "expected exactly one" .get(...) call, but the assertions only check the whole file and only check presence, not count—either tighten the assertions or adjust the messages to match what’s being asserted.

Suggested change
from pathlib import Path
def test_seedvr_vae_tiled_args_uses_get_not_pop():
path = Path(__file__).resolve().parents[2] / "comfy" / "ldm" / "seedvr" / "vae.py"
src = path.read_text(encoding="utf-8")
assert ".tiled_args.pop(" not in src, f"VideoAutoencoderKLWrapper.decode contains tiled_args.pop(...) which mutates self.tiled_args across calls; expected tiled_args.get(...) per the upstream fix in Comfy-Org/ComfyUI#11294 commit 3b418da. Source path: {path}"
assert ".tiled_args.get(" in src, f"VideoAutoencoderKLWrapper.decode does not read tiled_args via .get(); expected exactly one self.tiled_args.get(\"enable_tiling\", False) call per Slice 1 baseline. Source path: {path}"
import re
from pathlib import Path
def test_seedvr_vae_tiled_args_uses_get_not_pop():
path = Path(__file__).resolve().parents[2] / "comfy" / "ldm" / "seedvr" / "vae.py"
src = path.read_text(encoding="utf-8")
assert not re.search(r"(?:self\.)?tiled_args\.pop\s*\(", src), (
f"VideoAutoencoderKLWrapper.decode contains tiled_args.pop(...) which mutates tiled_args across calls; "
f"expected reads via .get(...) only per the upstream fix in Comfy-Org/ComfyUI#11294 commit 3b418da. "
f"Source path: {path}"
)
enable_tiling_get_calls = re.findall(
r"self\.tiled_args\.get\s*\(\s*[\"']enable_tiling[\"']\s*,\s*False\s*\)",
src,
)
assert len(enable_tiling_get_calls) == 1, (
f"VideoAutoencoderKLWrapper.decode should contain exactly one "
f"self.tiled_args.get('enable_tiling', False) call per Slice 1 baseline; "
f"found {len(enable_tiling_get_calls)}. Source path: {path}"
)

Copilot uses AI. Check for mistakes.
…+ exact-count assertion on enable_tiling .get call (catches aliased mutation; locks count to 1)
@qa-agent-seveneves
Copy link
Copy Markdown

Codex Review -- Round 2 -- KICKOFF

Runner: scripts/run_codex_review.py
PR: #30
Reviewer model: codex review (configured codex CLI default)
Kickoff timestamp (UTC): 2026-05-01T16:37:28Z

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line -- one of: 'APPROVE' (no P0/P1/P2 found), 'REVIEW CONDUCTED AND NO ISSUES FOUND' (no P0/P1/P2 found, equivalent to APPROVE for verdict-tracking purposes; use this if you are reluctant to emit a bare APPROVE token), or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Awaiting codex output. The result will be posted as a separate comment on this PR when codex exits.

@qa-agent-seveneves
Copy link
Copy Markdown

Codex Review -- Round 2 -- RESULT

PR: #30
Reviewer model: codex review (configured codex CLI default)
Result timestamp (UTC): 2026-05-01T16:40:07Z
Codex exit code: 0
Duration (s): 157.5
Detected verdict: UNKNOWN

Prompt sent to codex review

Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line -- one of: 'APPROVE' (no P0/P1/P2 found), 'REVIEW CONDUCTED AND NO ISSUES FOUND' (no P0/P1/P2 found, equivalent to APPROVE for verdict-tracking purposes; use this if you are reluctant to emit a bare APPROVE token), or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.

Verbatim codex review output

This PR was reviewed using codex review with the prompt above and produced the following output:


No introduced P0/P1/P2/P3 issues were identified in the added regression tests. The changed files are additive and align with the stated SeedVR2 invariants.


Codex runner stderr (non-evidence)

OpenAI Codex v0.128.0 (research preview)
--------
workdir: /home/johnj/dev_cuda_0/mydevelopment
model: gpt-5.5
provider: openai
approval: never
sandbox: workspace-write [workdir, /tmp, /home/johnj/.codex/memories]
reasoning effort: high
reasoning summaries: none
session id: 019de467-0878-79b0-9814-490efaa73af7
--------
user
Review the latest state of https://github.com/pollockjj/ComfyUI/pull/30. Output findings using strict P0-P3 priority tags ([P0], [P1], [P2], [P3]) at the start of each finding title. Surface P0/P1/P2 -- these block merge in this repo. Surface P3 with that tag explicitly. Do not surface style nits, formatting, typos, or pre-existing bugs. End your output with a single line -- one of: 'APPROVE' (no P0/P1/P2 found), 'REVIEW CONDUCTED AND NO ISSUES FOUND' (no P0/P1/P2 found, equivalent to APPROVE for verdict-tracking purposes; use this if you are reluctant to emit a bare APPROVE token), or 'REQUEST_CHANGES' (one or more P0/P1/P2 found). Output nothing else after that line.
exec
/bin/bash -lc 'cat /home/johnj/.codex/skills/pr-review/SKILL.md' in /home/johnj/dev_cuda_0/mydevelopment
 succeeded in 0ms:
---
name: pr-review
description: "Iterative codex + Copilot PR review loop, slice-LLM driven. ACTIVATE when: (1) user says 'review the PR', 'kick off PR review', '/pr-review', 'disposition the PR'; (2) a PR has just been opened or had a new commit pushed to its head. Fires codex review (P0-P3 priority tags + verdict line), fires Copilot review via add-reviewer, READS the full Copilot inline-comment array (never trusts the review-body summary), decides every finding REQUIRED / NOT-REQUIRED / INVALID, applies REQUIRED fixes, posts INVALID rationale on the comment thread, then loops until codex P0-P2 list is empty AND no remaining findings about newly-changed code. Codex P0/P1/P2 findings are NEVER allowed to be classified INVALID."
---

# pr-review — codex + Copilot iterative review loop

**Role.** You own the PR. You decide every finding. You apply fixes. You reply to disagreements. The user does not adjudicate review findings — you do.

## Reviewers and what they post

| Reviewer | How fired | What it posts |
|---|---|---|
| **codex** | `python scripts/run_codex_review.py {OWNER/REPO} {PR_NUMBER} {ISSUE_NUMBER} {ROUND}` | Two PR thread comments per round (kickoff + result) as `qa-agent-seveneves[bot]`. Result body has findings tagged `[P0]`/`[P1]`/`[P2]`/`[P3]` and ends with one of: `APPROVE`, `REVIEW CONDUCTED AND NO ISSUES FOUND` (verdict-equivalent to APPROVE), or `REQUEST_CHANGES`. |
| **Copilot** | `gh pr edit {PR_NUMBER} --repo {OWNER/REPO} --add-reviewer copilot-pull-request-reviewer` | One review (state COMMENTED) at the current head SHA, plus zero-or-more **inline comments** attached to specific file lines. The review-body summary is unreliable about inline counts. |

## The ONLY way to read Copilot's inline comments

gh api /repos/{OWNER/REPO}/pulls/{PR_NUMBER}/comments
--jq '.[] | select(.user.login == "copilot-pull-request-reviewer") | {id, path, line, body, in_reply_to_id}'


`gh pr view ... --json latestReviews` returns the review summary only. **It is not authoritative for inline counts.** The summary may say "generated 2 comments" while `latestReviews[].body` carries no comment data and other endpoints show empty until the inlines settle. Do NOT report "no actionable inline findings" based on the review body. Always read `/pulls/{N}/comments` and walk the array.

## Decision categories — every finding gets one

| Verdict | Meaning | Action |
|---|---|---|
| **REQUIRED** | Real defect or risk inside the PR's intended scope. | Apply the fix. Commit on the PR head branch. Push. |
| **NOT-REQUIRED** | Real, but optional / cosmetic / or genuinely outside the PR's intended scope. | Do nothing. On Copilot cycle #0, no reply needed. On cycle #1+, only worry if it concerns code touched in the most recent fix cycle. |
| **INVALID** | Reviewer is wrong: misunderstands the code, asserts a non-existent behavior, suggests a change that would break a stated invariant, or proposes a fix that defeats the test it claims to strengthen. | Post an inline reply on the comment thread with the technical rationale. Do not apply the change. |

**Hard rule:** A codex `[P0]` / `[P1]` / `[P2]` finding may NOT be classified INVALID. P0/P1/P2 are by definition merge-blocking — if you believe codex is wrong, the burden is on you to either (a) reclassify yourself privately and accept the risk by marking NOT-REQUIRED with explicit written rationale on the PR thread, or (b) apply a fix. You do not get to silently dismiss a codex P0/P1/P2.

Codex `[P3]` findings may be REQUIRED, NOT-REQUIRED, or INVALID.

## Posting an INVALID reply

gh api -X POST /repos/{OWNER/REPO}/pulls/{PR_NUMBER}/comments
-F in_reply_to={comment_id}
-f body="<REJECT rationale: name the false premise; cite the language/library semantics; cite the stated invariant>"


The reply must explain WHY the reviewer is wrong, not just that you disagree. If the reviewer is misunderstanding Python/git/HTTP/etc., cite the spec or stdlib doc. If they're proposing a change that would defeat the test's purpose, name the invariant the test guards.

## Cycle structure

A **cycle** = one round of (codex + Copilot) at the current head SHA, followed by your dispositioning of every finding, followed by any pushes you make.

- **Cycle #0**: the first time both reviewers run on this PR. Every finding warrants classification. NOT-REQUIRED Copilot findings get no reply on cycle #0.
- **Cycle #1+** (re-reviews after you pushed fixes): each Copilot re-review is a **fresh scan** of the entire PR — it will surface new findings, including ones about pre-existing code it didn't mention before. **You are only required to disposition cycle #1+ findings if they concern code that was changed in the most recent fix cycle.** Pre-existing-code findings Copilot just noticed may be ignored. (You did not introduce them in this round; they were tacit-NOT-REQUIRED on cycle #0.)

## The loop

cycle = 0
while True:
1. Capture current head SHA.
2. Fire codex review (round = cycle + 1):
python scripts/run_codex_review.py {OWNER/REPO} {PR} {ISSUE} {cycle+1}
Wait for the result comment to land.
3. Fire Copilot:
gh pr edit {PR} --repo {OWNER/REPO} --add-reviewer copilot-pull-request-reviewer
Wait for a review by copilot-pull-request-reviewer at the captured head SHA.
4. Read codex result body. Parse [P0]/[P1]/[P2]/[P3] findings.
5. READ Copilot inline comments via /pulls/{PR}/comments. Walk every entry.
6. Disposition every finding REQUIRED / NOT-REQUIRED / INVALID per the rules above.
(Cycle #1+: skip dispositioning Copilot findings unrelated to most-recent-fix code.)
7. For each REQUIRED: apply fix, commit on PR head branch.
For each INVALID: post inline reply with rationale.
8. If you committed any fixes in step 7: push to PR head branch.
9. Termination check — break when ALL hold:
a. codex has no [P0]/[P1]/[P2] findings
b. codex has no actionable [P3] (any P3 not classified INVALID/NOT-REQUIRED)
c. on the LATEST Copilot review, no findings concern code changed in the most recent fix cycle
d. every cycle-#0 Copilot finding has been classified (REQUIRED-applied / NOT-REQUIRED-acknowledged / INVALID-replied)
Else: cycle += 1; loop.


## Forbidden

- Reporting "no actionable inline findings" without fetching `/pulls/{N}/comments` and walking every entry. The `gh pr view --json latestReviews` body is not authoritative.
- Marking any codex `[P0]` / `[P1]` / `[P2]` as INVALID.
- Escalating individual Copilot findings to the user for their decision. The user does not adjudicate findings — that is your job. Showing the user a finding and asking what to do is offload.
- Stopping the loop while any unclassified codex finding or any unclassified Copilot inline comment from the latest cycle remains.
- Stopping the loop after pushing new code without firing a fresh codex + Copilot cycle on the new head SHA. New code = new review.
- Posting routine cycle status (kickoff fired, Co

...truncated 31894 bytes (full stderr in round2_codex_stderr.txt)...

ply_rope1(t_middle, freqs_mat)
    return torch.cat((t_left,t_middle_out,t_right), dim=-1).type(t.dtype)

cases=[('cpu', torch.float32, (1,8,16), (8,16), 0,1.0),('cpu', torch.float32, (2,16,32),(16,32),0,1.0),('cpu', torch.float32,(1,8,24),(8,16),4,1.0),('cpu', torch.float32,(1,8,16),(8,16),0,0.5),('cpu',torch.float32,(1,8,16),(12,16),0,1.0)]
for device,dtype,t_shape,freqs_shape,start,scale in cases:
    g=torch.Generator(device=device).manual_seed(0)
    t=torch.randn(*t_shape,dtype=dtype,device=device,generator=g)
    freqs=torch.randn(*freqs_shape,dtype=dtype,device=device,generator=g)
    with patch.object(seedvr_model,'apply_rope1', wraps=seedvr_model.apply_rope1) as spy:
        out=apply_rotary_emb(freqs,t,start_index=start,scale=scale)
    direct_out=direct(freqs,t,start_index=start,scale=scale)
    torch.testing.assert_close(out,direct_out,rtol=0,atol=0)
    print(device,dtype,t_shape,freqs_shape,start,scale,spy.call_count)
PY" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1797ms:
cpu torch.float32 (1, 8, 16) (8, 16) 0 1.0 1
cpu torch.float32 (2, 16, 32) (16, 32) 0 1.0 1
cpu torch.float32 (1, 8, 24) (8, 16) 4 1.0 1
cpu torch.float32 (1, 8, 16) (8, 16) 0 0.5 1
cpu torch.float32 (1, 8, 16) (12, 16) 0 1.0 1

exec
/bin/bash -lc "/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python - <<'PY'
import torch
for dtype in (torch.float16, torch.bfloat16):
    try:
        torch.cos(torch.zeros(1,dtype=dtype))
        print(dtype, 'ok')
    except Exception as e:
        print(dtype, type(e).__name__, e)
PY" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 963ms:
torch.float16 ok
torch.bfloat16 ok

exec
/bin/bash -lc "/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python - <<'PY'
from unittest.mock import patch
import torch
from comfy.cli_args import args
if not torch.cuda.is_available():
    args.cpu = True
import comfy.ldm.seedvr.model as seedvr_model
from comfy.ldm.flux.math import apply_rope1
from comfy.ldm.seedvr.model import apply_rotary_emb

def direct(freqs,t,start_index=0,scale=1.0,seq_dim=-2):
    if freqs.ndim == 2 and t.ndim == 3:
        seq_len=t.shape[seq_dim]
        freqs=freqs[-seq_len:]
    rot_feats=freqs.shape[-1]
    end_index=start_index+rot_feats
    t_left=t[..., :start_index]
    t_middle=t[..., start_index:end_index]
    t_right=t[..., end_index:]
    angles=freqs.to(t_middle.device)[..., ::2]
    cos=torch.cos(angles)*scale
    sin=torch.sin(angles)*scale
    col0=torch.stack([cos,sin], dim=-1)
    col1=torch.stack([-sin,cos], dim=-1)
    freqs_mat=torch.stack([col0,col1], dim=-1)
    t_middle_out=apply_rope1(t_middle, freqs_mat)
    return torch.cat((t_left,t_middle_out,t_right), dim=-1).type(t.dtype)

for dtype in [torch.float16, torch.bfloat16]:
    g=torch.Generator(device='cpu').manual_seed(0)
    t=torch.randn(1,8,16,dtype=dtype,device='cpu',generator=g)
    freqs=torch.randn(8,16,dtype=dtype,device='cpu',generator=g)
    with patch.object(seedvr_model,'apply_rope1', wraps=seedvr_model.apply_rope1) as spy:
        out=apply_rotary_emb(freqs,t,start_index=0,scale=1.0)
    direct_out=direct(freqs,t,start_index=0,scale=1.0)
    torch.testing.assert_close(out,direct_out,rtol=0,atol=0)
    print(dtype, spy.call_count)
PY" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1818ms:
torch.float16 1
torch.bfloat16 1

mcp: codex_apps/github_get_pr_diff started
mcp: codex_apps/github_get_pr_diff (completed)
exec
/bin/bash -lc "/home/johnj/dev_cuda_0/ComfyUI/.venv/bin/python - <<'PY'
import torch
from comfy.cli_args import args
args.cpu=True
from comfy.ldm.flux.math import _apply_rope1
x=torch.randn(1,8,16)
freqs=torch.randn(8,16)
angles=freqs[..., ::2]
cos=torch.cos(angles)
sin=torch.sin(angles)
col0=torch.stack([cos,sin], dim=-1)
col1=torch.stack([-sin,cos], dim=-1)
freqs_mat=torch.stack([col0,col1], dim=-1)
print(x.reshape(*x.shape[:-1], -1, 1, 2).shape, freqs_mat.shape)
print(_apply_rope1(x, freqs_mat).shape)
PY" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 1898ms:
torch.Size([1, 8, 8, 1, 2]) torch.Size([8, 8, 2, 2])
torch.Size([1, 8, 16])

exec
/bin/bash -lc "sed -n '160,230p' comfy/model_management.py" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 0ms:
            return True
    return False

def is_ascend_npu():
    global npu_available
    if npu_available:
        return True
    return False

def is_mlu():
    global mlu_available
    if mlu_available:
        return True
    return False

def is_ixuca():
    global ixuca_available
    if ixuca_available:
        return True
    return False

def is_wsl():
    version = platform.uname().release
    if version.endswith("-Microsoft"):
        return True
    elif version.endswith("microsoft-standard-WSL2"):
        return True
    return False

def get_torch_device():
    global directml_enabled
    global cpu_state
    if directml_enabled:
        global directml_device
        return directml_device
    if cpu_state == CPUState.MPS:
        return torch.device("mps")
    if cpu_state == CPUState.CPU:
        return torch.device("cpu")
    else:
        if is_intel_xpu():
            return torch.device("xpu", torch.xpu.current_device())
        elif is_ascend_npu():
            return torch.device("npu", torch.npu.current_device())
        elif is_mlu():
            return torch.device("mlu", torch.mlu.current_device())
        else:
            return torch.device(torch.cuda.current_device())

def get_total_memory(dev=None, torch_total_too=False):
    global directml_enabled
    if dev is None:
        dev = get_torch_device()

    if hasattr(dev, 'type') and (dev.type == 'cpu' or dev.type == 'mps'):
        mem_total = psutil.virtual_memory().total
        mem_total_torch = mem_total
    else:
        if directml_enabled:
            mem_total = 1024 * 1024 * 1024 #TODO
            mem_total_torch = mem_total
        elif is_intel_xpu():
            stats = torch.xpu.memory_stats(dev)
            mem_reserved = stats['reserved_bytes.all.current']
            mem_total_xpu = torch.xpu.get_device_properties(dev).total_memory
            mem_total_torch = mem_reserved
            mem_total = mem_total_xpu
        elif is_ascend_npu():
            stats = torch.npu.memory_stats(dev)
            mem_reserved = stats['reserved_bytes.all.current']
            _, mem_total_npu = torch.npu.mem_get_info(dev)

exec
/bin/bash -lc 'grep -n "cpu_state" -n comfy/model_management.py | head -20' in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 0ms:
51:cpu_state = CPUState.GPU
128:        cpu_state = CPUState.MPS
153:    cpu_state = CPUState.CPU
156:    global cpu_state
158:    if cpu_state == CPUState.GPU:
191:    global cpu_state
195:    if cpu_state == CPUState.MPS:
197:    if cpu_state == CPUState.CPU:
317:    global cpu_state
318:    if cpu_state == CPUState.GPU:
324:    global cpu_state
325:    if cpu_state == CPUState.GPU:
459:if cpu_state != CPUState.GPU:
462:if cpu_state == CPUState.MPS:
1422:    global cpu_state
1423:    if cpu_state != CPUState.GPU:
1531:    global cpu_state
1532:    return cpu_state == CPUState.CPU
1535:    global cpu_state
1536:    return cpu_state == CPUState.MPS

exec
/bin/bash -lc "sed -n '120,160p' comfy/model_management.py" in /home/johnj/dev_cuda_0/ComfyUI
 succeeded in 0ms:
try:
    _ = torch.xpu.device_count()
    xpu_available = torch.xpu.is_available()
except:
    xpu_available = False

try:
    if torch.backends.mps.is_available():
        cpu_state = CPUState.MPS
        import torch.mps
except:
    pass

try:
    import torch_npu  # noqa: F401
    _ = torch.npu.device_count()
    npu_available = torch.npu.is_available()
except:
    npu_available = False

try:
    import torch_mlu  # noqa: F401
    _ = torch.mlu.device_count()
    mlu_available = torch.mlu.is_available()
except:
    mlu_available = False

try:
    ixuca_available = hasattr(torch, "corex")
except:
    ixuca_available = False

if args.cpu:
    cpu_state = CPUState.CPU

def is_intel_xpu():
    global cpu_state
    global xpu_available
    if cpu_state == CPUState.GPU:
        if xpu_available:
            return True

codex
No introduced P0/P1/P2/P3 issues were identified in the added regression tests. The changed files are additive and align with the stated SeedVR2 invariants.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pollockjj pollockjj merged commit 4ffef0a into issue_101_pi May 1, 2026
10 checks passed
@pollockjj pollockjj deleted the issue_101_pi_tests branch May 1, 2026 16:48
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.

2 participants