refactor and restructuring of sampler code to give custom nodes more access
diff --git a/comfy/sample.py b/comfy/sample.py
new file mode 100644
index 0000000000..09ab20cd20
--- /dev/null
+++ b/comfy/sample.py
@@ -0,0 +1,57 @@
+import torch
+import comfy.model_management
+
+
+def prepare_noise(latent_image, seed, skip=0):
+    """
+    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.manual_seed(seed)
+    for _ in range(skip):
+        noise = torch.randn([1] + list(latent_image.size())[1:], dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device="cpu")
+    noise = torch.randn(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device="cpu")
+    return noise
+
+def prepare_mask(noise_mask, noise):
+    """ensures noise mask is of proper dimensions"""
+    device = comfy.model_management.get_torch_device()
+    noise_mask = torch.nn.functional.interpolate(noise_mask[None,None,], size=(noise.shape[2], noise.shape[3]), mode="bilinear")
+    noise_mask = noise_mask.round()
+    noise_mask = torch.cat([noise_mask] * noise.shape[1], dim=1)
+    noise_mask = torch.cat([noise_mask] * noise.shape[0])
+    noise_mask = noise_mask.to(device)
+    return noise_mask
+
+def broadcast_cond(cond, noise):
+    """broadcasts conditioning to the noise batch size"""
+    device = comfy.model_management.get_torch_device()
+    copy = []
+    for p in cond:
+        t = p[0]
+        if t.shape[0] < noise.shape[0]:
+            t = torch.cat([t] * noise.shape[0])
+        t = t.to(device)
+        copy += [[t] + p[1:]]
+    return copy
+
+def get_models_from_cond(cond, model_type):
+    models = []
+    for c in cond:
+        if model_type in c[1]:
+            models += [c[1][model_type]]
+    return models
+
+def load_additional_models(positive, negative):
+    """loads additional models in positive and negative conditioning"""
+    control_nets = get_models_from_cond(positive, "control") + get_models_from_cond(negative, "control")
+    gligen = get_models_from_cond(positive, "gligen") + get_models_from_cond(negative, "gligen")
+    gligen = [x[1] for x in gligen]
+    models = control_nets + gligen
+    comfy.model_management.load_controlnet_gpu(models)
+    return models
+
+def cleanup_additional_models(models):
+    """cleanup additional models that were loaded"""
+    for m in models:
+        m.cleanup()
\ No newline at end of file
diff --git a/comfy/samplers.py b/comfy/samplers.py
index b860f25f16..46bdb82a05 100644
--- a/comfy/samplers.py
+++ b/comfy/samplers.py
@@ -400,6 +400,38 @@ def encode_adm(noise_augmentor, conds, batch_size, device):
 
     return conds
 
+def calculate_sigmas(model, steps, scheduler, sampler):
+    """
+    Returns a tensor containing the sigmas corresponding to the given model, number of steps, scheduler type and sample technique
+    """
+    if not (isinstance(model, CompVisVDenoiser) or isinstance(model, k_diffusion_external.CompVisDenoiser)):
+        model = CFGNoisePredictor(model)
+        if model.inner_model.parameterization == "v":
+            model = CompVisVDenoiser(model, quantize=True)
+        else:
+            model = k_diffusion_external.CompVisDenoiser(model, quantize=True)
+            
+    sigmas = None
+
+    discard_penultimate_sigma = False
+    if sampler in ['dpm_2', 'dpm_2_ancestral']:
+        steps += 1
+        discard_penultimate_sigma = True
+
+    if scheduler == "karras":
+        sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=float(model.sigma_min), sigma_max=float(model.sigma_max))
+    elif scheduler == "normal":
+        sigmas = model.get_sigmas(steps)
+    elif scheduler == "simple":
+        sigmas = simple_scheduler(model, steps)
+    elif scheduler == "ddim_uniform":
+        sigmas = ddim_scheduler(model, steps)
+    else:
+        print("error invalid scheduler", scheduler)
+
+    if discard_penultimate_sigma:
+        sigmas = torch.cat([sigmas[:-2], sigmas[-1:]])
+    return sigmas
 
 class KSampler:
     SCHEDULERS = ["karras", "normal", "simple", "ddim_uniform"]
@@ -429,41 +461,19 @@ def __init__(self, model, steps, device, sampler=None, scheduler=None, denoise=N
         self.denoise = denoise
         self.model_options = model_options
 
-    def _calculate_sigmas(self, steps):
-        sigmas = None
-
-        discard_penultimate_sigma = False
-        if self.sampler in ['dpm_2', 'dpm_2_ancestral']:
-            steps += 1
-            discard_penultimate_sigma = True
-
-        if self.scheduler == "karras":
-            sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=self.sigma_min, sigma_max=self.sigma_max, device=self.device)
-        elif self.scheduler == "normal":
-            sigmas = self.model_wrap.get_sigmas(steps).to(self.device)
-        elif self.scheduler == "simple":
-            sigmas = simple_scheduler(self.model_wrap, steps).to(self.device)
-        elif self.scheduler == "ddim_uniform":
-            sigmas = ddim_scheduler(self.model_wrap, steps).to(self.device)
-        else:
-            print("error invalid scheduler", self.scheduler)
-
-        if discard_penultimate_sigma:
-            sigmas = torch.cat([sigmas[:-2], sigmas[-1:]])
-        return sigmas
-
     def set_steps(self, steps, denoise=None):
         self.steps = steps
         if denoise is None or denoise > 0.9999:
-            self.sigmas = self._calculate_sigmas(steps)
+            self.sigmas = calculate_sigmas(self.model_wrap, steps, self.scheduler, self.sampler).to(self.device)
         else:
             new_steps = int(steps/denoise)
-            sigmas = self._calculate_sigmas(new_steps)
+            sigmas = calculate_sigmas(self.model_wrap, new_steps, self.scheduler, self.sampler).to(self.device)
             self.sigmas = sigmas[-(steps + 1):]
 
 
-    def sample(self, noise, positive, negative, cfg, latent_image=None, start_step=None, last_step=None, force_full_denoise=False, denoise_mask=None):
-        sigmas = self.sigmas
+    def sample(self, noise, positive, negative, cfg, latent_image=None, start_step=None, last_step=None, force_full_denoise=False, denoise_mask=None, sigmas=None):
+        if sigmas is None:
+            sigmas = self.sigmas
         sigma_min = self.sigma_min
 
         if last_step is not None and last_step < (len(sigmas) - 1):
diff --git a/nodes.py b/nodes.py
index 6ca73fa0c8..f9bedc97eb 100644
--- a/nodes.py
+++ b/nodes.py
@@ -16,6 +16,7 @@
 
 import comfy.diffusers_convert
 import comfy.samplers
+import comfy.sample
 import comfy.sd
 import comfy.utils
 
@@ -739,31 +740,19 @@ def set_mask(self, samples, mask):
         s["noise_mask"] = mask
         return (s,)
 
-
 def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0, disable_noise=False, start_step=None, last_step=None, force_full_denoise=False):
-    latent_image = latent["samples"]
-    noise_mask = None
     device = comfy.model_management.get_torch_device()
+    latent_image = latent["samples"]
 
     if disable_noise:
         noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
     else:
-        batch_index = 0
-        if "batch_index" in latent:
-            batch_index = latent["batch_index"]
-
-        generator = torch.manual_seed(seed)
-        for i in range(batch_index):
-            noise = torch.randn([1] + list(latent_image.size())[1:], dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device="cpu")
-        noise = torch.randn(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, generator=generator, device="cpu")
+        skip = latent["batch_index"] if "batch_index" in latent else 0
+        noise = comfy.sample.prepare_noise(latent_image, seed, skip)
 
+    noise_mask = None
     if "noise_mask" in latent:
-        noise_mask = latent['noise_mask']
-        noise_mask = torch.nn.functional.interpolate(noise_mask[None,None,], size=(noise.shape[2], noise.shape[3]), mode="bilinear")
-        noise_mask = noise_mask.round()
-        noise_mask = torch.cat([noise_mask] * noise.shape[1], dim=1)
-        noise_mask = torch.cat([noise_mask] * noise.shape[0])
-        noise_mask = noise_mask.to(device)
+        noise_mask = comfy.sample.prepare_mask(latent["noise_mask"], noise)
 
     real_model = None
     comfy.model_management.load_model_gpu(model)
@@ -772,34 +761,10 @@ def common_ksampler(model, seed, steps, cfg, sampler_name, scheduler, positive,
     noise = noise.to(device)
     latent_image = latent_image.to(device)
 
-    positive_copy = []
-    negative_copy = []
-
-    control_nets = []
-    def get_models(cond):
-        models = []
-        for c in cond:
-            if 'control' in c[1]:
-                models += [c[1]['control']]
-            if 'gligen' in c[1]:
-                models += [c[1]['gligen'][1]]
-        return models
-
-    for p in positive:
-        t = p[0]
-        if t.shape[0] < noise.shape[0]:
-            t = torch.cat([t] * noise.shape[0])
-        t = t.to(device)
-        positive_copy += [[t] + p[1:]]
-    for n in negative:
-        t = n[0]
-        if t.shape[0] < noise.shape[0]:
-            t = torch.cat([t] * noise.shape[0])
-        t = t.to(device)
-        negative_copy += [[t] + n[1:]]
-
-    models = get_models(positive) + get_models(negative)
-    comfy.model_management.load_controlnet_gpu(models)
+    positive_copy = comfy.sample.broadcast_cond(positive, noise)
+    negative_copy = comfy.sample.broadcast_cond(negative, noise)
+
+    models = comfy.sample.load_additional_models(positive, negative)
 
     if sampler_name in comfy.samplers.KSampler.SAMPLERS:
         sampler = comfy.samplers.KSampler(real_model, steps=steps, device=device, sampler=sampler_name, scheduler=scheduler, denoise=denoise, model_options=model.model_options)
@@ -809,8 +774,8 @@ def get_models(cond):
 
     samples = sampler.sample(noise, positive_copy, negative_copy, cfg=cfg, latent_image=latent_image, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, denoise_mask=noise_mask)
     samples = samples.cpu()
-    for m in models:
-        m.cleanup()
+    
+    comfy.sample.cleanup_additional_models(models)
 
     out = latent.copy()
     out["samples"] = samples

This PR makes the following changes:

- refactors the code in [common_ksampler](https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py#L743) to a set of functions that are easier to reuse.
- makes it possible for other code to calculate sigmas without creating a Ksampler to call [_calculate_sigmas](https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/samplers.py#L432)
- allow calls to [Ksampler.sample](https://github.com/comfyanonymous/ComfyUI/blob/master/comfy/samplers.py#L465) to supply a set of sigmas to be used instead of the pre-calculated ones.

I was not entirely sure where to put the functions that resulted from the refactoring of the common_ksampler function, these currently reside in comfy/sample.py

These changes make it possible to for instance create the custom nodes in [this](https://github.com/BlenderNeko/ComfyUI_Noise) repo