Same weights. Same input images. Same seeds. Two inference paths that each reproduced their own results.
Yet 11.3% of the output's 8-bit channel values differed between them.
That was one result from verifying my unofficial PixRestore integration for ComfyUI. The difference was small—at most 5/255—but enough to make “matches the official output” an incomplete claim.
The useful part of this integration was defining exactly what “matches” meant: which execution mode, which process settings, and which representation of an image.
Here are the problems documented in the implementation and verification runs, and the checks that made the comparison meaningful.
First, define which official run you mean
PixRestore is an image restoration research project. Its released PixRestore-S model restores 512×512 images with one denoiser call, using DINOv2 features as conditioning. My integration exposes it through ComfyUI's usual image inputs and outputs.
The reference environment already required a decision. Upstream pinned PyTorch 2.4.0, but that build did not have the kernels needed for the RTX 5090 used in these tests. The recorded reference therefore used PyTorch 2.14.1+cu130, with the other documented upstream dependency pins.
Then came torch.compile.
Several upstream functions had compile decorators. On the tested Windows environment, the unmodified inference run stopped with TritonMissing at the first compiled call. That left two paths to compare:
- Run the official script in eager mode, with
TORCHDYNAMO_DISABLE=1. - Add
triton-windowsto a copy of the reference environment and run the compiled path.
Both ran. They did not produce exactly the same output.
Across the 12 demo inputs, the DINOv2 input, all six recorded DINOv2 feature layers, and the initial noise matched. The diffusion transformer's output differed. The verification report attributes the difference to the Inductor-compiled kernels under bf16 autocast.
The final 8-bit comparison was:
- 11.3% of channel values differed. This is not the percentage of pixels that looked visibly different.
- Maximum absolute difference: 5/255.
- Mean absolute difference: 0.11/255.
Two compiled runs matched each other. Three eager runs matched each other.
So a repeatability test could pass for both implementations while an equivalence test between them failed.
The ComfyUI node uses eager execution, removing the compile wrappers from the pinned PixRestore modules. The claim in the README is deliberately specific: it reproduces the official eager path on the tested setup. Compiled execution inside ComfyUI is not part of that claim.
A node inherits the application's numerical settings
A standalone inference script starts in its own process. A custom node runs inside an application that has already configured PyTorch.
In this setup, ComfyUI enabled reduced-precision math reductions for scaled dot-product attention. The official reference process used a different setting. Matching the checkpoint and seed would leave that difference in place.
The integration captures the caller's backend settings, applies the reference settings for its call, and restores the caller's settings afterward. The implementation covers TF32, attention backends, and other precision-related flags.
This excerpt from runtime.py shows the scope:
@contextlib.contextmanager
def official_flags():
with _LOCK:
old = _flag_state()
_apply_flags(OFFICIAL_FLAGS)
try:
yield old
finally:
_apply_flags(old)
The tests check both sides: the reference settings are active inside the context, and the caller's settings return afterward.
These are process-wide flags. The local lock serializes this pack's use; it does not coordinate every other extension in the process. This is a scoped compatibility measure, not general isolation for arbitrary concurrent PyTorch work.
Random-number state needs similar care. The node seeds the relevant generator inside torch.random.fork_rng, so restoration does not leave ComfyUI's RNG state changed.
The execution environment is part of the comparison, even when it never appears as an argument to the model.
The last conversion is still part of inference
ComfyUI passes images as floating-point tensors. The reference script reads and writes 8-bit images.
That creates another boundary to verify: a float tensor that looks correct must still save to the intended integer values.
The integration follows the reference normalization and output conversion, then returns the resulting 8-bit values to ComfyUI as floats. A small test checks the final round trip for every possible 8-bit value:
u8 = torch.arange(256, dtype=torch.uint8)
image = u8.float() / 255.0
saved = np.clip(255.0 * image.numpy(), 0, 255).astype(np.uint8)
assert (saved == u8.numpy()).all()
This is a shortened version of the repository's image conversion test. It checks the specific conversion path rather than assuming that a float-to-integer round trip is harmless.
For model comparisons, the report also records hashes of the normalized input, noise, DINOv2 features, model output, final float image, and 8-bit result. That makes it possible to locate where a mismatch starts.
Make sure the parity test can fail
The released-weight GPU checks matched the official eager path across 12 demo inputs and four center-crop cases, including execution through ComfyUI's HTTP queue.
But exact matches alone leave a question: is the test actually sensitive to a wrong computation?
The CPU parity tests include two deliberate perturbations:
- Change the seed.
- Multiply the extracted DINOv2 features by
1.001.
Both must break equality with the reference. The test code makes that expectation explicit.
The real ComfyUI checks also ran an A → B → A sequence with caching disabled and confirmed that the calls executed. An unload-and-reload check verified that the same result survived a fresh model load.
There is an important boundary around the CPU tests: CI uses random weights with the released architecture and fp32 execution. It checks the implementation path, not restoration quality or released-weight GPU parity. The user-facing node still requires CUDA.
I would reuse these checks for another integration: record intermediate values, deliberately perturb an input to the computation, and confirm that a repeatability test is actually rerunning the model.
Exact reproduction does not guarantee a good restoration
Once the integration matched its reference, image quality remained a separate question.
Columns: clean source, degraded input, restoration. Rows: Gaussian noise, blur, JPEG compression. These are 200×200 crops from a synthetic source image, not real-photo restoration results.
The demo contains four synthetic source images with three synthetic degradations each. All 12 cases improved in PSNR against their source images. Noise removal also removed fine texture, and sharpened edges could contain newly synthesized detail.
That is consistent with a generative restoration model: it produces a plausible image, which can differ from the original.
The wrapper's current scope stays narrow: PixRestore-S, one step, CFG 1.0, and 512×512 input. Other sizes are rejected unless the user explicitly selects the official center-crop path. There is no tiling, and the quality of the crop path on real photographs was not evaluated.
Those limits keep the tested behavior identifiable. They also make the next useful experiment clear: evaluate suitable real images before making broader quality claims.
Where the implementation stands
The repository contains Loader, Restore, and Unload nodes, example workflows, and the setup instructions. The measurements and comparisons above come from the October 5 verification runs on Windows 11, an RTX 5090, and ComfyUI v0.38.0; they are not new runs performed for this article.
The lesson I would carry into the next port is to define the reference before chasing exact equality. Pin the code and weights, name the execution mode, compare intermediate tensors, and test the image conversion boundary. Then test output quality as its own problem.
If you have moved a model from a standalone script into a larger application, where did your first numerical mismatch appear: preprocessing, backend settings, or output conversion?
PixRestore research and pretrained models are by the upstream authors. This ComfyUI integration is unofficial. This article was drafted with AI assistance using the repository's source code and recorded verification results.

Top comments (0)