enable batched taesd

This commit is contained in:
Vladimir Mandic
2024-01-03 10:38:30 -05:00
parent a8c779a54d
commit 17b30a320e
13 changed files with 77 additions and 37 deletions
+8 -1
View File
@@ -1,6 +1,6 @@
# Change Log for SD.Next
## Update for 2023-01-02
## Update for 2023-01-03
Following-up on a major release, some more functionality in new Control module
And it also includes fixes for all reported issues so far
@@ -18,8 +18,12 @@ And it also includes fixes for all reported issues so far
- auto-refresh available models on tab activate
- reduce usage of temp files
- add context menu to action buttons
- resize by now applies to input image or frame individually
allows for processing where input images are of different sizes
- fix input image size
- fix video color mode
- fix correct image mode
- fix batch/folder/video modes
- **Improvements**
- allow deployment without git clone
for example, you can now deploy a zip of the sdnext folder
@@ -29,6 +33,8 @@ And it also includes fixes for all reported issues so far
- cli: sdapi.py allow manual api invoke
example: `python cli/sdapi.py /sdapi/v1/sd-models`
- memory: add ram usage monitoring in addition to gpu memory usage monitoring
- vae: enable taesd batch decode
enable/disable with settings -> diffusers > vae slicing
- updated core requirements
- **Compile**
- new option: **fused projections**
@@ -55,6 +61,7 @@ And it also includes fixes for all reported issues so far
- sampler: guard against invalid sampler index
- config: reset default cfg scale to 6.0
- processing: correct display metadata
- live preview: fix when using `bfloat16`
- upscale: fix ldsr
## Update for 2023-12-29
+8 -3
View File
@@ -1,7 +1,12 @@
function controlInputMode(inputMode, ...args) {
if (!gradioApp().getElementById('control_input_select').classList.contains('hidden')) inputMode = 'Select';
else if (!gradioApp().getElementById('control_input_resize').classList.contains('hidden')) inputMode = 'Outpaint';
else if (!gradioApp().getElementById('control_input_inpaint').classList.contains('hidden')) inputMode = 'Inpaint';
const tab = gradioApp().querySelector('#control-tab-input button.selected');
if (!tab) return ['Select', ...args];
inputMode = tab.innerText;
if (inputMode === 'Image') {
if (!gradioApp().getElementById('control_input_select').classList.contains('hidden')) inputMode = 'Select';
else if (!gradioApp().getElementById('control_input_resize').classList.contains('hidden')) inputMode = 'Outpaint';
else if (!gradioApp().getElementById('control_input_inpaint').classList.contains('hidden')) inputMode = 'Inpaint';
}
return [inputMode, ...args];
}
+2 -2
View File
@@ -85,7 +85,7 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres
let livePreview;
let img;
const init = () => {
const initLivePreview = () => {
img = new Image();
if (parentGallery) {
livePreview = document.createElement('div');
@@ -123,7 +123,7 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres
return;
}
setProgress(res);
if (res.live_preview && !livePreview) init();
if (res.live_preview && !livePreview) initLivePreview();
if (res.live_preview && galleryEl) img.src = res.live_preview;
if (onProgress) onProgress(res);
setTimeout(() => start(id_task, id_live_preview), opts.live_preview_refresh_period || 500);
+7 -3
View File
@@ -130,9 +130,6 @@ def control_run(units: List[unit.Unit], inputs, inits, mask, unit_type: str, is_
if resize_mode != 0 or inputs is None or inputs == [None]:
p.width = width # pylint: disable=attribute-defined-outside-init
p.height = height # pylint: disable=attribute-defined-outside-init
if selected_scale_tab == 1:
width = int(width * scale_by)
height = int(height * scale_by)
else:
del p.width
del p.height
@@ -293,6 +290,8 @@ def control_run(units: List[unit.Unit], inputs, inits, mask, unit_type: str, is_
w, h = int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
codec = util.decode_fourcc(video.get(cv2.CAP_PROP_FOURCC))
status, frame = video.read()
if status:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
shared.log.debug(f'Control: input video: path={inputs} frames={frames} fps={fps} size={w}x{h} codec={codec}')
except Exception as e:
msg = f'Control: video open failed: path={inputs} {e}'
@@ -344,6 +343,9 @@ def control_run(units: List[unit.Unit], inputs, inits, mask, unit_type: str, is_
# resize
if p.resize_mode != 0 and input_image is not None:
p.extra_generation_params["Control resize"] = f'{resize_time}: {resize_name}'
if selected_scale_tab == 1:
width = int(input_image.width * scale_by)
height = int(input_image.height * scale_by)
if p.resize_mode != 0 and input_image is not None and resize_time == 'Before':
debug(f'Control resize: image={input_image} width={width} height={height} mode={p.resize_mode} name={resize_name} sequence={resize_time}')
input_image = images.resize_image(p.resize_mode, input_image, width, height, resize_name)
@@ -502,6 +504,8 @@ def control_run(units: List[unit.Unit], inputs, inits, mask, unit_type: str, is_
if video is not None and frame is not None:
status, frame = video.read()
if status:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
debug(f'Control: video frame={index} frames={frames} status={status} skip={index % (video_skip_frames + 1)} progress={index/frames:.2f}')
else:
status = False
+7 -7
View File
@@ -181,7 +181,7 @@ def test_fp16():
if shared.cmd_opts.experimental:
return True
try:
x = torch.tensor([[1.5,.0,.0,.0]]).to(device).half()
x = torch.tensor([[1.5,.0,.0,.0]]).to(device=device, dtype=torch.float16)
layerNorm = torch.nn.LayerNorm(4, eps=0.00001, elementwise_affine=True, dtype=torch.float16, device=device)
_y = layerNorm(x)
return True
@@ -228,20 +228,20 @@ def set_cuda_params():
dtype = torch.float32
dtype_vae = torch.float32
dtype_unet = torch.float32
if shared.opts.cuda_dtype == 'BF16' or dtype == torch.bfloat16:
fp16_ok = None
bf16_ok = None
elif shared.opts.cuda_dtype == 'BF16' or dtype == torch.bfloat16:
fp16_ok = test_fp16()
bf16_ok = test_bf16()
dtype = torch.bfloat16 if bf16_ok else torch.float16
dtype_vae = torch.bfloat16 if bf16_ok else torch.float16
dtype_unet = torch.bfloat16 if bf16_ok else torch.float16
else:
bf16_ok = False
if shared.opts.cuda_dtype == 'FP16' or dtype == torch.float16:
elif shared.opts.cuda_dtype == 'FP16' or dtype == torch.float16:
fp16_ok = test_fp16()
bf16_ok = None
dtype = torch.float16 if fp16_ok else torch.float32
dtype_vae = torch.float16 if fp16_ok else torch.float32
dtype_unet = torch.float16 if fp16_ok else torch.float32
else:
fp16_ok = False
if shared.opts.no_half:
log.info('Torch override dtype: no-half set')
dtype = torch.float32
+7 -4
View File
@@ -80,12 +80,15 @@ def full_vae_encode(image, model):
def taesd_vae_decode(latents):
debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape}')
debug(f'VAE decode: name=TAESD images={len(latents)} latents={latents.shape} slicing={shared.opts.diffusers_vae_slicing}')
if len(latents) == 0:
return []
decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device)
for i in range(latents.shape[0]):
decoded[i] = sd_vae_taesd.decode(latents[i])
if shared.opts.diffusers_vae_slicing:
decoded = torch.zeros((len(latents), 3, latents.shape[2] * 8, latents.shape[3] * 8), dtype=devices.dtype_vae, device=devices.device)
for i in range(latents.shape[0]):
decoded[i] = sd_vae_taesd.decode(latents[i])
else:
decoded = sd_vae_taesd.decode(latents)
return decoded
+7 -3
View File
@@ -31,13 +31,15 @@ def setup_img2img_steps(p, steps=None):
def single_sample_to_image(sample, approximation=None):
# sample should be [4,64,64]
if approximation is None:
approximation = approximation_indexes.get(shared.opts.show_progress_type, None)
if approximation is None:
warn_once('Unknown decode type, please reset preview method')
approximation = 0
# normal sample is [4,64,64]
if sample.dtype == torch.bfloat16:
sample = sample.to(torch.float16)
if len(sample.shape) > 4: # likely unknown video latent (e.g. svd)
return Image.new(mode="RGB", size=(512, 512))
if len(sample.shape) == 4 and sample.shape[0]: # likely animatediff latent
@@ -47,7 +49,7 @@ def single_sample_to_image(sample, approximation=None):
elif approximation == 1: # Approximate
x_sample = sd_vae_approx.nn_approximation(sample) * 0.5 + 0.5
if shared.sd_model_type == "sdxl":
x_sample = x_sample[[2,1,0],:,:] # BGR to RGB
x_sample = x_sample[[2,1,0], :, :] # BGR to RGB
elif approximation == 2: # TAESD
x_sample = sd_vae_taesd.decode(sample)
x_sample = (1.0 + x_sample) / 2.0 # preview requires smaller range
@@ -58,10 +60,12 @@ def single_sample_to_image(sample, approximation=None):
return Image.new(mode="RGB", size=(512, 512))
try:
if x_sample.dtype == torch.bfloat16:
x_sample.to(torch.float16)
transform = T.ToPILImage()
image = transform(x_sample)
except Exception as e:
warn_once(f'Transform tensor to image: {e}')
warn_once(f'Live preview: {e}')
image = Image.new(mode="RGB", size=(512, 512))
return image
+5 -3
View File
@@ -42,10 +42,10 @@ def nn_approximation(sample): # Approximate NN
approx_weights = torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)
sd_vae_approx_model.load_state_dict(approx_weights)
sd_vae_approx_model.eval()
sd_vae_approx_model.to(devices.device, devices.dtype)
sd_vae_approx_model.to(devices.device, sample.dtype)
shared.log.debug(f'Load VAE decode approximate: model="{model_path}"')
try:
in_sample = sample.to(devices.device, devices.dtype).unsqueeze(0)
in_sample = sample.to(devices.device).unsqueeze(0)
x_sample = sd_vae_approx_model(in_sample)
x_sample = x_sample[0]
return x_sample
@@ -71,7 +71,9 @@ def cheap_approximation(sample): # Approximate simple
]).reshape(3, 4, 1, 1)
simple_bias = None
try:
x_sample = nn.functional.conv2d(sample, simple_weights.to(sample.device, sample.dtype), simple_bias.to(sample.device, sample.dtype) if simple_bias is not None else None) # pylint: disable=not-callable
weights = simple_weights.to(sample.device, sample.dtype)
bias = simple_bias.to(sample.device, sample.dtype) if simple_bias is not None else None
x_sample = nn.functional.conv2d(sample, weights, bias) # pylint: disable=not-callable
return x_sample
except Exception as e:
shared.log.error(f'Decode simple: {e}')
+1 -1
View File
@@ -367,7 +367,7 @@ options_templates.update(options_section(('advanced', "Inference Settings"), {
"inference_other_sep": OptionInfo("<h2>Other</h2>", "", gr.HTML),
"batch_frame_mode": OptionInfo(False, "Process multiple images in batch in parallel"),
"inference_mode": OptionInfo("no-grad", "Torch inference mode", gr.Radio, {"choices": ["no-grad", "inference-mode", "none"]}),
"sd_vae_sliced_encode": OptionInfo(False, "VAE Slicing (original)"),
"sd_vae_sliced_encode": OptionInfo(False, "VAE sliced encode"),
}))
options_templates.update(options_section(('diffusers', "Diffusers Settings"), {
+13 -4
View File
@@ -58,10 +58,19 @@ def decode(latents):
taesd_models[f'{model_class}-decoder'] = TAESD(decoder_path=model_path, encoder_path=None)
vae = taesd_models[f'{model_class}-decoder']
vae.to(devices.device, devices.dtype_vae)
enc = latents.unsqueeze(0).to(devices.device, devices.dtype_vae)
image = vae.decoder(enc).clamp(0, 1).detach()
image = 2.0 * image - 1.0 # typical normalized range except for preview which runs denormalization
return image[0]
latents.to(devices.device, devices.dtype_vae)
if len(latents.shape) == 3:
latents = latents.unsqueeze(0)
image = vae.decoder(latents).clamp(0, 1).detach()
image = 2.0 * image - 1.0 # typical normalized range except for preview which runs denormalization
return image[0]
elif len(latents.shape) == 4:
image = vae.decoder(latents).clamp(0, 1).detach()
image = 2.0 * image - 1.0 # typical normalized range except for preview which runs denormalization
return image
else:
shared.log.error(f'TAESD decode unsupported latent type: {latents.shape}')
return latents
def encode(image):
+10 -4
View File
@@ -155,7 +155,7 @@ def expand_mask(image: Image.Image, blur: int = 0, erode: int = 3, dilate: int =
return image_mask
def select_input(input_mode, input_image, selected_init, init_type, input_resize, input_inpaint, mask_blur, mask_overlap):
def select_input(input_mode, input_image, selected_init, init_type, input_resize, input_inpaint, input_video, input_batch, input_folder, mask_blur, mask_overlap):
global busy, input_source, input_init, input_mask # pylint: disable=global-statement
busy = True
if input_mode == 'Select':
@@ -164,6 +164,12 @@ def select_input(input_mode, input_image, selected_init, init_type, input_resize
selected_input = input_resize
elif input_mode == 'Inpaint':
selected_input = input_inpaint
elif input_mode == 'Video':
selected_input = input_video
elif input_mode == 'Batch':
selected_input = input_batch
elif input_mode == 'Folder':
selected_input = input_folder
else:
selected_input = None
if selected_input is None:
@@ -318,7 +324,7 @@ def create_ui(_blocks: gr.Blocks=None):
with gr.Row():
input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type')
with gr.Row():
denoising_strength = gr.Slider(minimum=0.01, maximum=0.99, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength")
denoising_strength = gr.Slider(minimum=0.01, maximum=1.0, step=0.01, label='Denoising strength', value=0.50, elem_id="control_denoising_strength")
with gr.Row():
mask_blur = gr.Slider(minimum=0, maximum=100, step=1, label='Blur', value=8, elem_id="control_mask_blur")
mask_overlap = gr.Slider(minimum=0, maximum=100, step=1, label='Overlap', value=8, elem_id="control_mask_overlap")
@@ -338,7 +344,7 @@ def create_ui(_blocks: gr.Blocks=None):
video_skip_frames = gr.Slider(minimum=0, maximum=100, step=1, label='Skip input frames', value=0, elem_id="control_video_skip_frames")
with gr.Row():
video_type = gr.Dropdown(label='Video file', choices=['None', 'GIF', 'PNG', 'MP4'], value='None')
video_duration = gr.Slider(label='Duration', minimum=0.25, maximum=10, step=0.25, value=2, visible=False)
video_duration = gr.Slider(label='Duration', minimum=0.25, maximum=300, step=0.25, value=2, visible=False)
with gr.Row():
video_loop = gr.Checkbox(label='Loop', value=True, visible=False)
video_pad = gr.Slider(label='Pad frames', minimum=0, maximum=24, step=1, value=1, visible=False)
@@ -677,7 +683,7 @@ def create_ui(_blocks: gr.Blocks=None):
btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter])
btn_negative_counter.click(fn=call_queue.wrap_queued_call(ui.update_token_counter), inputs=[negative, steps], outputs=[negative_counter])
select_fields = [input_mode, input_image, init_image, input_type, input_resize, input_inpaint, mask_blur, mask_overlap]
select_fields = [input_mode, input_image, init_image, input_type, input_resize, input_inpaint, input_video, input_batch, input_folder, mask_blur, mask_overlap]
select_output = [output_tabs, result_txt]
select_dict = dict(
fn=select_input,
+1 -1
Submodule wiki updated: 4e9d25efd5...ff8c396891