File size: 5,114 Bytes
9ab8b5f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# https://github.com/shiimizu/ComfyUI_smZNodes
import numpy as np

philox_m = [0xD2511F53, 0xCD9E8D57]
philox_w = [0x9E3779B9, 0xBB67AE85]

two_pow32_inv = np.array([2.3283064e-10], dtype=np.float32)
two_pow32_inv_2pi = np.array([2.3283064e-10 * 6.2831855], dtype=np.float32)


def uint32(x):
    """Converts (N,) np.uint64 array into (2, N) np.unit32 array."""
    return x.view(np.uint32).reshape(-1, 2).transpose(1, 0)


def philox4_round(counter, key):
    """A single round of the Philox 4x32 random number generator."""

    v1 = uint32(counter[0].astype(np.uint64) * philox_m[0])
    v2 = uint32(counter[2].astype(np.uint64) * philox_m[1])

    counter[0] = v2[1] ^ counter[1] ^ key[0]
    counter[1] = v2[0]
    counter[2] = v1[1] ^ counter[3] ^ key[1]
    counter[3] = v1[0]


def philox4_32(counter, key, rounds=10):
    """Generates 32-bit random numbers using the Philox 4x32 random number generator.



    Parameters:

        counter (numpy.ndarray): A 4xN array of 32-bit integers representing the counter values (offset into generation).

        key (numpy.ndarray): A 2xN array of 32-bit integers representing the key values (seed).

        rounds (int): The number of rounds to perform.



    Returns:

        numpy.ndarray: A 4xN array of 32-bit integers containing the generated random numbers.

    """

    for _ in range(rounds - 1):
        philox4_round(counter, key)

        key[0] = key[0] + philox_w[0]
        key[1] = key[1] + philox_w[1]

    philox4_round(counter, key)
    return counter


def box_muller(x, y):
    """Returns just the first out of two numbers generated by Box–Muller transform algorithm."""
    u = x * two_pow32_inv + two_pow32_inv / 2
    v = y * two_pow32_inv_2pi + two_pow32_inv_2pi / 2

    s = np.sqrt(-2.0 * np.log(u))

    r1 = s * np.sin(v)
    return r1.astype(np.float32)


class Generator:
    """RNG that produces same outputs as torch.randn(..., device='cuda') on CPU"""

    def __init__(self, seed):
        self.seed = seed
        self.offset = 0

    def randn(self, shape):
        """Generate a sequence of n standard normal random variables using the Philox 4x32 random number generator and the Box-Muller transform."""

        n = 1
        for x in shape:
            n *= x

        counter = np.zeros((4, n), dtype=np.uint32)
        counter[0] = self.offset
        counter[2] = np.arange(n, dtype=np.uint32)  # up to 2^32 numbers can be generated - if you want more you'd need to spill into counter[3]
        self.offset += 1

        key = np.empty(n, dtype=np.uint64)
        key.fill(self.seed)
        key = uint32(key)

        g = philox4_32(counter, key)

        return box_muller(g[0], g[1]).reshape(shape)  # discard g[2] and g[3]

#=======================================================================================================================
# Monkey Patch "prepare_noise" function
# https://github.com/shiimizu/ComfyUI_smZNodes
import torch
import functools
from comfy.sample import np
import comfy.model_management

def rng_rand_source(rand_source='cpu'):
    device = comfy.model_management.text_encoder_device()

    def prepare_noise(latent_image, seed, noise_inds=None, device='cpu'):
        """

        creates random noise given a latent image and a seed.

        optional arg skip can be used to skip and discard x number of noise generations for a given seed

        """
        generator = torch.Generator(device).manual_seed(seed)
        if rand_source == 'nv':
            rng = Generator(seed)
        if noise_inds is None:
            shape = latent_image.size()
            if rand_source == 'nv':
                return torch.asarray(rng.randn(shape), device=device)
            else:
                return torch.randn(shape, dtype=latent_image.dtype, layout=latent_image.layout, generator=generator,
                                   device=device)

        unique_inds, inverse = np.unique(noise_inds, return_inverse=True)
        noises = []
        for i in range(unique_inds[-1] + 1):
            shape = [1] + list(latent_image.size())[1:]
            if rand_source == 'nv':
                noise = torch.asarray(rng.randn(shape), device=device)
            else:
                noise = torch.randn(shape, dtype=latent_image.dtype, layout=latent_image.layout, generator=generator,
                                    device=device)
            if i in unique_inds:
                noises.append(noise)
        noises = [noises[i] for i in inverse]
        noises = torch.cat(noises, axis=0)
        return noises

    if rand_source == 'cpu':
        if hasattr(comfy.sample, 'prepare_noise_orig'):
            comfy.sample.prepare_noise = comfy.sample.prepare_noise_orig
    else:
        if not hasattr(comfy.sample, 'prepare_noise_orig'):
            comfy.sample.prepare_noise_orig = comfy.sample.prepare_noise
        _prepare_noise = functools.partial(prepare_noise, device=device)
        comfy.sample.prepare_noise = _prepare_noise