From ddfc95d4a6321c6dde4b732d5a4d97a78175c0c1 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Sun, 5 Oct 2025 23:54:41 -0500 Subject: [PATCH 01/22] Deprecate HunyuanVideoWrapper support Re-synchronize first node for WanVideoWrapper - LoadWanVideoT5TextEncoder --- __init__.py | 34 +- device_utils.py | 34 +- .../hunyuanvideowrapper_native_vae.json | 717 -------------- .../hunyuanvideowrapper_select_device.json | 784 --------------- nodes.py | 145 --- wanvideo.py | 519 +--------- wanvideo_deprecated.py | 911 ++++++++++++++++++ 7 files changed, 959 insertions(+), 2185 deletions(-) delete mode 100644 examples/hunyuanvideowrapper/hunyuanvideowrapper_native_vae.json delete mode 100644 examples/hunyuanvideowrapper/hunyuanvideowrapper_select_device.json create mode 100644 wanvideo_deprecated.py diff --git a/__init__.py b/__init__.py index 41b4f15..a3fa9c3 100644 --- a/__init__.py +++ b/__init__.py @@ -21,7 +21,7 @@ from .model_management_mgpu import ( ) WEB_DIRECTORY = "./web" -MGPU_MM_LOG = False +MGPU_MM_LOG = True DEBUG_LOG = False logger = logging.getLogger("MultiGPU") @@ -95,8 +95,6 @@ mm.get_torch_device = get_torch_device_patched mm.text_encoder_device = text_encoder_device_patched from .nodes import ( - DeviceSelectorMultiGPU, - HunyuanVideoEmbeddingsAdapter, UnetLoaderGGUF, UnetLoaderGGUFAdvanced, CLIPLoaderGGUF, @@ -114,21 +112,11 @@ from .nodes import ( PulidModelLoader, PulidInsightFaceLoader, PulidEvaClipLoader, - HyVideoModelLoader, - HyVideoVAELoader, - DownloadAndLoadHyVideoTextEncoder, UNetLoaderLP, ) from .wanvideo import ( - WanVideoModelLoader, - WanVideoModelLoader_2, - WanVideoVAELoader, LoadWanVideoT5TextEncoder, - LoadWanVideoClipTextEncoder, - WanVideoTextEncode, - WanVideoBlockSwap, - WanVideoSampler ) from .wrappers import ( @@ -158,8 +146,6 @@ from .checkpoint_multigpu import ( ) NODE_CLASS_MAPPINGS = { - "DeviceSelectorMultiGPU": DeviceSelectorMultiGPU, - "HunyuanVideoEmbeddingsAdapter": HunyuanVideoEmbeddingsAdapter, "CheckpointLoaderAdvancedMultiGPU": CheckpointLoaderAdvancedMultiGPU, "CheckpointLoaderAdvancedDisTorch2MultiGPU": CheckpointLoaderAdvancedDisTorch2MultiGPU, "UNetLoaderLP": UNetLoaderLP, @@ -266,22 +252,8 @@ pulid_nodes = { } register_and_count(["PuLID_ComfyUI", "pulid_comfyui"], pulid_nodes) -hunyuan_nodes = { - "HyVideoModelLoaderMultiGPU": override_class(HyVideoModelLoader), - "HyVideoVAELoaderMultiGPU": override_class(HyVideoVAELoader), - "DownloadAndLoadHyVideoTextEncoderMultiGPU": override_class(DownloadAndLoadHyVideoTextEncoder) -} -register_and_count(["ComfyUI-HunyuanVideoWrapper", "comfyui-hunyuanvideowrapper"], hunyuan_nodes) - wanvideo_nodes = { - "WanVideoModelLoaderMultiGPU": WanVideoModelLoader, - "WanVideoModelLoaderMultiGPU_2": WanVideoModelLoader_2, - "WanVideoVAELoaderMultiGPU": WanVideoVAELoader, - "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder, - "LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder, - "WanVideoTextEncodeMultiGPU": WanVideoTextEncode, - "WanVideoBlockSwapMultiGPU": WanVideoBlockSwap, - "WanVideoSamplerMultiGPU": WanVideoSampler + "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) @@ -289,4 +261,4 @@ for item in registration_data: logger.info(fmt_reg.format(item['name'], item['found'], str(item['count']))) logger.info(dash_line) -logger.info(f"[MultiGPU] Registration complete. Final mappings: {', '.join(NODE_CLASS_MAPPINGS.keys())}") \ No newline at end of file +logger.info(f"[MultiGPU] Registration complete. Final mappings: {', '.join(NODE_CLASS_MAPPINGS.keys())}") diff --git a/device_utils.py b/device_utils.py index a86cf84..8d93f5b 100644 --- a/device_utils.py +++ b/device_utils.py @@ -237,32 +237,28 @@ def soft_empty_cache_distorch2_patched(force=False): from .model_management_mgpu import multigpu_memory_log, check_cpu_memory_threshold, trigger_executor_cache_reset from .distorch_2 import safetensor_allocation_store, create_safetensor_model_hash - multigpu_memory_log("patched_soft_empty", f"start:force={force}") is_distorch_active = False # Detect DisTorch2-managed models - logger.mgpu_mm_log(f"[DETECT_DEBUG] Checking DisTorch2 active status - loaded models: {len(mm.current_loaded_models)}, store entries: {len(safetensor_allocation_store)}") + # logger.mgpu_mm_log(f"[DETECT_DEBUG] Checking DisTorch2 active status - loaded models: {len(mm.current_loaded_models)}, store entries: {len(safetensor_allocation_store)}") for i, lm in enumerate(mm.current_loaded_models): mp = lm.model # weakref call to ModelPatcher if mp is not None: - try: - model_hash = create_safetensor_model_hash(mp, "cache_patch_check") - in_store = model_hash in safetensor_allocation_store - alloc_value = safetensor_allocation_store.get(model_hash, "") - model_name = type(getattr(mp, 'model', mp)).__name__ - unload_distorch_model = getattr(getattr(mp, 'model', None), '_mgpu_unload_distorch_model', False) - - logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: {model_name}, hash={model_hash[:8]}, in_store={in_store}, alloc_value='{alloc_value}', unload_distorch_model={unload_distorch_model}") - - if in_store and alloc_value: - is_distorch_active = True - logger.mgpu_mm_log(f"[DETECT_DEBUG] DisTorch2 ACTIVE detected on model: {model_name}") - break - except Exception as e: - logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: Error during detection - {e}") + model_hash = create_safetensor_model_hash(mp, "cache_patch_check") + in_store = model_hash in safetensor_allocation_store + alloc_value = safetensor_allocation_store.get(model_hash, "") + model_name = type(getattr(mp, 'model', mp)).__name__ + unload_distorch_model = getattr(getattr(mp, 'model', None), '_mgpu_unload_distorch_model', False) + + #logger.mgpu_mm_log(f"[DETECT_DEBUG] Model {i}: {model_name}, hash={model_hash[:8]}, in_store={in_store}, alloc_value='{alloc_value}', unload_distorch_model={unload_distorch_model}") + + if in_store and alloc_value: + is_distorch_active = True + #logger.mgpu_mm_log(f"[DETECT_DEBUG] DisTorch2 ACTIVE detected on model: {model_name}") + break - logger.mgpu_mm_log(f"[DETECT_DEBUG] Final DisTorch2 active status: {is_distorch_active}") + #logger.mgpu_mm_log(f"[DETECT_DEBUG] Final DisTorch2 active status: {is_distorch_active}") # Phase 2: adaptive CPU memory management check_cpu_memory_threshold() @@ -272,7 +268,6 @@ def soft_empty_cache_distorch2_patched(force=False): logger.mgpu_mm_log("DisTorch2 active: clearing allocator caches on all devices (VRAM)") soft_empty_cache_multigpu() else: - logger.mgpu_mm_log("DisTorch2 not active: delegating allocator cache clear (VRAM) to original mm.soft_empty_cache") original_soft_empty_cache(force) # Optional: return CPU heap to OS (not part of Comfy Core) @@ -280,7 +275,6 @@ def soft_empty_cache_distorch2_patched(force=False): if force: logger.mgpu_mm_log("Force flag active: triggering executor cache reset (CPU)") trigger_executor_cache_reset(reason="forced_soft_empty", force=True) - multigpu_memory_log("patched_soft_empty", "end") mm.soft_empty_cache = soft_empty_cache_distorch2_patched diff --git a/examples/hunyuanvideowrapper/hunyuanvideowrapper_native_vae.json b/examples/hunyuanvideowrapper/hunyuanvideowrapper_native_vae.json deleted file mode 100644 index b6320f5..0000000 --- a/examples/hunyuanvideowrapper/hunyuanvideowrapper_native_vae.json +++ /dev/null @@ -1,717 +0,0 @@ -{ - "last_node_id": 51, - "last_link_id": 70, - "nodes": [ - { - "id": 7, - "type": "HyVideoVAELoader", - "pos": [ - -980.2922973632812, - -830.076171875 - ], - "size": [ - 379.166748046875, - 82 - ], - "flags": {}, - "order": 0, - "mode": 4, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "vae", - "type": "VAE", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoVAELoader" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "bf16" - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 30, - "type": "HyVideoTextEncode", - "pos": [ - -194.8070831298828, - -79.95932006835938 - ], - "size": [ - 425.64068603515625, - 286.85968017578125 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "text_encoders", - "type": "HYVIDTEXTENCODER", - "link": 66 - }, - { - "name": "custom_prompt_template", - "type": "PROMPT_TEMPLATE", - "link": null, - "shape": 7 - }, - { - "name": "clip_l", - "type": "CLIP", - "link": null, - "shape": 7 - }, - { - "name": "hyvid_cfg", - "type": "HYVID_CFG", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "hyvid_embeds", - "type": "HYVIDEMBEDS", - "links": [ - 36 - ] - } - ], - "properties": { - "Node name for S&R": "HyVideoTextEncode" - }, - "widgets_values": [ - "A serene Minnesota lake stretches out at sunset, the water's surface a mirror reflecting the vibrant orange and pink sky. In the foreground, a pair of loons glide gracefully across the water, their sleek black and white feathers contrasting with the warm hues of the sunset. The loons' long, slender necks curve elegantly as they dip their heads into the water, searching for fish. The camera pans slowly from left to right, capturing the tranquil scene. The shoreline is visible in the distance, lined with tall pine trees that cast long shadows across the water. The loons' haunting calls echo across the lake, adding to the peaceful atmosphere.", - false, - "video" - ] - }, - { - "id": 49, - "type": "DownloadAndLoadHyVideoTextEncoderMultiGPU", - "pos": [ - -745.2869262695312, - -80.3648452758789 - ], - "size": [ - 516.5999755859375, - 202 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "hyvid_text_encoder", - "type": "HYVIDTEXTENCODER", - "links": [ - 66 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "DownloadAndLoadHyVideoTextEncoderMultiGPU" - }, - "widgets_values": [ - "Kijai/llava-llama-3-8b-text-encoder-tokenizer", - "disabled", - "bf16", - false, - 2, - "disabled", - "cuda:1" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 48, - "type": "HyVideoModelLoaderMultiGPU", - "pos": [ - -338.0295715332031, - -403.1601257324219 - ], - "size": [ - 497.3603210449219, - 252.03509521484375 - ], - "flags": {}, - "order": 2, - "mode": 0, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - }, - { - "name": "block_swap_args", - "type": "BLOCKSWAPARGS", - "link": null, - "shape": 7 - }, - { - "name": "lora", - "type": "HYVIDLORA", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "links": [ - 65 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoModelLoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors", - "fp32", - "fp8_e4m3fn", - "main_device", - "sdpa", - false, - "cuda:0" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 16, - "type": "DownloadAndLoadHyVideoTextEncoder", - "pos": [ - -1011.1117553710938, - -1076.6143798828125 - ], - "size": [ - 441, - 178 - ], - "flags": {}, - "order": 3, - "mode": 4, - "inputs": [], - "outputs": [ - { - "name": "hyvid_text_encoder", - "type": "HYVIDTEXTENCODER", - "links": [] - } - ], - "properties": { - "Node name for S&R": "DownloadAndLoadHyVideoTextEncoder" - }, - "widgets_values": [ - "Kijai/llava-llama-3-8b-text-encoder-tokenizer", - "openai/clip-vit-large-patch14", - "fp16", - false, - 2, - "disabled" - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 1, - "type": "HyVideoModelLoader", - "pos": [ - -557.619384765625, - -1092.4908447265625 - ], - "size": [ - 435.37628173828125, - 221.34506225585938 - ], - "flags": {}, - "order": 4, - "mode": 4, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - }, - { - "name": "block_swap_args", - "type": "BLOCKSWAPARGS", - "link": null, - "shape": 7 - }, - { - "name": "lora", - "type": "HYVIDLORA", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoModelLoader" - }, - "widgets_values": [ - "hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors", - "bf16", - "fp8_e4m3fn", - "main_device", - "sdpa", - false - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 34, - "type": "VHS_VideoCombine", - "pos": [ - 847.0758666992188, - -415.1882629394531 - ], - "size": [ - 580.7774658203125, - 698.4859008789062 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 69 - }, - { - "name": "audio", - "type": "AUDIO", - "link": null, - "shape": 7 - }, - { - "name": "meta_batch", - "type": "VHS_BatchManager", - "link": null, - "shape": 7 - }, - { - "name": "vae", - "type": "VAE", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "Filenames", - "type": "VHS_FILENAMES", - "links": null - } - ], - "properties": { - "Node name for S&R": "VHS_VideoCombine" - }, - "widgets_values": { - "frame_rate": 24, - "loop_count": 0, - "filename_prefix": "HunyuanVideo", - "format": "video/h264-mp4", - "pix_fmt": "yuv420p", - "crf": 19, - "save_metadata": true, - "trim_to_audio": false, - "pingpong": false, - "save_output": true, - "videopreview": { - "hidden": false, - "paused": false, - "params": { - "filename": "HunyuanVideo_00161.mp4", - "subfolder": "", - "type": "output", - "format": "video/h264-mp4", - "frame_rate": 24, - "workflow": "HunyuanVideo_00161.png", - "fullpath": "/home/johnj/ComfyUI/output/HunyuanVideo_00161.mp4" - }, - "muted": false - } - } - }, - { - "id": 45, - "type": "VAEDecodeTiled", - "pos": [ - 532.4385986328125, - -644.8389282226562 - ], - "size": [ - 315, - 150 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "samples", - "type": "LATENT", - "link": 70 - }, - { - "name": "vae", - "type": "VAE", - "link": 56 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 69 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAEDecodeTiled" - }, - "widgets_values": [ - 256, - 64, - 64, - 8 - ] - }, - { - "id": 44, - "type": "VAELoaderMultiGPU", - "pos": [ - -230.99607849121094, - -639.45654296875 - ], - "size": [ - 315, - 82 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "VAE", - "type": "VAE", - "links": [ - 56 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAELoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "cuda:1" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 47, - "type": "HyVideoVAELoaderMultiGPU", - "pos": [ - -512.9594116210938, - -822.8096313476562 - ], - "size": [ - 315, - 106 - ], - "flags": {}, - "order": 6, - "mode": 4, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "vae", - "type": "VAE", - "links": [ - 64 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoVAELoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "bf16", - "cuda:1" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 5, - "type": "HyVideoDecode", - "pos": [ - -106.15780639648438, - -843.2271728515625 - ], - "size": [ - 345.4285888671875, - 150 - ], - "flags": {}, - "order": 9, - "mode": 4, - "inputs": [ - { - "name": "vae", - "type": "VAE", - "link": 64 - }, - { - "name": "samples", - "type": "LATENT", - "link": null - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoDecode" - }, - "widgets_values": [ - true, - 86, - 256, - true - ] - }, - { - "id": 3, - "type": "HyVideoSampler", - "pos": [ - 255.96482849121094, - -403.58502197265625 - ], - "size": [ - 315, - 630 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "link": 65 - }, - { - "name": "hyvid_embeds", - "type": "HYVIDEMBEDS", - "link": 36 - }, - { - "name": "samples", - "type": "LATENT", - "link": null, - "shape": 7 - }, - { - "name": "stg_args", - "type": "STGARGS", - "link": null, - "shape": 7 - }, - { - "name": "context_options", - "type": "COGCONTEXT", - "link": null, - "shape": 7 - }, - { - "name": "feta_args", - "type": "FETAARGS", - "link": null, - "shape": 7 - }, - { - "name": "teacache_args", - "type": "TEACACHEARGS", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "samples", - "type": "LATENT", - "links": [ - 70 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoSampler" - }, - "widgets_values": [ - 512, - 320, - 85, - 20, - 6, - 9, - 5770521, - "fixed", - false, - 1, - "FlowMatchDiscreteScheduler" - ] - }, - { - "id": 51, - "type": "Note", - "pos": [ - 449.2930908203125, - -955.3724975585938 - ], - "size": [ - 1160.6077880859375, - 211.52166748046875 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "kijai already does an impressive amount of memory management in these nodes, so it is important for MultiGPU to \"play nice\" and vice-versa.\n\nFor this version of the workflow:\n\n• Only two of kijai's three nodes are used - model and text. For VAE we relying on Comfy's native VAE Loader node wrapped by MultiGPU.\n\n• HunyuanVideo TextEncode: \"force_offload\" is set to \"false\". Setting this option to \"true\" would defeat the purpose of selecting a different main_device to load to.\n\n• The main model and VAE devices are set to different cuda devices (cuda0 and cuda1). This is possible because the HunyuanVideo Sampler node outputs latents compatible with Comfy's native TiledVAE Decode.\n• Consequentially, now that we no longer have any other component on cuda0, we set \"force_offload\" to \"false\" on the \"HunyuanVideo Sampler\" node. This keeps the main model in VRAM, eliminating load times for the subsequent generations.\n\n**NOTE** This the **OPTIMAL** way to use MultiGPU with kijai's awesome nodes. If you want to use all three of kijai's loader nodes, please see device_selector_lowvram_flux_controlnet.json in ./examples" - ], - "color": "#432", - "bgcolor": "#653" - } - ], - "links": [ - [ - 36, - 30, - 0, - 3, - 1, - "HYVIDEMBEDS" - ], - [ - 56, - 44, - 0, - 45, - 1, - "VAE" - ], - [ - 64, - 47, - 0, - 5, - 0, - "VAE" - ], - [ - 65, - 48, - 0, - 3, - 0, - "HYVIDEOMODEL" - ], - [ - 66, - 49, - 0, - 30, - 0, - "HYVIDTEXTENCODER" - ], - [ - 69, - 45, - 0, - 34, - 0, - "IMAGE" - ], - [ - 70, - 3, - 0, - 45, - 0, - "LATENT" - ] - ], - "groups": [], - "config": {}, - "extra": { - "ds": { - "scale": 0.8769226950001201, - "offset": [ - 895.3038892918638, - 1121.109618095784 - ] - }, - "ue_links": [], - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0 - }, - "version": 0.4 -} \ No newline at end of file diff --git a/examples/hunyuanvideowrapper/hunyuanvideowrapper_select_device.json b/examples/hunyuanvideowrapper/hunyuanvideowrapper_select_device.json deleted file mode 100644 index 9dd1f8e..0000000 --- a/examples/hunyuanvideowrapper/hunyuanvideowrapper_select_device.json +++ /dev/null @@ -1,784 +0,0 @@ -{ - "last_node_id": 51, - "last_link_id": 68, - "nodes": [ - { - "id": 7, - "type": "HyVideoVAELoader", - "pos": [ - -980.2922973632812, - -830.076171875 - ], - "size": [ - 379.166748046875, - 82 - ], - "flags": {}, - "order": 0, - "mode": 4, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "vae", - "type": "VAE", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoVAELoader" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "bf16" - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 30, - "type": "HyVideoTextEncode", - "pos": [ - -194.8070831298828, - -79.95932006835938 - ], - "size": [ - 425.64068603515625, - 286.85968017578125 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "text_encoders", - "type": "HYVIDTEXTENCODER", - "link": 66 - }, - { - "name": "custom_prompt_template", - "type": "PROMPT_TEMPLATE", - "link": null, - "shape": 7 - }, - { - "name": "clip_l", - "type": "CLIP", - "link": null, - "shape": 7 - }, - { - "name": "hyvid_cfg", - "type": "HYVID_CFG", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "hyvid_embeds", - "type": "HYVIDEMBEDS", - "links": [ - 36 - ] - } - ], - "properties": { - "Node name for S&R": "HyVideoTextEncode" - }, - "widgets_values": [ - "A serene Minnesota lake stretches out at sunset, the water's surface a mirror reflecting the vibrant orange and pink sky. In the foreground, a pair of loons glide gracefully across the water, their sleek black and white feathers contrasting with the warm hues of the sunset. The loons' long, slender necks curve elegantly as they dip their heads into the water, searching for fish. The camera pans slowly from left to right, capturing the tranquil scene. The shoreline is visible in the distance, lined with tall pine trees that cast long shadows across the water. The loons' haunting calls echo across the lake, adding to the peaceful atmosphere.", - false, - "video" - ] - }, - { - "id": 49, - "type": "DownloadAndLoadHyVideoTextEncoderMultiGPU", - "pos": [ - -745.2869262695312, - -80.3648452758789 - ], - "size": [ - 516.5999755859375, - 202 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "hyvid_text_encoder", - "type": "HYVIDTEXTENCODER", - "links": [ - 66 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "DownloadAndLoadHyVideoTextEncoderMultiGPU" - }, - "widgets_values": [ - "Kijai/llava-llama-3-8b-text-encoder-tokenizer", - "disabled", - "bf16", - false, - 2, - "disabled", - "cuda:1" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 48, - "type": "HyVideoModelLoaderMultiGPU", - "pos": [ - -338.0295715332031, - -403.1601257324219 - ], - "size": [ - 497.3603210449219, - 252.03509521484375 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - }, - { - "name": "block_swap_args", - "type": "BLOCKSWAPARGS", - "link": null, - "shape": 7 - }, - { - "name": "lora", - "type": "HYVIDLORA", - "link": null, - "shape": 7 - }, - { - "name": "device", - "type": "COMBO", - "link": 67, - "widget": { - "name": "device" - }, - "shape": 7 - } - ], - "outputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "links": [ - 65 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoModelLoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors", - "fp32", - "fp8_e4m3fn", - "main_device", - "sdpa", - false, - "cuda:0" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 16, - "type": "DownloadAndLoadHyVideoTextEncoder", - "pos": [ - -1011.1117553710938, - -1076.6143798828125 - ], - "size": [ - 441, - 178 - ], - "flags": {}, - "order": 2, - "mode": 4, - "inputs": [], - "outputs": [ - { - "name": "hyvid_text_encoder", - "type": "HYVIDTEXTENCODER", - "links": [] - } - ], - "properties": { - "Node name for S&R": "DownloadAndLoadHyVideoTextEncoder" - }, - "widgets_values": [ - "Kijai/llava-llama-3-8b-text-encoder-tokenizer", - "openai/clip-vit-large-patch14", - "fp16", - false, - 2, - "disabled" - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 1, - "type": "HyVideoModelLoader", - "pos": [ - -557.619384765625, - -1092.4908447265625 - ], - "size": [ - 435.37628173828125, - 221.34506225585938 - ], - "flags": {}, - "order": 3, - "mode": 4, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - }, - { - "name": "block_swap_args", - "type": "BLOCKSWAPARGS", - "link": null, - "shape": 7 - }, - { - "name": "lora", - "type": "HYVIDLORA", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoModelLoader" - }, - "widgets_values": [ - "hunyuan_video_720_cfgdistill_fp8_e4m3fn.safetensors", - "bf16", - "fp8_e4m3fn", - "main_device", - "sdpa", - false - ], - "color": "#332922", - "bgcolor": "#593930" - }, - { - "id": 34, - "type": "VHS_VideoCombine", - "pos": [ - 847.0758666992188, - -415.1882629394531 - ], - "size": [ - 580.7774658203125, - 698.4859008789062 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 63 - }, - { - "name": "audio", - "type": "AUDIO", - "link": null, - "shape": 7 - }, - { - "name": "meta_batch", - "type": "VHS_BatchManager", - "link": null, - "shape": 7 - }, - { - "name": "vae", - "type": "VAE", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "Filenames", - "type": "VHS_FILENAMES", - "links": null - } - ], - "properties": { - "Node name for S&R": "VHS_VideoCombine" - }, - "widgets_values": { - "frame_rate": 24, - "loop_count": 0, - "filename_prefix": "HunyuanVideo", - "format": "video/h264-mp4", - "pix_fmt": "yuv420p", - "crf": 19, - "save_metadata": true, - "trim_to_audio": false, - "pingpong": false, - "save_output": true, - "videopreview": { - "hidden": false, - "paused": false, - "params": { - "filename": "HunyuanVideo_00161.mp4", - "subfolder": "", - "type": "output", - "format": "video/h264-mp4", - "frame_rate": 24, - "workflow": "HunyuanVideo_00161.png", - "fullpath": "/home/johnj/ComfyUI/output/HunyuanVideo_00161.mp4" - }, - "muted": false - } - } - }, - { - "id": 5, - "type": "HyVideoDecode", - "pos": [ - 538.94189453125, - -625.3562622070312 - ], - "size": [ - 345.4285888671875, - 150 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "vae", - "type": "VAE", - "link": 64 - }, - { - "name": "samples", - "type": "LATENT", - "link": 4 - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "links": [ - 63 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoDecode" - }, - "widgets_values": [ - true, - 86, - 256, - true - ] - }, - { - "id": 50, - "type": "DeviceSelectorMultiGPU", - "pos": [ - -716.0182495117188, - -492.63983154296875 - ], - "size": [ - 315, - 58 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "device", - "type": "COMBO", - "links": [ - 67, - 68 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "DeviceSelectorMultiGPU" - }, - "widgets_values": [ - "cuda:0" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 47, - "type": "HyVideoVAELoaderMultiGPU", - "pos": [ - -313.7779846191406, - -620.58740234375 - ], - "size": [ - 315, - 106 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "compile_args", - "type": "COMPILEARGS", - "link": null, - "shape": 7 - }, - { - "name": "device", - "type": "COMBO", - "link": 68, - "widget": { - "name": "device" - }, - "shape": 7 - } - ], - "outputs": [ - { - "name": "vae", - "type": "VAE", - "links": [ - 64 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoVAELoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "bf16", - "cuda:1" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 3, - "type": "HyVideoSampler", - "pos": [ - 255.96482849121094, - -403.58502197265625 - ], - "size": [ - 315, - 630 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "HYVIDEOMODEL", - "link": 65 - }, - { - "name": "hyvid_embeds", - "type": "HYVIDEMBEDS", - "link": 36 - }, - { - "name": "samples", - "type": "LATENT", - "link": null, - "shape": 7 - }, - { - "name": "stg_args", - "type": "STGARGS", - "link": null, - "shape": 7 - }, - { - "name": "context_options", - "type": "COGCONTEXT", - "link": null, - "shape": 7 - }, - { - "name": "feta_args", - "type": "FETAARGS", - "link": null, - "shape": 7 - }, - { - "name": "teacache_args", - "type": "TEACACHEARGS", - "link": null, - "shape": 7 - } - ], - "outputs": [ - { - "name": "samples", - "type": "LATENT", - "links": [ - 4 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "HyVideoSampler" - }, - "widgets_values": [ - 512, - 320, - 85, - 20, - 6, - 9, - 5770521, - "fixed", - true, - 1, - "FlowMatchDiscreteScheduler" - ] - }, - { - "id": 44, - "type": "VAELoaderMultiGPU", - "pos": [ - -556.3764038085938, - -809.7488403320312 - ], - "size": [ - 315, - 82 - ], - "flags": {}, - "order": 5, - "mode": 4, - "inputs": [], - "outputs": [ - { - "name": "VAE", - "type": "VAE", - "links": [ - 56 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAELoaderMultiGPU" - }, - "widgets_values": [ - "hunyuan_video_vae_bf16.safetensors", - "cuda:0" - ] - }, - { - "id": 45, - "type": "VAEDecodeTiled", - "pos": [ - -195.86590576171875, - -828.8155517578125 - ], - "size": [ - 315, - 150 - ], - "flags": {}, - "order": 10, - "mode": 4, - "inputs": [ - { - "name": "samples", - "type": "LATENT", - "link": null - }, - { - "name": "vae", - "type": "VAE", - "link": 56 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAEDecodeTiled" - }, - "widgets_values": [ - 256, - 64, - 64, - 8 - ] - }, - { - "id": 51, - "type": "Note", - "pos": [ - 217.09609985351562, - -905.6065063476562 - ], - "size": [ - 1160.6077880859375, - 211.52166748046875 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "kijai already does an impressive amount of memory management in these nodes, so it is important for MultiGPU to \"play nice\" and vice-versa.\n\nFor this version of the workflow:\n\n• All three of kijai's nodes are used - model, text, and VAE\n\n• HunyuanVideo TextEncode: \"force_offload\" is set to \"false\". Setting this option to \"true\" would defeat the purpose of selecting a different main_device to load to.\n\n• The main model and VAE devices are linked. This is because kijai's \"HunyuanVideo Decode\" expects both the model and the VAE to be on the same device. \n• Consequentially, to eliminate out-of-memory errors, \"force_offload\" is set to \"true\" on the \"HunyuanVideo Sampler\" node.\n\n\n**NOTE** This is not the optimial way to use MultiGPU. Please see the workflow at for an example of loading the VAE to a different cuda device using the native VAE loader and tiled decode." - ], - "color": "#432", - "bgcolor": "#653" - } - ], - "links": [ - [ - 4, - 3, - 0, - 5, - 1, - "LATENT" - ], - [ - 36, - 30, - 0, - 3, - 1, - "HYVIDEMBEDS" - ], - [ - 56, - 44, - 0, - 45, - 1, - "VAE" - ], - [ - 63, - 5, - 0, - 34, - 0, - "IMAGE" - ], - [ - 64, - 47, - 0, - 5, - 0, - "VAE" - ], - [ - 65, - 48, - 0, - 3, - 0, - "HYVIDEOMODEL" - ], - [ - 66, - 49, - 0, - 30, - 0, - "HYVIDTEXTENCODER" - ], - [ - 67, - 50, - 0, - 48, - 3, - "COMBO" - ], - [ - 68, - 50, - 0, - 47, - 1, - "COMBO" - ] - ], - "groups": [], - "config": {}, - "extra": { - "ds": { - "scale": 0.7972024500001089, - "offset": [ - 1218.3831555808085, - 1163.8844215880747 - ] - }, - "ue_links": [], - "VHS_latentpreview": false, - "VHS_latentpreviewrate": 0 - }, - "version": 0.4 -} \ No newline at end of file diff --git a/nodes.py b/nodes.py index a5ad9b4..822d059 100644 --- a/nodes.py +++ b/nodes.py @@ -5,60 +5,6 @@ from nodes import NODE_CLASS_MAPPINGS from .device_utils import get_device_list from .model_management_mgpu import force_full_system_cleanup -class DeviceSelectorMultiGPU: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - return { - "required": { - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0]}) - } - } - - RETURN_TYPES = (get_device_list(),) - RETURN_NAMES = ("device",) - FUNCTION = "select_device" - CATEGORY = "multigpu" - - def select_device(self, device): - """Select target device from available device list.""" - return (device,) - - -class HunyuanVideoEmbeddingsAdapter: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "hyvid_embeds": ("HYVIDEMBEDS",), - } - } - - RETURN_TYPES = ("CONDITIONING",) - FUNCTION = "adapt_embeddings" - CATEGORY = "multigpu" - - def adapt_embeddings(self, hyvid_embeds): - """Adapt HunyuanVideo embeddings to standard ComfyUI conditioning format.""" - cond = hyvid_embeds["prompt_embeds"] - - pooled_dict = { - "pooled_output": hyvid_embeds["prompt_embeds_2"], - "cross_attn": hyvid_embeds["prompt_embeds"], - "attention_mask": hyvid_embeds["attention_mask"], - } - - if hyvid_embeds["attention_mask_2"] is not None: - pooled_dict["attention_mask_controlnet"] = hyvid_embeds["attention_mask_2"] - - if hyvid_embeds["cfg"] is not None: - pooled_dict["guidance"] = float(hyvid_embeds["cfg"]) - pooled_dict["start_percent"] = float(hyvid_embeds["start_percent"]) if hyvid_embeds["start_percent"] is not None else 0.0 - pooled_dict["end_percent"] = float(hyvid_embeds["end_percent"]) if hyvid_embeds["end_percent"] is not None else 1.0 - - return ([[cond, pooled_dict]],) - - class UnetLoaderGGUF: @classmethod def INPUT_TYPES(s): @@ -465,97 +411,6 @@ class PulidEvaClipLoader: original_loader = NODE_CLASS_MAPPINGS["PulidEvaClipLoader"]() return original_loader.load_eva_clip() - -class HyVideoModelLoader: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": (folder_paths.get_filename_list("diffusion_models"), {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' -folder",}), - "base_precision": (["fp32", "bf16"], {"default": "bf16"}), - "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_scaled', 'torchao_fp8dq', "torchao_fp8dqrow", "torchao_int8dq", "torchao_fp6", "torchao_int4", "torchao_int8"], {"default": 'disabled', "tooltip": "optional quantization method"}), - "load_device": (["main_device"], {"default": "main_device"}), - }, - "optional": { - "attention_mode": ([ - "sdpa", - "flash_attn_varlen", - "sageattn_varlen", - "comfy", - ], {"default": "flash_attn"}), - "compile_args": ("COMPILEARGS", ), - "block_swap_args": ("BLOCKSWAPARGS", ), - "lora": ("HYVIDLORA", {"default": None}), - "auto_cpu_offload": ("BOOLEAN", {"default": False, "tooltip": "Enable auto offloading for reduced VRAM usage, implementation from DiffSynth-Studio, slightly different from block swapping and uses even less VRAM, but can be slower as you can't define how much VRAM to use"}), - } - } - - RETURN_TYPES = ("HYVIDEOMODEL",) - RETURN_NAMES = ("model", ) - FUNCTION = "loadmodel" - CATEGORY = "HunyuanVideoWrapper" - - def loadmodel(self, model, base_precision, load_device, quantization, compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, auto_cpu_offload=False): - """Load HunyuanVideo model with specified precision and quantization.""" - original_loader = NODE_CLASS_MAPPINGS["HyVideoModelLoader"]() - return original_loader.loadmodel(model, base_precision, load_device, quantization, compile_args, attention_mode, block_swap_args, lora, auto_cpu_offload) - -class HyVideoVAELoader: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model_name": (folder_paths.get_filename_list("vae"), {"tooltip": "These models are loaded from 'ComfyUI/models/vae'"}), - }, - "optional": { - "precision": (["fp16", "fp32", "bf16"], - {"default": "bf16"} - ), - "compile_args":("COMPILEARGS", ), - } - } - - RETURN_TYPES = ("VAE",) - RETURN_NAMES = ("vae", ) - FUNCTION = "loadmodel" - CATEGORY = "HunyuanVideoWrapper" - DESCRIPTION = "Loads Hunyuan VAE model from 'ComfyUI/models/vae'" - - def loadmodel(self, model_name, precision, compile_args=None): - """Load HunyuanVideo VAE model.""" - original_loader = NODE_CLASS_MAPPINGS["HyVideoVAELoader"]() - return original_loader.loadmodel(model_name, precision, compile_args) - -class DownloadAndLoadHyVideoTextEncoder: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "llm_model": (["Kijai/llava-llama-3-8b-text-encoder-tokenizer","xtuner/llava-llama-3-8b-v1_1-transformers"],), - "clip_model": (["disabled","openai/clip-vit-large-patch14",],), - "precision": (["fp16", "fp32", "bf16"], - {"default": "bf16"} - ), - }, - "optional": { - "apply_final_norm": ("BOOLEAN", {"default": False}), - "hidden_state_skip_layer": ("INT", {"default": 2}), - "quantization": (['disabled', 'bnb_nf4', "fp8_e4m3fn"], {"default": 'disabled'}), - } - } - - RETURN_TYPES = ("HYVIDTEXTENCODER",) - RETURN_NAMES = ("hyvid_text_encoder", ) - FUNCTION = "loadmodel" - CATEGORY = "HunyuanVideoWrapper" - DESCRIPTION = "Loads Hunyuan text_encoder model from 'ComfyUI/models/LLM'" - - def loadmodel(self, llm_model, clip_model, precision, apply_final_norm=False, hidden_state_skip_layer=2, quantization="disabled"): - """Download and load HunyuanVideo text encoder from HuggingFace.""" - original_loader = NODE_CLASS_MAPPINGS["DownloadAndLoadHyVideoTextEncoder"]() - return original_loader.loadmodel(llm_model, clip_model, precision, apply_final_norm, hidden_state_skip_layer, quantization) - - class UNetLoaderLP: """UNet Loader (Low Precision) - sets LoRA precision to False for CPU storage optimization""" @classmethod diff --git a/wanvideo.py b/wanvideo.py index 40a8045..00e2104 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -4,510 +4,53 @@ import sys import inspect import folder_paths import comfy.model_management as mm -from .device_utils import get_device_list, comfyui_memory_load +from nodes import NODE_CLASS_MAPPINGS +from .device_utils import get_device_list +from .model_management_mgpu import multigpu_memory_log -class WanVideoModelLoader: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - - return { - "required": { - "model": (folder_paths.get_filename_list("unet_gguf") + folder_paths.get_filename_list("diffusion_models"), - {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' folder",}), - "base_precision": (["fp32", "bf16", "fp16", "fp16_fast"], {"default": "bf16"}), - "quantization": ( - ["disabled", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2", "fp8_e4m3fn_fast_no_ffn", "fp8_e4m3fn_scaled", "fp8_e5m2_scaled"], - {"default": "disabled", "tooltip": "optional quantization method"} - ), - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], "tooltip": "Device to load the model to"}), - }, - "optional": { - "attention_mode": ([ - "sdpa", - "flash_attn_2", - "flash_attn_3", - "sageattn", - "sageattn_3", - "flex_attention", - "radial_sage_attention", - ], {"default": "sdpa"}), - "compile_args": ("WANCOMPILEARGS", ), - "block_swap_args": ("BLOCKSWAPARGS", ), - "lora": ("WANVIDLORA", {"default": None}), - "vram_management_args": ("VRAM_MANAGEMENTARGS", {"default": None, "tooltip": "Alternative offloading method from DiffSynth-Studio, more aggressive in reducing memory use than block swapping, but can be slower"}), - "extra_model": ("VACEPATH", {"default": None, "tooltip": "Extra model to add to the main model, ie. VACE or MTV Crafter"}), - "fantasytalking_model": ("FANTASYTALKMODEL", {"default": None, "tooltip": "FantasyTalking model https://github.com/Fantasy-AMAP"}), - "multitalk_model": ("MULTITALKMODEL", {"default": None, "tooltip": "Multitalk model"}), - "fantasyportrait_model": ("FANTASYPORTRAITMODEL", {"default": None, "tooltip": "FantasyPortrait model"}), - } - } - - RETURN_TYPES = ("WANVIDEOMODEL",) - RETURN_NAMES = ("model", ) - FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - - def loadmodel(self, model, base_precision, device, quantization, - compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, vram_management_args=None, extra_model=None, fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None): - logging.debug(f"[MultiGPU] WanVideoModelLoader: User selected device: {device}") - - selected_device = torch.device(device) - - load_device = "offload_device" if device == "cpu" else "main_device" - - from nodes import NODE_CLASS_MAPPINGS - original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() - - loader_module = inspect.getmodule(original_loader) - - if loader_module: - logging.debug(f"[MultiGPU] Patching WanVideo modules to use {selected_device}") - - original_device = getattr(loader_module, 'device', None) - original_offload = getattr(loader_module, 'offload_device', None) - - model_offload_override = getattr(loader_module, '_model_offload_device_override', None) - - setattr(loader_module, 'device', selected_device) - if model_offload_override: - setattr(loader_module, 'offload_device', model_offload_override) - logging.debug(f"[MultiGPU] Using model offload override: {model_offload_override}") - elif device == "cpu": - setattr(loader_module, 'offload_device', selected_device) - - nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') - if nodes_module_name in sys.modules: - nodes_module = sys.modules[nodes_module_name] - setattr(nodes_module, 'device', selected_device) - - nodes_model_offload_override = getattr(nodes_module, '_model_offload_device_override', None) - if nodes_model_offload_override: - setattr(nodes_module, 'offload_device', nodes_model_offload_override) - elif device == "cpu": - setattr(nodes_module, 'offload_device', selected_device) - logging.debug(f"[MultiGPU] Both WanVideo modules patched successfully") - - logging.debug(f"[MultiGPU] Calling original WanVideo loader") - try: - logging.info(comfyui_memory_load(f"pre-model-load:wan-model:{model}")) - except Exception: - pass - result = original_loader.loadmodel(model, base_precision, load_device, quantization, - compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model) - try: - logging.info(comfyui_memory_load(f"post-model-load:wan-model:{model}")) - except Exception: - pass - - if result and len(result) > 0 and hasattr(result[0], 'model'): - model_obj = result[0] - if hasattr(model_obj.model, 'diffusion_model'): - transformer = model_obj.model.diffusion_model - - block_swap_override = getattr(loader_module, '_block_swap_device_override', None) - if block_swap_override: - transformer.offload_device = block_swap_override - logging.debug(f"[MultiGPU] Patched WanVideo transformer for block swap to use: {block_swap_override}") - - logging.info(f"[MultiGPU] WanVideo model loaded on {selected_device}") - - return result - else: - logging.error(f"[MultiGPU] Could not patch WanVideo modules, falling back") - return original_loader.loadmodel(model, base_precision, load_device, quantization, - compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model) - - -class WanVideoVAELoader: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - - return { - "required": { - "model_name": (folder_paths.get_filename_list("vae"), - {"tooltip": "These models are loaded from 'ComfyUI/models/vae'"}), - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], - "tooltip": "Device to load the VAE to"}), - }, - "optional": { - "precision": (["fp16", "fp32", "bf16"], {"default": "bf16"}), - "compile_args": ("WANCOMPILEARGS", ), - } - } - - RETURN_TYPES = ("WANVAE",) - RETURN_NAMES = ("vae", ) - FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Loads Wan VAE model with explicit device selection" - - def loadmodel(self, model_name, device, precision="bf16", compile_args=None): - logging.debug(f"[MultiGPU] WanVideoVAELoader: User selected device: {device}") - - from nodes import NODE_CLASS_MAPPINGS - original_loader = NODE_CLASS_MAPPINGS["WanVideoVAELoader"]() - - loader_module = inspect.getmodule(original_loader) - - if loader_module: - selected_device = torch.device(device) - logging.debug(f"[MultiGPU] Patching WanVideo VAE modules to use {selected_device}") - - setattr(loader_module, 'offload_device', selected_device) - setattr(loader_module, 'device', selected_device) - - nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') - if nodes_module_name in sys.modules: - nodes_module = sys.modules[nodes_module_name] - setattr(nodes_module, 'device', selected_device) - setattr(nodes_module, 'offload_device', selected_device) - - try: - logging.info(comfyui_memory_load(f"pre-model-load:wan-vae:{model_name}")) - except Exception: - pass - result = original_loader.loadmodel(model_name, precision, compile_args) - try: - logging.info(comfyui_memory_load(f"post-model-load:wan-vae:{model_name}")) - except Exception: - pass - - # Attach device info to VAE object for downstream nodes - if result and len(result) > 0: - result[0].load_device = selected_device - - logging.info(f"[MultiGPU] WanVideo VAE loaded on {selected_device}") - return result - else: - logging.error(f"[MultiGPU] Could not patch WanVideo VAE modules") - return original_loader.loadmodel(model_name, precision, compile_args) +logger = logging.getLogger("MultiGPU") class LoadWanVideoT5TextEncoder: @classmethod def INPUT_TYPES(s): devices = get_device_list() - + default_device = devices[1] if len(devices) > 1 else devices[0] return { "required": { - "model_name": (folder_paths.get_filename_list("text_encoders"), - {"tooltip": "These models are loaded from 'ComfyUI/models/text_encoders'"}), - "precision": (["fp32", "bf16"], {"default": "bf16"}), - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], - "tooltip": "Device to load the text encoder to"}), + "model_name": (folder_paths.get_filename_list("text_encoders"), {"tooltip": "These models are loaded from 'ComfyUI/models/text_encoders'"}), + "precision": (["fp32", "bf16"], + {"default": "bf16"} + ), }, "optional": { - "quantization": (['disabled', 'fp8_e4m3fn'], - {"default": 'disabled', "tooltip": "optional quantization method"}), + "device": (devices, {"default": default_device}), + "quantization": (['disabled', 'fp8_e4m3fn'], {"default": 'disabled', "tooltip": "optional quantization method"}), } } - RETURN_TYPES = ("WANTEXTENCODER",) - RETURN_NAMES = ("wan_t5_model", ) + RETURN_TYPES = ("WANTEXTENCODER", "STRING") + RETURN_NAMES = ("wan_t5_model", "device") FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Loads Wan text_encoder model from 'ComfyUI/models/text_encoders'" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Loads Wan text_encoder model from 'ComfyUI/models/LLM'" - def loadmodel(self, model_name, precision, device, quantization="disabled"): - logging.debug(f"[MultiGPU] LoadWanVideoT5TextEncoder: User selected device: {device}") + def loadmodel(self, model_name, precision, device=None, quantization="disabled"): + from . import set_current_device + + if device is not None: + set_current_device(device) - selected_device = torch.device(device) - load_device = "offload_device" if device == "cpu" else "main_device" - - from nodes import NODE_CLASS_MAPPINGS + if device == "cpu": + load_device = "offload_device" + else: + load_device = "main_device" + + logger.info(f"[MultiGPU WanVideoWrapper] current_device set to: {device}") + logger.info(f"[MultiGPU WanVideoWrapper] load_device set to: {load_device}") + original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoT5TextEncoder"]() - - loader_module = inspect.getmodule(original_loader) - - if loader_module: - logging.debug(f"[MultiGPU] Patching WanVideo T5 modules to use {selected_device}") - - setattr(loader_module, 'device', selected_device) - if device == "cpu": - setattr(loader_module, 'offload_device', selected_device) - - nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') - if nodes_module_name in sys.modules: - nodes_module = sys.modules[nodes_module_name] - setattr(nodes_module, 'device', selected_device) - if device == "cpu": - setattr(nodes_module, 'offload_device', selected_device) - - try: - logging.info(comfyui_memory_load(f"pre-model-load:wan-textenc:{model_name}")) - except Exception: - pass - result = original_loader.loadmodel(model_name, precision, load_device, quantization) - try: - logging.info(comfyui_memory_load(f"post-model-load:wan-textenc:{model_name}")) - except Exception: - pass - - logging.info(f"[MultiGPU] WanVideo T5 Text encoder loaded on {selected_device}") - - return result - else: - logging.error(f"[MultiGPU] Could not patch WanVideo T5 modules, falling back") - return original_loader.loadmodel(model_name, precision, load_device, quantization) + text_encoder = original_loader.loadmodel(model_name, precision, load_device, quantization) -class WanVideoTextEncode: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - - return {"required": { - "positive_prompt": ("STRING", {"default": "", "multiline": True} ), - "negative_prompt": ("STRING", {"default": "", "multiline": True} ), - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], - "tooltip": "Device to run the text encoding on"}), - }, - "optional": { - "t5": ("WANTEXTENCODER",), - "force_offload": ("BOOLEAN", {"default": True}), - "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), - "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use"}), - } - } - - RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) - RETURN_NAMES = ("text_embeds",) - FUNCTION = "process" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Encodes text prompts with explicit device selection" - - def process(self, positive_prompt, negative_prompt, device, t5=None, force_offload=True, - model_to_offload=None, use_disk_cache=False): - logging.debug(f"[MultiGPU] WanVideoTextEncode: User selected device: {device}") - - original_device = "gpu" if device != "cpu" else "cpu" - - from nodes import NODE_CLASS_MAPPINGS - original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() - - encoder_module = inspect.getmodule(original_encoder) - - if encoder_module: - selected_device = torch.device(device) - logging.debug(f"[MultiGPU] Patching WanVideo TextEncode module to use {selected_device}") - setattr(encoder_module, 'device', selected_device) - - model_loading_name = encoder_module.__name__.replace('.nodes', '.nodes_model_loading') - if model_loading_name in sys.modules: - model_loading_module = sys.modules[model_loading_name] - setattr(model_loading_module, 'device', selected_device) - - result = original_encoder.process(positive_prompt, negative_prompt, t5=t5, - force_offload=force_offload, model_to_offload=model_to_offload, - use_disk_cache=use_disk_cache, device=original_device) - - logging.info(f"[MultiGPU] WanVideo TextEncode completed on {selected_device}") - return result - else: - return original_encoder.process(positive_prompt, negative_prompt, t5=t5, - force_offload=force_offload, model_to_offload=model_to_offload, - use_disk_cache=use_disk_cache, device=original_device) - -class LoadWanVideoClipTextEncoder: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - - return { - "required": { - "model_name": (folder_paths.get_filename_list("clip_vision") + folder_paths.get_filename_list("text_encoders"), - {"tooltip": "These models are loaded from 'ComfyUI/models/clip_vision'"}), - "precision": (["fp16", "fp32", "bf16"], {"default": "fp16"}), - "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], - "tooltip": "Device to load the CLIP encoder to"}), - } - } - - RETURN_TYPES = ("CLIP_VISION",) - RETURN_NAMES = ("clip_vision", ) - FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Loads Wan CLIP text encoder model from 'ComfyUI/models/clip_vision'" - - def loadmodel(self, model_name, precision, device): - logging.debug(f"[MultiGPU] LoadWanVideoClipTextEncoder: User selected device: {device}") - - selected_device = torch.device(device) - load_device = "offload_device" if device == "cpu" else "main_device" - - from nodes import NODE_CLASS_MAPPINGS - original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoClipTextEncoder"]() - - loader_module = inspect.getmodule(original_loader) - - if loader_module: - logging.debug(f"[MultiGPU] Patching WanVideo CLIP modules to use {selected_device}") - - setattr(loader_module, 'device', selected_device) - if device == "cpu": - setattr(loader_module, 'offload_device', selected_device) - - nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') - if nodes_module_name in sys.modules: - nodes_module = sys.modules[nodes_module_name] - setattr(nodes_module, 'device', selected_device) - if device == "cpu": - setattr(nodes_module, 'offload_device', selected_device) - - try: - logging.info(comfyui_memory_load(f"pre-model-load:wan-clip:{model_name}")) - except Exception: - pass - result = original_loader.loadmodel(model_name, precision, load_device) - try: - logging.info(comfyui_memory_load(f"post-model-load:wan-clip:{model_name}")) - except Exception: - pass - - logging.info(f"[MultiGPU] WanVideo CLIP encoder loaded on {selected_device}") - - return result - else: - logging.error(f"[MultiGPU] Could not patch WanVideo CLIP modules, falling back") - return original_loader.loadmodel(model_name, precision, load_device) - -class WanVideoModelLoader_2: - @classmethod - def INPUT_TYPES(s): - return WanVideoModelLoader.INPUT_TYPES() - - RETURN_TYPES = WanVideoModelLoader.RETURN_TYPES - RETURN_NAMES = WanVideoModelLoader.RETURN_NAMES - FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Second model loader instance for workflows using multiple models on different devices" - - def loadmodel(self, model, base_precision, device, quantization, - compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, - vram_management_args=None, vace_model=None, fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None): - loader = WanVideoModelLoader() - return loader.loadmodel(model, base_precision, device, quantization, - compile_args, attention_mode, block_swap_args, lora, - vram_management_args, vace_model, fantasytalking_model, multitalk_model, fantasyportrait_model) - -class WanVideoSampler: - @classmethod - def INPUT_TYPES(s): - from nodes import NODE_CLASS_MAPPINGS - original_types = NODE_CLASS_MAPPINGS["WanVideoSampler"].INPUT_TYPES() - return original_types - - RETURN_TYPES = ("LATENT", "LATENT",) - RETURN_NAMES = ("samples", "denoised_samples",) - FUNCTION = "process" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "MultiGPU-aware sampler that ensures correct device for each model" - - def process(self, model, **kwargs): - model_device = model.load_device - logging.info(f"[MultiGPU] WanVideoSampler: Processing on device: {model_device}") - - for module_name in sys.modules.keys(): - if 'WanVideoWrapper' in module_name and hasattr(sys.modules[module_name], 'device'): - sys.modules[module_name].device = model_device - - from nodes import NODE_CLASS_MAPPINGS - original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() - return original_sampler.process(model, **kwargs) - -class WanVideoVACEEncode: - @classmethod - def INPUT_TYPES(s): - from nodes import NODE_CLASS_MAPPINGS - original_types = NODE_CLASS_MAPPINGS["WanVideoVACEEncode"].INPUT_TYPES() - return original_types - - RETURN_TYPES = ("LATENT",) - RETURN_NAMES = ("latent",) - FUNCTION = "process" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "MultiGPU-aware VACE encoder that uses device from input VAE" - - def process(self, vae, **kwargs): - # Get device from VAE object - vae_device = vae.load_device - logging.info(f"[MultiGPU] WanVideoVACEEncode: Processing on device: {vae_device}") - - # Patch all WanVideo modules to use the VAE's device - for module_name in sys.modules.keys(): - if 'WanVideoWrapper' in module_name and hasattr(sys.modules[module_name], 'device'): - sys.modules[module_name].device = vae_device - - from nodes import NODE_CLASS_MAPPINGS - original_encoder = NODE_CLASS_MAPPINGS["WanVideoVACEEncode"]() - return original_encoder.process(vae, **kwargs) - -class WanVideoBlockSwap: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - - return { - "required": { - "blocks_to_swap": ("INT", {"default": 20, "min": 0, "max": 40, "step": 1, - "tooltip": "Number of transformer blocks to swap, the 14B model has 40, while the 1.3B model has 30 blocks"}), - "swap_device": (devices, {"default": "cpu", - "tooltip": "Device to swap blocks to during sampling (default: cpu for standard behavior)"}), - "model_offload_device": (devices, {"default": "cpu", - "tooltip": "Device to offload entire model to when done (default: cpu)"}), - "offload_img_emb": ("BOOLEAN", {"default": False, "tooltip": "Offload img_emb to swap_device"}), - "offload_txt_emb": ("BOOLEAN", {"default": False, "tooltip": "Offload time_emb to swap_device"}), - }, - "optional": { - "use_non_blocking": ("BOOLEAN", {"default": False, - "tooltip": "Use non-blocking memory transfer for offloading, reserves more RAM but is faster"}), - "vace_blocks_to_swap": ("INT", {"default": 0, "min": 0, "max": 15, "step": 1, - "tooltip": "Number of VACE blocks to swap, the VACE model has 15 blocks"}), - "prefetch_blocks": ("INT", {"default": 0, "min": 0, "max": 40, "step": 1, "tooltip": "Number of blocks to prefetch ahead, can speed up processing but increases memory usage. 1 is usually enough to offset speed loss from block swapping, use the debug option to confirm it for your system"}), - "block_swap_debug": ("BOOLEAN", {"default": False, "tooltip": "Enable debug logging for block swapping"}), - }, - } - - RETURN_TYPES = ("BLOCKSWAPARGS",) - RETURN_NAMES = ("block_swap_args",) - FUNCTION = "setargs" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Block swap settings with explicit device selection for memory management across GPUs" - - def setargs(self, blocks_to_swap, swap_device, model_offload_device, offload_img_emb, offload_txt_emb, - use_non_blocking=False, vace_blocks_to_swap=0, prefetch_blocks=0, block_swap_debug=False): - logging.debug(f"[MultiGPU] WanVideoBlockSwap: swap_device={swap_device}, model_offload_device={model_offload_device}, blocks_to_swap={blocks_to_swap}") - - selected_swap_device = torch.device(swap_device) - selected_offload_device = torch.device(model_offload_device) - - for module_name in sys.modules.keys(): - if 'WanVideoWrapper' in module_name and 'nodes_model_loading' in module_name: - module = sys.modules[module_name] - setattr(module, 'offload_device', selected_offload_device) - setattr(module, '_block_swap_device_override', selected_swap_device) - setattr(module, '_model_offload_device_override', selected_offload_device) - logging.debug(f"[MultiGPU] Patched {module_name} for offload to {selected_offload_device} and swap to {selected_swap_device}") - - if 'WanVideoWrapper' in module_name and module_name.endswith('.nodes'): - module = sys.modules[module_name] - setattr(module, 'offload_device', selected_offload_device) - setattr(module, '_block_swap_device_override', selected_swap_device) - setattr(module, '_model_offload_device_override', selected_offload_device) - - block_swap_args = { - "blocks_to_swap": blocks_to_swap, - "offload_img_emb": offload_img_emb, - "offload_txt_emb": offload_txt_emb, - "use_non_blocking": use_non_blocking, - "vace_blocks_to_swap": vace_blocks_to_swap, - "prefetch_blocks": prefetch_blocks, - "block_swap_debug": block_swap_debug, - "swap_device": swap_device, - "model_offload_device": model_offload_device, - } - - logging.info(f"[MultiGPU] WanVideoBlockSwap configuration complete") - - return (block_swap_args,) + # Return both the text encoder AND the selected device + return text_encoder, device diff --git a/wanvideo_deprecated.py b/wanvideo_deprecated.py new file mode 100644 index 0000000..1247aea --- /dev/null +++ b/wanvideo_deprecated.py @@ -0,0 +1,911 @@ +import logging +import torch +import sys +import inspect +import folder_paths +import comfy.model_management as mm +from .device_utils import get_device_list +from .model_management_mgpu import multigpu_memory_log + +logger = logging.getLogger("MultiGPU") + +class WanVideoModelLoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return { + "required": { + "model": (folder_paths.get_filename_list("unet_gguf") + folder_paths.get_filename_list("diffusion_models"), + {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' folder",}), + "base_precision": (["fp32", "bf16", "fp16", "fp16_fast"], {"default": "bf16"}), + "quantization": ( + ["disabled", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2", "fp8_e4m3fn_fast_no_ffn", "fp8_e4m3fn_scaled", "fp8_e5m2_scaled"], + {"default": "disabled", "tooltip": "optional quantization method"} + ), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], "tooltip": "Device to load the model to"}), + }, + "optional": { + "attention_mode": ([ + "sdpa", + "flash_attn_2", + "flash_attn_3", + "sageattn", + "sageattn_3", + "flex_attention", + "radial_sage_attention", + ], {"default": "sdpa"}), + "compile_args": ("WANCOMPILEARGS", ), + "block_swap_args": ("BLOCKSWAPARGS", ), + "lora": ("WANVIDLORA", {"default": None}), + "vram_management_args": ("VRAM_MANAGEMENTARGS", {"default": None, "tooltip": "Alternative offloading method from DiffSynth-Studio, more aggressive in reducing memory use than block swapping, but can be slower"}), + "extra_model": ("VACEPATH", {"default": None, "tooltip": "Extra model to add to the main model, ie. VACE or MTV Crafter"}), + "fantasytalking_model": ("FANTASYTALKMODEL", {"default": None, "tooltip": "FantasyTalking model https://github.com/Fantasy-AMAP"}), + "multitalk_model": ("MULTITALKMODEL", {"default": None, "tooltip": "Multitalk model"}), + "fantasyportrait_model": ("FANTASYPORTRAITMODEL", {"default": None, "tooltip": "FantasyPortrait model"}), + "rms_norm_function": (["default", "pytorch"], {"default": "default", "tooltip": "RMSNorm function to use, 'pytorch' is the new native torch RMSNorm, which is faster (when not using torch.compile mostly) but changes results slightly. 'default' is the original WanRMSNorm"}), + } + } + + RETURN_TYPES = ("WANVIDEOMODEL",) + RETURN_NAMES = ("model", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + + def loadmodel(self, model, base_precision, device, quantization, + compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, vram_management_args=None, extra_model=None, fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, rms_norm_function="default"): + from . import set_current_device + + logging.debug(f"[MultiGPU] WanVideoModelLoader: User selected device: {device}") + + selected_device = torch.device(device) + + # UPDATE GLOBAL DEVICE CONTEXT + set_current_device(selected_device) + + load_device = "offload_device" if device == "cpu" else "main_device" + + from nodes import NODE_CLASS_MAPPINGS + original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() + + loader_module = inspect.getmodule(original_loader) + + if loader_module: + logging.debug(f"[MultiGPU] Patching WanVideo modules to use {selected_device}") + + original_device = getattr(loader_module, 'device', None) + original_offload = getattr(loader_module, 'offload_device', None) + + model_offload_override = getattr(loader_module, '_model_offload_device_override', None) + + setattr(loader_module, 'device', selected_device) + if model_offload_override: + setattr(loader_module, 'offload_device', model_offload_override) + logging.debug(f"[MultiGPU] Using model offload override: {model_offload_override}") + elif device == "cpu": + setattr(loader_module, 'offload_device', selected_device) + + nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') + if nodes_module_name in sys.modules: + nodes_module = sys.modules[nodes_module_name] + setattr(nodes_module, 'device', selected_device) + + nodes_model_offload_override = getattr(nodes_module, '_model_offload_device_override', None) + if nodes_model_offload_override: + setattr(nodes_module, 'offload_device', nodes_model_offload_override) + elif device == "cpu": + setattr(nodes_module, 'offload_device', selected_device) + logging.debug(f"[MultiGPU] Both WanVideo modules patched successfully") + + logger.info(f"[MultiGPU WanVideo] Device patching complete. Calling original loader...") + logger.info(f"[MultiGPU WanVideo] Module variables: device={loader_module.device}, offload_device={getattr(loader_module, 'offload_device', 'NOT SET')}") + + multigpu_memory_log("wanvideo_model_load", "pre-load") + + result = original_loader.loadmodel(model, base_precision, load_device, quantization, + compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function) + + multigpu_memory_log("wanvideo_model_load", "post-load") + + if result and len(result) > 0 and hasattr(result[0], 'model'): + model_obj = result[0] + if hasattr(model_obj.model, 'diffusion_model'): + transformer = model_obj.model.diffusion_model + + block_swap_override = getattr(loader_module, '_block_swap_device_override', None) + if block_swap_override: + transformer.offload_device = block_swap_override + logging.debug(f"[MultiGPU] Patched WanVideo transformer for block swap to use: {block_swap_override}") + + logging.info(f"[MultiGPU] WanVideo model loaded on {selected_device}") + + return result + else: + logging.error(f"[MultiGPU] Could not patch WanVideo modules, falling back") + return original_loader.loadmodel(model, base_precision, load_device, quantization, + compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function) + + +class WanVideoVAELoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return { + "required": { + "model_name": (folder_paths.get_filename_list("vae"), + {"tooltip": "These models are loaded from 'ComfyUI/models/vae'"}), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to load the VAE to"}), + }, + "optional": { + "precision": (["fp16", "fp32", "bf16"], {"default": "bf16"}), + "compile_args": ("WANCOMPILEARGS", ), + } + } + + RETURN_TYPES = ("WANVAE",) + RETURN_NAMES = ("vae", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Loads Wan VAE model with explicit device selection" + + def loadmodel(self, model_name, device, precision="bf16", compile_args=None): + from . import set_current_device + + logging.debug(f"[MultiGPU] WanVideoVAELoader: User selected device: {device}") + + selected_device = torch.device(device) + + # UPDATE GLOBAL DEVICE CONTEXT + set_current_device(selected_device) + + from nodes import NODE_CLASS_MAPPINGS + original_loader = NODE_CLASS_MAPPINGS["WanVideoVAELoader"]() + + loader_module = inspect.getmodule(original_loader) + + if loader_module: + logging.debug(f"[MultiGPU] Patching WanVideo VAE modules to use {selected_device}") + + setattr(loader_module, 'offload_device', selected_device) + setattr(loader_module, 'device', selected_device) + + nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') + if nodes_module_name in sys.modules: + nodes_module = sys.modules[nodes_module_name] + setattr(nodes_module, 'device', selected_device) + setattr(nodes_module, 'offload_device', selected_device) + + multigpu_memory_log("wanvideo_vae_load", "pre-load") + + result = original_loader.loadmodel(model_name, precision, compile_args) + + multigpu_memory_log("wanvideo_vae_load", "post-load") + + # Attach device info to VAE object for downstream nodes + if result and len(result) > 0: + result[0].load_device = selected_device + + logger.info(f"[MultiGPU WanVideo VAE] VAE loaded successfully on {selected_device}") + return result + else: + logging.error(f"[MultiGPU] Could not patch WanVideo VAE modules") + return original_loader.loadmodel(model_name, precision, compile_args) + + +class LoadWanVideoT5TextEncoder: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return { + "required": { + "model_name": (folder_paths.get_filename_list("text_encoders"), + {"tooltip": "These models are loaded from 'ComfyUI/models/text_encoders'"}), + "precision": (["fp32", "bf16"], {"default": "bf16"}), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to load the text encoder to"}), + }, + "optional": { + "quantization": (['disabled', 'fp8_e4m3fn'], + {"default": 'disabled', "tooltip": "optional quantization method"}), + } + } + + RETURN_TYPES = ("WANTEXTENCODER",) + RETURN_NAMES = ("wan_t5_model", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Loads Wan text_encoder model from 'ComfyUI/models/text_encoders'" + + def loadmodel(self, model_name, precision, device, quantization="disabled"): + import traceback + from . import set_current_device + + logger.info(f"[T5 INSTRUMENT] ====== START LoadWanVideoT5TextEncoder ======") + logger.info(f"[T5 INSTRUMENT] User selected device: {device}") + logger.info(f"[T5 INSTRUMENT] Model name: {model_name}, Precision: {precision}, Quantization: {quantization}") + + selected_device = torch.device(device) + load_device = "offload_device" if device == "cpu" else "main_device" + + from nodes import NODE_CLASS_MAPPINGS + original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoT5TextEncoder"]() + + loader_module = inspect.getmodule(original_loader) + + if loader_module: + # PRE-PATCH STATE + logger.info(f"[T5 INSTRUMENT] PRE-PATCH loader_module.device = {getattr(loader_module, 'device', 'NOT SET')}") + logger.info(f"[T5 INSTRUMENT] PRE-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + # UPDATE GLOBAL DEVICE CONTEXT + logger.info(f"[T5 INSTRUMENT] Calling set_current_device({selected_device})") + set_current_device(selected_device) + + # WRAP mm.get_torch_device() TO LOG ALL CALLS + original_mm_get_device = mm.get_torch_device + call_count = [0] + + def logged_get_torch_device(): + call_count[0] += 1 + result = original_mm_get_device() + stack = traceback.extract_stack() + # Get caller info (skip this function and get the actual caller) + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[DEVICE CALL #{call_count[0]}] mm.get_torch_device() called from {caller.filename}:{caller.lineno} in {caller.name}() → returning {result}") + return result + + mm.get_torch_device = logged_get_torch_device + + # WRAP MODULE DEVICE ACCESS + original_device = getattr(loader_module, 'device', None) + access_count = [0] + + class DeviceAccessLogger: + def __init__(self, actual_device): + self._actual_device = actual_device + + def __str__(self): + access_count[0] += 1 + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[MODULE ACCESS #{access_count[0]}] loader_module.device accessed from {caller.filename}:{caller.lineno} → returning {self._actual_device}") + return str(self._actual_device) + + def __repr__(self): + return str(self) + + # PATCH MODULE VARIABLES + logger.info(f"[T5 INSTRUMENT] PATCHING loader_module.device to {selected_device}") + setattr(loader_module, 'device', selected_device) + if device == "cpu": + setattr(loader_module, 'offload_device', selected_device) + + nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') + if nodes_module_name in sys.modules: + nodes_module = sys.modules[nodes_module_name] + logger.info(f"[T5 INSTRUMENT] PATCHING nodes_module.device to {selected_device}") + setattr(nodes_module, 'device', selected_device) + if device == "cpu": + setattr(nodes_module, 'offload_device', selected_device) + + # POST-PATCH STATE + logger.info(f"[T5 INSTRUMENT] POST-PATCH loader_module.device = {loader_module.device}") + logger.info(f"[T5 INSTRUMENT] POST-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + multigpu_memory_log("wanvideo_t5_load", "pre-load") + + logger.info(f"[T5 INSTRUMENT] ===== CALLING ORIGINAL LOADER =====") + logger.info(f"[T5 INSTRUMENT] Watch for DEVICE CALL and MODULE ACCESS logs below:") + + result = original_loader.loadmodel(model_name, precision, load_device, quantization) + + logger.info(f"[T5 INSTRUMENT] ===== ORIGINAL LOADER RETURNED =====") + logger.info(f"[T5 INSTRUMENT] Total mm.get_torch_device() calls: {call_count[0]}") + logger.info(f"[T5 INSTRUMENT] Total module.device accesses: {access_count[0]}") + + # RESTORE ORIGINAL + mm.get_torch_device = original_mm_get_device + + multigpu_memory_log("wanvideo_t5_load", "post-load") + + # POST-LOAD STATE + if result and len(result) > 0: + t5_encoder = result[0] + if isinstance(t5_encoder, dict) and 'model' in t5_encoder: + try: + actual_device = next(t5_encoder['model'].parameters()).device + logger.info(f"[T5 INSTRUMENT] ACTUAL model device after load = {actual_device}") + except Exception as e: + logger.info(f"[T5 INSTRUMENT] Could not determine actual model device: {e}") + + logger.info(f"[T5 INSTRUMENT] ====== END LoadWanVideoT5TextEncoder ======") + + return result + else: + logger.error(f"[T5 INSTRUMENT] Could not get loader module - falling back") + return original_loader.loadmodel(model_name, precision, load_device, quantization) + +class WanVideoTextEncode: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return {"required": { + "positive_prompt": ("STRING", {"default": "", "multiline": True} ), + "negative_prompt": ("STRING", {"default": "", "multiline": True} ), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to run the text encoding on"}), + }, + "optional": { + "t5": ("WANTEXTENCODER",), + "force_offload": ("BOOLEAN", {"default": True}), + "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), + "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use"}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) + RETURN_NAMES = ("text_embeds",) + FUNCTION = "process" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Encodes text prompts with explicit device selection" + + def process(self, positive_prompt, negative_prompt, device, t5=None, force_offload=True, + model_to_offload=None, use_disk_cache=False): + import traceback + from . import set_current_device + + logger.info(f"[TEXTENCODE INSTRUMENT] ====== START WanVideoTextEncode ======") + logger.info(f"[TEXTENCODE INSTRUMENT] User selected device: {device}") + + selected_device = torch.device(device) + original_device = "gpu" if device != "cpu" else "cpu" + + from nodes import NODE_CLASS_MAPPINGS + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() + + encoder_module = inspect.getmodule(original_encoder) + + if encoder_module: + # PRE-PATCH STATE + logger.info(f"[TEXTENCODE INSTRUMENT] PRE-PATCH encoder_module.device = {getattr(encoder_module, 'device', 'NOT SET')}") + logger.info(f"[TEXTENCODE INSTRUMENT] PRE-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + # UPDATE GLOBAL DEVICE CONTEXT + logger.info(f"[TEXTENCODE INSTRUMENT] Calling set_current_device({selected_device})") + set_current_device(selected_device) + + # WRAP mm.get_torch_device() TO LOG ALL CALLS + original_mm_get_device = mm.get_torch_device + call_count = [0] + + def logged_get_torch_device(): + call_count[0] += 1 + result = original_mm_get_device() + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[DEVICE CALL #{call_count[0]}] mm.get_torch_device() called from {caller.filename}:{caller.lineno} in {caller.name}() → returning {result}") + return result + + mm.get_torch_device = logged_get_torch_device + + # PATCH MODULE VARIABLES + logger.info(f"[TEXTENCODE INSTRUMENT] PATCHING encoder_module.device to {selected_device}") + setattr(encoder_module, 'device', selected_device) + + model_loading_name = encoder_module.__name__.replace('.nodes', '.nodes_model_loading') + if model_loading_name in sys.modules: + model_loading_module = sys.modules[model_loading_name] + logger.info(f"[TEXTENCODE INSTRUMENT] PATCHING model_loading_module.device to {selected_device}") + setattr(model_loading_module, 'device', selected_device) + + # POST-PATCH STATE + logger.info(f"[TEXTENCODE INSTRUMENT] POST-PATCH encoder_module.device = {encoder_module.device}") + logger.info(f"[TEXTENCODE INSTRUMENT] POST-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + multigpu_memory_log("wanvideo_textencode", "pre-encode") + + logger.info(f"[TEXTENCODE INSTRUMENT] ===== CALLING ORIGINAL ENCODER =====") + + result = original_encoder.process(positive_prompt, negative_prompt, t5=t5, + force_offload=force_offload, model_to_offload=model_to_offload, + use_disk_cache=use_disk_cache, device=original_device) + + multigpu_memory_log("wanvideo_textencode", "post-encode") + + logger.info(f"[TEXTENCODE INSTRUMENT] ===== ORIGINAL ENCODER RETURNED =====") + logger.info(f"[TEXTENCODE INSTRUMENT] Total mm.get_torch_device() calls: {call_count[0]}") + + # RESTORE ORIGINAL + mm.get_torch_device = original_mm_get_device + + logger.info(f"[TEXTENCODE INSTRUMENT] ====== END WanVideoTextEncode ======") + return result + else: + logger.error(f"[TEXTENCODE INSTRUMENT] Could not get encoder module - falling back") + return original_encoder.process(positive_prompt, negative_prompt, t5=t5, + force_offload=force_offload, model_to_offload=model_to_offload, + use_disk_cache=use_disk_cache, device=original_device) + +class WanVideoTextEncodeSingle: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return {"required": { + "prompt": ("STRING", {"default": "", "multiline": True}), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to run the text encoding on"}), + }, + "optional": { + "t5": ("WANTEXTENCODER",), + "force_offload": ("BOOLEAN", {"default": True}), + "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), + "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use"}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS",) + RETURN_NAMES = ("text_embeds",) + FUNCTION = "process" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Encodes single text prompt with explicit device selection" + + def process(self, prompt, device, t5=None, force_offload=True, + model_to_offload=None, use_disk_cache=False): + import traceback + from . import set_current_device + + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] ====== START WanVideoTextEncodeSingle ======") + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] User selected device: {device}") + + selected_device = torch.device(device) + original_device = "gpu" if device != "cpu" else "cpu" + + from nodes import NODE_CLASS_MAPPINGS + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeSingle"]() + + encoder_module = inspect.getmodule(original_encoder) + + if encoder_module: + # PRE-PATCH STATE + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] PRE-PATCH encoder_module.device = {getattr(encoder_module, 'device', 'NOT SET')}") + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] PRE-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + # UPDATE GLOBAL DEVICE CONTEXT + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] Calling set_current_device({selected_device})") + set_current_device(selected_device) + + # WRAP mm.get_torch_device() TO LOG ALL CALLS + original_mm_get_device = mm.get_torch_device + call_count = [0] + + def logged_get_torch_device(): + call_count[0] += 1 + result = original_mm_get_device() + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[DEVICE CALL #{call_count[0]}] mm.get_torch_device() called from {caller.filename}:{caller.lineno} in {caller.name}() → returning {result}") + return result + + mm.get_torch_device = logged_get_torch_device + + # PATCH MODULE VARIABLES + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] PATCHING encoder_module.device to {selected_device}") + setattr(encoder_module, 'device', selected_device) + + model_loading_name = encoder_module.__name__.replace('.nodes', '.nodes_model_loading') + if model_loading_name in sys.modules: + model_loading_module = sys.modules[model_loading_name] + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] PATCHING model_loading_module.device to {selected_device}") + setattr(model_loading_module, 'device', selected_device) + + # POST-PATCH STATE + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] POST-PATCH encoder_module.device = {encoder_module.device}") + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] POST-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + multigpu_memory_log("wanvideo_textencodesingle", "pre-encode") + + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] ===== CALLING ORIGINAL ENCODER =====") + + result = original_encoder.process(prompt, t5=t5, + force_offload=force_offload, model_to_offload=model_to_offload, + use_disk_cache=use_disk_cache, device=original_device) + + multigpu_memory_log("wanvideo_textencodesingle", "post-encode") + + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] ===== ORIGINAL ENCODER RETURNED =====") + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] Total mm.get_torch_device() calls: {call_count[0]}") + + # RESTORE ORIGINAL + mm.get_torch_device = original_mm_get_device + + logger.info(f"[TEXTENCODESINGLE INSTRUMENT] ====== END WanVideoTextEncodeSingle ======") + return result + else: + logger.error(f"[TEXTENCODESINGLE INSTRUMENT] Could not get encoder module - falling back") + return original_encoder.process(prompt, t5=t5, + force_offload=force_offload, model_to_offload=model_to_offload, + use_disk_cache=use_disk_cache, device=original_device) + + +class WanVideoTextEncodeCached: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return {"required": { + "model_name": (folder_paths.get_filename_list("text_encoders"), + {"tooltip": "These models are loaded from 'ComfyUI/models/text_encoders'"}), + "precision": (["fp32", "bf16"], {"default": "bf16"}), + "positive_prompt": ("STRING", {"default": "", "multiline": True}), + "negative_prompt": ("STRING", {"default": "", "multiline": True}), + "quantization": (["disabled", "fp8_e4m3fn"], {"default": "disabled"}), + "use_disk_cache": ("BOOLEAN", {"default": True}), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to run the text encoding on"}), + }, + "optional": { + "extender_args": ("WANVIDEOPROMPTEXTENDER_ARGS",), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", "WANVIDEOTEXTEMBEDS", "STRING") + RETURN_NAMES = ("text_embeds", "negative_text_embeds", "positive_prompt") + FUNCTION = "process" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Cached text encoding with explicit device selection" + + def process(self, model_name, precision, positive_prompt, negative_prompt, + quantization, use_disk_cache, device, extender_args=None): + import traceback + from . import set_current_device + + logger.info(f"[TEXTENCODECACHED INSTRUMENT] ====== START WanVideoTextEncodeCached ======") + logger.info(f"[TEXTENCODECACHED INSTRUMENT] User selected device: {device}") + + selected_device = torch.device(device) + original_device = "gpu" if device != "cpu" else "cpu" + + from nodes import NODE_CLASS_MAPPINGS + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeCached"]() + + encoder_module = inspect.getmodule(original_encoder) + + if encoder_module: + # PRE-PATCH STATE + logger.info(f"[TEXTENCODECACHED INSTRUMENT] PRE-PATCH encoder_module.device = {getattr(encoder_module, 'device', 'NOT SET')}") + logger.info(f"[TEXTENCODECACHED INSTRUMENT] PRE-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + # UPDATE GLOBAL DEVICE CONTEXT + logger.info(f"[TEXTENCODECACHED INSTRUMENT] Calling set_current_device({selected_device})") + set_current_device(selected_device) + + # WRAP mm.get_torch_device() TO LOG ALL CALLS + original_mm_get_device = mm.get_torch_device + call_count = [0] + + def logged_get_torch_device(): + call_count[0] += 1 + result = original_mm_get_device() + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[DEVICE CALL #{call_count[0]}] mm.get_torch_device() called from {caller.filename}:{caller.lineno} in {caller.name}() → returning {result}") + return result + + mm.get_torch_device = logged_get_torch_device + + # PATCH MODULE VARIABLES + logger.info(f"[TEXTENCODECACHED INSTRUMENT] PATCHING encoder_module.device to {selected_device}") + setattr(encoder_module, 'device', selected_device) + + model_loading_name = encoder_module.__name__.replace('.nodes', '.nodes_model_loading') + if model_loading_name in sys.modules: + model_loading_module = sys.modules[model_loading_name] + logger.info(f"[TEXTENCODECACHED INSTRUMENT] PATCHING model_loading_module.device to {selected_device}") + setattr(model_loading_module, 'device', selected_device) + + # POST-PATCH STATE + logger.info(f"[TEXTENCODECACHED INSTRUMENT] POST-PATCH encoder_module.device = {encoder_module.device}") + logger.info(f"[TEXTENCODECACHED INSTRUMENT] POST-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + multigpu_memory_log("wanvideo_textencodecached", "pre-encode") + + logger.info(f"[TEXTENCODECACHED INSTRUMENT] ===== CALLING ORIGINAL ENCODER =====") + + result = original_encoder.process(model_name, precision, positive_prompt, negative_prompt, + quantization, use_disk_cache, device=original_device, + extender_args=extender_args) + + multigpu_memory_log("wanvideo_textencodecached", "post-encode") + + logger.info(f"[TEXTENCODECACHED INSTRUMENT] ===== ORIGINAL ENCODER RETURNED =====") + logger.info(f"[TEXTENCODECACHED INSTRUMENT] Total mm.get_torch_device() calls: {call_count[0]}") + + # RESTORE ORIGINAL + mm.get_torch_device = original_mm_get_device + + logger.info(f"[TEXTENCODECACHED INSTRUMENT] ====== END WanVideoTextEncodeCached ======") + return result + else: + logger.error(f"[TEXTENCODECACHED INSTRUMENT] Could not get encoder module - falling back") + return original_encoder.process(model_name, precision, positive_prompt, negative_prompt, + quantization, use_disk_cache, device=original_device, + extender_args=extender_args) + + +class LoadWanVideoClipTextEncoder: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return { + "required": { + "model_name": (folder_paths.get_filename_list("clip_vision") + folder_paths.get_filename_list("text_encoders"), + {"tooltip": "These models are loaded from 'ComfyUI/models/clip_vision'"}), + "precision": (["fp16", "fp32", "bf16"], {"default": "fp16"}), + "device": (devices, {"default": devices[1] if len(devices) > 1 else devices[0], + "tooltip": "Device to load the CLIP encoder to"}), + } + } + + RETURN_TYPES = ("CLIP_VISION",) + RETURN_NAMES = ("clip_vision", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Loads Wan CLIP text encoder model from 'ComfyUI/models/clip_vision'" + + def loadmodel(self, model_name, precision, device): + import traceback + from . import set_current_device + + logger.info(f"[CLIP INSTRUMENT] ====== START LoadWanVideoClipTextEncoder ======") + logger.info(f"[CLIP INSTRUMENT] User selected device: {device}") + logger.info(f"[CLIP INSTRUMENT] Model name: {model_name}, Precision: {precision}") + + selected_device = torch.device(device) + load_device = "offload_device" if device == "cpu" else "main_device" + + from nodes import NODE_CLASS_MAPPINGS + original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoClipTextEncoder"]() + + loader_module = inspect.getmodule(original_loader) + + if loader_module: + # PRE-PATCH STATE + logger.info(f"[CLIP INSTRUMENT] PRE-PATCH loader_module.device = {getattr(loader_module, 'device', 'NOT SET')}") + logger.info(f"[CLIP INSTRUMENT] PRE-PATCH loader_module.offload_device = {getattr(loader_module, 'offload_device', 'NOT SET')}") + logger.info(f"[CLIP INSTRUMENT] PRE-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + # UPDATE GLOBAL DEVICE CONTEXT + logger.info(f"[CLIP INSTRUMENT] Calling set_current_device({selected_device})") + set_current_device(selected_device) + + # WRAP mm.get_torch_device() TO LOG ALL CALLS + original_mm_get_device = mm.get_torch_device + call_count = [0] + + def logged_get_torch_device(): + call_count[0] += 1 + result = original_mm_get_device() + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[DEVICE CALL #{call_count[0]}] mm.get_torch_device() called from {caller.filename}:{caller.lineno} in {caller.name}() → returning {result}") + return result + + mm.get_torch_device = logged_get_torch_device + + # WRAP MODULE DEVICE ACCESS + original_device = getattr(loader_module, 'device', None) + access_count = [0] + + class DeviceAccessLogger: + def __init__(self, actual_device): + self._actual_device = actual_device + + def __str__(self): + access_count[0] += 1 + stack = traceback.extract_stack() + caller = stack[-2] if len(stack) >= 2 else stack[-1] + logger.info(f"[MODULE ACCESS #{access_count[0]}] loader_module.device accessed from {caller.filename}:{caller.lineno} → returning {self._actual_device}") + return str(self._actual_device) + + def __repr__(self): + return str(self) + + # PATCH MODULE VARIABLES + logger.info(f"[CLIP INSTRUMENT] PATCHING loader_module.device to {selected_device}") + setattr(loader_module, 'device', selected_device) + if device == "cpu": + setattr(loader_module, 'offload_device', selected_device) + + nodes_module_name = loader_module.__name__.replace('.nodes_model_loading', '.nodes') + if nodes_module_name in sys.modules: + nodes_module = sys.modules[nodes_module_name] + logger.info(f"[CLIP INSTRUMENT] PRE-PATCH nodes_module.device = {getattr(nodes_module, 'device', 'NOT SET')}") + logger.info(f"[CLIP INSTRUMENT] PATCHING nodes_module.device to {selected_device}") + setattr(nodes_module, 'device', selected_device) + if device == "cpu": + setattr(nodes_module, 'offload_device', selected_device) + + # POST-PATCH STATE + logger.info(f"[CLIP INSTRUMENT] POST-PATCH loader_module.device = {loader_module.device}") + logger.info(f"[CLIP INSTRUMENT] POST-PATCH loader_module.offload_device = {getattr(loader_module, 'offload_device', 'NOT SET')}") + logger.info(f"[CLIP INSTRUMENT] POST-PATCH mm.get_torch_device() = {mm.get_torch_device()}") + + multigpu_memory_log("wanvideo_clip_load", "pre-load") + + logger.info(f"[CLIP INSTRUMENT] ===== CALLING ORIGINAL LOADER =====") + logger.info(f"[CLIP INSTRUMENT] Watch for DEVICE CALL and MODULE ACCESS logs below:") + + result = original_loader.loadmodel(model_name, precision, load_device) + + logger.info(f"[CLIP INSTRUMENT] ===== ORIGINAL LOADER RETURNED =====") + logger.info(f"[CLIP INSTRUMENT] Total mm.get_torch_device() calls: {call_count[0]}") + logger.info(f"[CLIP INSTRUMENT] Total module.device accesses: {access_count[0]}") + + # RESTORE ORIGINAL + mm.get_torch_device = original_mm_get_device + + multigpu_memory_log("wanvideo_clip_load", "post-load") + + # POST-LOAD STATE + if result and len(result) > 0: + clip_model = result[0] + if hasattr(clip_model, 'model'): + try: + actual_device = next(clip_model.model.parameters()).device + logger.info(f"[CLIP INSTRUMENT] ACTUAL model device after load = {actual_device}") + except Exception as e: + logger.info(f"[CLIP INSTRUMENT] Could not determine actual model device: {e}") + logger.info(f"[CLIP INSTRUMENT] Result type: {type(clip_model)}") + + logger.info(f"[CLIP INSTRUMENT] ====== END LoadWanVideoClipTextEncoder ======") + + return result + else: + logger.error(f"[CLIP INSTRUMENT] Could not get loader module - falling back") + return original_loader.loadmodel(model_name, precision, load_device) + +class WanVideoModelLoader_2: + @classmethod + def INPUT_TYPES(s): + return WanVideoModelLoader.INPUT_TYPES() + + RETURN_TYPES = WanVideoModelLoader.RETURN_TYPES + RETURN_NAMES = WanVideoModelLoader.RETURN_NAMES + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Second model loader instance for workflows using multiple models on different devices" + + def loadmodel(self, model, base_precision, device, quantization, + compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, + vram_management_args=None, vace_model=None, fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, rms_norm_function="default"): + loader = WanVideoModelLoader() + return loader.loadmodel(model, base_precision, device, quantization, + compile_args, attention_mode, block_swap_args, lora, + vram_management_args, vace_model, fantasytalking_model, multitalk_model, fantasyportrait_model, rms_norm_function) + +class WanVideoSampler: + @classmethod + def INPUT_TYPES(s): + from nodes import NODE_CLASS_MAPPINGS + original_types = NODE_CLASS_MAPPINGS["WanVideoSampler"].INPUT_TYPES() + return original_types + + RETURN_TYPES = ("LATENT", "LATENT",) + RETURN_NAMES = ("samples", "denoised_samples",) + FUNCTION = "process" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware sampler that ensures correct device for each model" + + def process(self, model, **kwargs): + model_device = model.load_device + logging.info(f"[MultiGPU] WanVideoSampler: Processing on device: {model_device}") + + for module_name in sys.modules.keys(): + if 'WanVideoWrapper' in module_name and hasattr(sys.modules[module_name], 'device'): + sys.modules[module_name].device = model_device + + from nodes import NODE_CLASS_MAPPINGS + original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() + return original_sampler.process(model, **kwargs) + +class WanVideoVACEEncode: + @classmethod + def INPUT_TYPES(s): + from nodes import NODE_CLASS_MAPPINGS + original_types = NODE_CLASS_MAPPINGS["WanVideoVACEEncode"].INPUT_TYPES() + return original_types + + RETURN_TYPES = ("LATENT",) + RETURN_NAMES = ("latent",) + FUNCTION = "process" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware VACE encoder that uses device from input VAE" + + def process(self, vae, **kwargs): + # Get device from VAE object + vae_device = vae.load_device + logging.info(f"[MultiGPU] WanVideoVACEEncode: Processing on device: {vae_device}") + + # Patch all WanVideo modules to use the VAE's device + for module_name in sys.modules.keys(): + if 'WanVideoWrapper' in module_name and hasattr(sys.modules[module_name], 'device'): + sys.modules[module_name].device = vae_device + + from nodes import NODE_CLASS_MAPPINGS + original_encoder = NODE_CLASS_MAPPINGS["WanVideoVACEEncode"]() + return original_encoder.process(vae, **kwargs) + +class WanVideoBlockSwap: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + + return { + "required": { + "blocks_to_swap": ("INT", {"default": 20, "min": 0, "max": 40, "step": 1, + "tooltip": "Number of transformer blocks to swap, the 14B model has 40, while the 1.3B model has 30 blocks"}), + "swap_device": (devices, {"default": "cpu", + "tooltip": "Device to swap blocks to during sampling (default: cpu for standard behavior)"}), + "model_offload_device": (devices, {"default": "cpu", + "tooltip": "Device to offload entire model to when done (default: cpu)"}), + "offload_img_emb": ("BOOLEAN", {"default": False, "tooltip": "Offload img_emb to swap_device"}), + "offload_txt_emb": ("BOOLEAN", {"default": False, "tooltip": "Offload time_emb to swap_device"}), + }, + "optional": { + "use_non_blocking": ("BOOLEAN", {"default": False, + "tooltip": "Use non-blocking memory transfer for offloading, reserves more RAM but is faster"}), + "vace_blocks_to_swap": ("INT", {"default": 0, "min": 0, "max": 15, "step": 1, + "tooltip": "Number of VACE blocks to swap, the VACE model has 15 blocks"}), + "prefetch_blocks": ("INT", {"default": 0, "min": 0, "max": 40, "step": 1, "tooltip": "Number of blocks to prefetch ahead, can speed up processing but increases memory usage. 1 is usually enough to offset speed loss from block swapping, use the debug option to confirm it for your system"}), + "block_swap_debug": ("BOOLEAN", {"default": False, "tooltip": "Enable debug logging for block swapping"}), + }, + } + + RETURN_TYPES = ("BLOCKSWAPARGS",) + RETURN_NAMES = ("block_swap_args",) + FUNCTION = "setargs" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Block swap settings with explicit device selection for memory management across GPUs" + + def setargs(self, blocks_to_swap, swap_device, model_offload_device, offload_img_emb, offload_txt_emb, + use_non_blocking=False, vace_blocks_to_swap=0, prefetch_blocks=0, block_swap_debug=False): + logging.debug(f"[MultiGPU] WanVideoBlockSwap: swap_device={swap_device}, model_offload_device={model_offload_device}, blocks_to_swap={blocks_to_swap}") + + selected_swap_device = torch.device(swap_device) + selected_offload_device = torch.device(model_offload_device) + + for module_name in sys.modules.keys(): + if 'WanVideoWrapper' in module_name and 'nodes_model_loading' in module_name: + module = sys.modules[module_name] + setattr(module, 'offload_device', selected_offload_device) + setattr(module, '_block_swap_device_override', selected_swap_device) + setattr(module, '_model_offload_device_override', selected_offload_device) + logging.debug(f"[MultiGPU] Patched {module_name} for offload to {selected_offload_device} and swap to {selected_swap_device}") + + if 'WanVideoWrapper' in module_name and module_name.endswith('.nodes'): + module = sys.modules[module_name] + setattr(module, 'offload_device', selected_offload_device) + setattr(module, '_block_swap_device_override', selected_swap_device) + setattr(module, '_model_offload_device_override', selected_offload_device) + + block_swap_args = { + "blocks_to_swap": blocks_to_swap, + "offload_img_emb": offload_img_emb, + "offload_txt_emb": offload_txt_emb, + "use_non_blocking": use_non_blocking, + "vace_blocks_to_swap": vace_blocks_to_swap, + "prefetch_blocks": prefetch_blocks, + "block_swap_debug": block_swap_debug, + "swap_device": swap_device, + "model_offload_device": model_offload_device, + } + + logging.info(f"[MultiGPU] WanVideoBlockSwap configuration complete") + + return (block_swap_args,) From cfe6ebc70f445a58065704b74e4255f165f6a5c0 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Mon, 6 Oct 2025 00:55:34 -0500 Subject: [PATCH 02/22] Re-coded T5 loader and WanVideo Text Encoder 2/24 complete --- __init__.py | 4 +++- wanvideo.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index a3fa9c3..d46b732 100644 --- a/__init__.py +++ b/__init__.py @@ -117,6 +117,7 @@ from .nodes import ( from .wanvideo import ( LoadWanVideoT5TextEncoder, + WanVideoTextEncode, ) from .wrappers import ( @@ -253,7 +254,8 @@ pulid_nodes = { register_and_count(["PuLID_ComfyUI", "pulid_comfyui"], pulid_nodes) wanvideo_nodes = { - "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder + "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder, + "WanVideoTextEncodeMultiGPU": WanVideoTextEncode } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index 00e2104..0157f7f 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -29,8 +29,8 @@ class LoadWanVideoT5TextEncoder: } } - RETURN_TYPES = ("WANTEXTENCODER", "STRING") - RETURN_NAMES = ("wan_t5_model", "device") + RETURN_TYPES = ("WANTEXTENCODER", "MULTIGPUDEVICE") + RETURN_NAMES = ("wan_t5_model", "load_device") FUNCTION = "loadmodel" CATEGORY = "multigpu/WanVideoWrapper" DESCRIPTION = "Loads Wan text_encoder model from 'ComfyUI/models/LLM'" @@ -46,11 +46,63 @@ class LoadWanVideoT5TextEncoder: else: load_device = "main_device" - logger.info(f"[MultiGPU WanVideoWrapper] current_device set to: {device}") - logger.info(f"[MultiGPU WanVideoWrapper] load_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][LoadWanVideoT5TextEncoder] current_device set to: {device}") + logger.info(f"[MultiGPU WanVideoWrapper][LoadWanVideoT5TextEncoder] load_device set to: {load_device}") original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoT5TextEncoder"]() text_encoder = original_loader.loadmodel(model_name, precision, load_device, quantization) # Return both the text encoder AND the selected device return text_encoder, device + + +class WanVideoTextEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "positive_prompt": ("STRING", {"default": "", "multiline": True} ), + "negative_prompt": ("STRING", {"default": "", "multiline": True} ), + }, + "optional": { + "t5": ("WANTEXTENCODER",), + "load_device": ("MULTIGPUDEVICE",), + "force_offload": ("BOOLEAN", {"default": True}), + "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), + "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), + #"device": (["gpu", "cpu"], {"default": "gpu", "tooltip": "Device to run the text encoding on."}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) + RETURN_NAMES = ("text_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Encodes text prompts into text embeddings. For rudimentary prompt travel you can input multiple prompts separated by '|', they will be equally spread over the video length" + + + def process(self, positive_prompt, negative_prompt, t5=None, load_device=None,force_offload=True, model_to_offload=None, use_disk_cache=False): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + if load_device == "cpu": + device = "cpu" + else: + + if t5 is not None: + text_encoder = t5[0] + else: + text_encoder = None + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncode] current_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncode] device set to: {device}") + + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() + prompt_embeds_dict = original_encoder.process(positive_prompt, negative_prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) + return (prompt_embeds_dict) + + def parse_prompt_weights(self, prompt): + """Extract text and weights from prompts with (text:weight) format""" + original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() + return original_parser.parse_prompt_weights(prompt) From 616a89c23f8c50caac7029d8bc83b0a9ef260f82 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Mon, 6 Oct 2025 05:53:23 -0500 Subject: [PATCH 03/22] feat: add multi-GPU WanVideo VAE loader, encode, and decode nodes - Added imports and registrations for WanVideoVAELoader, WanVideoTinyVAELoader, WanVideoImageToVideoEncode, and WanVideoDecode in multi-GPU versions. --- __init__.py | 10 +- wanvideo.py | 385 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 391 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index d46b732..99dcd60 100644 --- a/__init__.py +++ b/__init__.py @@ -118,6 +118,10 @@ from .nodes import ( from .wanvideo import ( LoadWanVideoT5TextEncoder, WanVideoTextEncode, + WanVideoVAELoader, + WanVideoTinyVAELoader, + WanVideoImageToVideoEncode, + WanVideoDecode, ) from .wrappers import ( @@ -255,7 +259,11 @@ register_and_count(["PuLID_ComfyUI", "pulid_comfyui"], pulid_nodes) wanvideo_nodes = { "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder, - "WanVideoTextEncodeMultiGPU": WanVideoTextEncode + "WanVideoTextEncodeMultiGPU": WanVideoTextEncode, + "WanVideoVAELoaderMultiGPU": WanVideoVAELoader, + "WanVideoTinyVAELoaderMultiGPU": WanVideoTinyVAELoader, + "WanVideoImageToVideoEncodeMultiGPU": WanVideoImageToVideoEncode, + "WanVideoDecodeMultiGPU": WanVideoDecode, } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index 0157f7f..5421f81 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -7,6 +7,8 @@ import comfy.model_management as mm from nodes import NODE_CLASS_MAPPINGS from .device_utils import get_device_list from .model_management_mgpu import multigpu_memory_log +import gc + logger = logging.getLogger("MultiGPU") @@ -79,7 +81,6 @@ class WanVideoTextEncode: CATEGORY = "multigpu/WanVideoWrapper" DESCRIPTION = "Encodes text prompts into text embeddings. For rudimentary prompt travel you can input multiple prompts separated by '|', they will be equally spread over the video length" - def process(self, positive_prompt, negative_prompt, t5=None, load_device=None,force_offload=True, model_to_offload=None, use_disk_cache=False): from . import set_current_device @@ -89,14 +90,15 @@ class WanVideoTextEncode: if load_device == "cpu": device = "cpu" else: + device = "gpu" if t5 is not None: text_encoder = t5[0] else: text_encoder = None - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncode] current_device set to: {load_device}") - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncode] device set to: {device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] current_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] device set to: {device}") original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() prompt_embeds_dict = original_encoder.process(positive_prompt, negative_prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) @@ -106,3 +108,380 @@ class WanVideoTextEncode: """Extract text and weights from prompts with (text:weight) format""" original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() return original_parser.parse_prompt_weights(prompt) + +class WanVideoVAELoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model_name": (folder_paths.get_filename_list("vae"), {"tooltip": "These models are loaded from 'ComfyUI/models/vae'"}), + }, + "optional": { + "load_device": (devices, {"default": default_device}), + "precision": (["fp16", "fp32", "bf16"], + {"default": "bf16"} + ), + "compile_args": ("WANCOMPILEARGS", ), + } + } + + RETURN_TYPES = ("WANVAE", "MULTIGPUDEVICE",) + RETURN_NAMES = ("vae", "load_device",) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Loads Wan VAE model from 'ComfyUI/models/vae'" + + def loadmodel(self, model_name, load_device=None, precision="fp16", compile_args=None): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoVAELoader] load_device set to: {load_device}") + + original_loader = NODE_CLASS_MAPPINGS["WanVideoVAELoader"]() + vae_model = original_loader.loadmodel(model_name, precision, compile_args) + + # Return both the VAE model AND the selected device for device propagation + return vae_model, load_device + +class WanVideoTinyVAELoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model_name": (folder_paths.get_filename_list("vae_approx"), {"tooltip": "These models are loaded from 'ComfyUI/models/vae_approx'"}), + }, + "optional": { + "load_device": (devices, {"default": default_device}), + "precision": (["fp16", "fp32", "bf16"], {"default": "fp16"}), + "parallel": ("BOOLEAN", {"default": False, "tooltip": "uses more memory but is faster"}), + } + } + + RETURN_TYPES = ("WANVAE","MULTIGPUDEVICE") + RETURN_NAMES = ("vae", "load_device") + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Loads Wan VAE model from 'ComfyUI/models/vae_approx'" + + def loadmodel(self, model_name, load_device=None, precision="fp16", parallel=False): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTinyVAELoader] load_device set to: {load_device}") + + original_loader = NODE_CLASS_MAPPINGS["WanVideoTinyVAELoader"]() + vae_model = original_loader.loadmodel(model_name, precision, parallel) + + # Return both the VAE model AND the selected device for device propagation + return vae_model, load_device + +class WanVideoImageToVideoEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "width": ("INT", {"default": 832, "min": 64, "max": 8096, "step": 8, "tooltip": "Width of the image to encode"}), + "height": ("INT", {"default": 480, "min": 64, "max": 8096, "step": 8, "tooltip": "Height of the image to encode"}), + "num_frames": ("INT", {"default": 81, "min": 1, "max": 10000, "step": 4, "tooltip": "Number of frames to encode"}), + "noise_aug_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Strength of noise augmentation, helpful for I2V where some noise can add motion and give sharper results"}), + "start_latent_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Additional latent multiplier, helpful for I2V where lower values allow for more motion"}), + "end_latent_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Additional latent multiplier, helpful for I2V where lower values allow for more motion"}), + "force_offload": ("BOOLEAN", {"default": True}), + }, + "optional": { + "vae": ("WANVAE",), + "load_device": ("MULTIGPUDEVICE",), + "clip_embeds": ("WANVIDIMAGE_CLIPEMBEDS", {"tooltip": "Clip vision encoded image"}), + "start_image": ("IMAGE", {"tooltip": "Image to encode"}), + "end_image": ("IMAGE", {"tooltip": "end frame"}), + "control_embeds": ("WANVIDIMAGE_EMBEDS", {"tooltip": "Control signal for the Fun -model"}), + "fun_or_fl2v_model": ("BOOLEAN", {"default": True, "tooltip": "Enable when using official FLF2V or Fun model"}), + "temporal_mask": ("MASK", {"tooltip": "mask"}), + "extra_latents": ("LATENT", {"tooltip": "Extra latents to add to the input front, used for Skyreels A2 reference images"}), + "tiled_vae": ("BOOLEAN", {"default": False, "tooltip": "Use tiled VAE encoding for reduced memory use"}), + "add_cond_latents": ("ADD_COND_LATENTS", {"advanced": True, "tooltip": "Additional cond latents WIP"}), + } + } + + RETURN_TYPES = ("WANVIDIMAGE_EMBEDS",) + RETURN_NAMES = ("image_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + + def process(self, width, height, num_frames, force_offload, noise_aug_strength, + start_latent_strength, end_latent_strength, start_image=None, end_image=None, control_embeds=None, fun_or_fl2v_model=False, + temporal_mask=None, extra_latents=None, clip_embeds=None, tiled_vae=False, add_cond_latents=None, vae=None, load_device=None): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] load device: {load_device}") + + device = mm.get_torch_device() + PATCH_SIZE = (1, 2, 2) + offload_device = mm.unet_offload_device() + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] torch device: {device}") + + if vae is not None: + vae = vae[0] + + if start_image is None and end_image is None and add_cond_latents is None: + return WanVideoEmptyEmbeds().process( + num_frames, width, height, control_embeds=control_embeds, extra_latents=extra_latents, + ) + if vae is None: + raise ValueError("VAE is required for image encoding.") + H = height + W = width + + lat_h = H // vae.upsampling_factor + lat_w = W // vae.upsampling_factor + + num_frames = ((num_frames - 1) // 4) * 4 + 1 + two_ref_images = start_image is not None and end_image is not None + + if start_image is None and end_image is not None: + fun_or_fl2v_model = True # end image alone only works with this option + + base_frames = num_frames + (1 if two_ref_images and not fun_or_fl2v_model else 0) + if temporal_mask is None: + mask = torch.zeros(1, base_frames, lat_h, lat_w, device=device, dtype=vae.dtype) + if start_image is not None: + mask[:, 0:start_image.shape[0]] = 1 # First frame + if end_image is not None: + mask[:, -end_image.shape[0]:] = 1 # End frame if exists + else: + mask = common_upscale(temporal_mask.unsqueeze(1).to(device), lat_w, lat_h, "nearest", "disabled").squeeze(1) + if mask.shape[0] > base_frames: + mask = mask[:base_frames] + elif mask.shape[0] < base_frames: + mask = torch.cat([mask, torch.zeros(base_frames - mask.shape[0], lat_h, lat_w, device=device)]) + mask = mask.unsqueeze(0).to(device, vae.dtype) + + # Repeat first frame and optionally end frame + start_mask_repeated = torch.repeat_interleave(mask[:, 0:1], repeats=4, dim=1) # T, C, H, W + if end_image is not None and not fun_or_fl2v_model: + end_mask_repeated = torch.repeat_interleave(mask[:, -1:], repeats=4, dim=1) # T, C, H, W + mask = torch.cat([start_mask_repeated, mask[:, 1:-1], end_mask_repeated], dim=1) + else: + mask = torch.cat([start_mask_repeated, mask[:, 1:]], dim=1) + + # Reshape mask into groups of 4 frames + mask = mask.view(1, mask.shape[1] // 4, 4, lat_h, lat_w) # 1, T, C, H, W + mask = mask.movedim(1, 2)[0]# C, T, H, W + + # Resize and rearrange the input image dimensions + if start_image is not None: + start_image = start_image[..., :3] + if start_image.shape[1] != H or start_image.shape[2] != W: + resized_start_image = common_upscale(start_image.movedim(-1, 1), W, H, "lanczos", "disabled").movedim(0, 1) + else: + resized_start_image = start_image.permute(3, 0, 1, 2) # C, T, H, W + resized_start_image = resized_start_image * 2 - 1 + if noise_aug_strength > 0.0: + resized_start_image = add_noise_to_reference_video(resized_start_image, ratio=noise_aug_strength) + + if end_image is not None: + end_image = end_image[..., :3] + if end_image.shape[1] != H or end_image.shape[2] != W: + resized_end_image = common_upscale(end_image.movedim(-1, 1), W, H, "lanczos", "disabled").movedim(0, 1) + else: + resized_end_image = end_image.permute(3, 0, 1, 2) # C, T, H, W + resized_end_image = resized_end_image * 2 - 1 + if noise_aug_strength > 0.0: + resized_end_image = add_noise_to_reference_video(resized_end_image, ratio=noise_aug_strength) + + # Concatenate image with zero frames and encode + if temporal_mask is None: + if start_image is not None and end_image is None: + zero_frames = torch.zeros(3, num_frames-start_image.shape[0], H, W, device=device, dtype=vae.dtype) + concatenated = torch.cat([resized_start_image.to(device, dtype=vae.dtype), zero_frames], dim=1) + del resized_start_image, zero_frames + elif start_image is None and end_image is not None: + zero_frames = torch.zeros(3, num_frames-end_image.shape[0], H, W, device=device, dtype=vae.dtype) + concatenated = torch.cat([zero_frames, resized_end_image.to(device, dtype=vae.dtype)], dim=1) + del zero_frames + elif start_image is None and end_image is None: + concatenated = torch.zeros(3, num_frames, H, W, device=device, dtype=vae.dtype) + else: + if fun_or_fl2v_model: + zero_frames = torch.zeros(3, num_frames-(start_image.shape[0]+end_image.shape[0]), H, W, device=device, dtype=vae.dtype) + else: + zero_frames = torch.zeros(3, num_frames-1, H, W, device=device, dtype=vae.dtype) + concatenated = torch.cat([resized_start_image.to(device, dtype=vae.dtype), zero_frames, resized_end_image.to(device, dtype=vae.dtype)], dim=1) + del resized_start_image, zero_frames + else: + temporal_mask = common_upscale(temporal_mask.unsqueeze(1), W, H, "nearest", "disabled").squeeze(1) + concatenated = resized_start_image[:,:num_frames].to(vae.dtype) * temporal_mask[:num_frames].unsqueeze(0).to(vae.dtype) + del resized_start_image, temporal_mask + + mm.soft_empty_cache() + gc.collect() + + vae.to(device) + y = vae.encode([concatenated], device, end_=(end_image is not None and not fun_or_fl2v_model),tiled=tiled_vae)[0] + del concatenated + + has_ref = False + if extra_latents is not None: + samples = extra_latents["samples"].squeeze(0) + y = torch.cat([samples, y], dim=1) + mask = torch.cat([torch.ones_like(mask[:, 0:samples.shape[1]]), mask], dim=1) + num_frames += samples.shape[1] * 4 + has_ref = True + y[:, :1] *= start_latent_strength + y[:, -1:] *= end_latent_strength + + # Calculate maximum sequence length + patches_per_frame = lat_h * lat_w // (PATCH_SIZE[1] * PATCH_SIZE[2]) + frames_per_stride = (num_frames - 1) // 4 + (2 if end_image is not None and not fun_or_fl2v_model else 1) + max_seq_len = frames_per_stride * patches_per_frame + + if add_cond_latents is not None: + add_cond_latents["ref_latent_neg"] = vae.encode(torch.zeros(1, 3, 1, H, W, device=device, dtype=vae.dtype), device) + + if force_offload: + vae.model.to(offload_device) + mm.soft_empty_cache() + gc.collect() + + image_embeds = { + "image_embeds": y, + "clip_context": clip_embeds.get("clip_embeds", None) if clip_embeds is not None else None, + "negative_clip_context": clip_embeds.get("negative_clip_embeds", None) if clip_embeds is not None else None, + "max_seq_len": max_seq_len, + "num_frames": num_frames, + "lat_h": lat_h, + "lat_w": lat_w, + "control_embeds": control_embeds["control_embeds"] if control_embeds is not None else None, + "end_image": resized_end_image if end_image is not None else None, + "fun_or_fl2v_model": fun_or_fl2v_model, + "has_ref": has_ref, + "add_cond_latents": add_cond_latents, + "mask": mask + } + + return (image_embeds,) + +class WanVideoDecode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "vae": ("WANVAE",), + "load_device": ("MULTIGPUDEVICE",), + "samples": ("LATENT",), + "enable_vae_tiling": ("BOOLEAN", {"default": False, "tooltip": ( + "Drastically reduces memory use but will introduce seams at tile stride boundaries. " + "The location and number of seams is dictated by the tile stride size. " + "The visibility of seams can be controlled by increasing the tile size. " + "Seams become less obvious at 1.5x stride and are barely noticeable at 2x stride size. " + "Which is to say if you use a stride width of 160, the seams are barely noticeable with a tile width of 320." + )}), + "tile_x": ("INT", {"default": 272, "min": 40, "max": 2048, "step": 8, "tooltip": "Tile width in pixels. Smaller values use less VRAM but will make seams more obvious."}), + "tile_y": ("INT", {"default": 272, "min": 40, "max": 2048, "step": 8, "tooltip": "Tile height in pixels. Smaller values use less VRAM but will make seams more obvious."}), + "tile_stride_x": ("INT", {"default": 144, "min": 32, "max": 2040, "step": 8, "tooltip": "Tile stride width in pixels. Smaller values use less VRAM but will introduce more seams."}), + "tile_stride_y": ("INT", {"default": 128, "min": 32, "max": 2040, "step": 8, "tooltip": "Tile stride height in pixels. Smaller values use less VRAM but will introduce more seams."}), + }, + "optional": { + "normalization": (["default", "minmax"], {"advanced": True}), + } + } + + @classmethod + def VALIDATE_INPUTS(s, tile_x, tile_y, tile_stride_x, tile_stride_y): + if tile_x <= tile_stride_x: + return "Tile width must be larger than the tile stride width." + if tile_y <= tile_stride_y: + return "Tile height must be larger than the tile stride height." + return True + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "decode" + CATEGORY = "multigpu/WanVideoWrapper" + + def decode(self, vae, load_device, samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization="default"): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] load device: {load_device}") + + device = mm.get_torch_device() + PATCH_SIZE = (1, 2, 2) + offload_device = mm.unet_offload_device() + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] torch device: {device}") + + if vae is not None: + vae = vae[0] + + mm.soft_empty_cache() + video = samples.get("video", None) + if video is not None: + video.clamp_(-1.0, 1.0) + video.add_(1.0).div_(2.0) + return video.cpu().float(), + latents = samples["samples"] + end_image = samples.get("end_image", None) + has_ref = samples.get("has_ref", False) + drop_last = samples.get("drop_last", False) + is_looped = samples.get("looped", False) + + vae.to(device) + + latents = latents.to(device = device, dtype = vae.dtype) + + mm.soft_empty_cache() + + if has_ref: + latents = latents[:, :, 1:] + if drop_last: + latents = latents[:, :, :-1] + + if type(vae).__name__ == "TAEHV": + images = vae.decode_video(latents.permute(0, 2, 1, 3, 4))[0].permute(1, 0, 2, 3) + images = torch.clamp(images, 0.0, 1.0) + images = images.permute(1, 2, 3, 0).cpu().float() + return (images,) + else: + if end_image is not None: + enable_vae_tiling = False + images = vae.decode(latents, device=device, end_=(end_image is not None), tiled=enable_vae_tiling, tile_size=(tile_x//8, tile_y//8), tile_stride=(tile_stride_x//8, tile_stride_y//8))[0] + + + images = images.cpu().float() + + if normalization == "minmax": + images.sub_(images.min()).div_(images.max() - images.min()) + else: + images.clamp_(-1.0, 1.0) + images.add_(1.0).div_(2.0) + + if is_looped: + temp_latents = torch.cat([latents[:, :, -3:]] + [latents[:, :, :2]], dim=2) + temp_images = vae.decode(temp_latents, device=device, end_=(end_image is not None), tiled=enable_vae_tiling, tile_size=(tile_x//vae.upsampling_factor, tile_y//vae.upsampling_factor), tile_stride=(tile_stride_x//vae.upsampling_factor, tile_stride_y//vae.upsampling_factor))[0] + temp_images = temp_images.cpu().float() + temp_images = (temp_images - temp_images.min()) / (temp_images.max() - temp_images.min()) + images = torch.cat([temp_images[:, 9:].to(images), images[:, 5:]], dim=1) + + if end_image is not None: + images = images[:, 0:-1] + + + vae.to(offload_device) + mm.soft_empty_cache() + + images.clamp_(0.0, 1.0) + + return (images.permute(1, 2, 3, 0),) From b034901a05e914b727a38968b7eb556f5198aaad Mon Sep 17 00:00:00 2001 From: John Pollock Date: Tue, 7 Oct 2025 04:00:12 -0500 Subject: [PATCH 04/22] Housecleaning --- .gitignore | 3 +- __init__.py | 4 ++ wanvideo.py | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 172 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8ac9c09..2c3d9a0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __pycache__/ .clinerules .vscode -memory-bank/ \ No newline at end of file +memory-bank/ +.github/ \ No newline at end of file diff --git a/__init__.py b/__init__.py index 99dcd60..f110ab7 100644 --- a/__init__.py +++ b/__init__.py @@ -122,6 +122,8 @@ from .wanvideo import ( WanVideoTinyVAELoader, WanVideoImageToVideoEncode, WanVideoDecode, + WanVideoModelLoader, + WanVideoSampler, ) from .wrappers import ( @@ -264,6 +266,8 @@ wanvideo_nodes = { "WanVideoTinyVAELoaderMultiGPU": WanVideoTinyVAELoader, "WanVideoImageToVideoEncodeMultiGPU": WanVideoImageToVideoEncode, "WanVideoDecodeMultiGPU": WanVideoDecode, + "WanVideoModelLoaderMultiGPU": WanVideoModelLoader, + "WanVideoSamplerMultiGPU": WanVideoSampler } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index 5421f81..101d85a 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -1,3 +1,33 @@ +""" + WanVideoControlnetLoader, controlnet/nodes.py + FantasyTalkingModelLoader, fantasytalking/nodes.py + MultiTalkModelLoader, multitalk/nodes.py + Wav2VecModelLoader, multitalk/nodes.py + WanVideoSetBlockSwap, nodes.py + WanVideoBlockList, nodes.py + WanVideoTextEncodeCached, nodes.py + X WanVideoTextEncode, nodes.py + WanVideoTextEncodeSingle, nodes.py + WanVideoClipVisionEncode, nodes.py + X WanVideoImageToVideoEncode, nodes.py + WanVideoVACEEncode, nodes.py + X WanVideoDecode, nodes.py + WanVideoImageClipEncode, nodes_deprecated.py + WanVideoModelLoader, nodes_model_loading.py + X WanVideoVAELoader, nodes_model_loading.py + WanVideoLoraBlockEdit, nodes_model_loading.py + X WanVideoTinyVAELoader, nodes_model_loading.py + WanVideoBlockSwap, nodes_model_loading.py + WanVideoVRAMManagement, nodes_model_loading.py + WanVideoTorchCompileSettings, nodes_model_loading.py + X LoadWanVideoT5TextEncoder, nodes_model_loading.py + LoadWanVideoClipTextEncoder, nodes_model_loading.py + WanVideoUni3C_ControlnetLoader, uni3c/nodes.py + """ + + + + import logging import torch import sys @@ -7,12 +37,34 @@ import comfy.model_management as mm from nodes import NODE_CLASS_MAPPINGS from .device_utils import get_device_list from .model_management_mgpu import multigpu_memory_log +from comfy.utils import load_torch_file, ProgressBar import gc +import numpy as np +from accelerate import init_empty_weights +import os +import importlib.util + +scheduler_list = [ + "unipc", "unipc/beta", + "dpm++", "dpm++/beta", + "dpm++_sde", "dpm++_sde/beta", + "euler", "euler/beta", + "deis", + "lcm", "lcm/beta", + "res_multistep", + "flowmatch_causvid", + "flowmatch_distill", + "flowmatch_pusa", + "multitalk", + "sa_ode_stable" +] + +rope_functions = ["default", "comfy", "comfy_chunked"] + logger = logging.getLogger("MultiGPU") - class LoadWanVideoT5TextEncoder: @classmethod def INPUT_TYPES(s): @@ -425,7 +477,7 @@ class WanVideoDecode: if vae is not None: vae = vae[0] - + mm.soft_empty_cache() video = samples.get("video", None) if video is not None: @@ -485,3 +537,115 @@ class WanVideoDecode: images.clamp_(0.0, 1.0) return (images.permute(1, 2, 3, 0),) +class WanVideoModelLoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + # Get the original node's input types to stay up-to-date + original_types = NODE_CLASS_MAPPINGS["WanVideoModelLoader"].INPUT_TYPES() + + # Update with our custom device selection + original_types["required"]["compute_device"] = (devices, {"default": default_device}) + + return original_types + + RETURN_TYPES = ("WANVIDEOMODEL", "MULTIGPUDEVICE",) + RETURN_NAMES = ("model", "compute_device",) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + + def loadmodel(self, model, base_precision, compute_device, quantization, load_device, + compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, + vram_management_args=None, extra_model=None, vace_model=None, + fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, + rms_norm_function="default"): + from . import set_current_device + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}") + + selected_device = torch.device(compute_device) + + # Set the global device context for any downstream operations + set_current_device(selected_device) + + # Find the original loader and its module + original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() + loader_module = inspect.getmodule(original_loader) + + if loader_module: + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}' to use device: {selected_device}") + + # Store original values to restore later if needed, though it's less critical in this workflow + original_module_device = getattr(loader_module, 'device', None) + + # Overwrite the module-level 'device' variable. This is the key to the fix. + setattr(loader_module, 'device', selected_device) + + # Also patch the offload device if the user chose CPU + if compute_device == "cpu": + setattr(loader_module, 'offload_device', selected_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Device patching complete. Calling original loader...") + + # Call the original loader function with all the arguments it expects + result = original_loader.loadmodel( + model, base_precision, load_device, quantization, + compile_args, attention_mode, block_swap_args, lora, + vram_management_args, extra_model=extra_model, vace_model=vace_model, + fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, + fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function + ) + + # Restore the original device if you want to be a good citizen, though not strictly necessary + # if the execution context is self-contained. + if original_module_device is not None: + setattr(loader_module, 'device', original_module_device) + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] WanVideo model loaded on {selected_device}") + + # Return the model and the device for the sampler + return (result[0], compute_device) + else: + logger.error("[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Could not find the module for WanVideoModelLoader to patch.") + # Fallback to original behavior without patching + return (original_loader.loadmodel( + model, base_precision, load_device, quantization, + compile_args, attention_mode, block_swap_args, lora, + vram_management_args, extra_model=extra_model, vace_model=vace_model, + fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, + fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function + ), compute_device) + + +class WanVideoSampler: + @classmethod + def INPUT_TYPES(s): + # Get original inputs and add our device input + original_types = NODE_CLASS_MAPPINGS["WanVideoSampler"].INPUT_TYPES() + original_types["required"]["compute_device"] = ("MULTIGPUDEVICE",) + return original_types + + RETURN_TYPES = ("LATENT", "LATENT",) + RETURN_NAMES = ("samples", "denoised_samples",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware sampler that ensures correct device for each model" + + def process(self, model, compute_device, **kwargs): + from . import set_current_device + + logger.info(f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}") + + # Set the global device context for the sampler's operations + if compute_device: + set_current_device(torch.device(compute_device)) + + # The model is already on the correct device thanks to the patched loader. + # We no longer need to patch the model here. We just need to call the original sampler. + logger.info("[MultiGPU WanVideoSampler] Model is pre-configured. Calling original sampler.") + + original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() + + # The original sampler will internally use mm.get_torch_device(), which is now correctly set. + return original_sampler.process(model=model, **kwargs) From 27cfb8373312f1e1bc96628024cdba00fe432922 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Tue, 7 Oct 2025 13:19:09 -0500 Subject: [PATCH 05/22] feat: add MultiGPU logging enhancements and new CI scripts for workflow execution and log summarization, WanVidwoWrapper Model Loader/Sampler re-implemented --- __init__.py | 97 ++++++++++++++++++- ci/extract_allocation.py | 62 ++++++++++++ ci/run_workflows.py | 198 +++++++++++++++++++++++++++++++++++++++ ci/smoke_test.sh | 30 ++++++ ci/summarize_log.py | 59 ++++++++++++ wanvideo.py | 82 ++++++++-------- 6 files changed, 486 insertions(+), 42 deletions(-) create mode 100644 ci/extract_allocation.py create mode 100644 ci/run_workflows.py create mode 100644 ci/smoke_test.sh create mode 100644 ci/summarize_log.py diff --git a/__init__.py b/__init__.py index f110ab7..834a750 100644 --- a/__init__.py +++ b/__init__.py @@ -3,6 +3,8 @@ import logging import weakref import os import copy +import json +from datetime import datetime from pathlib import Path import folder_paths import comfy.model_management as mm @@ -27,6 +29,16 @@ DEBUG_LOG = False logger = logging.getLogger("MultiGPU") logger.propagate = False +FOCUS_LOG_LEVEL = logging.INFO + 5 +logging.addLevelName(FOCUS_LOG_LEVEL, "FOCUS") + +if not hasattr(logging.Logger, "focus"): + def focus(self, message, *args, **kwargs): + if self.isEnabledFor(FOCUS_LOG_LEVEL): + self._log(FOCUS_LOG_LEVEL, message, args, **kwargs) + + logging.Logger.focus = focus # type: ignore[attr-defined] + if not logger.handlers: log_level = logging.DEBUG if DEBUG_LOG else logging.INFO handler = logging.StreamHandler() @@ -35,10 +47,93 @@ if not logger.handlers: logger.addHandler(handler) logger.setLevel(log_level) + json_log_path = os.environ.get("MGPU_JSON_LOG_PATH") + json_static_fields = {} + if json_log_path: + try: + json_static_fields = json.loads(os.environ.get("MGPU_JSON_STATIC_FIELDS", "{}")) + except json.JSONDecodeError: + json_static_fields = {} + + level_aliases = { + "CRITICAL": logging.CRITICAL, + "ERROR": logging.ERROR, + "WARNING": logging.WARNING, + "FOCUS": FOCUS_LOG_LEVEL, + "INFO": logging.INFO, + "DEBUG": logging.DEBUG, + } + + json_min_level = FOCUS_LOG_LEVEL + configured_min_level = os.environ.get("MGPU_JSON_MIN_LEVEL") + if configured_min_level: + value = configured_min_level.strip() + upper_value = value.upper() + if upper_value in level_aliases: + json_min_level = level_aliases[upper_value] + else: + try: + json_min_level = int(value) + except ValueError: + json_min_level = FOCUS_LOG_LEVEL + + class JsonLineFileHandler(logging.Handler): + def __init__(self, path, static_fields, min_level, overwrite): + super().__init__() + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self.static_fields = static_fields + self.setLevel(min_level) + if overwrite: + try: + with self.path.open("w", encoding="utf-8") as handle: + handle.write("") + except OSError: + pass + + def emit(self, record): + message = record.getMessage() + category = None + if message.startswith("[") and "]" in message: + bracket_split = message.split("]", 1) + category = bracket_split[0].strip("[]") + payload = { + "timestamp": datetime.utcnow().isoformat() + "Z", + "level": record.levelname, + "name": record.name, + "message": message, + } + if category: + payload["event_category"] = category + if hasattr(record, "mgpu_context") and isinstance(record.mgpu_context, dict): + payload.update(record.mgpu_context) + workflow_id = os.environ.get("MGPU_JSON_WORKFLOW") + prompt_id = os.environ.get("MGPU_JSON_PROMPT") + if workflow_id: + payload.setdefault("workflow_id", workflow_id) + if prompt_id: + payload.setdefault("prompt_id", prompt_id) + if self.static_fields: + payload.update(self.static_fields) + try: + with self.path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(payload, ensure_ascii=True) + "\n") + except OSError: + # Fail silently for JSON logging so primary logging continues. + pass + + overwrite_value = os.environ.get("MGPU_JSON_OVERWRITE", "true").strip().lower() + overwrite_enabled = overwrite_value not in {"0", "false", "no"} + + logger.addHandler(JsonLineFileHandler(json_log_path, json_static_fields, json_min_level, overwrite_enabled)) + def mgpu_mm_log_method(self, msg): """Add MultiGPU model management logging method to logger instance.""" if MGPU_MM_LOG: - self.info(f"[MultiGPU Model Management] {msg}") + self.focus( + f"[MultiGPU Model Management] {msg}", + extra={"mgpu_context": {"component": "model_management"}}, + ) logger.mgpu_mm_log = mgpu_mm_log_method.__get__(logger, type(logger)) def check_module_exists(module_path): diff --git a/ci/extract_allocation.py b/ci/extract_allocation.py new file mode 100644 index 0000000..a11c05e --- /dev/null +++ b/ci/extract_allocation.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Filter MultiGPU JSON logs for allocation summaries.""" + +import argparse +import json +from pathlib import Path +from typing import Iterable, Iterator, Dict, Any + + +def load_json_lines(path: Path) -> Iterator[Dict[str, Any]]: + with path.open("r", encoding="utf-8") as handle: + for line in handle: + line = line.strip() + if not line: + continue + try: + yield json.loads(line) + except json.JSONDecodeError: + continue + + +def is_allocation_event(entry: Dict[str, Any], keywords: Iterable[str]) -> bool: + message = entry.get("message", "") + return any(keyword in message for keyword in keywords) + + +def main() -> int: + parser = argparse.ArgumentParser(description="Extract allocation-related events from MultiGPU JSON logs") + parser.add_argument("logfile", type=Path, help="Path to JSONL log produced by MGPU_JSON_LOG_PATH") + parser.add_argument( + "--keywords", + nargs="*", + default=["Final Allocation String", "Total memory", "Virtual VRAM"], + help="Keywords that mark allocation events", + ) + args = parser.parse_args() + + entries = list(load_json_lines(args.logfile)) + if not entries: + print("No entries found in log file.") + return 0 + + matched = [entry for entry in entries if is_allocation_event(entry, args.keywords)] + if not matched: + print("No allocation events matched provided keywords.") + return 0 + + for entry in matched: + timestamp = entry.get("timestamp", "unknown") + category = entry.get("event_category", "") + component = entry.get("component", "") + header_bits = [bit for bit in (timestamp, category, component) if bit] + header = " | ".join(header_bits) if header_bits else "allocation" + print(f"## {header}") + print(entry.get("message", "")) + print() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ci/run_workflows.py b/ci/run_workflows.py new file mode 100644 index 0000000..d7f4cd3 --- /dev/null +++ b/ci/run_workflows.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Minimal ComfyUI workflow runner for CI smoke tests.""" + +import argparse +import json +import os +import sys +import time +import uuid +from pathlib import Path +from typing import Iterable, Optional + +import requests +import websocket + +DEFAULT_HOST = os.environ.get("COMFYUI_HOST", "127.0.0.1") +DEFAULT_PORT = int(os.environ.get("COMFYUI_PORT", "8188")) +DEFAULT_CONNECT_TIMEOUT = int(os.environ.get("COMFYUI_CONNECT_TIMEOUT", "60")) +DEFAULT_WORKFLOW_TIMEOUT = int(os.environ.get("COMFYUI_WORKFLOW_TIMEOUT", "900")) + + +class ComfyWorkflowRunner: + def __init__(self, host: str, port: int, connect_timeout: int, workflow_timeout: int) -> None: + self.host = host + self.port = port + self.base_http = f"http://{host}:{port}" + self.base_ws = f"ws://{host}:{port}/ws" + self.connect_timeout = connect_timeout + self.workflow_timeout = workflow_timeout + self.client_id = str(uuid.uuid4()) + self.session = requests.Session() + self.websocket: Optional[websocket.WebSocket] = None + + def wait_for_server(self) -> None: + deadline = time.monotonic() + self.connect_timeout + while time.monotonic() < deadline: + try: + response = self.session.get(f"{self.base_http}/system_stats", timeout=5) + if response.status_code == 200: + return + except requests.RequestException: + time.sleep(1) + raise TimeoutError(f"ComfyUI server not reachable at {self.base_http}") + + def open_websocket(self) -> None: + ws = websocket.WebSocket() + ws.settimeout(5) + ws.connect(f"{self.base_ws}?clientId={self.client_id}") + self.websocket = ws + + def close_websocket(self) -> None: + if self.websocket: + try: + self.websocket.close() + finally: + self.websocket = None + + def queue_prompt(self, prompt: dict) -> str: + payload = {"prompt": prompt, "client_id": self.client_id} + response = self.session.post(f"{self.base_http}/prompt", json=payload, timeout=15) + response.raise_for_status() + data = response.json() + prompt_id = data.get("prompt_id") + if not prompt_id: + raise RuntimeError("No prompt_id returned from ComfyUI") + return prompt_id + + def wait_for_completion(self, prompt_id: str) -> bool: + if not self.websocket: + raise RuntimeError("WebSocket connection not established") + deadline = time.monotonic() + self.workflow_timeout + ws = self.websocket + while time.monotonic() < deadline: + try: + message = ws.recv() + except websocket.WebSocketTimeoutException: + continue + except Exception as exc: # noqa: BLE001 + print(f"WebSocket error: {exc}", file=sys.stderr, flush=True) + return False + + if isinstance(message, bytes): + continue + + try: + payload = json.loads(message) + except json.JSONDecodeError: + continue + + message_type = payload.get("type") + data = payload.get("data", {}) + + if message_type == "execution_error": + if data.get("prompt_id") == prompt_id: + print(f"Execution error: {payload}", file=sys.stderr, flush=True) + return False + elif message_type == "status" and data.get("status") == "error": + if data.get("prompt_id") == prompt_id: + print(f"Status error: {payload}", file=sys.stderr, flush=True) + return False + elif message_type == "executing": + if data.get("prompt_id") == prompt_id and data.get("node") is None: + return True + print("Workflow timed out", file=sys.stderr, flush=True) + return False + + def run_workflow(self, workflow_path: Path) -> bool: + previous_workflow = os.environ.get("MGPU_JSON_WORKFLOW") + previous_prompt = os.environ.get("MGPU_JSON_PROMPT") + + def restore_env() -> None: + if previous_workflow is None: + os.environ.pop("MGPU_JSON_WORKFLOW", None) + else: + os.environ["MGPU_JSON_WORKFLOW"] = previous_workflow + if previous_prompt is None: + os.environ.pop("MGPU_JSON_PROMPT", None) + else: + os.environ["MGPU_JSON_PROMPT"] = previous_prompt + + if workflow_path: + os.environ["MGPU_JSON_WORKFLOW"] = workflow_path.name + try: + with workflow_path.open("r", encoding="utf-8") as handle: + workflow = json.load(handle) + except (OSError, json.JSONDecodeError) as exc: + print(f"Failed to load workflow {workflow_path}: {exc}", file=sys.stderr, flush=True) + restore_env() + return False + + print(f"Running workflow {workflow_path}", flush=True) + start = time.monotonic() + try: + prompt_id = self.queue_prompt(workflow) + os.environ["MGPU_JSON_PROMPT"] = prompt_id + except requests.HTTPError as exc: + print(f"HTTP error while queueing workflow: {exc}", file=sys.stderr, flush=True) + restore_env() + return False + except requests.RequestException as exc: + print(f"Request error while queueing workflow: {exc}", file=sys.stderr, flush=True) + restore_env() + return False + except RuntimeError as exc: + print(str(exc), file=sys.stderr, flush=True) + restore_env() + return False + + try: + if not self.wait_for_completion(prompt_id): + return False + duration = time.monotonic() - start + print(f"Workflow {workflow_path} completed in {duration:.2f}s", flush=True) + return True + finally: + restore_env() + + def run_suite(self, workflows: Iterable[Path], fail_fast: bool) -> bool: + self.wait_for_server() + self.open_websocket() + try: + overall = True + for workflow in workflows: + ok = self.run_workflow(workflow) + if not ok: + overall = False + if fail_fast: + break + return overall + finally: + self.close_websocket() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Run ComfyUI workflows via the HTTP/WebSocket API") + parser.add_argument("workflows", nargs="+", type=Path, help="Workflow files in ComfyUI API JSON format") + parser.add_argument("--host", default=DEFAULT_HOST, help="ComfyUI HTTP host") + parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="ComfyUI HTTP port") + parser.add_argument("--connect-timeout", type=int, default=DEFAULT_CONNECT_TIMEOUT, help="Seconds to wait for the server to come online") + parser.add_argument("--workflow-timeout", type=int, default=DEFAULT_WORKFLOW_TIMEOUT, help="Seconds to wait for each workflow to finish") + parser.add_argument("--fail-fast", action="store_true", help="Stop on first workflow failure") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + runner = ComfyWorkflowRunner( + host=args.host, + port=args.port, + connect_timeout=args.connect_timeout, + workflow_timeout=args.workflow_timeout, + ) + success = runner.run_suite(args.workflows, fail_fast=args.fail_fast) + return 0 if success else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ci/smoke_test.sh b/ci/smoke_test.sh new file mode 100644 index 0000000..cfd9a79 --- /dev/null +++ b/ci/smoke_test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: COMFYUI_HOME=/path/to/ComfyUI ci/smoke_test.sh [...]" >&2 + exit 1 +fi + +if [[ -z "${COMFYUI_HOME:-}" ]]; then + echo "COMFYUI_HOME environment variable must point to the ComfyUI checkout" >&2 + exit 1 +fi + +PYTHON_BIN=${PYTHON_BIN:-python3} +HOST=${COMFYUI_HOST:-127.0.0.1} +PORT=${COMFYUI_PORT:-8188} +LOG_FILE=${COMFYUI_LOG:-comfyui_ci.log} + +pushd "${COMFYUI_HOME}" >/dev/null + +${PYTHON_BIN} -m pip install --upgrade pip >/dev/null +${PYTHON_BIN} -m pip install -r requirements.txt >/dev/null + +${PYTHON_BIN} main.py --disable-auto-launch --listen "${HOST}" --port "${PORT}" >"${LOG_FILE}" 2>&1 & +SERVER_PID=$! +trap 'kill ${SERVER_PID} >/dev/null 2>&1 || true' EXIT + +popd >/dev/null + +"${PYTHON_BIN}" "$(dirname "$0")/run_workflows.py" --host "${HOST}" --port "${PORT}" "$@" diff --git a/ci/summarize_log.py b/ci/summarize_log.py new file mode 100644 index 0000000..f1d3b4b --- /dev/null +++ b/ci/summarize_log.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Convert MultiGPU JSON log into a Markdown summary.""" + +import argparse +import json +from pathlib import Path +from typing import Iterator, Dict, Any + + +def load_json_lines(path: Path) -> Iterator[Dict[str, Any]]: + with path.open("r", encoding="utf-8") as handle: + for line in handle: + line = line.strip() + if not line: + continue + try: + yield json.loads(line) + except json.JSONDecodeError: + continue + + +def main() -> int: + parser = argparse.ArgumentParser(description="Summarize MultiGPU JSON logs into Markdown") + parser.add_argument("logfile", type=Path, help="Path to JSONL log produced by MGPU_JSON_LOG_PATH") + parser.add_argument("--severity", nargs="*", help="Optional severity levels to include (e.g. INFO WARN ERROR)") + parser.add_argument( + "--component", + nargs="*", + help="Optional component names to include (matches component or event_category fields)", + ) + args = parser.parse_args() + + entries = list(load_json_lines(args.logfile)) + if not entries: + print("No entries found in log file.") + return 0 + + print("| Timestamp | Level | Component | Message |") + print("| --- | --- | --- | --- |") + for entry in entries: + level = entry.get("level", "") + if args.severity and level not in args.severity: + continue + component_values = { + entry.get("component", ""), + entry.get("event_category", ""), + } + component = next((value for value in component_values if value), "") + if args.component and component not in args.component: + continue + timestamp = entry.get("timestamp", "") + message = entry.get("message", "").replace("|", "\u2502") + print(f"| {timestamp} | {level} | {component} | {message} |") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/wanvideo.py b/wanvideo.py index 101d85a..e412a11 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -1,29 +1,8 @@ -""" - WanVideoControlnetLoader, controlnet/nodes.py - FantasyTalkingModelLoader, fantasytalking/nodes.py - MultiTalkModelLoader, multitalk/nodes.py - Wav2VecModelLoader, multitalk/nodes.py - WanVideoSetBlockSwap, nodes.py - WanVideoBlockList, nodes.py - WanVideoTextEncodeCached, nodes.py - X WanVideoTextEncode, nodes.py - WanVideoTextEncodeSingle, nodes.py - WanVideoClipVisionEncode, nodes.py - X WanVideoImageToVideoEncode, nodes.py - WanVideoVACEEncode, nodes.py - X WanVideoDecode, nodes.py - WanVideoImageClipEncode, nodes_deprecated.py - WanVideoModelLoader, nodes_model_loading.py - X WanVideoVAELoader, nodes_model_loading.py - WanVideoLoraBlockEdit, nodes_model_loading.py - X WanVideoTinyVAELoader, nodes_model_loading.py - WanVideoBlockSwap, nodes_model_loading.py - WanVideoVRAMManagement, nodes_model_loading.py - WanVideoTorchCompileSettings, nodes_model_loading.py - X LoadWanVideoT5TextEncoder, nodes_model_loading.py - LoadWanVideoClipTextEncoder, nodes_model_loading.py - WanVideoUni3C_ControlnetLoader, uni3c/nodes.py - """ +"""WanVideoWrapper integration helpers. + +For the current progress checklist and outstanding tasks, see +`.github/instructions/ComfyUI-MultiGPU.instructions.md`. +""" @@ -561,8 +540,8 @@ class WanVideoModelLoader: fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, rms_norm_function="default"): from . import set_current_device - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}") + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}") selected_device = torch.device(compute_device) @@ -574,7 +553,7 @@ class WanVideoModelLoader: loader_module = inspect.getmodule(original_loader) if loader_module: - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}' to use device: {selected_device}") + logger.debug(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}' to use device: {selected_device}") # Store original values to restore later if needed, though it's less critical in this workflow original_module_device = getattr(loader_module, 'device', None) @@ -586,7 +565,7 @@ class WanVideoModelLoader: if compute_device == "cpu": setattr(loader_module, 'offload_device', selected_device) - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Device patching complete. Calling original loader...") + logger.debug("[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Device patching complete. Calling original loader...") # Call the original loader function with all the arguments it expects result = original_loader.loadmodel( @@ -634,18 +613,39 @@ class WanVideoSampler: def process(self, model, compute_device, **kwargs): from . import set_current_device - - logger.info(f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}") - # Set the global device context for the sampler's operations + logger.info(f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}") + + # Resolve the target device and update the global sampler context + target_device = None if compute_device: - set_current_device(torch.device(compute_device)) - - # The model is already on the correct device thanks to the patched loader. - # We no longer need to patch the model here. We just need to call the original sampler. - logger.info("[MultiGPU WanVideoSampler] Model is pre-configured. Calling original sampler.") + target_device = torch.device(compute_device) + set_current_device(target_device) + else: + target_device = mm.get_torch_device() original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() - - # The original sampler will internally use mm.get_torch_device(), which is now correctly set. - return original_sampler.process(model=model, **kwargs) + sampler_module = inspect.getmodule(original_sampler) + + original_module_device = None + if sampler_module is not None: + original_module_device = getattr(sampler_module, "device", None) + setattr(sampler_module, "device", target_device) + + # Align offload device when running on CPU so intermediate tensors stay colocated. + if compute_device == "cpu": + setattr(sampler_module, "offload_device", target_device) + + if original_module_device != target_device: + logger.debug( + f"[MultiGPU WanVideoSampler] Patched sampler module device: {original_module_device} -> {target_device}" + ) + else: + logger.error("[MultiGPU WanVideoSampler] Unable to resolve sampler module for device patching.") + + try: + # The original sampler will internally use mm.get_torch_device(), which is now correctly set. + return original_sampler.process(model=model, **kwargs) + finally: + if sampler_module is not None and original_module_device is not None: + setattr(sampler_module, "device", original_module_device) From d300c12e9f924e67c25ae203f51e020a0ed02b23 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Tue, 7 Oct 2025 14:22:01 -0500 Subject: [PATCH 06/22] feat: add WanVideoBlockSwap class and integrate into MultiGPU node mappings --- __init__.py | 2 + wanvideo.py | 239 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 180 insertions(+), 61 deletions(-) diff --git a/__init__.py b/__init__.py index 834a750..fbf1062 100644 --- a/__init__.py +++ b/__init__.py @@ -215,6 +215,7 @@ from .wanvideo import ( WanVideoTextEncode, WanVideoVAELoader, WanVideoTinyVAELoader, + WanVideoBlockSwap, WanVideoImageToVideoEncode, WanVideoDecode, WanVideoModelLoader, @@ -359,6 +360,7 @@ wanvideo_nodes = { "WanVideoTextEncodeMultiGPU": WanVideoTextEncode, "WanVideoVAELoaderMultiGPU": WanVideoVAELoader, "WanVideoTinyVAELoaderMultiGPU": WanVideoTinyVAELoader, + "WanVideoBlockSwapMultiGPU": WanVideoBlockSwap, "WanVideoImageToVideoEncodeMultiGPU": WanVideoImageToVideoEncode, "WanVideoDecodeMultiGPU": WanVideoDecode, "WanVideoModelLoaderMultiGPU": WanVideoModelLoader, diff --git a/wanvideo.py b/wanvideo.py index e412a11..1ea762a 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -11,6 +11,7 @@ import logging import torch import sys import inspect +import copy import folder_paths import comfy.model_management as mm from nodes import NODE_CLASS_MAPPINGS @@ -214,6 +215,35 @@ class WanVideoTinyVAELoader: # Return both the VAE model AND the selected device for device propagation return vae_model, load_device + +class WanVideoBlockSwap: + @classmethod + def INPUT_TYPES(s): + base_inputs = copy.deepcopy(NODE_CLASS_MAPPINGS["WanVideoBlockSwap"].INPUT_TYPES()) + devices = get_device_list() + default_device = "cpu" if "cpu" in devices else devices[0] + base_inputs.setdefault("optional", {}) + base_inputs["optional"]["swap_device"] = ( + devices, + { + "default": default_device, + "tooltip": "Device that receives swapped transformer blocks", + }, + ) + return base_inputs + + RETURN_TYPES = ("BLOCKSWAPARGS",) + RETURN_NAMES = ("block_swap_args",) + FUNCTION = "setargs" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Extends Wan block swap with explicit device selection" + + def setargs(self, swap_device=None, **kwargs): + block_swap_config = dict(kwargs) + if swap_device is not None: + block_swap_config["swap_device"] = str(swap_device) + return (block_swap_config,) + class WanVideoImageToVideoEncode: @classmethod def INPUT_TYPES(s): @@ -540,61 +570,117 @@ class WanVideoModelLoader: fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, rms_norm_function="default"): from . import set_current_device + logger.info( + f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}" + ) - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}") - selected_device = torch.device(compute_device) - - # Set the global device context for any downstream operations set_current_device(selected_device) - - # Find the original loader and its module + + normalized_block_swap = None + swap_device_override = None + if block_swap_args is not None: + normalized_block_swap = dict(block_swap_args) + swap_selection = normalized_block_swap.pop("swap_device", None) + if swap_selection is None: + swap_selection = normalized_block_swap.get("resolved_swap_device") + if swap_selection is None: + swap_selection = "cpu" + try: + swap_device_override = torch.device(str(swap_selection)) + except (TypeError, ValueError): + logger.warning( + "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Invalid swap_device '%s', falling back to CPU", + swap_selection, + ) + swap_device_override = torch.device("cpu") + normalized_block_swap["resolved_swap_device"] = str(swap_device_override) + original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() loader_module = inspect.getmodule(original_loader) - - if loader_module: - logger.debug(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}' to use device: {selected_device}") - - # Store original values to restore later if needed, though it's less critical in this workflow - original_module_device = getattr(loader_module, 'device', None) - - # Overwrite the module-level 'device' variable. This is the key to the fix. - setattr(loader_module, 'device', selected_device) - - # Also patch the offload device if the user chose CPU - if compute_device == "cpu": - setattr(loader_module, 'offload_device', selected_device) - logger.debug("[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Device patching complete. Calling original loader...") - - # Call the original loader function with all the arguments it expects + if not loader_module: + logger.error( + "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Could not resolve loader module; invoking original implementation without patches." + ) result = original_loader.loadmodel( - model, base_precision, load_device, quantization, - compile_args, attention_mode, block_swap_args, lora, - vram_management_args, extra_model=extra_model, vace_model=vace_model, - fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, - fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function + model, + base_precision, + load_device, + quantization, + compile_args, + attention_mode, + normalized_block_swap if normalized_block_swap is not None else block_swap_args, + lora, + vram_management_args, + extra_model=extra_model, + vace_model=vace_model, + fantasytalking_model=fantasytalking_model, + multitalk_model=multitalk_model, + fantasyportrait_model=fantasyportrait_model, + rms_norm_function=rms_norm_function, ) - - # Restore the original device if you want to be a good citizen, though not strictly necessary - # if the execution context is self-contained. - if original_module_device is not None: - setattr(loader_module, 'device', original_module_device) - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] WanVideo model loaded on {selected_device}") - - # Return the model and the device for the sampler return (result[0], compute_device) - else: - logger.error("[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Could not find the module for WanVideoModelLoader to patch.") - # Fallback to original behavior without patching - return (original_loader.loadmodel( - model, base_precision, load_device, quantization, - compile_args, attention_mode, block_swap_args, lora, - vram_management_args, extra_model=extra_model, vace_model=vace_model, - fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, - fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function - ), compute_device) + + logger.debug( + f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}'" + ) + original_module_device = getattr(loader_module, "device", None) + had_offload_attr = hasattr(loader_module, "offload_device") + original_module_offload = getattr(loader_module, "offload_device", None) + + setattr(loader_module, "device", selected_device) + if swap_device_override is not None: + setattr(loader_module, "offload_device", swap_device_override) + elif compute_device == "cpu": + setattr(loader_module, "offload_device", selected_device) + + try: + result = original_loader.loadmodel( + model, + base_precision, + load_device, + quantization, + compile_args, + attention_mode, + normalized_block_swap if normalized_block_swap is not None else block_swap_args, + lora, + vram_management_args, + extra_model=extra_model, + vace_model=vace_model, + fantasytalking_model=fantasytalking_model, + multitalk_model=multitalk_model, + fantasyportrait_model=fantasyportrait_model, + rms_norm_function=rms_norm_function, + ) + finally: + if original_module_device is not None: + setattr(loader_module, "device", original_module_device) + if had_offload_attr: + setattr(loader_module, "offload_device", original_module_offload) + else: + try: + delattr(loader_module, "offload_device") + except AttributeError: + pass + + patcher = result[0] + if normalized_block_swap is not None: + try: + transformer_options = patcher.model_options.setdefault("transformer_options", {}) + transformer_options["block_swap_args"] = normalized_block_swap + except AttributeError: + logger.warning( + "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Unable to propagate normalized block swap settings" + ) + + logger.info( + "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] WanVideo model loaded on %s with swap_device=%s", + selected_device, + str(swap_device_override) if swap_device_override is not None else "default", + ) + + return (patcher, compute_device) class WanVideoSampler: @@ -613,39 +699,70 @@ class WanVideoSampler: def process(self, model, compute_device, **kwargs): from . import set_current_device + logger.info( + f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}" + ) - logger.info(f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}") + patcher = model + transformer = None + if hasattr(patcher, "model"): + transformer = getattr(patcher.model, "diffusion_model", None) - # Resolve the target device and update the global sampler context - target_device = None if compute_device: target_device = torch.device(compute_device) set_current_device(target_device) else: target_device = mm.get_torch_device() + normalized_swap_device = None + transformer_options = {} + if hasattr(patcher, "model_options"): + transformer_options = patcher.model_options.get("transformer_options", {}) + block_swap_args = transformer_options.get("block_swap_args") if transformer_options else None + if block_swap_args: + swap_label = block_swap_args.get("resolved_swap_device") or block_swap_args.get("swap_device") + if swap_label: + try: + normalized_swap_device = torch.device(str(swap_label)) + except (TypeError, ValueError): + logger.warning( + "[MultiGPU WanVideoSampler] Invalid swap device '%s', leaving sampler offload unchanged", + swap_label, + ) + normalized_swap_device = None + original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() sampler_module = inspect.getmodule(original_sampler) original_module_device = None + original_module_offload = None + had_sampler_offload_attr = False if sampler_module is not None: original_module_device = getattr(sampler_module, "device", None) + had_sampler_offload_attr = hasattr(sampler_module, "offload_device") + original_module_offload = getattr(sampler_module, "offload_device", None) setattr(sampler_module, "device", target_device) - - # Align offload device when running on CPU so intermediate tensors stay colocated. - if compute_device == "cpu": + if normalized_swap_device is not None: + setattr(sampler_module, "offload_device", normalized_swap_device) + elif compute_device == "cpu": setattr(sampler_module, "offload_device", target_device) - - if original_module_device != target_device: - logger.debug( - f"[MultiGPU WanVideoSampler] Patched sampler module device: {original_module_device} -> {target_device}" - ) else: logger.error("[MultiGPU WanVideoSampler] Unable to resolve sampler module for device patching.") + if transformer is not None and normalized_swap_device is not None: + transformer.offload_device = normalized_swap_device + transformer.cache_device = normalized_swap_device + try: - # The original sampler will internally use mm.get_torch_device(), which is now correctly set. - return original_sampler.process(model=model, **kwargs) + return original_sampler.process(model=patcher, **kwargs) finally: - if sampler_module is not None and original_module_device is not None: - setattr(sampler_module, "device", original_module_device) + if sampler_module is not None: + if original_module_device is not None: + setattr(sampler_module, "device", original_module_device) + if had_sampler_offload_attr: + setattr(sampler_module, "offload_device", original_module_offload) + else: + try: + delattr(sampler_module, "offload_device") + except AttributeError: + pass From 04013c3ee55f04342fc39a3f31900ba21676629a Mon Sep 17 00:00:00 2001 From: John Pollock Date: Wed, 8 Oct 2025 06:08:16 -0500 Subject: [PATCH 07/22] feat: add WanVideoTextEncodeCached and WanVideoTextEncodeSingle classes for enhanced text encoding functionality --- __init__.py | 4 ++ wanvideo.py | 122 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 97 insertions(+), 29 deletions(-) diff --git a/__init__.py b/__init__.py index fbf1062..a6df0d1 100644 --- a/__init__.py +++ b/__init__.py @@ -213,6 +213,8 @@ from .nodes import ( from .wanvideo import ( LoadWanVideoT5TextEncoder, WanVideoTextEncode, + WanVideoTextEncodeCached, + WanVideoTextEncodeSingle, WanVideoVAELoader, WanVideoTinyVAELoader, WanVideoBlockSwap, @@ -358,6 +360,8 @@ register_and_count(["PuLID_ComfyUI", "pulid_comfyui"], pulid_nodes) wanvideo_nodes = { "LoadWanVideoT5TextEncoderMultiGPU": LoadWanVideoT5TextEncoder, "WanVideoTextEncodeMultiGPU": WanVideoTextEncode, + "WanVideoTextEncodeCachedMultiGPU": WanVideoTextEncodeCached, + "WanVideoTextEncodeSingleMultiGPU": WanVideoTextEncodeSingle, "WanVideoVAELoaderMultiGPU": WanVideoVAELoader, "WanVideoTinyVAELoaderMultiGPU": WanVideoTinyVAELoader, "WanVideoBlockSwapMultiGPU": WanVideoBlockSwap, diff --git a/wanvideo.py b/wanvideo.py index 1ea762a..58b485a 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -1,12 +1,3 @@ -"""WanVideoWrapper integration helpers. - -For the current progress checklist and outstanding tasks, see -`.github/instructions/ComfyUI-MultiGPU.instructions.md`. -""" - - - - import logging import torch import sys @@ -24,25 +15,6 @@ from accelerate import init_empty_weights import os import importlib.util -scheduler_list = [ - "unipc", "unipc/beta", - "dpm++", "dpm++/beta", - "dpm++_sde", "dpm++_sde/beta", - "euler", "euler/beta", - "deis", - "lcm", "lcm/beta", - "res_multistep", - "flowmatch_causvid", - "flowmatch_distill", - "flowmatch_pusa", - "multitalk", - "sa_ode_stable" -] - -rope_functions = ["default", "comfy", "comfy_chunked"] - - - logger = logging.getLogger("MultiGPU") class LoadWanVideoT5TextEncoder: @@ -90,6 +62,55 @@ class LoadWanVideoT5TextEncoder: return text_encoder, device +class WanVideoTextEncodeCached: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model_name": (folder_paths.get_filename_list("text_encoders"), {"tooltip": "These models are loaded from 'ComfyUI/models/text_encoders'"}), + "precision": (["fp32", "bf16"], {"default": "bf16"}), + "positive_prompt": ("STRING", {"default": "", "multiline": True} ), + "negative_prompt": ("STRING", {"default": "", "multiline": True} ), + "quantization": (['disabled', 'fp8_e4m3fn'], {"default": 'disabled', "tooltip": "optional quantization method"}), + "use_disk_cache": ("BOOLEAN", {"default": True, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), + "load_device": (devices, {"default": default_device} + ), + }, + "optional": { + "extender_args": ("WANVIDEOPROMPTEXTENDER_ARGS", {"tooltip": "Use this node to extend the prompt with additional text."}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", "WANVIDEOTEXTEMBEDS", "STRING") + RETURN_NAMES = ("text_embeds", "negative_text_embeds", "positive_prompt") + OUTPUT_TOOLTIPS = ("The text embeddings for both prompts", "The text embeddings for the negative prompt only (for NAG)", "Positive prompt to display prompt extender results") + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = """Encodes text prompts into text embeddings. This node loads and completely unloads the T5 after done, leaving no VRAM or RAM imprint.""" + + + def process(self, model_name, precision, positive_prompt, negative_prompt, quantization='disabled', use_disk_cache=True, load_device=None, extender_args=None): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + if load_device == "cpu": + device = "cpu" + else: + device = "gpu" + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeCachedMulitiGPU] current_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeCachedMulitiGPU] device set to: {device}") + + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeCached"]() + prompt_embeds_dict, negative_text_embeds, positive_prompt_out = original_encoder.process(model_name, precision, positive_prompt, negative_prompt, quantization, use_disk_cache, device, extender_args) + + return prompt_embeds_dict, negative_text_embeds, positive_prompt_out + + class WanVideoTextEncode: @classmethod def INPUT_TYPES(s): @@ -103,7 +124,6 @@ class WanVideoTextEncode: "force_offload": ("BOOLEAN", {"default": True}), "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), - #"device": (["gpu", "cpu"], {"default": "gpu", "tooltip": "Device to run the text encoding on."}), } } @@ -141,6 +161,50 @@ class WanVideoTextEncode: original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() return original_parser.parse_prompt_weights(prompt) +class WanVideoTextEncodeSingle: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "prompt": ("STRING", {"default": "", "multiline": True} ), + }, + "optional": { + "t5": ("WANTEXTENCODER",), + "load_device": ("MULTIGPUDEVICE",), + "force_offload": ("BOOLEAN", {"default": True}), + "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), + "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) + RETURN_NAMES = ("text_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Encodes text prompt into text embedding." + + def process(self, prompt, t5=None, load_device=None, force_offload=True, model_to_offload=None, use_disk_cache=False): + from . import set_current_device + + if load_device is not None: + set_current_device(load_device) + + if load_device == "cpu": + device = "cpu" + else: + device = "gpu" + + if t5 is not None: + text_encoder = t5[0] + else: + text_encoder = None + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeSingleMulitiGPU] current_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeSingleMulitiGPU] device set to: {device}") + + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeSingle"]() + prompt_embeds_dict = original_encoder.process(prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) + return (prompt_embeds_dict) + class WanVideoVAELoader: @classmethod def INPUT_TYPES(s): From 7c87983d7208f60e161c323001d204a91f25de04 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Wed, 8 Oct 2025 23:06:54 -0500 Subject: [PATCH 08/22] WIP --- wanvideo.py | 383 ++++++++++++++++++---------------------------------- 1 file changed, 129 insertions(+), 254 deletions(-) diff --git a/wanvideo.py b/wanvideo.py index 58b485a..5362f4e 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -17,6 +17,123 @@ import importlib.util logger = logging.getLogger("MultiGPU") + +class WanVideoModelLoader: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model": (folder_paths.get_filename_list("unet_gguf") + folder_paths.get_filename_list("diffusion_models"), {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' -folder",}), + + "base_precision": (["fp32", "bf16", "fp16", "fp16_fast"], {"default": "bf16"}), + "quantization": (["disabled", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e4m3fn_scaled", "fp8_e4m3fn_scaled_fast", "fp8_e5m2", "fp8_e5m2_fast", "fp8_e5m2_scaled", "fp8_e5m2_scaled_fast"], {"default": "disabled", + "tooltip": "Optional quantization method, 'disabled' acts as autoselect based by weights. Scaled modes only work with matching weights, _fast modes (fp8 matmul) require CUDA compute capability >= 8.9 (NVIDIA 4000 series and up), e4m3fn generally can not be torch.compiled on compute capability < 8.9 (3000 series and under)"}), + "load_device": (["main_device", "offload_device"], {"default": "offload_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + "compute_device": (devices, {"default": default_device}), + }, + "optional": { + "attention_mode": ([ + "sdpa", + "flash_attn_2", + "flash_attn_3", + "sageattn", + "sageattn_3", + "radial_sage_attention", + ], {"default": "sdpa"}), + "compile_args": ("WANCOMPILEARGS", ), + "block_swap_args": ("BLOCKSWAPARGS", ), + "lora": ("WANVIDLORA", {"default": None}), + "vram_management_args": ("VRAM_MANAGEMENTARGS", {"default": None, "tooltip": "Alternative offloading method from DiffSynth-Studio, more aggressive in reducing memory use than block swapping, but can be slower"}), + "extra_model": ("VACEPATH", {"default": None, "tooltip": "Extra model to add to the main model, ie. VACE or MTV Crafter"}), + "fantasytalking_model": ("FANTASYTALKINGMODEL", {"default": None, "tooltip": "FantasyTalking model https://github.com/Fantasy-AMAP"}), + "multitalk_model": ("MULTITALKMODEL", {"default": None, "tooltip": "Multitalk model"}), + "fantasyportrait_model": ("FANTASYPORTRAITMODEL", {"default": None, "tooltip": "FantasyPortrait model"}), + "rms_norm_function": (["default", "pytorch"], {"default": "default", "tooltip": "RMSNorm function to use, 'pytorch' is the new native torch RMSNorm, which is faster (when not using torch.compile mostly) but changes results slightly. 'default' is the original WanRMSNorm"}), + } + } + + RETURN_TYPES = ("WANVIDEOMODEL", "MULTIGPUDEVICE",) + RETURN_NAMES = ("model", "compute_device",) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + + def loadmodel(self, model, base_precision, compute_device, quantization, load_device, + compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, + vram_management_args=None, extra_model=None, vace_model=None, + fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, + rms_norm_function="default"): + from . import set_current_device + + set_current_device(compute_device) + compute_device_to_be_patched = mm.get_torch_device() + + original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() + loader_module = inspect.getmodule(original_loader) + + original_module_device = loader_module.device + + loader_module.device = compute_device_to_be_patched + + result = original_loader.loadmodel(model, base_precision, load_device, quantization, compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, + vace_model=vace_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function,) + + loader_module.device = original_module_device + + patcher = result[0] + + return (patcher, compute_device) + +class WanVideoTextEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "positive_prompt": ("STRING", {"default": "", "multiline": True} ), + "negative_prompt": ("STRING", {"default": "", "multiline": True} ), + }, + "optional": { + "t5": ("WANTEXTENCODER",), + "load_device": ("MULTIGPUDEVICE",), + "force_offload": ("BOOLEAN", {"default": True}), + "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), + "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), + } + } + + RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) + RETURN_NAMES = ("text_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Encodes text prompts into text embeddings. For rudimentary prompt travel you can input multiple prompts separated by '|', they will be equally spread over the video length" + + def process(self, positive_prompt, negative_prompt, t5=None, load_device=None,force_offload=True, model_to_offload=None, use_disk_cache=False): + from . import set_current_device + + set_current_device(load_device) + + if load_device == "cpu": + device = "cpu" + else: + device = "gpu" + + if t5 is not None: + text_encoder = t5[0] + else: + text_encoder = None + + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] current_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] device set to: {device}") + + original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() + prompt_embeds_dict = original_encoder.process(positive_prompt, negative_prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) + return (prompt_embeds_dict) + + def parse_prompt_weights(self, prompt): + """Extract text and weights from prompts with (text:weight) format""" + original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() + return original_parser.parse_prompt_weights(prompt) + class LoadWanVideoT5TextEncoder: @classmethod def INPUT_TYPES(s): @@ -58,7 +175,6 @@ class LoadWanVideoT5TextEncoder: original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoT5TextEncoder"]() text_encoder = original_loader.loadmodel(model_name, precision, load_device, quantization) - # Return both the text encoder AND the selected device return text_encoder, device @@ -110,57 +226,6 @@ class WanVideoTextEncodeCached: return prompt_embeds_dict, negative_text_embeds, positive_prompt_out - -class WanVideoTextEncode: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "positive_prompt": ("STRING", {"default": "", "multiline": True} ), - "negative_prompt": ("STRING", {"default": "", "multiline": True} ), - }, - "optional": { - "t5": ("WANTEXTENCODER",), - "load_device": ("MULTIGPUDEVICE",), - "force_offload": ("BOOLEAN", {"default": True}), - "model_to_offload": ("WANVIDEOMODEL", {"tooltip": "Model to move to offload_device before encoding"}), - "use_disk_cache": ("BOOLEAN", {"default": False, "tooltip": "Cache the text embeddings to disk for faster re-use, under the custom_nodes/ComfyUI-WanVideoWrapper/text_embed_cache directory"}), - } - } - - RETURN_TYPES = ("WANVIDEOTEXTEMBEDS", ) - RETURN_NAMES = ("text_embeds",) - FUNCTION = "process" - CATEGORY = "multigpu/WanVideoWrapper" - DESCRIPTION = "Encodes text prompts into text embeddings. For rudimentary prompt travel you can input multiple prompts separated by '|', they will be equally spread over the video length" - - def process(self, positive_prompt, negative_prompt, t5=None, load_device=None,force_offload=True, model_to_offload=None, use_disk_cache=False): - from . import set_current_device - - if load_device is not None: - set_current_device(load_device) - - if load_device == "cpu": - device = "cpu" - else: - device = "gpu" - - if t5 is not None: - text_encoder = t5[0] - else: - text_encoder = None - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] current_device set to: {load_device}") - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] device set to: {device}") - - original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() - prompt_embeds_dict = original_encoder.process(positive_prompt, negative_prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) - return (prompt_embeds_dict) - - def parse_prompt_weights(self, prompt): - """Extract text and weights from prompts with (text:weight) format""" - original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() - return original_parser.parse_prompt_weights(prompt) - class WanVideoTextEncodeSingle: @classmethod def INPUT_TYPES(s): @@ -235,7 +300,7 @@ class WanVideoVAELoader: if load_device is not None: set_current_device(load_device) - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoVAELoader] load_device set to: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoVAELoaderMultiGPU] load_device set to: {load_device}") original_loader = NODE_CLASS_MAPPINGS["WanVideoVAELoader"]() vae_model = original_loader.loadmodel(model_name, precision, compile_args) @@ -345,8 +410,7 @@ class WanVideoImageToVideoEncode: temporal_mask=None, extra_latents=None, clip_embeds=None, tiled_vae=False, add_cond_latents=None, vae=None, load_device=None): from . import set_current_device - if load_device is not None: - set_current_device(load_device) + set_current_device(load_device) logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] load device: {load_device}") @@ -537,214 +601,25 @@ class WanVideoDecode: def decode(self, vae, load_device, samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization="default"): from . import set_current_device - if load_device is not None: - set_current_device(load_device) + set_current_device(load_device) + compute_device_to_be_patched = mm.get_torch_device() - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] load device: {load_device}") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoDecodeMultiGPU] load device: {load_device}") - device = mm.get_torch_device() - PATCH_SIZE = (1, 2, 2) - offload_device = mm.unet_offload_device() - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] torch device: {device}") - - if vae is not None: - vae = vae[0] - - mm.soft_empty_cache() - video = samples.get("video", None) - if video is not None: - video.clamp_(-1.0, 1.0) - video.add_(1.0).div_(2.0) - return video.cpu().float(), - latents = samples["samples"] - end_image = samples.get("end_image", None) - has_ref = samples.get("has_ref", False) - drop_last = samples.get("drop_last", False) - is_looped = samples.get("looped", False) - - vae.to(device) - - latents = latents.to(device = device, dtype = vae.dtype) - - mm.soft_empty_cache() - - if has_ref: - latents = latents[:, :, 1:] - if drop_last: - latents = latents[:, :, :-1] - - if type(vae).__name__ == "TAEHV": - images = vae.decode_video(latents.permute(0, 2, 1, 3, 4))[0].permute(1, 0, 2, 3) - images = torch.clamp(images, 0.0, 1.0) - images = images.permute(1, 2, 3, 0).cpu().float() - return (images,) - else: - if end_image is not None: - enable_vae_tiling = False - images = vae.decode(latents, device=device, end_=(end_image is not None), tiled=enable_vae_tiling, tile_size=(tile_x//8, tile_y//8), tile_stride=(tile_stride_x//8, tile_stride_y//8))[0] - - - images = images.cpu().float() - - if normalization == "minmax": - images.sub_(images.min()).div_(images.max() - images.min()) - else: - images.clamp_(-1.0, 1.0) - images.add_(1.0).div_(2.0) - - if is_looped: - temp_latents = torch.cat([latents[:, :, -3:]] + [latents[:, :, :2]], dim=2) - temp_images = vae.decode(temp_latents, device=device, end_=(end_image is not None), tiled=enable_vae_tiling, tile_size=(tile_x//vae.upsampling_factor, tile_y//vae.upsampling_factor), tile_stride=(tile_stride_x//vae.upsampling_factor, tile_stride_y//vae.upsampling_factor))[0] - temp_images = temp_images.cpu().float() - temp_images = (temp_images - temp_images.min()) / (temp_images.max() - temp_images.min()) - images = torch.cat([temp_images[:, 9:].to(images), images[:, 5:]], dim=1) - - if end_image is not None: - images = images[:, 0:-1] - - - vae.to(offload_device) - mm.soft_empty_cache() - - images.clamp_(0.0, 1.0) - - return (images.permute(1, 2, 3, 0),) -class WanVideoModelLoader: - @classmethod - def INPUT_TYPES(s): - devices = get_device_list() - default_device = devices[1] if len(devices) > 1 else devices[0] - # Get the original node's input types to stay up-to-date - original_types = NODE_CLASS_MAPPINGS["WanVideoModelLoader"].INPUT_TYPES() - - # Update with our custom device selection - original_types["required"]["compute_device"] = (devices, {"default": default_device}) - - return original_types - - RETURN_TYPES = ("WANVIDEOMODEL", "MULTIGPUDEVICE",) - RETURN_NAMES = ("model", "compute_device",) - FUNCTION = "loadmodel" - CATEGORY = "multigpu/WanVideoWrapper" - - def loadmodel(self, model, base_precision, compute_device, quantization, load_device, - compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, - vram_management_args=None, extra_model=None, vace_model=None, - fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, - rms_norm_function="default"): - from . import set_current_device - logger.info( - f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] User selected device: {compute_device}" - ) - - selected_device = torch.device(compute_device) - set_current_device(selected_device) - - normalized_block_swap = None - swap_device_override = None - if block_swap_args is not None: - normalized_block_swap = dict(block_swap_args) - swap_selection = normalized_block_swap.pop("swap_device", None) - if swap_selection is None: - swap_selection = normalized_block_swap.get("resolved_swap_device") - if swap_selection is None: - swap_selection = "cpu" - try: - swap_device_override = torch.device(str(swap_selection)) - except (TypeError, ValueError): - logger.warning( - "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Invalid swap_device '%s', falling back to CPU", - swap_selection, - ) - swap_device_override = torch.device("cpu") - normalized_block_swap["resolved_swap_device"] = str(swap_device_override) - - original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() + original_loader = NODE_CLASS_MAPPINGS["WanVideoDecode"]() loader_module = inspect.getmodule(original_loader) - if not loader_module: - logger.error( - "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Could not resolve loader module; invoking original implementation without patches." - ) - result = original_loader.loadmodel( - model, - base_precision, - load_device, - quantization, - compile_args, - attention_mode, - normalized_block_swap if normalized_block_swap is not None else block_swap_args, - lora, - vram_management_args, - extra_model=extra_model, - vace_model=vace_model, - fantasytalking_model=fantasytalking_model, - multitalk_model=multitalk_model, - fantasyportrait_model=fantasyportrait_model, - rms_norm_function=rms_norm_function, - ) - return (result[0], compute_device) + original_module_device = loader_module.device - logger.debug( - f"[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Patching '{loader_module.__name__}'" - ) - original_module_device = getattr(loader_module, "device", None) - had_offload_attr = hasattr(loader_module, "offload_device") - original_module_offload = getattr(loader_module, "offload_device", None) + loader_module.device = compute_device_to_be_patched - setattr(loader_module, "device", selected_device) - if swap_device_override is not None: - setattr(loader_module, "offload_device", swap_device_override) - elif compute_device == "cpu": - setattr(loader_module, "offload_device", selected_device) + result = original_loader.decode(vae[0], samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization) - try: - result = original_loader.loadmodel( - model, - base_precision, - load_device, - quantization, - compile_args, - attention_mode, - normalized_block_swap if normalized_block_swap is not None else block_swap_args, - lora, - vram_management_args, - extra_model=extra_model, - vace_model=vace_model, - fantasytalking_model=fantasytalking_model, - multitalk_model=multitalk_model, - fantasyportrait_model=fantasyportrait_model, - rms_norm_function=rms_norm_function, - ) - finally: - if original_module_device is not None: - setattr(loader_module, "device", original_module_device) - if had_offload_attr: - setattr(loader_module, "offload_device", original_module_offload) - else: - try: - delattr(loader_module, "offload_device") - except AttributeError: - pass + loader_module.device = original_module_device - patcher = result[0] - if normalized_block_swap is not None: - try: - transformer_options = patcher.model_options.setdefault("transformer_options", {}) - transformer_options["block_swap_args"] = normalized_block_swap - except AttributeError: - logger.warning( - "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] Unable to propagate normalized block swap settings" - ) + decode = result[0] - logger.info( - "[MultiGPU WanVideoWrapper][WanVideoModelLoaderMultiGPU] WanVideo model loaded on %s with swap_device=%s", - selected_device, - str(swap_device_override) if swap_device_override is not None else "default", - ) - - return (patcher, compute_device) + return (decode,) class WanVideoSampler: From 15ff29c63b816a5d4139557730da6b4dae2840cf Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 03:06:25 -0500 Subject: [PATCH 09/22] methodological convergence and cleanup --- wanvideo.py | 255 ++++++++++++++++++++++++---------------------------- 1 file changed, 118 insertions(+), 137 deletions(-) diff --git a/wanvideo.py b/wanvideo.py index 5362f4e..2fafb79 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -18,6 +18,23 @@ import importlib.util logger = logging.getLogger("MultiGPU") +scheduler_list = [ + "unipc", "unipc/beta", + "dpm++", "dpm++/beta", + "dpm++_sde", "dpm++_sde/beta", + "euler", "euler/beta", + "deis", + "lcm", "lcm/beta", + "res_multistep", + "flowmatch_causvid", + "flowmatch_distill", + "flowmatch_pusa", + "multitalk", + "sa_ode_stable" +] + +rope_functions = ["default", "comfy", "comfy_chunked"] + class WanVideoModelLoader: @classmethod def INPUT_TYPES(s): @@ -59,31 +76,109 @@ class WanVideoModelLoader: FUNCTION = "loadmodel" CATEGORY = "multigpu/WanVideoWrapper" - def loadmodel(self, model, base_precision, compute_device, quantization, load_device, - compile_args=None, attention_mode="sdpa", block_swap_args=None, lora=None, - vram_management_args=None, extra_model=None, vace_model=None, - fantasytalking_model=None, multitalk_model=None, fantasyportrait_model=None, - rms_norm_function="default"): + def loadmodel(self, model, base_precision, compute_device, quantization, load_device, **kwargs): from . import set_current_device + original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() + loader_module = inspect.getmodule(original_loader) + original_module_device = loader_module.device + set_current_device(compute_device) compute_device_to_be_patched = mm.get_torch_device() - original_loader = NODE_CLASS_MAPPINGS["WanVideoModelLoader"]() - loader_module = inspect.getmodule(original_loader) - - original_module_device = loader_module.device - loader_module.device = compute_device_to_be_patched - result = original_loader.loadmodel(model, base_precision, load_device, quantization, compile_args, attention_mode, block_swap_args, lora, vram_management_args, extra_model=extra_model, - vace_model=vace_model, fantasytalking_model=fantasytalking_model, multitalk_model=multitalk_model, fantasyportrait_model=fantasyportrait_model, rms_norm_function=rms_norm_function,) - - loader_module.device = original_module_device + result = original_loader.loadmodel(model, base_precision, load_device, quantization, **kwargs,) patcher = result[0] - return (patcher, compute_device) + try: + return (patcher, compute_device) + + finally: + loader_module.device = original_module_device + +class WanVideoSampler: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("WANVIDEOMODEL",), + "compute_device": ("MULTIGPUDEVICE",), + "image_embeds": ("WANVIDIMAGE_EMBEDS", ), + "steps": ("INT", {"default": 30, "min": 1}), + "cfg": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 30.0, "step": 0.01}), + "shift": ("FLOAT", {"default": 5.0, "min": 0.0, "max": 1000.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "force_offload": ("BOOLEAN", {"default": True, "tooltip": "Moves the model to the offload device after sampling"}), + "scheduler": (scheduler_list, {"default": "unipc",}), + "riflex_freq_index": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1, "tooltip": "Frequency index for RIFLEX, disabled when 0, default 6. Allows for new frames to be generated after without looping"}), + }, + "optional": { + "text_embeds": ("WANVIDEOTEXTEMBEDS", ), + "samples": ("LATENT", {"tooltip": "init Latents to use for video2video process"} ), + "denoise_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "feta_args": ("FETAARGS", ), + "context_options": ("WANVIDCONTEXT", ), + "cache_args": ("CACHEARGS", ), + "flowedit_args": ("FLOWEDITARGS", ), + "batched_cfg": ("BOOLEAN", {"default": False, "tooltip": "Batch cond and uncond for faster sampling, possibly faster on some hardware, uses more memory"}), + "slg_args": ("SLGARGS", ), + "rope_function": (rope_functions, {"default": "comfy", "tooltip": "Comfy's RoPE implementation doesn't use complex numbers and can thus be compiled, that should be a lot faster when using torch.compile. Chunked version has reduced peak VRAM usage when not using torch.compile"}), + "loop_args": ("LOOPARGS", ), + "experimental_args": ("EXPERIMENTALARGS", ), + "sigmas": ("SIGMAS", ), + "unianimate_poses": ("UNIANIMATE_POSE", ), + "fantasytalking_embeds": ("FANTASYTALKING_EMBEDS", ), + "uni3c_embeds": ("UNI3C_EMBEDS", ), + "multitalk_embeds": ("MULTITALK_EMBEDS", ), + "freeinit_args": ("FREEINITARGS", ), + "start_step": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1, "tooltip": "Start step for the sampling, 0 means full sampling, otherwise samples only from this step"}), + "end_step": ("INT", {"default": -1, "min": -1, "max": 10000, "step": 1, "tooltip": "End step for the sampling, -1 means full sampling, otherwise samples only until this step"}), + "add_noise_to_samples": ("BOOLEAN", {"default": False, "tooltip": "Add noise to the samples before sampling, needed for video2video sampling when starting from clean video"}), + } + } + + RETURN_TYPES = ("LATENT", "LATENT",) + RETURN_NAMES = ("samples", "denoised_samples",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware sampler that ensures correct device for each model" + + def process(self, model, compute_device, **kwargs): + from . import set_current_device + + original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() + sampler_module = inspect.getmodule(original_sampler) + + original_module_device = sampler_module.device + original_module_offload_device = sampler_module.offload_device + + set_current_device(compute_device) + compute_device_to_be_patched = mm.get_torch_device() + sampler_module.device = compute_device_to_be_patched + + transformer = model.model.diffusion_model + transformer_options = model.model_options.get("transformer_options", {}) + block_swap_args = transformer_options.get("block_swap_args") + + multi_gpu_block_swap = block_swap_args is not None and "swap_device" in block_swap_args + offload_device_to_be_patched = None + if multi_gpu_block_swap: + swap_label = block_swap_args.get("swap_device") + logger.info(f"[MultiGPU WanVideoWrapper][WanVideoSamplerMultiGPU] block swap enabled, swap device: {swap_label}") + offload_device_to_be_patched = torch.device(str(swap_label)) + sampler_module.offload_device = offload_device_to_be_patched + + if transformer is not None and offload_device_to_be_patched is not None: + transformer.offload_device = offload_device_to_be_patched + transformer.cache_device = offload_device_to_be_patched + + try: + return original_sampler.process(model, **kwargs) + finally: + sampler_module.device = original_module_device + sampler_module.offload_device = original_module_offload_device class WanVideoTextEncode: @classmethod @@ -117,20 +212,13 @@ class WanVideoTextEncode: else: device = "gpu" - if t5 is not None: - text_encoder = t5[0] - else: - text_encoder = None - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] current_device set to: {load_device}") - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeMulitiGPU] device set to: {device}") + text_encoder = t5[0] original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() prompt_embeds_dict = original_encoder.process(positive_prompt, negative_prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) return (prompt_embeds_dict) def parse_prompt_weights(self, prompt): - """Extract text and weights from prompts with (text:weight) format""" original_parser = NODE_CLASS_MAPPINGS["WanVideoTextEncode"]() return original_parser.parse_prompt_weights(prompt) @@ -161,17 +249,13 @@ class LoadWanVideoT5TextEncoder: def loadmodel(self, model_name, precision, device=None, quantization="disabled"): from . import set_current_device - if device is not None: - set_current_device(device) + set_current_device(device) if device == "cpu": load_device = "offload_device" else: load_device = "main_device" - logger.info(f"[MultiGPU WanVideoWrapper][LoadWanVideoT5TextEncoder] current_device set to: {device}") - logger.info(f"[MultiGPU WanVideoWrapper][LoadWanVideoT5TextEncoder] load_device set to: {load_device}") - original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoT5TextEncoder"]() text_encoder = original_loader.loadmodel(model_name, precision, load_device, quantization) @@ -210,17 +294,13 @@ class WanVideoTextEncodeCached: def process(self, model_name, precision, positive_prompt, negative_prompt, quantization='disabled', use_disk_cache=True, load_device=None, extender_args=None): from . import set_current_device - if load_device is not None: - set_current_device(load_device) + set_current_device(load_device) if load_device == "cpu": device = "cpu" else: device = "gpu" - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeCachedMulitiGPU] current_device set to: {load_device}") - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeCachedMulitiGPU] device set to: {device}") - original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeCached"]() prompt_embeds_dict, negative_text_embeds, positive_prompt_out = original_encoder.process(model_name, precision, positive_prompt, negative_prompt, quantization, use_disk_cache, device, extender_args) @@ -250,21 +330,14 @@ class WanVideoTextEncodeSingle: def process(self, prompt, t5=None, load_device=None, force_offload=True, model_to_offload=None, use_disk_cache=False): from . import set_current_device - if load_device is not None: - set_current_device(load_device) + set_current_device(load_device) if load_device == "cpu": device = "cpu" else: device = "gpu" - if t5 is not None: - text_encoder = t5[0] - else: - text_encoder = None - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeSingleMulitiGPU] current_device set to: {load_device}") - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTextEncodeSingleMulitiGPU] device set to: {device}") + text_encoder = t5[0] original_encoder = NODE_CLASS_MAPPINGS["WanVideoTextEncodeSingle"]() prompt_embeds_dict = original_encoder.process(prompt, text_encoder, force_offload, model_to_offload, use_disk_cache, device) @@ -297,15 +370,11 @@ class WanVideoVAELoader: def loadmodel(self, model_name, load_device=None, precision="fp16", compile_args=None): from . import set_current_device - if load_device is not None: - set_current_device(load_device) - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoVAELoaderMultiGPU] load_device set to: {load_device}") + set_current_device(load_device) original_loader = NODE_CLASS_MAPPINGS["WanVideoVAELoader"]() vae_model = original_loader.loadmodel(model_name, precision, compile_args) - # Return both the VAE model AND the selected device for device propagation return vae_model, load_device class WanVideoTinyVAELoader: @@ -333,15 +402,11 @@ class WanVideoTinyVAELoader: def loadmodel(self, model_name, load_device=None, precision="fp16", parallel=False): from . import set_current_device - if load_device is not None: - set_current_device(load_device) - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoTinyVAELoader] load_device set to: {load_device}") + set_current_device(load_device) original_loader = NODE_CLASS_MAPPINGS["WanVideoTinyVAELoader"]() vae_model = original_loader.loadmodel(model_name, precision, parallel) - # Return both the VAE model AND the selected device for device propagation return vae_model, load_device @@ -369,8 +434,7 @@ class WanVideoBlockSwap: def setargs(self, swap_device=None, **kwargs): block_swap_config = dict(kwargs) - if swap_device is not None: - block_swap_config["swap_device"] = str(swap_device) + block_swap_config["swap_device"] = str(swap_device) return (block_swap_config,) class WanVideoImageToVideoEncode: @@ -622,86 +686,3 @@ class WanVideoDecode: return (decode,) -class WanVideoSampler: - @classmethod - def INPUT_TYPES(s): - # Get original inputs and add our device input - original_types = NODE_CLASS_MAPPINGS["WanVideoSampler"].INPUT_TYPES() - original_types["required"]["compute_device"] = ("MULTIGPUDEVICE",) - return original_types - - RETURN_TYPES = ("LATENT", "LATENT",) - RETURN_NAMES = ("samples", "denoised_samples",) - FUNCTION = "process" - CATEGORY = "multigpu/WanVideoWrapper" - DESCRIPTION = "MultiGPU-aware sampler that ensures correct device for each model" - - def process(self, model, compute_device, **kwargs): - from . import set_current_device - logger.info( - f"[MultiGPU WanVideoSampler] Received request to process on: {compute_device}" - ) - - patcher = model - transformer = None - if hasattr(patcher, "model"): - transformer = getattr(patcher.model, "diffusion_model", None) - - if compute_device: - target_device = torch.device(compute_device) - set_current_device(target_device) - else: - target_device = mm.get_torch_device() - - normalized_swap_device = None - transformer_options = {} - if hasattr(patcher, "model_options"): - transformer_options = patcher.model_options.get("transformer_options", {}) - block_swap_args = transformer_options.get("block_swap_args") if transformer_options else None - if block_swap_args: - swap_label = block_swap_args.get("resolved_swap_device") or block_swap_args.get("swap_device") - if swap_label: - try: - normalized_swap_device = torch.device(str(swap_label)) - except (TypeError, ValueError): - logger.warning( - "[MultiGPU WanVideoSampler] Invalid swap device '%s', leaving sampler offload unchanged", - swap_label, - ) - normalized_swap_device = None - - original_sampler = NODE_CLASS_MAPPINGS["WanVideoSampler"]() - sampler_module = inspect.getmodule(original_sampler) - - original_module_device = None - original_module_offload = None - had_sampler_offload_attr = False - if sampler_module is not None: - original_module_device = getattr(sampler_module, "device", None) - had_sampler_offload_attr = hasattr(sampler_module, "offload_device") - original_module_offload = getattr(sampler_module, "offload_device", None) - setattr(sampler_module, "device", target_device) - if normalized_swap_device is not None: - setattr(sampler_module, "offload_device", normalized_swap_device) - elif compute_device == "cpu": - setattr(sampler_module, "offload_device", target_device) - else: - logger.error("[MultiGPU WanVideoSampler] Unable to resolve sampler module for device patching.") - - if transformer is not None and normalized_swap_device is not None: - transformer.offload_device = normalized_swap_device - transformer.cache_device = normalized_swap_device - - try: - return original_sampler.process(model=patcher, **kwargs) - finally: - if sampler_module is not None: - if original_module_device is not None: - setattr(sampler_module, "device", original_module_device) - if had_sampler_offload_attr: - setattr(sampler_module, "offload_device", original_module_offload) - else: - try: - delattr(sampler_module, "offload_device") - except AttributeError: - pass From f0313e49aa3b94379af0b04107814b3850c76ade Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 08:12:26 -0500 Subject: [PATCH 10/22] WanVideoDecode cleanup --- wanvideo.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/wanvideo.py b/wanvideo.py index 2fafb79..bcd9164 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -261,7 +261,6 @@ class LoadWanVideoT5TextEncoder: return text_encoder, device - class WanVideoTextEncodeCached: @classmethod def INPUT_TYPES(s): @@ -409,7 +408,6 @@ class WanVideoTinyVAELoader: return vae_model, load_device - class WanVideoBlockSwap: @classmethod def INPUT_TYPES(s): @@ -665,24 +663,15 @@ class WanVideoDecode: def decode(self, vae, load_device, samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization="default"): from . import set_current_device + original_decode = NODE_CLASS_MAPPINGS["WanVideoDecode"]() + decode_module = inspect.getmodule(original_decode) + original_module_device = decode_module.device + set_current_device(load_device) compute_device_to_be_patched = mm.get_torch_device() - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoDecodeMultiGPU] load device: {load_device}") - - original_loader = NODE_CLASS_MAPPINGS["WanVideoDecode"]() - loader_module = inspect.getmodule(original_loader) - - original_module_device = loader_module.device - - loader_module.device = compute_device_to_be_patched - - result = original_loader.decode(vae[0], samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization) - - loader_module.device = original_module_device - - decode = result[0] - - return (decode,) - + decode_module.device = compute_device_to_be_patched + try: + return original_decode.decode(vae[0], samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization) + finally: + decode_module.device = original_module_device From b04ae1e3787da83824aa38dc3b17782c7b739c54 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 12:32:29 -0500 Subject: [PATCH 11/22] Coverted WanVideoImageToVideoEncode to shim --- wanvideo.py | 161 ++++++---------------------------------------------- 1 file changed, 16 insertions(+), 145 deletions(-) diff --git a/wanvideo.py b/wanvideo.py index bcd9164..7bda09b 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -472,156 +472,27 @@ class WanVideoImageToVideoEncode: temporal_mask=None, extra_latents=None, clip_embeds=None, tiled_vae=False, add_cond_latents=None, vae=None, load_device=None): from . import set_current_device + original_encoder = NODE_CLASS_MAPPINGS["WanVideoImageToVideoEncode"]() + encoder_module = inspect.getmodule(original_encoder) + + original_module_device = encoder_module.device + original_module_offload = encoder_module.offload_device + set_current_device(load_device) - - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] load device: {load_device}") - device = mm.get_torch_device() - PATCH_SIZE = (1, 2, 2) - offload_device = mm.unet_offload_device() + compute_device_to_be_patched = mm.get_torch_device() + encoder_module.device = compute_device_to_be_patched - logger.info(f"[MultiGPU WanVideoWrapper][WanVideoImageToVideoEncodeMultiGPU] torch device: {device}") + encoder_module.offload_device = mm.unet_offload_device() - if vae is not None: - vae = vae[0] - - if start_image is None and end_image is None and add_cond_latents is None: - return WanVideoEmptyEmbeds().process( - num_frames, width, height, control_embeds=control_embeds, extra_latents=extra_latents, - ) - if vae is None: - raise ValueError("VAE is required for image encoding.") - H = height - W = width - - lat_h = H // vae.upsampling_factor - lat_w = W // vae.upsampling_factor + inner_vae = vae[0] - num_frames = ((num_frames - 1) // 4) * 4 + 1 - two_ref_images = start_image is not None and end_image is not None - - if start_image is None and end_image is not None: - fun_or_fl2v_model = True # end image alone only works with this option - - base_frames = num_frames + (1 if two_ref_images and not fun_or_fl2v_model else 0) - if temporal_mask is None: - mask = torch.zeros(1, base_frames, lat_h, lat_w, device=device, dtype=vae.dtype) - if start_image is not None: - mask[:, 0:start_image.shape[0]] = 1 # First frame - if end_image is not None: - mask[:, -end_image.shape[0]:] = 1 # End frame if exists - else: - mask = common_upscale(temporal_mask.unsqueeze(1).to(device), lat_w, lat_h, "nearest", "disabled").squeeze(1) - if mask.shape[0] > base_frames: - mask = mask[:base_frames] - elif mask.shape[0] < base_frames: - mask = torch.cat([mask, torch.zeros(base_frames - mask.shape[0], lat_h, lat_w, device=device)]) - mask = mask.unsqueeze(0).to(device, vae.dtype) - - # Repeat first frame and optionally end frame - start_mask_repeated = torch.repeat_interleave(mask[:, 0:1], repeats=4, dim=1) # T, C, H, W - if end_image is not None and not fun_or_fl2v_model: - end_mask_repeated = torch.repeat_interleave(mask[:, -1:], repeats=4, dim=1) # T, C, H, W - mask = torch.cat([start_mask_repeated, mask[:, 1:-1], end_mask_repeated], dim=1) - else: - mask = torch.cat([start_mask_repeated, mask[:, 1:]], dim=1) - - # Reshape mask into groups of 4 frames - mask = mask.view(1, mask.shape[1] // 4, 4, lat_h, lat_w) # 1, T, C, H, W - mask = mask.movedim(1, 2)[0]# C, T, H, W - - # Resize and rearrange the input image dimensions - if start_image is not None: - start_image = start_image[..., :3] - if start_image.shape[1] != H or start_image.shape[2] != W: - resized_start_image = common_upscale(start_image.movedim(-1, 1), W, H, "lanczos", "disabled").movedim(0, 1) - else: - resized_start_image = start_image.permute(3, 0, 1, 2) # C, T, H, W - resized_start_image = resized_start_image * 2 - 1 - if noise_aug_strength > 0.0: - resized_start_image = add_noise_to_reference_video(resized_start_image, ratio=noise_aug_strength) - - if end_image is not None: - end_image = end_image[..., :3] - if end_image.shape[1] != H or end_image.shape[2] != W: - resized_end_image = common_upscale(end_image.movedim(-1, 1), W, H, "lanczos", "disabled").movedim(0, 1) - else: - resized_end_image = end_image.permute(3, 0, 1, 2) # C, T, H, W - resized_end_image = resized_end_image * 2 - 1 - if noise_aug_strength > 0.0: - resized_end_image = add_noise_to_reference_video(resized_end_image, ratio=noise_aug_strength) - - # Concatenate image with zero frames and encode - if temporal_mask is None: - if start_image is not None and end_image is None: - zero_frames = torch.zeros(3, num_frames-start_image.shape[0], H, W, device=device, dtype=vae.dtype) - concatenated = torch.cat([resized_start_image.to(device, dtype=vae.dtype), zero_frames], dim=1) - del resized_start_image, zero_frames - elif start_image is None and end_image is not None: - zero_frames = torch.zeros(3, num_frames-end_image.shape[0], H, W, device=device, dtype=vae.dtype) - concatenated = torch.cat([zero_frames, resized_end_image.to(device, dtype=vae.dtype)], dim=1) - del zero_frames - elif start_image is None and end_image is None: - concatenated = torch.zeros(3, num_frames, H, W, device=device, dtype=vae.dtype) - else: - if fun_or_fl2v_model: - zero_frames = torch.zeros(3, num_frames-(start_image.shape[0]+end_image.shape[0]), H, W, device=device, dtype=vae.dtype) - else: - zero_frames = torch.zeros(3, num_frames-1, H, W, device=device, dtype=vae.dtype) - concatenated = torch.cat([resized_start_image.to(device, dtype=vae.dtype), zero_frames, resized_end_image.to(device, dtype=vae.dtype)], dim=1) - del resized_start_image, zero_frames - else: - temporal_mask = common_upscale(temporal_mask.unsqueeze(1), W, H, "nearest", "disabled").squeeze(1) - concatenated = resized_start_image[:,:num_frames].to(vae.dtype) * temporal_mask[:num_frames].unsqueeze(0).to(vae.dtype) - del resized_start_image, temporal_mask - - mm.soft_empty_cache() - gc.collect() - - vae.to(device) - y = vae.encode([concatenated], device, end_=(end_image is not None and not fun_or_fl2v_model),tiled=tiled_vae)[0] - del concatenated - - has_ref = False - if extra_latents is not None: - samples = extra_latents["samples"].squeeze(0) - y = torch.cat([samples, y], dim=1) - mask = torch.cat([torch.ones_like(mask[:, 0:samples.shape[1]]), mask], dim=1) - num_frames += samples.shape[1] * 4 - has_ref = True - y[:, :1] *= start_latent_strength - y[:, -1:] *= end_latent_strength - - # Calculate maximum sequence length - patches_per_frame = lat_h * lat_w // (PATCH_SIZE[1] * PATCH_SIZE[2]) - frames_per_stride = (num_frames - 1) // 4 + (2 if end_image is not None and not fun_or_fl2v_model else 1) - max_seq_len = frames_per_stride * patches_per_frame - - if add_cond_latents is not None: - add_cond_latents["ref_latent_neg"] = vae.encode(torch.zeros(1, 3, 1, H, W, device=device, dtype=vae.dtype), device) - - if force_offload: - vae.model.to(offload_device) - mm.soft_empty_cache() - gc.collect() - - image_embeds = { - "image_embeds": y, - "clip_context": clip_embeds.get("clip_embeds", None) if clip_embeds is not None else None, - "negative_clip_context": clip_embeds.get("negative_clip_embeds", None) if clip_embeds is not None else None, - "max_seq_len": max_seq_len, - "num_frames": num_frames, - "lat_h": lat_h, - "lat_w": lat_w, - "control_embeds": control_embeds["control_embeds"] if control_embeds is not None else None, - "end_image": resized_end_image if end_image is not None else None, - "fun_or_fl2v_model": fun_or_fl2v_model, - "has_ref": has_ref, - "add_cond_latents": add_cond_latents, - "mask": mask - } - - return (image_embeds,) + try: + return original_encoder.process(width, height, num_frames, force_offload, noise_aug_strength, start_latent_strength, end_latent_strength, start_image, + end_image, control_embeds, fun_or_fl2v_model, temporal_mask, extra_latents, clip_embeds, tiled_vae, add_cond_latents, inner_vae,) + finally: + encoder_module.device = original_module_device + encoder_module.offload_device = original_module_offload class WanVideoDecode: @classmethod From ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 13:01:20 -0500 Subject: [PATCH 12/22] improve documentation on supporting ROCm cuda-enumerated GPUs. --- device_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device_utils.py b/device_utils.py index 8d93f5b..18d55e1 100644 --- a/device_utils.py +++ b/device_utils.py @@ -17,7 +17,7 @@ def get_device_list(): Returns a comprehensive list of all available devices across all types: - CPU (always available) - - CUDA devices (NVIDIA GPUs) + - CUDA devices (NVIDIA GPUs + AMD w/ ROCm GPUs) - XPU devices (Intel GPUs) - NPU devices (Ascend NPUs from Huawei) - MLU devices (Cambricon MLUs) From 2f605e0db3c459d4c86a8e35a9d096db653faf64 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 14:41:53 -0500 Subject: [PATCH 13/22] Add WanVideoVACEEncode and enhance WanVideoEncode for multi-GPU support - Implemented WanVideoVACEEncode class for encoding with VACE, including input parameters for width, height, number of frames, and strength. - Enhanced WanVideoEncode class to support multi-GPU encoding, with additional parameters for noise augmentation and latent strength. - Updated device management to ensure proper device context during encoding processes. --- __init__.py | 6 +- ...1_3B_control_lora_example_01_MultiGPU.json | 1274 +++++++++-------- wanvideo.py | 86 ++ 3 files changed, 786 insertions(+), 580 deletions(-) diff --git a/__init__.py b/__init__.py index a6df0d1..53c313c 100644 --- a/__init__.py +++ b/__init__.py @@ -222,6 +222,8 @@ from .wanvideo import ( WanVideoDecode, WanVideoModelLoader, WanVideoSampler, + WanVideoVACEEncode, + WanVideoEncode, ) from .wrappers import ( @@ -368,7 +370,9 @@ wanvideo_nodes = { "WanVideoImageToVideoEncodeMultiGPU": WanVideoImageToVideoEncode, "WanVideoDecodeMultiGPU": WanVideoDecode, "WanVideoModelLoaderMultiGPU": WanVideoModelLoader, - "WanVideoSamplerMultiGPU": WanVideoSampler + "WanVideoSamplerMultiGPU": WanVideoSampler, + "WanVideoVACEEncodeMultiGPU": WanVideoVACEEncode, + "WanVideoEncodeMultiGPU": WanVideoEncode, } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/examples/wannvideowrapper/wanvideo_1_3B_control_lora_example_01_MultiGPU.json b/examples/wannvideowrapper/wanvideo_1_3B_control_lora_example_01_MultiGPU.json index 9bf08bf..32ebe53 100755 --- a/examples/wannvideowrapper/wanvideo_1_3B_control_lora_example_01_MultiGPU.json +++ b/examples/wannvideowrapper/wanvideo_1_3B_control_lora_example_01_MultiGPU.json @@ -1,8 +1,8 @@ { "id": "04f1de76-e363-4c27-bec7-eb184ac6e476", "revision": 0, - "last_node_id": 111, - "last_link_id": 161, + "last_node_id": 114, + "last_link_id": 175, "nodes": [ { "id": 36, @@ -51,6 +51,8 @@ } ], "properties": { + "cnr_id": "ComfyUI-WanVideoWrapper", + "ver": "76ea2aa6a664edbd553799f78d6bafdd23e34dc6", "Node name for S&R": "WanVideoTorchCompileSettings" }, "widgets_values": [ @@ -111,56 +113,6 @@ "color": "#432", "bgcolor": "#653" }, - { - "id": 28, - "type": "WanVideoDecode", - "pos": [ - 1704.86572265625, - -604.0441284179688 - ], - "size": [ - 315, - 198 - ], - "flags": {}, - "order": 17, - "mode": 0, - "inputs": [ - { - "name": "vae", - "type": "WANVAE", - "link": 159 - }, - { - "name": "samples", - "type": "LATENT", - "link": 154 - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 145 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoDecode" - }, - "widgets_values": [ - false, - 272, - 272, - 144, - 128, - "default" - ], - "color": "#322", - "bgcolor": "#533" - }, { "id": 30, "type": "VHS_VideoCombine", @@ -208,6 +160,8 @@ } ], "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "08e8df15db24da292d4b7f943c460dc2ab442b24", "Node name for S&R": "VHS_VideoCombine" }, "widgets_values": { @@ -225,239 +179,17 @@ "hidden": false, "paused": false, "params": { - "filename": "WanVideoWrapper_I2V_00004.mp4", + "filename": "WanVideoWrapper_I2V_00002.mp4", "subfolder": "", "type": "output", "format": "video/h264-mp4", "frame_rate": 16, - "workflow": "WanVideoWrapper_I2V_00004.png", - "fullpath": "/home/johnj/ComfyUI/output/WanVideoWrapper_I2V_00004.mp4" + "workflow": "WanVideoWrapper_I2V_00002.png", + "fullpath": "/home/johnj/ComfyUI/output/WanVideoWrapper_I2V_00002.mp4" } } } }, - { - "id": 97, - "type": "VHS_LoadVideo", - "pos": [ - -257.8260498046875, - 26.503103256225586 - ], - "size": [ - 247.455078125, - 551.455078125 - ], - "flags": {}, - "order": 4, - "mode": 0, - "inputs": [ - { - "name": "meta_batch", - "shape": 7, - "type": "VHS_BatchManager", - "link": null - }, - { - "name": "vae", - "shape": 7, - "type": "VAE", - "link": null - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 147 - ] - }, - { - "name": "frame_count", - "type": "INT", - "links": null - }, - { - "name": "audio", - "type": "AUDIO", - "links": null - }, - { - "name": "video_info", - "type": "VHS_VIDEOINFO", - "links": null - } - ], - "properties": { - "Node name for S&R": "VHS_LoadVideo" - }, - "widgets_values": { - "video": "wolf_interpolated.mp4", - "force_rate": 0, - "custom_width": 0, - "custom_height": 0, - "frame_load_cap": 0, - "skip_first_frames": 0, - "select_every_nth": 1, - "format": "AnimateDiff", - "choose video to upload": "image", - "videopreview": { - "hidden": false, - "paused": false, - "params": { - "filename": "wolf_interpolated.mp4", - "type": "input", - "format": "video/mp4", - "force_rate": 0, - "custom_width": 0, - "custom_height": 0, - "frame_load_cap": 0, - "skip_first_frames": 0, - "select_every_nth": 1 - } - } - } - }, - { - "id": 95, - "type": "WanVideoEncode", - "pos": [ - 280.9074401855469, - -116.20671844482422 - ], - "size": [ - 315, - 242 - ], - "flags": {}, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "vae", - "type": "WANVAE", - "link": 158 - }, - { - "name": "image", - "type": "IMAGE", - "link": 148 - }, - { - "name": "mask", - "shape": 7, - "type": "MASK", - "link": null - } - ], - "outputs": [ - { - "name": "samples", - "type": "LATENT", - "links": [ - 132 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoEncode" - }, - "widgets_values": [ - false, - 272, - 272, - 144, - 128, - 0, - 1.0000000000000002 - ] - }, - { - "id": 104, - "type": "ImageBlur", - "pos": [ - 89.50601959228516, - 217.970947265625 - ], - "size": [ - 315, - 82 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 147 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 148, - 149 - ] - } - ], - "properties": { - "Node name for S&R": "ImageBlur" - }, - "widgets_values": [ - 4, - 1 - ] - }, - { - "id": 103, - "type": "ImageConcatMulti", - "pos": [ - 2055.855224609375, - -543.0326538085938 - ], - "size": [ - 315, - 150 - ], - "flags": {}, - "order": 18, - "mode": 0, - "inputs": [ - { - "name": "image_1", - "type": "IMAGE", - "link": 149 - }, - { - "name": "image_2", - "shape": 7, - "type": "IMAGE", - "link": 145 - } - ], - "outputs": [ - { - "name": "images", - "type": "IMAGE", - "slot_index": 0, - "links": [ - 146 - ] - } - ], - "properties": {}, - "widgets_values": [ - 2, - "right", - false, - null - ] - }, { "id": 33, "type": "Note", @@ -470,7 +202,7 @@ 88 ], "flags": {}, - "order": 5, + "order": 4, "mode": 0, "inputs": [], "outputs": [], @@ -499,7 +231,7 @@ { "name": "latents", "type": "LATENT", - "link": 132 + "link": 164 }, { "name": "fun_ref_image", @@ -519,6 +251,8 @@ } ], "properties": { + "cnr_id": "ComfyUI-WanVideoWrapper", + "ver": "76ea2aa6a664edbd553799f78d6bafdd23e34dc6", "Node name for S&R": "WanVideoControlEmbeds" }, "widgets_values": [ @@ -526,159 +260,6 @@ 0.7 ] }, - { - "id": 52, - "type": "WanVideoTeaCache", - "pos": [ - 1334.42626953125, - -588.2476196289062 - ], - "size": [ - 315, - 178 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "cache_args", - "type": "CACHEARGS", - "links": [ - 153 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoTeaCache" - }, - "widgets_values": [ - 0.1, - 1, - -1, - "offload_device", - "true", - "e" - ] - }, - { - "id": 64, - "type": "WanVideoTorchCompileSettings", - "pos": [ - -276.8500671386719, - -1050.6326904296875 - ], - "size": [ - 390.5999755859375, - 202 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "torch_compile_args", - "type": "WANCOMPILEARGS", - "slot_index": 0, - "links": [ - 157 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoTorchCompileSettings" - }, - "widgets_values": [ - "inductor", - false, - "default", - false, - 64, - true, - 128 - ] - }, - { - "id": 109, - "type": "WanVideoVAELoaderMultiGPU", - "pos": [ - -246.82862854003906, - -338.9017333984375 - ], - "size": [ - 368.710693359375, - 106 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "compile_args", - "shape": 7, - "type": "WANCOMPILEARGS", - "link": null - } - ], - "outputs": [ - { - "name": "vae", - "type": "WANVAE", - "links": [ - 158, - 159 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoVAELoaderMultiGPU" - }, - "widgets_values": [ - "wan_2.1_vae.safetensors", - "cuda:0", - "bf16" - ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 111, - "type": "LoadWanVideoT5TextEncoderMultiGPU", - "pos": [ - 334.64263916015625, - -356.91937255859375 - ], - "size": [ - 348.9195251464844, - 130 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "wan_t5_model", - "type": "WANTEXTENCODER", - "links": [ - 161 - ] - } - ], - "properties": { - "Node name for S&R": "LoadWanVideoT5TextEncoderMultiGPU" - }, - "widgets_values": [ - "umt5-xxl-enc-bf16.safetensors", - "bf16", - "cuda:0", - "disabled" - ], - "color": "#233", - "bgcolor": "#355" - }, { "id": 110, "type": "WanVideoTextEncodeMultiGPU", @@ -691,7 +272,7 @@ 234 ], "flags": {}, - "order": 12, + "order": 11, "mode": 0, "inputs": [ { @@ -700,6 +281,12 @@ "type": "WANTEXTENCODER", "link": 161 }, + { + "name": "load_device", + "shape": 7, + "type": "MULTIGPUDEVICE", + "link": 166 + }, { "name": "model_to_offload", "shape": 7, @@ -717,18 +304,153 @@ } ], "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", "Node name for S&R": "WanVideoTextEncodeMultiGPU" }, "widgets_values": [ "video of a wolf", "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走", "cuda:0", - false, false ], "color": "#233", "bgcolor": "#355" }, + { + "id": 111, + "type": "LoadWanVideoT5TextEncoderMultiGPU", + "pos": [ + 278.705810546875, + -281.6587829589844 + ], + "size": [ + 348.9195251464844, + 150 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "wan_t5_model", + "type": "WANTEXTENCODER", + "links": [ + 161 + ] + }, + { + "name": "load_device", + "type": "MULTIGPUDEVICE", + "links": [ + 166 + ] + } + ], + "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", + "Node name for S&R": "LoadWanVideoT5TextEncoderMultiGPU" + }, + "widgets_values": [ + "umt5-xxl-enc-bf16.safetensors", + "bf16", + "cuda:0", + "disabled" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 64, + "type": "WanVideoTorchCompileSettings", + "pos": [ + -276.8500671386719, + -1050.6326904296875 + ], + "size": [ + 390.5999755859375, + 202 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "torch_compile_args", + "type": "WANCOMPILEARGS", + "slot_index": 0, + "links": [ + 168 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WanVideoWrapper", + "ver": "76ea2aa6a664edbd553799f78d6bafdd23e34dc6", + "Node name for S&R": "WanVideoTorchCompileSettings" + }, + "widgets_values": [ + "inductor", + false, + "default", + false, + 64, + true, + 128 + ] + }, + { + "id": 98, + "type": "WanVideoLoraSelect", + "pos": [ + -161.7538604736328, + -680.8223876953125 + ], + "size": [ + 315, + 150 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "prev_lora", + "shape": 7, + "type": "WANVIDLORA", + "link": null + }, + { + "name": "blocks", + "shape": 7, + "type": "SELECTEDBLOCKS", + "link": null + } + ], + "outputs": [ + { + "name": "lora", + "type": "WANVIDLORA", + "links": [ + 169 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WanVideoWrapper", + "ver": "76ea2aa6a664edbd553799f78d6bafdd23e34dc6", + "Node name for S&R": "WanVideoLoraSelect" + }, + "widgets_values": [ + "wan2.1-1.3b-control-lora-tile-v1.1_comfy.safetensors", + 1, + false, + true + ] + }, { "id": 108, "type": "WanVideoSamplerMultiGPU", @@ -738,7 +460,7 @@ ], "size": [ 327.80859375, - 902 + 922 ], "flags": {}, "order": 16, @@ -747,7 +469,12 @@ { "name": "model", "type": "WANVIDEOMODEL", - "link": 155 + "link": 170 + }, + { + "name": "compute_device", + "type": "MULTIGPUDEVICE", + "link": 171 }, { "name": "image_embeds", @@ -850,7 +577,7 @@ "name": "samples", "type": "LATENT", "links": [ - 154 + 174 ] }, { @@ -860,6 +587,8 @@ } ], "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", "Node name for S&R": "WanVideoSamplerMultiGPU" }, "widgets_values": [ @@ -882,25 +611,410 @@ "bgcolor": "#355" }, { - "id": 107, - "type": "WanVideoModelLoaderMultiGPU", + "id": 109, + "type": "WanVideoVAELoaderMultiGPU", "pos": [ - 295.29010009765625, - -723.5640258789062 + 689.3143310546875, + -934.9156494140625 ], "size": [ - 400.63916015625, - 274 + 368.710693359375, + 126 ], "flags": {}, - "order": 13, + "order": 8, "mode": 0, "inputs": [ { "name": "compile_args", "shape": 7, "type": "WANCOMPILEARGS", - "link": 157 + "link": null + } + ], + "outputs": [ + { + "name": "vae", + "type": "WANVAE", + "links": [ + 162, + 172 + ] + }, + { + "name": "load_device", + "type": "MULTIGPUDEVICE", + "links": [ + 163, + 173 + ] + } + ], + "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", + "Node name for S&R": "WanVideoVAELoaderMultiGPU" + }, + "widgets_values": [ + "wan_2.1_vae.safetensors", + "cuda:0", + "bf16" + ], + "color": "#233", + "bgcolor": "#355" + }, + { + "id": 97, + "type": "VHS_LoadVideo", + "pos": [ + -854.7728271484375, + -166.42774963378906 + ], + "size": [ + 247.455078125, + 551.455078125 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 147 + ] + }, + { + "name": "frame_count", + "type": "INT", + "links": null + }, + { + "name": "audio", + "type": "AUDIO", + "links": null + }, + { + "name": "video_info", + "type": "VHS_VIDEOINFO", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "08e8df15db24da292d4b7f943c460dc2ab442b24", + "Node name for S&R": "VHS_LoadVideo" + }, + "widgets_values": { + "video": "wolf_interpolated.mp4", + "force_rate": 0, + "custom_width": 0, + "custom_height": 0, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1, + "format": "AnimateDiff", + "choose video to upload": "image", + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "wolf_interpolated.mp4", + "type": "input", + "format": "video/mp4", + "force_rate": 0, + "custom_width": 0, + "custom_height": 0, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1 + } + } + } + }, + { + "id": 103, + "type": "ImageConcatMulti", + "pos": [ + 1796.8583984375, + -624.5692138671875 + ], + "size": [ + 315, + 150 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "image_1", + "type": "IMAGE", + "link": 149 + }, + { + "name": "image_2", + "shape": 7, + "type": "IMAGE", + "link": 175 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 146 + ] + } + ], + "properties": { + "cnr_id": "comfyui-kjnodes", + "ver": "3fcd22f2fe2be69c3229f192362b91888277cbcb" + }, + "widgets_values": [ + 2, + "right", + false, + null + ] + }, + { + "id": 52, + "type": "WanVideoTeaCache", + "pos": [ + 784.0582885742188, + 198.33489990234375 + ], + "size": [ + 315, + 178 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "cache_args", + "type": "CACHEARGS", + "links": [ + 153 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-WanVideoWrapper", + "ver": "76ea2aa6a664edbd553799f78d6bafdd23e34dc6", + "Node name for S&R": "WanVideoTeaCache" + }, + "widgets_values": [ + 0.1, + 1, + -1, + "offload_device", + "true", + "e" + ] + }, + { + "id": 104, + "type": "ImageBlur", + "pos": [ + -336.56787109375, + -98.77837371826172 + ], + "size": [ + 315, + 82 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 147 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "slot_index": 0, + "links": [ + 149, + 165 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.64", + "Node name for S&R": "ImageBlur" + }, + "widgets_values": [ + 4, + 1 + ] + }, + { + "id": 112, + "type": "WanVideoEncodeMultiGPU", + "pos": [ + 169.56695556640625, + 108.46249389648438 + ], + "size": [ + 271.0992126464844, + 262 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "vae", + "type": "WANVAE", + "link": 162 + }, + { + "name": "load_device", + "type": "MULTIGPUDEVICE", + "link": 163 + }, + { + "name": "image", + "type": "IMAGE", + "link": 165 + }, + { + "name": "mask", + "shape": 7, + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "samples", + "type": "LATENT", + "links": [ + 164 + ] + } + ], + "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", + "Node name for S&R": "WanVideoEncodeMultiGPU" + }, + "widgets_values": [ + false, + 272, + 272, + 144, + 128, + 0, + 1 + ], + "color": "#008181", + "bgcolor": "rgba(24,24,27,.9)" + }, + { + "id": 114, + "type": "WanVideoDecodeMultiGPU", + "pos": [ + 1679.09765625, + -978.121826171875 + ], + "size": [ + 271.8716735839844, + 218 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "vae", + "type": "WANVAE", + "link": 172 + }, + { + "name": "load_device", + "type": "MULTIGPUDEVICE", + "link": 173 + }, + { + "name": "samples", + "type": "LATENT", + "link": 174 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 175 + ] + } + ], + "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", + "Node name for S&R": "WanVideoDecodeMultiGPU" + }, + "widgets_values": [ + false, + 272, + 272, + 144, + 128, + "default" + ], + "color": "#008181", + "bgcolor": "rgba(24,24,27,.9)" + }, + { + "id": 113, + "type": "WanVideoModelLoaderMultiGPU", + "pos": [ + 304.0582275390625, + -755.562255859375 + ], + "size": [ + 325.787109375, + 342 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "compile_args", + "shape": 7, + "type": "WANCOMPILEARGS", + "link": 168 }, { "name": "block_swap_args", @@ -912,7 +1026,7 @@ "name": "lora", "shape": 7, "type": "WANVIDLORA", - "link": 156 + "link": 169 }, { "name": "vram_management_args", @@ -921,7 +1035,7 @@ "link": null }, { - "name": "vace_model", + "name": "extra_model", "shape": 7, "type": "VACEPATH", "link": null @@ -937,6 +1051,12 @@ "shape": 7, "type": "MULTITALKMODEL", "link": null + }, + { + "name": "fantasyportrait_model", + "shape": 7, + "type": "FANTASYPORTRAITMODEL", + "link": null } ], "outputs": [ @@ -944,88 +1064,36 @@ "name": "model", "type": "WANVIDEOMODEL", "links": [ - 155 + 170 + ] + }, + { + "name": "compute_device", + "type": "MULTIGPUDEVICE", + "links": [ + 171 ] } ], "properties": { + "cnr_id": "comfyui-multigpu", + "ver": "ba0a3a6e21b0a7f54bd24c51419a8e85b9217c3f", "Node name for S&R": "WanVideoModelLoaderMultiGPU" }, "widgets_values": [ - "wan2.1_t2v_1.3B_bf16.safetensors", + "Wan2_1-T2V-1_3B_bf16.safetensors", "bf16", "disabled", + "offload_device", "cuda:1", - "sdpa" + "sdpa", + "default" ], - "color": "#233", - "bgcolor": "#355" - }, - { - "id": 98, - "type": "WanVideoLoraSelect", - "pos": [ - -161.7538604736328, - -680.8223876953125 - ], - "size": [ - 315, - 150 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "prev_lora", - "shape": 7, - "type": "WANVIDLORA", - "link": null - }, - { - "name": "blocks", - "shape": 7, - "type": "SELECTEDBLOCKS", - "link": null - } - ], - "outputs": [ - { - "name": "lora", - "type": "WANVIDLORA", - "links": [ - 156 - ] - } - ], - "properties": { - "Node name for S&R": "WanVideoLoraSelect" - }, - "widgets_values": [ - "wan2.1-1.3b-control-lora-tile-v1.1_comfy.safetensors", - 1, - false, - true - ] + "color": "#008181", + "bgcolor": "rgba(24,24,27,.9)" } ], "links": [ - [ - 132, - 95, - 0, - 96, - 0, - "LATENT" - ], - [ - 145, - 28, - 0, - 103, - 1, - "IMAGE" - ], [ 146, 103, @@ -1042,14 +1110,6 @@ 0, "IMAGE" ], - [ - 148, - 104, - 0, - 95, - 1, - "IMAGE" - ], [ 149, 104, @@ -1063,7 +1123,7 @@ 96, 0, 108, - 1, + 2, "WANVIDIMAGE_EMBEDS" ], [ @@ -1071,63 +1131,15 @@ 52, 0, 108, - 6, + 7, "CACHEARGS" ], - [ - 154, - 108, - 0, - 28, - 1, - "LATENT" - ], - [ - 155, - 107, - 0, - 108, - 0, - "WANVIDEOMODEL" - ], - [ - 156, - 98, - 0, - 107, - 2, - "WANVIDLORA" - ], - [ - 157, - 64, - 0, - 107, - 0, - "WANCOMPILEARGS" - ], - [ - 158, - 109, - 0, - 95, - 0, - "WANVAE" - ], - [ - 159, - 109, - 0, - 28, - 0, - "WANVAE" - ], [ 160, 110, 0, 108, - 2, + 3, "WANVIDEOTEXTEMBEDS" ], [ @@ -1137,18 +1149,123 @@ 110, 0, "WANTEXTENCODER" + ], + [ + 162, + 109, + 0, + 112, + 0, + "WANVAE" + ], + [ + 163, + 109, + 1, + 112, + 1, + "MULTIGPUDEVICE" + ], + [ + 164, + 112, + 0, + 96, + 0, + "LATENT" + ], + [ + 165, + 104, + 0, + 112, + 2, + "IMAGE" + ], + [ + 166, + 111, + 1, + 110, + 1, + "MULTIGPUDEVICE" + ], + [ + 168, + 64, + 0, + 113, + 0, + "WANCOMPILEARGS" + ], + [ + 169, + 98, + 0, + 113, + 2, + "WANVIDLORA" + ], + [ + 170, + 113, + 0, + 108, + 0, + "WANVIDEOMODEL" + ], + [ + 171, + 113, + 1, + 108, + 1, + "MULTIGPUDEVICE" + ], + [ + 172, + 109, + 0, + 114, + 0, + "WANVAE" + ], + [ + 173, + 109, + 1, + 114, + 1, + "MULTIGPUDEVICE" + ], + [ + 174, + 108, + 0, + 114, + 2, + "LATENT" + ], + [ + 175, + 114, + 0, + 103, + 1, + "IMAGE" ] ], "groups": [], "config": {}, "extra": { "ds": { - "scale": 0.814027493868451, + "scale": 0.5559917313492605, "offset": [ - 714.8717021081054, - 1253.694882650272 + 1542.2773971567624, + 1325.1448841491515 ] }, + "frontendVersion": "1.27.10", "node_versions": { "ComfyUI-WanVideoWrapper": "5a2383621a05825d0d0437781afcb8552d9590fd", "ComfyUI-VideoHelperSuite": "0a75c7958fe320efcb052f1d9f8451fd20c730a8", @@ -1158,8 +1275,7 @@ "VHS_latentpreview": false, "VHS_latentpreviewrate": 0, "VHS_MetadataImage": true, - "VHS_KeepIntermediate": true, - "frontendVersion": "1.25.11" + "VHS_KeepIntermediate": true }, "version": 0.4 } \ No newline at end of file diff --git a/wanvideo.py b/wanvideo.py index 7bda09b..f4164dd 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -546,3 +546,89 @@ class WanVideoDecode: return original_decode.decode(vae[0], samples, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, normalization) finally: decode_module.device = original_module_device + + +class WanVideoVACEEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "vae": ("WANVAE",), + "load_device": ("MULTIGPUDEVICE",), + "width": ("INT", {"default": 832, "min": 64, "max": 8096, "step": 8, "tooltip": "Width of the image to encode"}), + "height": ("INT", {"default": 480, "min": 64, "max": 8096, "step": 8, "tooltip": "Height of the image to encode"}), + "num_frames": ("INT", {"default": 81, "min": 1, "max": 10000, "step": 4, "tooltip": "Number of frames to encode"}), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}), + "vace_start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Start percent of the steps to apply VACE"}), + "vace_end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "End percent of the steps to apply VACE"}), + }, + "optional": { + "input_frames": ("IMAGE",), + "ref_images": ("IMAGE",), + "input_masks": ("MASK",), + "prev_vace_embeds": ("WANVIDIMAGE_EMBEDS",), + "tiled_vae": ("BOOLEAN", {"default": False, "tooltip": "Use tiled VAE encoding for reduced memory use"}), + }, + } + + RETURN_TYPES = ("WANVIDIMAGE_EMBEDS", ) + RETURN_NAMES = ("vace_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + + def process(self, vae, load_device, width, height, num_frames, strength, vace_start_percent, vace_end_percent, input_frames=None, ref_images=None, input_masks=None, prev_vace_embeds=None, tiled_vae=False): + from . import set_current_device + + original_encode = NODE_CLASS_MAPPINGS["WanVideoVACEEncode"]() + encode_module = inspect.getmodule(original_encode) + original_module_device = encode_module.device + + set_current_device(load_device) + compute_device_to_be_patched = mm.get_torch_device() + encode_module.device = compute_device_to_be_patched + + try: + return original_encode.process(vae[0], width, height, num_frames, strength, vace_start_percent, vace_end_percent, input_frames, ref_images, input_masks, prev_vace_embeds, tiled_vae) + finally: + encode_module.device = original_module_device + + +class WanVideoEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "vae": ("WANVAE",), + "load_device": ("MULTIGPUDEVICE",), + "image": ("IMAGE",), + "enable_vae_tiling": ("BOOLEAN", {"default": False, "tooltip": "Drastically reduces memory use but may introduce seams"}), + "tile_x": ("INT", {"default": 272, "min": 64, "max": 2048, "step": 1, "tooltip": "Tile size in pixels, smaller values use less VRAM, may introduce more seams"}), + "tile_y": ("INT", {"default": 272, "min": 64, "max": 2048, "step": 1, "tooltip": "Tile size in pixels, smaller values use less VRAM, may introduce more seams"}), + "tile_stride_x": ("INT", {"default": 144, "min": 32, "max": 2048, "step": 32, "tooltip": "Tile stride in pixels, smaller values use less VRAM, may introduce more seams"}), + "tile_stride_y": ("INT", {"default": 128, "min": 32, "max": 2048, "step": 32, "tooltip": "Tile stride height in pixels, smaller values use less VRAM, may introduce more seams"}), + }, + "optional": { + "noise_aug_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Strength of noise augmentation, helpful for leapfusion I2V where some noise can add motion and give sharper results"}), + "latent_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Additional latent multiplier, helpful for leapfusion I2V where lower values allow for more motion"}), + "mask": ("MASK", ), + } + } + + RETURN_TYPES = ("LATENT",) + RETURN_NAMES = ("samples",) + FUNCTION = "encode" + CATEGORY = "multigpu/WanVideoWrapper" + + def encode(self, vae, load_device, image, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, noise_aug_strength=0.0, latent_strength=1.0, mask=None): + from . import set_current_device + + original_encode = NODE_CLASS_MAPPINGS["WanVideoEncode"]() + encode_module = inspect.getmodule(original_encode) + original_module_device = encode_module.device + + set_current_device(load_device) + compute_device_to_be_patched = mm.get_torch_device() + encode_module.device = compute_device_to_be_patched + + try: + return original_encode.encode(vae[0], image, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, noise_aug_strength, latent_strength, mask) + finally: + encode_module.device = original_module_device From 375cbd093c1aa6107d555e5a0dd17e06643595fc Mon Sep 17 00:00:00 2001 From: John Pollock Date: Thu, 9 Oct 2025 19:05:28 -0500 Subject: [PATCH 14/22] Add LoadWanVideoClipTextEncoder and WanVideoClipVisionEncode for multi-GPU support --- __init__.py | 4 +++ wanvideo.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/__init__.py b/__init__.py index 53c313c..d543d60 100644 --- a/__init__.py +++ b/__init__.py @@ -224,6 +224,8 @@ from .wanvideo import ( WanVideoSampler, WanVideoVACEEncode, WanVideoEncode, + LoadWanVideoClipTextEncoder, + WanVideoClipVisionEncode, ) from .wrappers import ( @@ -373,6 +375,8 @@ wanvideo_nodes = { "WanVideoSamplerMultiGPU": WanVideoSampler, "WanVideoVACEEncodeMultiGPU": WanVideoVACEEncode, "WanVideoEncodeMultiGPU": WanVideoEncode, + "LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder, + "WanVideoClipVisionEncodeMultiGPU": WanVideoClipVisionEncode, } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index f4164dd..a89cff1 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -261,6 +261,44 @@ class LoadWanVideoT5TextEncoder: return text_encoder, device +class LoadWanVideoClipTextEncoder: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model_name": (folder_paths.get_filename_list("clip_vision") + folder_paths.get_filename_list("text_encoders"), {"tooltip": "These models are loaded from 'ComfyUI/models/clip_vision'"}), + "precision": (["fp16", "fp32", "bf16"], + {"default": "fp16"} + ), + }, + "optional": { + "device": (devices, {"default": default_device}), + } + } + + RETURN_TYPES = ("CLIP_VISION", "MULTIGPUDEVICE") + RETURN_NAMES = ("wan_clip_vision", "load_device") + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "Loads Wan clip_vision model from 'ComfyUI/models/clip_vision'" + + def loadmodel(self, model_name, precision, device=None): + from . import set_current_device + + set_current_device(device) + + if device == "cpu": + load_device = "offload_device" + else: + load_device = "main_device" + + original_loader = NODE_CLASS_MAPPINGS["LoadWanVideoClipTextEncoder"]() + clip_model = original_loader.loadmodel(model_name, precision, load_device) + + return clip_model, device + class WanVideoTextEncodeCached: @classmethod def INPUT_TYPES(s): @@ -632,3 +670,45 @@ class WanVideoEncode: return original_encode.encode(vae[0], image, enable_vae_tiling, tile_x, tile_y, tile_stride_x, tile_stride_y, noise_aug_strength, latent_strength, mask) finally: encode_module.device = original_module_device + +class WanVideoClipVisionEncode: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "clip_vision": ("CLIP_VISION",), + "load_device": ("MULTIGPUDEVICE",), + "image_1": ("IMAGE", {"tooltip": "Image to encode"}), + "strength_1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Additional clip embed multiplier"}), + "strength_2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001, "tooltip": "Additional clip embed multiplier"}), + "crop": (["center", "disabled"], {"default": "center", "tooltip": "Crop image to 224x224 before encoding"}), + "combine_embeds": (["average", "sum", "concat", "batch"], {"default": "average", "tooltip": "Method to combine multiple clip embeds"}), + "force_offload": ("BOOLEAN", {"default": True}), + }, + "optional": { + "image_2": ("IMAGE", ), + "negative_image": ("IMAGE", {"tooltip": "image to use for uncond"}), + "tiles": ("INT", {"default": 0, "min": 0, "max": 16, "step": 2, "tooltip": "Use matteo's tiled image encoding for improved accuracy"}), + "ratio": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Ratio of the tile average"}), + } + } + + RETURN_TYPES = ("WANVIDIMAGE_CLIPEMBEDS",) + RETURN_NAMES = ("image_embeds",) + FUNCTION = "process" + CATEGORY = "multigpu/WanVideoWrapper" + + def process(self, clip_vision, load_device, image_1, strength_1, strength_2, force_offload, crop, combine_embeds, image_2=None, negative_image=None, tiles=0, ratio=1.0): + from . import set_current_device + + original_encode = NODE_CLASS_MAPPINGS["WanVideoClipVisionEncode"]() + encode_module = inspect.getmodule(original_encode) + original_module_device = encode_module.device + + set_current_device(load_device) + compute_device_to_be_patched = mm.get_torch_device() + encode_module.device = compute_device_to_be_patched + + try: + return original_encode.process(clip_vision[0], image_1, strength_1, strength_2, force_offload, crop, combine_embeds, image_2, negative_image, tiles, ratio) + finally: + encode_module.device = original_module_device From 610c2102ddf54e3218e4b1c93afa088a5050ce6d Mon Sep 17 00:00:00 2001 From: John Pollock Date: Fri, 10 Oct 2025 04:35:16 -0500 Subject: [PATCH 15/22] Add multi-GPU support for remaining planned WanVideo model loaders --- __init__.py | 10 +++ wanvideo.py | 227 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) diff --git a/__init__.py b/__init__.py index d543d60..556630c 100644 --- a/__init__.py +++ b/__init__.py @@ -226,6 +226,11 @@ from .wanvideo import ( WanVideoEncode, LoadWanVideoClipTextEncoder, WanVideoClipVisionEncode, + WanVideoControlnetLoaderMultiGPU, + FantasyTalkingModelLoaderMultiGPU, + Wav2VecModelLoaderMultiGPU, + WanVideoUni3C_ControlnetLoaderMultiGPU, + DownloadAndLoadWav2VecModelMultiGPU, ) from .wrappers import ( @@ -377,6 +382,11 @@ wanvideo_nodes = { "WanVideoEncodeMultiGPU": WanVideoEncode, "LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder, "WanVideoClipVisionEncodeMultiGPU": WanVideoClipVisionEncode, + "WanVideoControlnetLoaderMultiGPU": WanVideoControlnetLoaderMultiGPU, + "FantasyTalkingModelLoaderMultiGPU": FantasyTalkingModelLoaderMultiGPU, + "Wav2VecModelLoaderMultiGPU": Wav2VecModelLoaderMultiGPU, + "WanVideoUni3C_ControlnetLoaderMultiGPU": WanVideoUni3C_ControlnetLoaderMultiGPU, + "DownloadAndLoadWav2VecModelMultiGPU": DownloadAndLoadWav2VecModelMultiGPU, } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index a89cff1..69f8214 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -712,3 +712,230 @@ class WanVideoClipVisionEncode: return original_encode.process(clip_vision[0], image_1, strength_1, strength_2, force_offload, crop, combine_embeds, image_2, negative_image, tiles, ratio) finally: encode_module.device = original_module_device + +class WanVideoControlnetLoader: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": (folder_paths.get_filename_list("controlnet"), {"tooltip": "These models are loaded from the 'ComfyUI/models/controlnet' -folder",}), + + "base_precision": (["fp32", "bf16", "fp16"], {"default": "bf16"}), + "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2', 'fp8_e4m3fn_fast_no_ffn'], {"default": 'disabled', "tooltip": "optional quantization method"}), + "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + }, + } + + RETURN_TYPES = ("WANVIDEOCONTROLNET",) + RETURN_NAMES = ("controlnet", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Loads ControlNet model from 'https://huggingface.co/collections/TheDenk/wan21-controlnets-68302b430411dafc0d74d2fc'" + + def loadmodel(self, model, base_precision, load_device, quantization): + + device = mm.get_torch_device() + offload_device = mm.unet_offload_device() + + transformer_load_device = device if load_device == "main_device" else offload_device + + base_dtype = {"fp8_e4m3fn": torch.float8_e4m3fn, "fp8_e4m3fn_fast": torch.float8_e4m3fn, "bf16": torch.bfloat16, "fp16": torch.float16, "fp16_fast": torch.float16, "fp32": torch.float32}[base_precision] + + model_path = folder_paths.get_full_path_or_raise("controlnet", model) + + sd = load_torch_file(model_path, device=transformer_load_device, safe_load=True) + + num_layers = 8 if "blocks.7.scale_shift_table" in sd else 6 + out_proj_dim = sd["controlnet_blocks.0.bias"].shape[0] + downscale_coef = 16 if out_proj_dim == 3072 else 8 + vae_channels = 48 if out_proj_dim == 3072 else 16 + + if not "control_encoder.0.0.weight" in sd: + raise ValueError("Invalid ControlNet model") + + controlnet_cfg = { + "added_kv_proj_dim": None, + "attention_head_dim": 128, + "cross_attn_norm": None, + "downscale_coef": downscale_coef, + "eps": 1e-06, + "ffn_dim": 8960, + "freq_dim": 256, + "image_dim": None, + "in_channels": 3, + "num_attention_heads": 12, + "num_layers": num_layers, + "out_proj_dim": out_proj_dim, + "patch_size": [ + 1, + 2, + 2 + ], + "qk_norm": "rms_norm_across_heads", + "rope_max_seq_len": 1024, + "text_dim": 4096, + "vae_channels": vae_channels + } + print(f"Loading WanControlnet with config: {controlnet_cfg}") + + from .wan_controlnet import WanControlnet + + with init_empty_weights(): + controlnet = WanControlnet(**controlnet_cfg) + controlnet.eval() + + if quantization == "disabled": + for k, v in sd.items(): + if isinstance(v, torch.Tensor): + if v.dtype == torch.float8_e4m3fn: + quantization = "fp8_e4m3fn" + break + elif v.dtype == torch.float8_e5m2: + quantization = "fp8_e5m2" + break + + if "fp8_e4m3fn" in quantization: + dtype = torch.float8_e4m3fn + elif quantization == "fp8_e5m2": + dtype = torch.float8_e5m2 + else: + dtype = base_dtype + params_to_keep = {"norm", "head", "time_in", "vector_in", "controlnet_patch_embedding", "time_", "img_emb", "modulation", "text_embedding", "adapter"} + + log.info("Using accelerate to load and assign controlnet model weights to device...") + param_count = sum(1 for _ in controlnet.named_parameters()) + for name, param in tqdm(controlnet.named_parameters(), + desc=f"Loading transformer parameters to {transformer_load_device}", + total=param_count, + leave=True): + dtype_to_use = base_dtype if any(keyword in name for keyword in params_to_keep) else dtype + if "controlnet_patch_embedding" in name: + dtype_to_use = torch.float32 + set_module_tensor_to_device(controlnet, name, device=transformer_load_device, dtype=dtype_to_use, value=sd[name]) + + del sd + + if load_device == "offload_device" and controlnet.device != offload_device: + log.info(f"Moving controlnet model from {controlnet.device} to {offload_device}") + controlnet.to(offload_device) + gc.collect() + mm.soft_empty_cache() + + return (controlnet,) + +class WanVideoControlnetLoaderMultiGPU: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model": (folder_paths.get_filename_list("controlnet"), {"tooltip": "These models are loaded from the 'ComfyUI/models/controlnet' -folder",}), + "base_precision": (["fp32", "bf16", "fp16"], {"default": "bf16"}), + "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2', 'fp8_e4m3fn_fast_no_ffn'], {"default": 'disabled', "tooltip": "optional quantization method"}), + "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + "device": (devices, {"default": default_device}), + }, + } + + RETURN_TYPES = ("WANVIDEOCONTROLNET",) + RETURN_NAMES = ("controlnet", ) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware ControlNet loader for WanVideo models" + + def loadmodel(self, model, base_precision, load_device, quantization, device): + from . import set_current_device + + set_current_device(device) + + original_loader = NODE_CLASS_MAPPINGS["WanVideoControlnetLoader"]() + return original_loader.loadmodel(model, base_precision, load_device, quantization) + +class FantasyTalkingModelLoaderMultiGPU: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model": (folder_paths.get_filename_list("diffusion_models"), {"tooltip": "These models are loaded from the 'ComfyUI/models/diffusion_models' -folder",}), + "base_precision": (["fp32", "bf16", "fp16"], {"default": "fp16"}), + "device": (devices, {"default": default_device}), + }, + } + + RETURN_TYPES = ("FANTASYTALKINGMODEL",) + RETURN_NAMES = ("model", ) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware FantasyTalking model loader" + + def loadmodel(self, model, base_precision, device): + from . import set_current_device + + set_current_device(device) + + original_loader = NODE_CLASS_MAPPINGS["FantasyTalkingModelLoader"]() + return original_loader.loadmodel(model, base_precision) + +class Wav2VecModelLoaderMultiGPU: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model": (folder_paths.get_filename_list("wav2vec2"), {"tooltip": "These models are loaded from the 'ComfyUI/models/wav2vec2' -folder",}), + "base_precision": (["fp32", "bf16", "fp16"], {"default": "fp16"}), + "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + "device": (devices, {"default": default_device}), + }, + } + + RETURN_TYPES = ("WAV2VECMODEL",) + RETURN_NAMES = ("wav2vec_model", ) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware Wav2Vec model loader" + + def loadmodel(self, model, base_precision, load_device, device): + from . import set_current_device + + set_current_device(device) + + original_loader = NODE_CLASS_MAPPINGS["Wav2VecModelLoader"]() + return original_loader.loadmodel(model, base_precision, load_device) + +class DownloadAndLoadWav2VecModelMultiGPU: + @classmethod + def INPUT_TYPES(s): + devices = get_device_list() + default_device = devices[1] if len(devices) > 1 else devices[0] + return { + "required": { + "model": ( + [ + "TencentGameMate/chinese-wav2vec2-base", + "facebook/wav2vec2-base-960h" + ], + ), + "base_precision": (["fp32", "bf16", "fp16"], {"default": "fp16"}), + "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + "device": (devices, {"default": default_device}), + }, + } + + RETURN_TYPES = ("WAV2VECMODEL",) + RETURN_NAMES = ("wav2vec_model", ) + FUNCTION = "loadmodel" + CATEGORY = "multigpu/WanVideoWrapper" + DESCRIPTION = "MultiGPU-aware downloadable Wav2Vec model loader" + + def loadmodel(self, model, base_precision, load_device, device): + from . import set_current_device + + set_current_device(device) + + original_loader = NODE_CLASS_MAPPINGS["DownloadAndLoadWav2VecModel"]() + return original_loader.loadmodel(model, base_precision, load_device) \ No newline at end of file From faef324975f1f54e8d55b43219e43e83130e1868 Mon Sep 17 00:00:00 2001 From: John Pollock Date: Fri, 10 Oct 2025 09:28:58 -0500 Subject: [PATCH 16/22] correcting implementation of salvagable nodes and obiliterating one abomination --- __init__.py | 20 ++++----- wanvideo.py | 116 ++-------------------------------------------------- 2 files changed, 13 insertions(+), 123 deletions(-) diff --git a/__init__.py b/__init__.py index 556630c..da7e686 100644 --- a/__init__.py +++ b/__init__.py @@ -226,11 +226,11 @@ from .wanvideo import ( WanVideoEncode, LoadWanVideoClipTextEncoder, WanVideoClipVisionEncode, - WanVideoControlnetLoaderMultiGPU, - FantasyTalkingModelLoaderMultiGPU, - Wav2VecModelLoaderMultiGPU, - WanVideoUni3C_ControlnetLoaderMultiGPU, - DownloadAndLoadWav2VecModelMultiGPU, + WanVideoControlnetLoader, + FantasyTalkingModelLoader, + Wav2VecModelLoader, + WanVideoUni3C_ControlnetLoader, + DownloadAndLoadWav2VecModel, ) from .wrappers import ( @@ -382,11 +382,11 @@ wanvideo_nodes = { "WanVideoEncodeMultiGPU": WanVideoEncode, "LoadWanVideoClipTextEncoderMultiGPU": LoadWanVideoClipTextEncoder, "WanVideoClipVisionEncodeMultiGPU": WanVideoClipVisionEncode, - "WanVideoControlnetLoaderMultiGPU": WanVideoControlnetLoaderMultiGPU, - "FantasyTalkingModelLoaderMultiGPU": FantasyTalkingModelLoaderMultiGPU, - "Wav2VecModelLoaderMultiGPU": Wav2VecModelLoaderMultiGPU, - "WanVideoUni3C_ControlnetLoaderMultiGPU": WanVideoUni3C_ControlnetLoaderMultiGPU, - "DownloadAndLoadWav2VecModelMultiGPU": DownloadAndLoadWav2VecModelMultiGPU, + "WanVideoControlnetLoaderMultiGPU": WanVideoControlnetLoader, + "FantasyTalkingModelLoaderMultiGPU": FantasyTalkingModelLoader, + "Wav2VecModelLoaderMultiGPU": Wav2VecModelLoader, + "WanVideoUni3C_ControlnetLoaderMultiGPU": WanVideoUni3C_ControlnetLoader, + "DownloadAndLoadWav2VecModelMultiGPU": DownloadAndLoadWav2VecModel, } register_and_count(["ComfyUI-WanVideoWrapper", "comfyui-wanvideowrapper"], wanvideo_nodes) diff --git a/wanvideo.py b/wanvideo.py index 69f8214..dde7f1d 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -714,116 +714,6 @@ class WanVideoClipVisionEncode: encode_module.device = original_module_device class WanVideoControlnetLoader: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": (folder_paths.get_filename_list("controlnet"), {"tooltip": "These models are loaded from the 'ComfyUI/models/controlnet' -folder",}), - - "base_precision": (["fp32", "bf16", "fp16"], {"default": "bf16"}), - "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2', 'fp8_e4m3fn_fast_no_ffn'], {"default": 'disabled', "tooltip": "optional quantization method"}), - "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), - }, - } - - RETURN_TYPES = ("WANVIDEOCONTROLNET",) - RETURN_NAMES = ("controlnet", ) - FUNCTION = "loadmodel" - CATEGORY = "WanVideoWrapper" - DESCRIPTION = "Loads ControlNet model from 'https://huggingface.co/collections/TheDenk/wan21-controlnets-68302b430411dafc0d74d2fc'" - - def loadmodel(self, model, base_precision, load_device, quantization): - - device = mm.get_torch_device() - offload_device = mm.unet_offload_device() - - transformer_load_device = device if load_device == "main_device" else offload_device - - base_dtype = {"fp8_e4m3fn": torch.float8_e4m3fn, "fp8_e4m3fn_fast": torch.float8_e4m3fn, "bf16": torch.bfloat16, "fp16": torch.float16, "fp16_fast": torch.float16, "fp32": torch.float32}[base_precision] - - model_path = folder_paths.get_full_path_or_raise("controlnet", model) - - sd = load_torch_file(model_path, device=transformer_load_device, safe_load=True) - - num_layers = 8 if "blocks.7.scale_shift_table" in sd else 6 - out_proj_dim = sd["controlnet_blocks.0.bias"].shape[0] - downscale_coef = 16 if out_proj_dim == 3072 else 8 - vae_channels = 48 if out_proj_dim == 3072 else 16 - - if not "control_encoder.0.0.weight" in sd: - raise ValueError("Invalid ControlNet model") - - controlnet_cfg = { - "added_kv_proj_dim": None, - "attention_head_dim": 128, - "cross_attn_norm": None, - "downscale_coef": downscale_coef, - "eps": 1e-06, - "ffn_dim": 8960, - "freq_dim": 256, - "image_dim": None, - "in_channels": 3, - "num_attention_heads": 12, - "num_layers": num_layers, - "out_proj_dim": out_proj_dim, - "patch_size": [ - 1, - 2, - 2 - ], - "qk_norm": "rms_norm_across_heads", - "rope_max_seq_len": 1024, - "text_dim": 4096, - "vae_channels": vae_channels - } - print(f"Loading WanControlnet with config: {controlnet_cfg}") - - from .wan_controlnet import WanControlnet - - with init_empty_weights(): - controlnet = WanControlnet(**controlnet_cfg) - controlnet.eval() - - if quantization == "disabled": - for k, v in sd.items(): - if isinstance(v, torch.Tensor): - if v.dtype == torch.float8_e4m3fn: - quantization = "fp8_e4m3fn" - break - elif v.dtype == torch.float8_e5m2: - quantization = "fp8_e5m2" - break - - if "fp8_e4m3fn" in quantization: - dtype = torch.float8_e4m3fn - elif quantization == "fp8_e5m2": - dtype = torch.float8_e5m2 - else: - dtype = base_dtype - params_to_keep = {"norm", "head", "time_in", "vector_in", "controlnet_patch_embedding", "time_", "img_emb", "modulation", "text_embedding", "adapter"} - - log.info("Using accelerate to load and assign controlnet model weights to device...") - param_count = sum(1 for _ in controlnet.named_parameters()) - for name, param in tqdm(controlnet.named_parameters(), - desc=f"Loading transformer parameters to {transformer_load_device}", - total=param_count, - leave=True): - dtype_to_use = base_dtype if any(keyword in name for keyword in params_to_keep) else dtype - if "controlnet_patch_embedding" in name: - dtype_to_use = torch.float32 - set_module_tensor_to_device(controlnet, name, device=transformer_load_device, dtype=dtype_to_use, value=sd[name]) - - del sd - - if load_device == "offload_device" and controlnet.device != offload_device: - log.info(f"Moving controlnet model from {controlnet.device} to {offload_device}") - controlnet.to(offload_device) - gc.collect() - mm.soft_empty_cache() - - return (controlnet,) - -class WanVideoControlnetLoaderMultiGPU: @classmethod def INPUT_TYPES(s): devices = get_device_list() @@ -852,7 +742,7 @@ class WanVideoControlnetLoaderMultiGPU: original_loader = NODE_CLASS_MAPPINGS["WanVideoControlnetLoader"]() return original_loader.loadmodel(model, base_precision, load_device, quantization) -class FantasyTalkingModelLoaderMultiGPU: +class FantasyTalkingModelLoader: @classmethod def INPUT_TYPES(s): devices = get_device_list() @@ -879,7 +769,7 @@ class FantasyTalkingModelLoaderMultiGPU: original_loader = NODE_CLASS_MAPPINGS["FantasyTalkingModelLoader"]() return original_loader.loadmodel(model, base_precision) -class Wav2VecModelLoaderMultiGPU: +class Wav2VecModelLoader: @classmethod def INPUT_TYPES(s): devices = get_device_list() @@ -907,7 +797,7 @@ class Wav2VecModelLoaderMultiGPU: original_loader = NODE_CLASS_MAPPINGS["Wav2VecModelLoader"]() return original_loader.loadmodel(model, base_precision, load_device) -class DownloadAndLoadWav2VecModelMultiGPU: +class DownloadAndLoadWav2VecModel: @classmethod def INPUT_TYPES(s): devices = get_device_list() From db931d66c31acbcfce26f29babfb552509e586aa Mon Sep 17 00:00:00 2001 From: John Pollock Date: Fri, 10 Oct 2025 09:30:46 -0500 Subject: [PATCH 17/22] Add WanVideoControlnetLoader class for loading ControlNet models --- wanvideo.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/wanvideo.py b/wanvideo.py index dde7f1d..e27cbc3 100644 --- a/wanvideo.py +++ b/wanvideo.py @@ -828,4 +828,31 @@ class DownloadAndLoadWav2VecModel: set_current_device(device) original_loader = NODE_CLASS_MAPPINGS["DownloadAndLoadWav2VecModel"]() - return original_loader.loadmodel(model, base_precision, load_device) \ No newline at end of file + return original_loader.loadmodel(model, base_precision, load_device) + +class WanVideoControlnetLoader: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": (folder_paths.get_filename_list("controlnet"), {"tooltip": "These models are loaded from the 'ComfyUI/models/controlnet' -folder",}), + + "base_precision": (["fp32", "bf16", "fp16"], {"default": "bf16"}), + "quantization": (['disabled', 'fp8_e4m3fn', 'fp8_e4m3fn_fast', 'fp8_e5m2', 'fp8_e4m3fn_fast_no_ffn'], {"default": 'disabled', "tooltip": "optional quantization method"}), + "load_device": (["main_device", "offload_device"], {"default": "main_device", "tooltip": "Initial device to load the model to, NOT recommended with the larger models unless you have 48GB+ VRAM"}), + }, + } + + RETURN_TYPES = ("WANVIDEOCONTROLNET",) + RETURN_NAMES = ("controlnet", ) + FUNCTION = "loadmodel" + CATEGORY = "WanVideoWrapper" + DESCRIPTION = "Loads ControlNet model from 'https://huggingface.co/collections/TheDenk/wan21-controlnets-68302b430411dafc0d74d2fc'" + + def loadmodel(self, model, base_precision, load_device, quantization): + from . import set_current_device + + set_current_device(device) + + original_loader = NODE_CLASS_MAPPINGS["WanVideoControlnetLoader"]() + return original_loader.loadmodel(model, base_precision, load_device, quantization) \ No newline at end of file From 40d40ef9b67e92d8593784fcdf6b495df4a79f9f Mon Sep 17 00:00:00 2001 From: John Pollock Date: Fri, 10 Oct 2025 13:40:37 -0500 Subject: [PATCH 18/22] Finished implementation of wanvideowrapper nodes, updated example workflows, added some benchmarking files for reference. --- assets/flux1_dev_Q8_0_benchmark.png | Bin 0 -> 755565 bytes assets/flux1_kontext_dev_benchmark.png | Bin 0 -> 786920 bytes assets/qwen_image_fp16_benchmark.png | Bin 0 -> 559774 bytes assets/qwen_image_fp8_benchmark.png | Bin 0 -> 748904 bytes assets/wan2_2_benchmark_v2.png | Bin 0 -> 637984 bytes assets/wan2_2_qwen_combo_benchmark.png | Bin 0 -> 1687113 bytes ...ideo2_2_I2V_A14B_example_WIP_Multigpu.json | 2118 +++++++++-------- .../wanvideo_T2V_example_MultiGPU.json | 1095 ++++----- wanvideo.py | 30 +- 9 files changed, 1695 insertions(+), 1548 deletions(-) create mode 100755 assets/flux1_dev_Q8_0_benchmark.png create mode 100755 assets/flux1_kontext_dev_benchmark.png create mode 100755 assets/qwen_image_fp16_benchmark.png create mode 100755 assets/qwen_image_fp8_benchmark.png create mode 100755 assets/wan2_2_benchmark_v2.png create mode 100755 assets/wan2_2_qwen_combo_benchmark.png diff --git a/assets/flux1_dev_Q8_0_benchmark.png b/assets/flux1_dev_Q8_0_benchmark.png new file mode 100755 index 0000000000000000000000000000000000000000..aba5ddd21a4691774652de39e195aab03fd91cad GIT binary patch literal 755565 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ðgmkvzG= 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
uHQ zQsbhiY0v-q@}H!DvIGHX-~{*#zX&lQ*!AbPgy;}{eR6;D zKQ<6@H=Ye}({Uw6H@Uy;IY-3y%5vGappyL~-ck(CZjz`_ia>-Lbw$9^i!*G*r0}psmyiaE$aP<2%acgfs$cWryCQg%8Yapn`}nriTK@jI zH9d#c1|yDu3M4iT%;78$3jDw7 zBaj31EGc0iv8oFsgbH7?n!2_(Q&XUa5d9PKrh;xnD1@dGZpnc+?GFqD;TK?hNAUzm zAY{jX2BSv7Qh^6$Wd;GMYNiO@a5QY3SKP}=)_0l)+pG|h9YK{RoE+J(V~U@L*=A-{ z?j(|p6NpUlSt@CsHtKa4*RWs;mk{@1m$2#w!yXS*+GE!nFaz%0W8}uV?T5b13gkA$ zi&ungyah=9LeFhD#b)0Zu0P)F9gghF0+m8 zchjo*i(W*vEU@q2L>QvcE18WrT#F+)(73z2?UGsLlINJGRmr8?^p5O(Gxy}`wVj)$ zlye$AlSD@x?O&ip9QH7Wj+}e=7?)jZZLmL4n zNlL6Xmv!P6DBIfFlXZa0>*gr?+Q=}hgxJ#F<}}0fjfp1z{b=d*6bku&vbX-n8-}Yo zwQ=vAO=f^=cVbR*IsowYV=a-|gwn~ByB>IBoP(n)k|-u>}p?Ia_+7S`YGA3Z)es02u# zp_#D`an2mij_J>mw!BcaF4O84U2fRSks-q+hezqa7YOo_!H7)TBoHDi z03^zZiqhWjs-@>StNqm8KI{10FN_EK-+kx^)Op2@qka9B+RI3}+^)}AJ5fcewlQnm;A6OxK4h&V``G8h;cc6b5zUa@7RhyLa?0>pScLpU7AQ}_C-xiU1*z}US zY*Kz4)sOPl&S~^11hKK5USC<3Ih0E|m`noY5#Rc7%-pDpS{|~6{f4U=eTl|A@MS@|G5wR2_4q7Je9Acu^_1?5! zpxBrqhrwT@)}-qbac?b`k2|fV{SrZjRRnnR26o3kRxT^=l3W*)F$g*fL=OW!bzZRY z^dGoV+14s*Y#qgFfMWB)-)6^4WM&KUsdI|0i9FNrtH|Ej#2_~xhchr(x0 zO8iEin0drz2AN`5QgqF^BcdgW63}-+Vz#$b6|yJ7I*g6zU;|- z*asps{q1QeFHVr>dVUN*c_2Bo8TI1y%nXzjbq~m#;a7ni5bOR$V4(KyDzAa^`GC$% zY3%J5dt2)ekdgxRTp7cyt=D=l9DU!ty5d>DKOqDS=nXS?;X??Xv%CW}7*!=L|Owa(O#@TG491E7G3P)@tYfI&tTYgy9M z=Ke~I&>N^8O7+2Pb^CI1gKXU%m)>vQ0z2nsQh^*4`zl%e?0C%ugszDZSFJFhC3j+W zTbF4LhK=X!j~aHOApIMhYe~q=dFtd*e;O51GKlKd?yjaaDOObPwz(pZc6qn=$isXW z-Y`fZlPjXe)-u%@q+FOTN&n>6I5@fpgwu>cUQBv8VTfq8k}|;UWLEHM#k#mg_9uO| z`*W1Q%C=hfstIq~cU{h{t1y&f@JszCFV~+d`6?M`R5)(Xeeqg13Em??TY^rO=KH5z z1_NWOPpeBB*SY0hQf@C!DRURsgAw6#yE-SzVn1GkfCVp>3&JQt$q}tYB)vPi>>E46 zF5NL~{O$Mth{NX0Xq7xWb8AP;~5jl{n+^AOE6LRTh7^TnOJw2(Obmj*C4M( z?FLut{*(^v@Lxd}=Uf1ZfhkVcj5_~pLzAOxxiF4r?7Vi=nwWvBHgCTL#&2fY42 z?rSnr!@~^c&hj!IcdD$W8p!SEZMk-@X!Sl%_0Os^W5Cp%Mhc=lCL=kWmkjM38cIif zl!%DI&=8)Wyf(gNK_R)dE5%q*;^u~?vkp$Q#A(1tJP$TH^Xk*XOifMm>T+y^rCWT# z1ni;%Q257$sI1e9h7T7dkb4Z!qR$?7F zrm<=EpOsg_XL2OVq3(NlNsE{Ob?U$4NTQeI;|xhJwDb-kp>i4Y4EItk!)>S(iP*gE z)Q1BQL#9jb!gHB3;e)EL+TmO|4=gRFG51ykr4=5ufq6CBgEz8_2(_-PWAgC|u7&ff zkzX>=K@gZ;77J-w@w-HB>=ga$)y>^!e^O~YqALjwU+yim;Ad=1p`ytN8Y7y`bJBX% z#BoLuc+$BS_{|U((Uq87RA>u?65yGUX@$k<39iCI@r@JQ8!}pWs$Uy1m@fh&=3k0p z0=X)*&j7fcfAi+6sFn`U5&9kcM;9RwXjI=q+0h#GCTb0DtK7R6gC=Nc2?yN)W@)In zND&Uq?U#GyCDn%SJ7=FSO~6VWHSG(=>dEUIpyRM*^E9@GOF7-DE90jM65m7 z?48hcrn$XVY-?Ajc|)M_X7X~Gi`_l1#b$P+WxJU-(N1Cs*Qayis{%e4C3L%_@}Q@+ zcftL7D7Oh7ih9O-Nh7rjHxk4$pyu~PGIrTga~CTsZ}m0wa;!JIERW07-fbpg``Az( zcwMjtL0z>fBj2x7!$Erdf+>ns<&FAoU6-V&!ve+Axf&YVl!VTxsA&aCr+VN59Ui@{ zNlYF~_;H_eFQh`)aQZ}M?F0WpK__hO$%N^+f~A`0dLl?|g=&92OZJ`$y&!WYW{=as z*P2woyi9*`O=soU??b!i1(tQrDRCGWRFX4(?PAL}6Ow?_KFo(r9lYyQUdudu7&5>! z=;?jd!JRu$Tf(&vGH@JrJag5U~Neg)38hGrhNscJ4dqQ8!dy7oVrSa&2@72Drm zz0IUxofft)D48(g+pox-Pcv{#moH=5bW$^WoS-E4j#)b`W9w(!fM!-9LXmYX;JIW) z;96T}Veddgcn118QZBV&gUwM^N3w$JA-Bx_o`$JJ$@BR;mIFa zJQKxuk5~HU)tO5+gA=_c8kAf{hY4Et(c=BiZJyWPB;#-_tO7Dh(Sr-=3u!V+>R=M< zA@fudVz^nPBJz6uPvleaqNGOnvWxf|H>~fwWb+SU*mJj?zI?+}IFb2Qg^r)lIn2~Q z!}@xmBqz4SC!X-d$RZEd8fz+k3JDDw)XHEB;l9z0C^b^H9d7TNa*x6k)N+ zhbk4kYp2=Xbkt#SnbL<%2Dd8fj<2lg;NH4D9#=UjMO3=qxL&`_?hxsaHUE0^w#{V9 z(etUN%k57E9E*`Lb8u8lW$s=Mw30?zy zp}YB|E^Ey`yZAP5*m0$V-Z=6^mC@gvpbo6pNwY&hWK^?GQrjSSncBliQ~VP$vISQgbh$Qcq~!KoTnh(xFV2 zQXGiB;)ed&33%?KQL_`*Ud-LTmYy0!u|jG`xk88_ti~y;EcNNcPm_*z*bSFtk$3lO zKafFg8obY$`PpDTCeSS|JWJqUeyz8{%5&nb;0@!iufKjMdQjU5{th0;(O$8uvjj32 zt0}v1bMrx!V;M9pTf?iy0;=UP3HzE#GV`l5k{0B!F<1!J`O^A}bxwhL=UB|~`Om=% z&QTQ0L$kz0Z1r{!8AUfhH(I7r?cU3 zyeWa{>$i9AFR;euGzOCEwBNM3Gd)Lc5wUG8ViU_TSeYfy{$I~^w)|PEYKX?YY~1zN zQN~@J%<*3(ABuJkK_@omSZigk41G5#!yaN~>=5S878mJh26W19aCk>g z^CB01UJdanJ7OBFbaz4??>KE0@dpprcRpC~Oq|vL)x}pu%}$7q>0iVbx>vgT&=#cUF?w$iObtXJJwe+4xdXl|M=1#sKHjr~8GX zrW@~Ru)DJ?69y9t-;dvE>J*!&9<7von<<{osQQv*E`s4Yz=WA;jV5lwMH`F;7H(~9 zt4ZBe2+54bw3v1D+Zs=ci7@rMbh4tW=ccgZsY=XAUQS$*9L$`N?~ZOly-gp%otmm% zUN$dE{;7`9^&RwOKVBiF&!TPrZuxP5`%=;SB-Og~fQ-4_Hd!NM?B)T_PKv2e|jSex^bgUgOtX`aJ%o#$~k4d?GjS!A`k1b-R7xtTRzvVojb07plt>;?n! z{Gd;Pl6xKJL30Fkc2BFTQzhc4z87@)qz<4Vuar(_`PPI}q}gB!8>Yk6TWpq-0wG|z z@Zag@>As*+b^q6lmey5jCV18$`!&cA=x#^?p-dp!@F&iRu9*q(h<|G}#q$~j7w#QM+x$Cf}Vip_=I2pIyZ+2TXvx|U19k?Ld>vlyoM?cRigv3Lic9lp4=1YvF zCbUtTp;tHEetK%zL5{y?lX=L9?B9NHxl_aIU3N&c8#cMonz!Nh!-3#{(J=TE0h^J* z!sVgk*_C(*Rby)4aNYFd)O)ovrr7>eCD$l*B#BXJv4VKWxjUIv8m5tJ_DUWNrJGM5 z_Ke+hVoHGW+lfyJUvI5Bbm=(rG_Crb>}aud)%2QecKI@O(fD`wZfETx$$R87mfx*p zI1tJK5cQ^|PdYJ=Z+w17NO;_HlY6W0h+ME}@jStcBfjo;5>c&NE=JnE5y>)U zI-OhgED}%!((U-@0jKii#OXJ6fLwqd)evw-&tP)0HvTlDVC>^bdYu7a$aQ_~$#7W= z&jo{^n{o%rtT`TF8e3)YwBX7jt$#W3(FY4Qcig*RN-&ullkBNLL<cUK={LFk<%!*fv+zE&pup>% z!B1a_DYw9l^Gg4HFjC16qx{J_zD=9PMXOebN&s!ucpaKQ;X2|wrryCy%tKglD61d}Yr;<=lA z`G?cy_0h-|f!Wq$v*%@K&tfNBn`SRf7-a8JZ-Tq8p@li$GBkrfLPimfOGw!Ed@Of8 z=)D~ep&p{>W;RxH^iqj$QdvTqh0~oEr_V;Ya(z#=V{wyEmSYv~sJZ(v#NI@S9{8D$ zN4zqsRT>_4t8Qg{v6z~t+pPq;bsIag_t*Kclxkug?xZfFDcvCI+$1b z+LBdY_iWTCKSr%)4kls$%0r@MKeerMt4}T>2j0$_ExQF_yYkrR{Vuu877hKRc`tfR zDy2`!xVCy>OKWSwgmm`#so^vt<4PJBT=*V#RIutWaj(ZWB{MoKY5yIS)*pTYo^s$B z(9~zni$Qmrj@dNAbkaeLgYF7X@%wb(tfjZLyz`6JNvDC zCBE`RbkX@d2n@~~>f?Qn>he8N+0=L(Fp*g)NZfA7(B7-n$ zCo6-RqI|J$s55R*CVa8~LH;7=eTaj^@Iz>c9++63Jl2M-Mpo5~W+3~#%bn8oNOo%v zswKCECi}a3m^ek=^pp4`=U4V_L&Kg=^D16g+AzbgJy;~egB98BD&H&tPluF#HpC1R zH*O+#xV_Xya`ic>oo5A3Qchk^X!54Db*8KvS>yB4ojKP0oro~heun91(DTBjQ#7DSf~ zl}pH$edImTRIum8BzwT_eXFSPvQ%7jT0e)t6+V_)+&%{Ke5x*$HGBx{3PRhLJqXPr&!7QMG!3_URWW2JW!&DXEE z+JO<}^-n%9^MxB5s{jgJeo}#GoJ(}Nt%#KS5P(hcrVozPm$N}O>glMu&W0uN3+06N zZ{99lh@fSCfLO!vJ&#h`^%9iv{vttP+*9*1Te?01d&i63LL6oY?rM;QDy77A`Bsh9 zJgHCcyqU@M;6Ub}Xqp=KeL!bXCzWitGGRkn23bFj`*|I6?_j*}&AF(P!Tp7n)4Rv& z9D`9T`}@x)>is$*`h$J4h0=$ej$VIQ>cmRPdwyA!`nF>FQ;fBMM)!}Ad+9AfHa`7e zqC{{IM&Z7AE$4-s2an5!$5Y#iDvbxm$UJ02q%O-<#9~doM1uKS^^T5>Qbx@AR;o;# z5)+mSkCgB9)-D*4*gxw-W{2p$#T`7FIb1W?Dy1K_^4;XFxYu+f68&`JdaYL?R>bk( zUd5)4uigxJAYlXchNDrXk+sf;eWeevDO@XD7iZ$#8~r7#HiZo196A0 zo^IwODE{0#5L*)ytJ3wj5ob-yY29T=7m8k4Nyqt&ww^LI4(aH)-1QW52`Hx@C0%lB z>jNev(N+U?Ep*bwXz`|(BQ`PjaB~P%Qr`<8IF2zlx`mwvXBYf4Z0fW%(vzs$^AjBz z$!!mlG)FY(u-h{rbfdI%qs1V4;5b=}d&i?PIWRPQO*SyS)SExEG;r6?P&gkxLs7Hm zFyaPAm_G)sZdnZbI7o@{w2?myBx=-mO-YEDO*$G=`YXF3nE>GDPUtKalYMe z(ZdY+6DjEhN?#Y&3~uMn-SJ&<+KRGfegSPu4T05zX6c3PUUG>xQOYn}%HO!isRwb$z5JZA)9?-YaX>!p73d$i~7B z2#O#$loZew!u(3bxPkCX*HtQ@tz@Gq_GDV#TQW~o&FoRqZdaOuZ)o;TJvK$An=}d% zT3p!LC0=umZj+I9Y*J;W&P)ZZ^?Gg{2MgjBt$(0CH|%cif|QiFvv{{Ig1&TlDAhDY zN(st=rb@FMGz@fvh}Ww*O$75-^BmL4c)Mgu#}P7RT@P+jufvcS?FNV3Lw67OFJ8mY z9-|kohROGzc{=MQ;_c`;d3?_H{e{r0KmHGCZypZy+y8$n%UB0R#Ehjy_N5489YQ7h znymR`i7XjQ))huO~a zeZJ1u>-l^4AE4VYMK@FPujcm(sgsn?hg`)BU%ClZ8;3Dx;Qj z;o@re*sR5v(q}&VxYT%M&_fSDnLt$``yY3fGVkX*GV7dzhjpTeK&r_t1c@)e5R;M$>dv2?044BbJ#TTIv#WiHA5+N->kXey(RFLg9gptN`m-!smL}Kr^E6 zu2q4l70!ZM<)h=TQ!2MlrCr_%^4CBs1~(`Nzu#<$?A}s!jhOLfLj~a6-NU{;W{-NN zy`sZu$q*QJ^GQrVQC2UEdFmhx*sfSnZa8-v?E^76ueLdH6VDW7|GE)zE^gRE9Fk1j z|K@9TGj3sbcLp=gtrW_!)%n;h>TW|afk+e&zQiFa16=nsw!8x$8HJyEX_5ZT2u})$ z=_{*d!B>4q%}+v4d*_p&B@(cO9fX(~bbHJYpWdOQyQEa+jstp{OXcYn#ds@cZYxZ%8&o3j` zfXw-sFjO{r?IqDsk#bza^kWX9=TJ$ zdi~)eI|iS}72jJ_B0_+*N!jG(99GRl^%dol!C^CoX{culQ^W1WY>j?t2Oh1&8}Rez z_xVUj=>JpD%B^_ktL6ae7#Sx)&4oAcL>nV@`iU#P)148;dcDG-;J9W1C5Ok>LG~te z&m}Bv0aJR7ZFQA7%AEnuxixu>>PkYMiTY5tm9~ng5;dGPm@W=UyZzO^*S>8lx}jM6 z+bJbZ%TL)?n3wZUGpbr^>`sN6Qz63Ko<}rRw+5nwVe7Fq++RK4hnZy1L%Jjg{aAdF zB}`kie+Y|4SaOrIW*FV_DJxnw&9yw3O%HF`m@7Bn5AwWRH`V#|d7t%33*vFKFI|pP zXKd?*XEKY3#TvoP!hME7B!~$Vm37TrGUh=-9=KSY)X}Vn*j84Z@~AYrIj{I43V1;Z zn^Kl0T@4ATXLc-gOEqPq!4cgSb)|^G;fE&%H(Tnl;9!;LOiQYF!xJhs{LMLV-;tOE z7H1unrPmeG!uU-^9CGEZj%lwMJi0?5h~h%R)W->Zpf$s-tp-w#JAbKUcZOY;%BNvx zjY`@7&V=AW6iMwj_&X0n85nf~tK^%o=0?n6qHHpAlD2r`kY)}Ts~{4>cs;h|66l}b z|2}Es1k~El`;FEYnGfm}J7%QquxoqkQjdfmH{^cT1TUm;Hrxq{=E&e&L^FCIAyBr+ zcvATbc32VG520tataE;Y(w27O%hFWPA#2Lmsr!))*hCo>JMK`^{l&tciDTW<17{8= zkzOlv{a^VSZ-dg7qGMOA{v2wmhjO;#=5C>$*?EcK?9ZCaivko?Jd_wL6cv%==|pUy`zS=DcX5u6lC|Hez|MEo2-vDI1W;d zXUyP-jl&=Y5~YEZsvF~jn}?H{_fw5->RdikLiTtr=4nI~#k4}4f+)R=8 zTd(_j6b*WS3QQzZ-2c!s4-IYm5ng)x?1yE4Qt{f94Dd6_F`%?P!k?zP?4^m(lf&!2 z_5~VvxKG{x)<&VS8!iD$A`UITCdC%AN@DYC13}3f%#AW*b2RYCVKp77sECYFc%2$n znUcxYy1Ya)P$9QOvv05ukwmvt|t#-k{4)$2{F9wADW& znwQi%IGN4HawH>;@%ZR#cf1?DCy21xl6Pl~l)6cOV=3a3^jlYOp)Z_=vllolM%;ZQ zBmXIXzAM}PO5&py<$N*F2=EZ(w0^Jlr0yabfuC|Tw)%vGpo^d}bvKh(FzVy5?BDz1 ze)EQEOxMA1Nx|-zw(UXxf@(kj3Vfqz2J5$z?L}+-*lM@AxlSj#*TV;4{;2AQ^@urd zLjyObw-RdX_yjLi{mBJkI(L-5PHdyw5LtNK&fe*IogX*wZICTPoYnD%fE?H7``DKI z9Z2%-x03^C0ry7ffDJ?M#T#Wm%N}XRkiltd5VBjaM<%j{ORq$Ky^-3#K~0d#1MCAo z#zOB|TDVYecG{t3#xfGKoBCEjcqFY6xl3;QW4nD>`ING;)*h7{YL1M$((Glec%7jp zhS`kG+}wvbDx#e8i;oL`0v52(~@m<#YitSbrF?rAP1qB+dJWiU_ISdokZjGtw%B@yjPmBld)zn27Zf=-W$?Ip-o z0)q8>|6Lz@zY|wp_y?6|Zqr<$I-U^o+KmA%6`e+Zx>68c#W&rm4EpSRMMxC@2rB<@ zw|DYcJ-y_K03qoriy*_i{5lDs!D^C{Or8ISZ4mUW-1QYfA@qG~zjSjv7A;_%eg$@9 z+qI2V9=z|zjaO7V-DZ3X?1pkDihlV^<16!1p%;A98(-~f(&dYORKzJZrsB#;(K=ks zf_PDeRNU%-@JGw{YI8NJK3kU03&BA~!5!+ew{NGQcBPf!LLIhNcwaPl-eWN#pX=#G z%td^xhE~t;S`JXy=HEH0{6R#CN1i>S{@)}eP$Bju%lrcegLLb#-(!|l4fj1zQ20I;3zGrs@EQ*_BTYBNLN{Pz2ek< zd*tu^mjZk^9B_KY3|WFHtDg^Z=s{dH2^Lz`7Y)W)5+OArtifmQ0BH%9RfL?E0!@3X zG4svr4>@ycCH(I%6l^(ohK=9Sh04Ls%PCl8;X~9BSvaoI$EJ!y%H=Nj{q|t~J7|Ho z*ua$wqj770c`45+;;cruRLTv>VCB7WvXw#5gG{HKFC9yyY-|0^Er#|5*G{S~_2qhH zNLCMxa00Q}p1XENY@ka0qffY{(kJUJM@>2SK#aTUN#pY=_7wT+r!>b06g5`>_7%X^ zEATVgKq0L}`)GBQXi9d!Rzh*f`vxV}a#Eg86#Ha;N9JP4*^&S_OH=Ot`sRIrj9?JTguH2=u6;G2wKDhTmn+R%#-8&m_%ui&n0(TtX_A$aQ z7mN$4HYAZk*k-P1#_e=Ho|^tba5`inc^dPd3LI_*eF$~7ob%7`KgJFH1o`S>U78}M zX>z51_a?kULX0|=w~TQ}fs)s|_bZF*H3<%3s%!UdO~&tJP>S;I7O>h%np-sGlfw(F z=nuD<;DUFGa`^F=4+qkf2!mJ}rLTpVBC=`fS}PUDzj#|IB95GX zv)9oXzXOw&dkM0i$m1r*gKIi@7T95gmtGlM@-kniWiA7_1=Hw%2edTlTECP;@FSj zM$9PI?1FwW{3Wzg}-_+2?Kae!l1}h;Jg_VYd%zkO}IM zF)z-z7%ohvJW>yuauP2S zd*|S-+q`$_i$?BOrN*LZ_JtWK5mNuAmv*eZ;<6B$J#X_jz$ug)@`!`w*we$~*+!0Y zqZxn0h#Mef8wkmp3+1EF_S4)|t&QfK*>dLe)n>QKa^i*Wz!1WqXz{{tC zd|_n->S4c6>Ez!td~eNpn;JV0JZa&rn(9yFX{MCia>6wWc3Cn6iSh>iBr{ideYF>lQBGWI;p6nw0|lzIGg`c0h`sgHNUp@akF}%s#qN z28Gi4pv=7~%Uk0Zyfo=LCXwFtxri#?C8!p(AqZy@C94{Xt`Uqq1k3_vn-&sg+}^}1 zK`5F-UPmgOA{}SmnttYfrJ$M8z>fPz=xaNzH^}DN$8ruM&w9%dn5KL_5$JxQ)O;(skwn-a|#xX7ZlSZ2wK(J+G`SY6#1WmYrT(2Z2&#@J z&zuUvlQ8nFx_0-`ELjG}Jj0?L2*mC(dr_??#fV?@kyp{@LU;JGCJrgLurnfncUb`= zsNbC0UI|F!hoBq0)SCm8D$!?#xBdN-?$?ia)oa0uDs@eq6(8W5*-W)P+F9`=R+FDY z(2PUi6)yg%zk?{i3_g@*Pr9qg3IhBs!7L}P)qE`fdL`XWA{1|*F~KUOsPy%QkOo|z?c<|=&apbZ5)D-~gMX0AX}s>X_)Kau z;}_oUB}goZSRZe2TG9^U$8+txrIyB{$*E-Uh&CzrJ zItS6ErIPLHC*qxi1#!&vkNHJO7v-2xUXJJej9f+XllKeyMr{zBsIX_|8YApu0v@w; zEexKG<4cAc<<&h9_y-ZX5%&yZ)qAuNopVLd3rto&zweB@N(DhpbDh|A0bhLCwqeDw zIw|B02NV(J0OQ~pzp~=u>Gri$u>aCurE$ZJLJI0)A``bWTen8~53!`hEW+TU{}HKF z0zX1JT%<@xUQa0rT#nT@`FzX$!R)aMxXJbqcdDoR-NYYGt!ch5vmam3=&=x?r+{nn z5VFr(w=k!jYMt!1TIl?up>$5&Zk6MxuKd`?=A;H;`gq<1$8GcF5!3l}3osCpg(>^2 z6xj=}j=cxg4|*vE)YNwS&1G)9=%ttbT9VP67v-$MAU`o$pSRsdmmUIMqh3i;lT{!k zLMqK_c^&|G0I&(pVRBXGF-NLWa%C-2p9J+kT(s+qW@mQ zL;?6kdKTQfEuK*^=_+7O1%r5n3SkP>gTB*e##{mUVo|{GcF1lbadrv>(h!*&RY8>c zTiZH50EvIcg_3)DR*R2Nvz#?s^{@p^-?te27b4|_X5Ql63qQ1VO@yOcJ7#Aee{I3D zd7(jOg8iiGD}c}gQ`08Qf4b5cNNU(nqO8Tb=B?e>)~5sKII(tz{@9MeqB1Za9pJ@J zeYKAmr>rIO#aR_?*RTdF$ih)mX_G*Qe?9!ZWZTF#APYJSMj<`NVaKZ85bl@A1)SWP zP4%%LD;eDMu?^E|g-{$Ay+6WS!8h&B@l(@D@SDTeOLwz^Y!^@xcr2xPRXSj&97cBQ z1Nimhc|-5Y7EscE#*$)Aqh@VDCzC%ie9H{Jmf8w#nJa^8mAF@5Jw^!eSV}}!c3zB= zfjPStSQZPrihotPy5i}|?3W63P;Un16t4t9l?RC*fe&2VL{ern7561HX!o$KQQ~vY zg|=os8}d0`2T}^cx%JF19r*CnN6D@iHQq4a&W!7=jiN-{oZL(RXFhHFJ8@IxYq@AB z=YX;WyVi7geN6`bD_765wKtxV$IG(*XW(|h1^iU%|BSM}JWmIh;)fI75#a$FG@S_w z0l4IYwVeLf<{8m-vb8hb&gZ_>o~dG(4l8arWAzk}9`ZkRX6rF7X`aLaRWQdBSug|% zs7O`Nuxmzry>c+a?aodrk_UBQiLkMOH?JId&&?0yd;=x^}s>_qVWFCF@e425|6X(9~ zMj4B39ei`o?n~aCD?twsa%{xXt!u#ZYsm(M=ZL%JEEe7EzW<0CUbZ+j{R>NKF;KxI z%jdiU$KW+P*vRi>txHDD`BUj%9*VW2)Ux@m=0UzS%3kRAEaUehCG zip1O%|COGF8ku2kH#?v0oH0VPSC>qMkgA=?VmK89B>;ONxL;2JLjah@oQcpAD~^%B)x=MWkn2kA<8z9n71wUF_7a#l?C)dE-?azU+-G5cwN zS|f6PiAJ9{DQeGp<+NNYCpE9Mwr0&fOa(6D@X0-<%W!D~$fLvp4->Gmo?Tw|9@d1y z(cBP=s(If@d~e%LaLHN!2UZeU(9(Ky`2rpF7YsKTc{VtPYvQV%WyT%WzfaTcQefVC z_-$M4CKJfv6z&z7m@8;#Mw3P96?OwFQZ3dLOFh&5-_)%|RQt2^>bPdnWGDY}&*WQ8FJ^oj;P zcnxg~I{d7z-TIDA(S@74T*iyKztlNZRrrlbsBqy4Mg(su)Y+)%dNk}1i@Xw-PG$?9 zF|o>8eC;&0Kb;fSV*NeDk{Y$r!)-e&ilEBi5n{^Yzgh2ZVfwV##BHg}o?=0s3B!PL z`SzeZt83`~Uv6p_@S=Edd9HjU<0{qv65KjW5IINJhlK5>Zdsq9@87`(_Lj03_HCCb z$ETciY=yR-L9rT3nwl7SEM>0sy;^7xSv!Tqa4(tTtZ-^aO3v}s>$G0{c&v%qLYzff zb}xVE!kV|(EsUK{7mzMLq4RSl*s*1Kg%bbNITVA#yVKO~^+5T>A~}AWaliEBv6Rf& z7ho*FRp&`lf&Pb|F&m)BQGR^>nn0!z{$U&cAAmPtL;r-T_km#rl*IPS&|Ll0Kf@L1gYQd z?M%OBe;y!ZLT4-vF18&{yIZwjFo0IH^mG^sC31kDrwG(YF~!H}8n_o4$BfKv`(6Hl zbpyxgg@U|AkLMk&Bx7pgW6izGe$IIyB(vz6CxW4C~JbXhmBVn4w~ zGs(VQMBnG?y#XUMbgA^>EKq;Z%J9)HW2x2%z$nVdv2fIXgy8)bMA`c*m;73I4aLI|rpw;(h-57!AZTG|Wn~74{7plt$oiI_=wx(WD z8zGgOAwMV)3GbkYJHNUu@Rz?Y*eyP+@%KomNjR3wS|3F*AcqxcfTaHJ9#%X|gFFnE&XD7bhg4Ae2!g;M?T; zE|_gx>!)7#%cD#y^|*&mfAz``anPRBFKq`D?YHQnGzK4(Rx$HTZJ3m_++WNF&e^_@ z^t$)qL|Dw!X-~VGUp}21XA{>OxW*UNN_V$9?(ZbG+_#bZ`T1?W=9rOL_a}ZvyAosi zjXQ}bq3vwVw4yW$l6A3v?m6lh=o54kn!b^s%Vr(*DFo*G+sWFDr7l%0zXRhgQ0frI zbAl+z%Gw@qfcdj_V0n+}16Jip1FOBR>?p}jm5p6O1>c?#SV=)N=-KU& zu}dgXtl5=w95!vyZHY$Dctqw1U%|{kO z?dT8^e!FU6{LX03TF>m3?RHbc*1~)ITIhoCn^=wSSw++kUX#SN+b6T;Sr-a;hU8?y zN;sGA${{z?d@UOap8;Ajdbvem!;W=*u!+inwq*(=rgrjHPW{HdPyN7Wp9`4OnMp!n z;|S+L67SXc4uLye>8pL5w|Dm&_z&G=%ZUfJn_Z&Ur1UJfiA>)Xdv6W8qb|GOyLT4k zr=lM)GGzaq)!}}zX=cKqG@E!IfYp%Rbl^Uf3j8G>`tV9q&$ z-~u6%@vjWRaIK)8xPWUq2RNR9%yMB&+r%6RymP4ZbR9AnqP8YCr(?9O*{Ie=ln634 zQ|~4c(unRz;EemHz7K6?TH3pVnDiCuaNS6QB@}D39ufj^!`^#1Vn}px0RH;+214!( zO54+IN-qeHst+5pfIhK^%hOOFk>_t+Rhdt=184SyPGRA zT=B3L72y6W_scN65LFCF@_-cmn935M!ng-40E}J8v^%|d+i%5*2Wbb$W zH332WfMVfHp`LSXYQ@>2eqyqo-=y-6pU{H4qnkjz-t-Uu&|9DnMBA|@#&*&%beDuo zv~Ku?Z!6mjcxQ42UIZq<_BL!!^n>TRtT)K0_MXfTAk+I$y6Z^Fag$C(3}9zi7a}7? z$8A5FIe&v$d`l1P$ffygZJww#ow@CE1BW!dhQ^|oXWgOFcr0PwWh99O|5d(il)KD+ zb%O^%n3^z3jC3!II~XRx98o`}uzBXJ(3iHpFkLYUlH6BpJEa2C#9=s|ot zRQxt)b*@leS52*(a0^VN2FGCpuln%lmG3vt3Xc?WUX3t>7fDRM{^BiyFHu;6vMCtu zma!?}n!V}+8+U0@cDR7-@goyyxUf;7=M@ds9C{XR{L}v-?L#>MdEz!`DC#0>Y*s2p zbZ6c(Gd0=>wH2EymtM2IZLR<6_cT4gUS%KoPg2VOGPjf{Zgf;tB@G_GQ*@$q|MZlU z+Lwcnj2<2eiupD5o%M=AHNJ;`v9{?f(@WJ5F1IB}j z`d5UNwxmJAw~I=2qZL1#xEDqWGlAXzP@Ci_{eMErxfRDMm3BFm3lVT0?H(iY%M7UX z2Ne#)r#(up3cIpYvPZfQo+1lIupDVX1ZS)a4`MiQn`SlPYVuEEhLTd`W6_7DKyuV} zw1VK5GSNzY_e-AEAe42hnaXIFmt~vYK86}pqg-htroY^JvFd19&>2Q&O7DO{DH5*l zBClU^xS^frlDbx12+{zmW_LQak-^g95;u0G#oXBImmOJ7%b3w4!e)-N&H6UF3TO6I z*T@W{T3A4lh&|0RU5PL#Z}79P@-^?o27^jh=SMMTW#orA>3gQKvfcofj~klqYwQ=Z z^`ET;&(6--No=8OEQdDJ;^t8_qD(;sgl`eOg7f6R^RddGGS}!}$3%g=@T#*YWx9oW zpWjIjGp2TTOfjtasLX^jxrCr&A&Qujyl%$nI9BQ#skX}sF66{f>+g{0xbrLrP*A3| zMl;X;u-^Rr!f?NZi;VFnP~fg~O{{cf7wID z6ljM03RDV**e`bF3oR${tGZ&sOsw ztQdkQpWw|9`h)z1a}D4cq}i~0$t<1z{+-Di#VqR+%(EIYqU9zd1D+NxK60yTazw?w zCf4Qq8YUR$d2|ns^?I^u0Jc@-#Fj=6`C0ux`Z_W?-YOUeVsn$oQqOBj15_6FxZlS| zm=}8epngu=)4R5@t`mOH7jWBc>)c^4=coG{-6f#Bieh|t$ zR9vt6&2!=w2#wYTErdZpX5yqEp*;N$6;Jq|DQnpKvJZOY;)cb|V{H&7>v2j1uQF4< z6C=^5XGDC$d*hIYn-)kNO{O65*orMlX?Qm(;_1f9diJm`zxC-MYe#+%v^|BzaRJ}k zTa<~`@j{;eD0){bvO7CUeZH?87=y)^&C^#Ju#}f`skfES}gsZ(-0I)R$b`ys)OdWihqa2jms2~@)w z0+%WfHQ_XzB2m1XG#k{AP64lar|Olkb=Ug4j?y@}6?PWgC(UO-4v(xAN$4)6yXpcy ze{gJg`OrA%kH#x2-1Vn1^UOS)l)j!UP5|J25qxpfg*Ys&i0po9MyzRl2VbLfCasE; zHMkYUS%t)Q*t?aUZClN+?xBF6@f@I4`KpR?WBuyTrB1_p!PGz0u|o=s^qR(5x`wBn zsD8q}bD^(NZn)6;@dq*NSG3>SeNPDkx*kvyb&{hF9S;`l4j1S`d!;>}#Uezzz6u6W zjc!i@xjL-GODa)bsP7nl>9xQ2@|><`7`OuHgGsthF&NF7bf+mY?)PS&?+J!JUbLlQ zato+?Ezj@4z{3hAFEY38unPFehdDt>$0>H454g?bRga%ES@T0WXw4+i&|Ag06@jgT zG?jPqg$5ovj2+b)elSMFP5+R2)AY3l(Fj9-UFEd%1-qNy8&s{|Zm%fQ)=Jf3>6MB4 z9x2w)lDlh@Kei*47&ZSiTo}Z`702sS^|60mNm^9nqRnl2*W1Btf5)?cv{NEP8Dv4| z*m!V&uA27G?`tQFkY9;JH~s$C(!dhs#@=A~4NM5JeTN3LF#g`IGd@FWAfX0ffHDRM z2AC}59=N64;CN=MaJ&59Um;P-z#O8==p_`zT64k)avwQ4JY0{EB zo%)CV+!X8KZ_|Tg%Dg;Uz_`x=4MA4aP3;&X&j`Rhscj&3>A(W*jOLgp-$6t9@K`9P z>C!yb9yf$!vn{$BYxyDc{k@`pw0@@g5h@8J*E*ZLSLu${c_(^cd< zomCoGSN%ZgjHekb^wA|hV@f4bbrlHAeF4Cka-YEJW~VHeR<`8x#qM3}jBmXYP^<*e zgqp$Qn!^}sI+=D7_@i+_`>w*%24G>lRU-me-rz=Q`AT z(UoZJzC2#*)pj}UKxWn85EhChZu#r1h4k!Q9>VKlZ^4-E@UPSi^$tr($fIPW5yU>( zI)41TSISki`x(zZ)B}cDT`48_N20<3N6rLBza!xtDOKW{LmSPxGQl-T%9P{VuV7<9 z1KJlZmumNFhn2BtG}9@WhC&03)*h%#Sza$ZsQbK8;U!m!|P%&Nu zb}4ViQLU_gm1I!0LHctB9VhzJ-SMdM9plWy`nmwQs3=$=t1ah!?T|AotqdOy^xWMj z(`AlL)zluoBGJpwlzpvKHw*c5O|w$45Ufrt`uhQx30>4-B@(9Y^yNsXILZ-a$X6)Gs2HA zp?u`x(j32So+W+OU^$+?!1nJE#nIhFJ_zrGPdLw#%|7*u|cZAdK$-wvx{N^Il$${!8}o z?qFyCm%_<10Cnc1Q>QRSe$dl$Fm>ocnVDYttly|0Zl~zYpI~K1IBU7v6an&tUcpgP z_;C1MUQW))qBds7Pw0~+yWP5K6*6-!LWEWsnn)=_!eIIjEul+vh-)rln=`9SS!>u> zm#5c-4Kc7t86>zY4Wt75(SdOFbDDXZ^99`qN(;r^!J=Z~lp_j!Q_1$dwtVeNu-iB! z)6ka=1NVp87^*>QIteKb4E#geayt(U8xG*J5c2yF{5@>;Jml|0Ye=I++8V zDlg4u9Ml5WZ1-roYpz^K)3b3yy8D%kzHx{1H9Xn$Z7-yp$5M9V!1bivSEa-j=i*&C z4pf-AN_u*Y(v-@Y1vM9M1CWJAl2V)QB`Wt(eY@_TOL=DQ)vu#Ar6YO308`&a`hZ2(S&Q;m3b&r154*+?I37=bqxrb zSP~7)bfTq>w<2};I-T`$b!XRe2I*~SE)~_!^4yK`qwq(USdLigu_xVGTQiErmw4tM z>`&N9$8ot^OdZ}LpM;s;ETx$Y+iqcA4iU^^Q8!@@Nf-t~L-i~ z=w0nOytv;tN8$5BlxA&1q%o7d{YK|H+-(v4toA)Foh(g7MAO+u5ie@?{WR8O^8T? z>%aP*PqOi8`0d+y?`uMdJ}YazGTyxfOA=4%T!4{l$vD#)9hLLdp*ViHU)FCxV{mj> zQ%>%6(CLd&!v^U<)HnKFCU|eoZi-zNSDaa=FGO5M*Kui?V8Sz?DG?<$bfV z9%#c2;#mDj?#fAKH34e52gtguTrvvT4)NTzVB+U`edX{}CkYA)U7Mckk@SR;p5VJ!mWD!?p?)xZdH$V&KNrHxk*o#^XT zrPh+thpVpN0h)w~Doy4I7aez(Km!cR;Ovlfil_9W?RDqsk>zh#G(Q*C-3`~CTZ6!N zNSO>gS$mSxU4$Ra%?xyzhb4NyIjXMtF=*S3P|bz)V9~B4R7M6AEWyG6DpO&AVy1kf z?5S`dzZx}5(h0OqW1`YO7_N3y7eR!Vz+~JL0W`I6*?l8~;q+7rmRdlH8O*wJlK-6Y z+c>3!b{sbJ1Sf|=_LDebsk&V;?#BqrgaY-G6+jk#@~AwJbdaNz(ST41}KuR&Z;J! zW6R%@J#VZx(mmbJVPFP_`7ES4#4rEK2ZAh@Q^#wI?HkriyYrW?^d?B`?aEh_*=;{Z zAeOpp3Ut)4w6&sKH8edKvd1skbl6j1b3fMaa%Nk07 zP8yWv+V3|mBVjS_W*5EcU7d`cY1(z$X#G$A zR{`5Z{#Xh1cq&%;*4=7KDUdq!JoKTfjwq-ET7^gwpP$=eK z2bZU4YW{IrM407M|5P-TPDgz(q+%eo?09uEo5#_}Q^@6ZnRdt%MPwe*P6^GHv6)O;QG9o1Cv`B1nm9VKqcq6 zmCqdlm#VWeMHLc34mg!QkYb^I=w7)xkIlm`G5k!ppNuO*LP{r+wSWC`{n1spRrEz6 z!U~Eg>3g@hFS@%{3d*vrYe8cGNN2^lutSbTof!v$3U;0UBeP}XH-Kn zQU@jG+Ie|P$*QdA-svyX!KHv#63M;~ZapRs*FB%U9T#^i$iIiK*7J|Qp+;M*B7C@I zI7<@JkI7toc?K=FpKDH!DA5dtN2dJYJV>$gxL(6sj-?~P$h#VJ2jZ-PKKFj?yHZmU zQw@gWVG_>l#lP=}y6^LN*DErw>;-ewJPJx$K2J{hw-c}G{XBXNhY2D4KK%S|QqxrB z<-ZsJyB4g{rP`6c#W+-F*XPWc7gre{6De;%b4NtWrjw+83E0`SU`mHTMO-kLpPl@j zdjSEl!!T2s3qkx1QhcY+{J!?X-8a3AbPc^b0P^X8z#tB!0+)N*M%6Fzca9$VX#6@qpH9gnNfhd3? z7ONn|Ev$!QBYNRhn=2Fzr5HaC>%K?2?+u9V)F@l7Mz7I%v9gO&zELsrr2e)Kecb3Ex#ivdHPb1G?2nW z1tW(c=ag&)_C;X7yT(r@a>Tg#T4`E4R&7yg&V+=j2#y36)l5^q{YC&H(YNt{EuhK% zh?*p;15Bx9y0UBilTEIe+Ueq!J7zBrkKy*`cz!;1$SP!CUz}hY4|AAff{c6?ODpuaHzHjIAJB7vy!@k&K zk)nf%LHVGruZYe~+j+nHh&1kkz@KADleU0|?Tb{7{}H;!_{O^!38|6K$bmvE8tF;V z3a;Kj+f;TXGCs}VbuEHc4Hwv*nKpi1hoEaU^#gOgk3EEEL5AUu%Y%9a76diD6sN~t z4_LBkG;p#n$&CNw6UeeH)i=1MvXY^F9Qr0BiI@ngQ!qI?tO^q4FMMrk+fSrD{%oMymB%nk8_B?AtB3XJcCm=7bAgYv_Q6lqa)-R=0gbhFwyoX!u(6UGto)z z>Mj?7uk%fFK@vK%nmgtwKH3nUhE$u^zSG6FKQX3V(^X)65Smqfw2GSTB^ytaF*j8n zVV66Q6wGojDyaB`2_O4Rv|KX)_L6 znI|5FCtQSw@$K*6MF1FdxK^)p{3N2;mQ?0Y%w*e^@Ox&=Hu&0dA<9Y)Dzg&q9b^*? z+^2^oPK67K|FQ_AqFc#R;^TE0Wx%})M2gR8>wsLcdEC$gSJs<2#05ZP?QgjdelU4_ z&*a{-YF9D-fi-|-&M-wX{2eMKY#z~Z-~0%wa$GT@i>my@VzWIAdJU7aP>V@ zO$33Oe8jl4P2F!qgf@bNOYt9U&$6mlTsF2(g|o#Tn<-H2S9-Pk>K)jMe81M4x|;aA zcYm!mqgDu7-29YS!I&%rOHlf{k27eD&oF0ygAT!j0(!-$6+fT$Pk?3$ljjo_T= zl@-I>FW=tdVC(dAtbO(MUd{#Vd}!)eX5i6z)KdcM%Q*EuF$jb(aA%a|wOo0l5zm}% zez~yiLZc_x%9)R>;6K1u0~BOsqt0p~b6V6;Syc#}!<(o-Nm|kq`-)XO*jS zu%w%I0YAA%O9Q670bb%+XL>M1w%(j2$~-vI${7rL59 zdPigsqL+JRFQ%kT3@#3A50H?YqWy>LlM?_sg&`owVu&PR5J${<6TIp4fJJqHb5Gy= zw+{ee+1lON0kIP;>n=J47KS>vgoFx*i)f%qP`1$;;7lQsyz{;oeOjhZD197HscQ@@ zjFS)q$NGkd@A|3Be#`gz&9_tc_ec1SJd3qw7WF}v`cq|9L+D(JoLbxE2l{6B)HJ$< zGT4E|tx*rjXo0o-c6HHI0>HmBfW#EIPZUYe|C!hD-|}C*p+d2-d%eek%;jZ^YXMV+ zxWI^C)jmiqtr)zGwvO)YQX?~=LXno!jEwrJZ-|K=_eFdopHq_(!T5j)-p_Gaq%m1j zZd<%9CGspcW*~1d4*b9BU*!76rndn{C?iS5;U4>4!EyP4!j`)YD}?OM@sk}y(JKvR zD~LuXxDu3vjQc+kYFz&%!8>aK7X*C+LOULdDID({ZL{aTEigr0p27tQz^S}oo?`g- z#<`&3_49Zuw7kJ)G@Ka>(OpY61Is$rndBrSPSQmiF15BU4!-E%Bj82~9U2<4!2Y;5 zG=8S)opPV8T3$s(_o#GBh+>;I-+1}6`xh@}$t_0J9zw&9r=}E}xZ`#Euks08iRnod zcniWbK+YLUU@QnZWTakPtE7PK$=10F0~B--&Awk5*d>{qz~!#_zgkfE2ycEdhkb+FRem?ESh26zWny^CN852+XuzK9vh(b=e>DgNzrDMoZH4zu6A9(=)!pgS~faHTff1$d8T8 zR4y;jEwKS4{|5fSMBUFm1n8(mjfc7+GjAO%31 zsPeJIGVjWKvD^)AkSBs5bYB)h?=}Xg1oKzj3zA8vHk#UGNZ}d%o|j)934)$73X)}s z%FTjgUt$;uhzHa+)4PH&Ax7wV?kYA}aRrMdAGlE%ArNs+qi+F!k_7sn6$pvdRM-jm z8hZ=|Z?yXV0$dZB=xP6Ug3+)nxlQ}q8nH01eB~x?)ViPiAOYc75iQ_Srkp6@CU0=MwYGEj4X z(A#K?8XNSUYTEO0n`QPkQmE{jjDb#EZ%e;i8cUh;OT;ExG zmzQ5)%5>;vOOZOuKTKfs8|>@5wXjkq*eH(OQMWGroUxF=y!;nx0Vs z`C3^;!V#3R>#izI>6Ig+%~n&k*OCud`GGuu&rI##bYCRN7qtIWGYU^f{oKGc4M`4| z<`mIh?Ob4>w4e5W7BgU71RwGJjWXGDXuizyS@LiH>u`#vE;!uL^K!pM4H|#(xEmSK zpBwUF7sfBl9PQ^nY$Z`zF)-45&@9auh64f5HAa2N^kS`o=SLu@TkPvVj7S#fggmr= zboLZk15aznIm@ZdwY5fG0v|90Q}px>xxuLZy%Z?625@&2$Z%S6w(p|{D)84qm@mp-Ih)YR&8;v^y7#=rG{{6k9|F6Gy_2vh*{_=D3>reGRu0^^UnIh)` z_6IC&PdPJ#22)Bi1WEV|W71KlbtMruJ%u>of%zrCK<-coG zgr}Hg3utg|G7xbLr@ZZ3{}BgUJzo+7clvHtqx3uU1EzyCtDUG8oU-wP#QDKaj9 zM9eRx_3&ivxgSuNgaq+(co6LP)5%^T@zymPTu7+u($f1XW~HeO5UfGme$S34j{yR% zEG7-xxx~hpi(g*eHSwfzZichK#xI^69$=G=Y+(MpJ>LK8x7T3m@=Z@q`L5>}H*-Pl zG6-nSm-k2pbwS4WAjy_L`wN6A@w&Ft+G{0B_sWT!W;9`FHSmgV>(@Acym8U~(e=%; zS$%UIm2RQJi~~~7K*~$HIa;rK?utOT!73dFeP)v4nJ*^>9xj~XD)7-Go?=gftH6Ji zl;m46^@659h0i>uaaJ?jqU_a-QE__JkOLm%CkjQCN=zqBzMZe}X7reHX>tKFl{?MN zL7*MTzrT`D&Zz;litD(dP0UV}t~%4g(D7?Yz%_ZLgX86}jo###GRPevT2scy<%A2@ zmdh_+zO~@+7cn6gc#ftFe7EaqfXjvKLhiUE1PNi#2Vv^})xi)ga?bSeDSz-BxUI63 z2tpswXHtE`U&O#NiGU+t9*y65Z~hCx6Kt*B*>8_!>>wf4t=RWuTy5TQqZ2di3@a~C%I*1L07)7pw8}d<08yfNcWA`LVa!?#Ji123ha*=O;9vuI@}8l zO-eR0NM+7143C)oo|f4$a`J~=6=e%YOYoJ| zA=%v#?_8DKqKg~Al@8+Yl8mj~61$~1+`3LBA3CzzGN;=_2Mf6d#pIwhs4GU zx1aThv~s!xH<%Bo>m-L;3`=jR_NKXAx-EYQxf4`+h*Dklk;RRgdL zOa;pW*K|9E{se82hyV9MTe;czwN|`pVt2dSDSs4E%JVBrBu;*HxH$vZ)xR={>Kl~; zqgSMyD6m9jnB4>&Xkml+Ey`|p$LCsMf(z%;{J@7RC@a6o!g*h;q%1S&%TCPrpb9TrF2(BHc}SBZ3u ze>19EXZP3qj{zO6yuY^>`}g)z|DSGeyQ~MiKkv^DlHf4fq^DnO(c{N7z>)>~fN#2Y zqF?;V^;qI-+vyqo7_hPeCyHh*e5?49sqk~TB9l^Yw2;NLqYz%Vce z*p!2+(Pr!HI0ZX~m2e7d?dg5}-)~K_7f|fJFv6%4kOxpacPV;?b`i9R^E~-|M{cgn zO>wl%`eu#k@@WyMdG>g&YT*??(jx#g@WrHUN&9Sp+qZN1h$2mQ4+B;dPQ)nY8t=e@ zKL*s)E=^2bSjNh6$Z#LNQS+1Dlrd%Vggi1;rd+Rba(Jm)(P{KyRA4ma{qEYU%Kp1% z_a3nQv(D9_j>T59C3M<&njvz^>D3A1gh4=E`P`YBnL$?gcN-trp^rY`TX*6)K*v(1xyBm zQ}nSw<8k&Pgy4|foh?;lplQ#sm-ElzR%X)8qAoU#uh4ci z-fc^NP4Ss@@%2T8a|MOHZYzFLY~HRS;1YHCZykUug3j^QP6#jqk_20|m!kWC9Jt z3-<}}D^bSnO%Bxjs3UdLMhM$SKeV$W z<(+`ap&QW8@F9J11uxS#TvO=K+Trh4xBfL?{&6Eewiv80HB zK$=>Zr?14rehTh=YAv~W|310Qa@C|P`R)?-{!|sGKqA;cOh& z+ddm?zeIbR&rXg)A2q43SDzH~n-3W3P+!j8?2DTw25-*pHP#3ApXFy(w4G@t&pj7E zVZ+|23}7=a*Y&v~5o{A>JVA|Qp}nFlfc8NE2_@S!7)3VWGCsc{SPaO*w&)*x23$Y6 zgTDzK2)XJm%$zc8i#xAuCzuW}%$*`ui~!3^_E4BA6|oQRHR$+q2MfERhW?%xHdxF8 zqBa*nuV6FiU3GkkY9g?{1q1~%Q3l)?`(EZ%y-T6LUnTijUx{_H4jjcDgrfjoNszaLbOJgukSOMmcR(wR8Zs6(`NYQg1go zkOXP$xVJGD`3x|w=Gqm9rxWj+t#gdIkU)TKYH)Df@3WgBAtg==DquQycMoufSG+SEY+K|O0n;Ad9%4XfVTmt{M($Tci%80cdAKmvJQ;* zuU+%-E&ikY@lpR;#pxobTRj6m;=RbuqeA)DPXoqmr}IxB~}KgkFb=WM!O_%Z}W_W|qgS_0td_ zUhvg}Y{Bjj&Su$PS#$0=sw+tt*G*R{f3PA=;PwL7&7xjj7zUH|(Q6MT<+)W&&XR6WC0^ zC$PS)Tb13Qqs6mvFvj>yQ0~^XtKBq!fA^a^-zfCLNa0L*sbYr>tbSDvvZWcWBr(jV z14|<>e8Bic;yX2{w0F9b(}7zjSV<-UO0fc9|76P;!9%NS7gFxtFh1hZapRgu>NNJ> zu1e$Iu8Jnr3vzeN56`wYQSN>hjDPKQS|X>#w!>)k<~7On&MY)_w`i}Zdtv>3O6$pg zpSV(O&s&Sw&52YJQ9lFwDOnxT8|KuFT-|rUSkfAJJS>-%wEC4>M+LdWkxRM=+P>#i zb>&8Xo$*riI{le0>W8~5WFijnI@|;T7++fJ(G;Q8$@gI2>K%hzNiq7rdddJzMG?>R zRB?HT;KGvveSN@?DY_%GbrO5yx)2$BTFh?Y!F@kQAjsyF;p!+_9i_{Hg6R)$Qny&* z-kuu=zQ&v6yL~qT?^HQWS~-52WHNi^I4NlUM;K*8$f2EoDknQV?_oQTwdb}i?-~Ys zj>m6Lw1pwg+Q7_c^TML4f5GVe4UGPjbm3(4o$n?kV{}boWyNIl4R+piqJEK^f|BaN zVddnPKsFlKlBa68b=%l~ z(9b>LbLJ74uKzdYb0=_je_@|Z0bBw@?W?za%lRWoXzj0}ExS=WO-p3em8-}UaFP1=1H7vg?U+ei*be)LP; zC2bz;ojR7fH>bhLWu$~|G^^07622@L<6ZzQ-h3pu6^f&Ea*2x~e()Z&kk zRE%ppy10$GN>KnMc>2fPEd|$&^7L(Qvw^xF*f)RPjA8LUUEsI=I%AVeni27dU^S>J zC!hYKgt4m|x3k*6_cNbk>Tm6}1Y_+Gk= z5yP*8GzKWwPX^}y1}r(tdmV-j_OBW5J4W~z+i!#e;}w^7 z6Ka!^8ZC_8npc6-v3h;6+r|7{?n_@kf@+Ro#}uO+SMzLzjE~H$)riRo?~u1$E_uOi z&m5PXhn-h`<}(;{X@R-A>a7P?J0H$(L(%hKan+Zg4U^y+J+* zPa*2ZRGTthXNATv@SydCv|2jM8+lb-{!<;gzMZ;o zriC)92k%E*NiuF5S-@VPpW(p3?lUl-sJ@f1-Oqzs#Rz#cmPT{u?;3(N6a*vFLX;yE`j>^9WOAF%h>g$JOjOVOgnNC`rE{}O zQItO(5 z3W?~}xCsH$N>QI9={*S+h}DphH^E+)v$JG)Yr@j}9WVf~W&j<_4Gg`%o2k6a%=OH!ENYiZ%@q+0Bkzaw&+)fL56 z<*?=(N?FvNV7~vN{5+ltL2^uyAR}iHxTg_}uGQslSt?(3ny~Nt957KcDd2iO+U_${ zHHP%U41HwDC;5^y#ZG4kM`)@4*V38zsQw|0slW8MX?_JPp~o;ui+ad+zmw5zwU^}iTy-%h$CCuW33eL5My~Xi}_dY zPH3G-?Z?-~AHC2z=u3z1$E~*Qk8xaTP&|DaxS_peU%7lq#%f4=#3ygA_A|+uq~mer=Ce4KgqnbGr>~9 zBupM-xOr3zoUGQP&$NPe7d24^$H5m;2F)WE_Nz}EJjleu+*K@?Q>zL5A&o!UeRx#f z2%n!{JZVT&f7Gbqq}Zrr&`s`ug;n^YNF4tL6p`S$`dpUCQ3FMzF| z0i$rY3^913zM1G9++6Z5o);Vo%?v>DnwC z>Tvw{nntO2QEbN1Efo1Z9(SyZ?4|LD%_^ZESi45N~ZQ@WVN(!kLI(pc94dEbPnIees zi7eQY92!*5#-bsbBJnRjg;vCeDRuW^TKs1Efi()N+4a!9qbPIlHLaNlzm%CU_jWEi zH3AbTT|O}2Xi*iJe1xL*a(gDF6kfDOfZ}^q8`?K6)G_*@GbQSca{xEeRBIQ8_)T?vfPu46(&tAB^y=C;j`(i)s(d&tz-qS{{1@YrohW{k56zCGhb| z)M%h7Zu9*`{zaUG9!bK`jP66&5sfC-p|1*~82e565s6DyRbnpY_8s%{am$)H>6UjC z1}g-p>>e&>zN+}f-yyF#cHt|eMO2*eiyw?_x)^JPH3A{Xo8NMMA$aP7u@8$0w@tw{ zpPr(5=6aA9z#n7MeorP@mPRpnz2EP4Pf%J9){8?JB7nDZn6^bZ^~vU3Oj)Mua~nEX zl&jQXvK`$%XMr8PYkaZuOECdre^C5h`*?aJ2ZpV4JkOq~v?uZr*)4vKI;t~aw0S9= zePct}Iww^QZd!#04OLfH+30^M=d-gxx8aGM^OVkabnIpIfMAQtR-h|8?`DG9Q* z&yNKQ*0m&|W?InM=U1fu-Xv0~vjBTDsV!iB+P`hpoE|oeeJ603`1@;rh#cip1fim_ z?>Y=>{*3hgSlhcoS=5X;bwAlqE&=q{Q|~Uf_p>S}5B`q#!?JQitA;}(?Xtm&#O!rM zKD-9~nFb?Vns79ytr-v2$zY$iMZrNj5%fY{j&g;WxT5p3Xpr2%mzxrPmTy~)h)A;7 z$EoC+J|GxzI8&&?{2^4R$2_%MED4m<=LABed&Ondw+kX=UP}G|7xLy!9S&254~qq5 z`*fN|@7!T!QtrHi2Zaad$qY%6EnJI+>j#U$$@qMI6Aj&(%F!#+NOZ||^nLjxx)a3| z{2(+mXh`i@v2#6D0OV17FW$0UJ2Kz1L&EQcv$OVNpM=dc%t=J)6YbJ-f1f=EgQOFC z8xB0^hw!68c+xTj(X3~tKn4L>^-YP2nUg4t+sQmiwaN2rILQRiFQyw|3Y-oMH56j-`1n~G8| zm~0SAo&iUoE3=bYil_%{nJe)W98!a*w7q>mWo*ID6g%Fa%W`*TnmGA{_Wx=qiKyc@ z9jp1$!0j8hd9>|7&0JEhsHOv~mO=v+a6V85%0hyOsMv^UZV9Wa>4@6{W(EnWmbYYn zx~F_-KyhblrmtqGrKf_3D2@lPjXt(h2_e8&`=|(-f&&rwHEPMN00@cXakpFAWC)+? ztcSg`C|r>!t&S+jE@M;}Vc}@&dfURkGc}WBg0O(!R3lL@)`%6CVd&?Q`M`_#?>;N( z13icBS`GLXO^Y2C>D9V)MLeV#d{ga7!+}JrCbE*nYzt|)bxI6TqAx=oL~TogL(r*s zgxXkCe#oy?W4re4m7Jbp3f7FcOuzHu%yV`cRRSO^+HpZkCcOk|2F3Cpm|@kVY>lzH z(#D4vr0E3oC&8KY4vbh|`0l@zX}cWddlg#cAAYCSzKbDLjT%-RNsV27>eWU8{z|SX zK=$RKPh+T#!cbgK7`=qw5ivaEF)TP!Z=*n^Nr8o!?D4%&SCd<68Z>t}Wp1$^upb}C`SL{q`^7tF0_j3bq^vk;!lqtb4=)|u+Wa8p8p_w zn9TWgz=zsbXspz?J#nE3-7CSolqi>d|2JQBa!;Q1m)jZi|CTCEMB27WZe!bXs_IiB z#?nk^MK@~WrFkU<-A0C$B?XM}g^w5?BeBfB7U?(EjgXVe80)#S!dOap^(V8T3sz0? zj7my|lq63AUlnB>WYJ0cVVKx$^G?#I5XSIRB*f=5rg^+uT>&;*q3_?6bc}*5`bgfv^-v;WHZ&t^|GU`Tl?-nv#BPveIb394Il^-h3n-eDzBsXWvuplrJF z8FM3p@1(aE^Rlq6{i*5aaM-UoN4>0y5r2rGg0w-E8tGkobxeQAhRI0l%z*kOHu_Pl zzvW%A=TB0oUiw31ZJIlgUdlyiaq%q6G<(`n*KnCm-HS9aUq!fn2|g6II8Y?NL4%)$ zrb&wWLG@Q0^f+DU!Zz-Vs;IWVKO5*h6d(==KdPA$HG86n8j<0DA z-}Lp97%jAzUJ^hj8PCEuBEETZ>>csYrL__w#n+f9D9NdRJOTxNVU%aW-9HWmlpPX z*7wgDCr&Se#l%u(*n6jNU)^Q2=oP&j^})axNYo6?^%@bT3iqD$jS+e5OfQzc9|UhxN9Xvm z%~wBs{=yWPc*d`fNh(y9$AOvl$JA!Me}X=k3H%BA7!sKF^Rrf=+T}$tB=g74Uqaro zzUoM)FjxCltmVf-|DIR}?}&5+`l%fr%GooRem)wkRu6eFSXyV7tXk}0dr7I0Uxi#B zI;+-+5qiTmp9tFOpnw8NOylg^3mW@E+&Q;yV>62BR-NiVV-)x$nH(xsa#A0dQ^U4w zp0^E)aar5w4pOj4Tzx+C{S)zqS28RmF*M(_x{SJj*2d|gr+ovj0b$z#qLH4obgWHJ z!Y{MG9ra}0wI43@U}?dJ>v-Reej^Zj!{MW%#7LLHymBT{$$PChc-MGM(WK)q66DR4 z`!(Umj{><#$MYAx(bI0+055Im{ z#ko4}vSlayFbb*UXWA3cR_MEYgyJ~F{&z?3#qZMC{)rfn` z7>3s}RkOfK{B6>aV;)F3aZq-%*}GqTP_<*$3Mme?FbNQaS!ESL1WBL=y@X1OLv?nc zhPw|GzSuw{sHbQXH2l>r$?`l-*9tW#$|_p09`*3o zu0KO94E~-czG${-`NUV|FcRIJx`JmAf5_EeU)7)~s+X!UYVLuauKPYguDx8}%lMqZ z__oX47J;xwo0oyz6unctQ>$%ITL~UXKj@~gv0%0n%j_pm2Y^509eP-76-YNWw3VeL zzM;5|P6OKT>yFo}UMsfQp-OWLi}Zm2!n%3i-R|^i>9>mG(kXXpsD*5BX*ZVf9z>}0 zn^|eyUYB3LgN@6Nc`y9iEmra-XW0Uu!)Ei6IkX>@?AWTx(ZLvC6pO8|cN=5k$m{lo zM)Pa=Uk(M}6#bfGWa74X3fJ##Ng}I`ylO|y&lc?du$eFb3kX*N6Hob85?X&7`z(*f zVN8SAB%wVwa4c!PbHYMZ^7s|TnPpd@fcg$cEeR>VFhhsa+mM~o{0_}7<`k99!ok;n zf;sF%aF*vwY|%En!E*!Zk8rq|Vj`)eQl6}CT=gWfL>_LW*k#V4E9?73rS;n(OQpJl zyJ8T8$;o3$jYQREriOVpPXaRs?S4R64PECBii)g?s!nU-f4AnUEJv(f{7A z$l|w*Mn8Ofb6>FHeeGG$1(U~hq*qLL=-rc{7lb4kr6D=FzTu3EL)0?hP69fl&{UBorh9h(}3pSCCt1Zc>;; zqEY5WJHgkW6Wm6?*-a5}mR{Jyoar0x zF}PZ7PzVp-z@boRXK(+GIZpfL}Ab-Ym+TuMm* zW%RY)fF{>J^+@x*f9eq|e+Y>Zu`P4p?}b_CrBhqP{UJ%|V5cPa{>%^WOV{>W|9uu_#b16^44g=At6xqX7*mF<}QzlgN_dHr^Ne%fcPEruIVeQpZBJ@u1p0-~{Ftr#T+u z18vs`Tng08qxi@6s@5hhlB&qo0oFaWfVR`HpgC%osSjWn1E~9sI=7Uw-XnW-;@N{G#U|UuIgL9~@d`SYR~lj!PV-|^WonqufJr5 z?FK(@J71Xm0BT?3EvqsHOGU+}K0Ky04D10hus)Lej>A{umy=o&2Z@bQ(mck8DceN-3{zD!JI22f z7D}xDAgf#boN06@;^OQ^UO^to1YN-`3|;9Jc^;Xu6+)L0V^u8&?|1zC62mft7wtb{ zNCKO-#FpcJ&2WIAH*hq!1SWph@Cv|`kJ2CVFykXyA772S3TZl_W0uB01~Lspn2xTU zx~i?`xmNILXDUwO{%f=46Jsu=c)3!msv$H|AT=H_@WNTSK%RKeF%q2`uTo)!7+PsF zJ$)u@3H9;TXJkPmYlT^=RMy04VOz%z#)*$C9o{_Y2)XKGl;GAblc96gqt{#RBfXF= zp0G{@o05Euor$jTfoqg}ix70+SWXpcxdFA&w8-q9<5U z%|Eo0RzJ02X!`aR>rqCvffrPn+wCjDtIrD$%?yQ)w>RwV71wSe zy;8FMkY0I@ys34;Kx(1ZjBDvWvyBY41g0J)(yO>n93=|(<{cZxZ^0w2<-0l&osZ?l zPn1=`^Xl zP{@X7T*T?v2fn94`|KIUD|JkDuxFjSEjj@n^evj^t(re{W2~HsDoo_fJB*$Ra7B`Y z*Z$YJm40C4A?*>FU<9466}021>T|p}drnzkEI$6qc_)p*i7Mt(Pwc6=YC|$x_o~B- z-Mq!UZ(0pxSIszUI!sE=K$KK30P>-+YYwh?5?&)x>4$91$t<6=br-~gzF%7Kbc`10 zl|y=IxN^Q&1Q}IPl z%L*Z7lu_pdS|)6LxNpHH47E%m?u`f1OJMrEzm7`d+-6=L^|ziYb$SlRvpeMMDh#nv zfhz{!*LSm*+qV~I%q+TwJSprbcXKC(I(#ce&V0hpGFm3WcV>a9qSRNH6f?{q-D>XR z;sBytDh)EE!h@diZ|5(=cK4TJ(7hZT{wfQrI@;nrw0)O>iS-C;b^ih@;Js74%tLEwI685TrT;+^xd6Mb zP-m43)lt-jpvgxi39`nIli!PwR(o>~VYgm9{KM+mAY{RaWw}>gS*fh!>J@(HXQ--w zD9+i`_;MOplmr-$Pe$*gn^>@wl@)2nuduSMuMrXll|7kAS5~Lx&#Ic5w6(DE6kda2 z;Z7_86JtY~_2A&B%E5^0cNJ*fW*yp<`xI>#HznZ=nhjbo%KWG%;I?W9j)>^q-ve^v z-vjaxmXl6!O;vIu&*l|U1W~nJfqvKptx`i3Hb*lAo=xbcuk6EZbgOQnJUI8-;7*Br zZY;2^lR-(Zor?jQ592LcY*&Qylj!%q>h>!CZ%XHF%c0Rt-Ep2Duxq%W* z=ct6vfI&yt{$48L`MeGdf?6rg`bILuA`b)oTQiTER(4z{R01Jn!AoNm_sH+ zLq#R761PRI`1?O|w<_rN)55MQ0@A-&s|=rm4ir3N7^nh?aQ#6~JuYjK_>1mO*MJUrVHO$t0pf)sZat>@n3qn*+&yU%6VA5 zAd8Dinod4=9F8Flt2^{sSXdc$hS9zQs=JHeM|z_F_8hnV>p4=;sce&o;%RNQlA;)F z)zdG39$T0MCVj{Hd)B*sU`xb>Kt4t z075v$N!qU|LHju88-}zqc#Xjji~xGX>nsUZ4B;$LBZ1P{erhyGT149C+vgGJBgM6E zTRf5AXeS!vdvGEa|GES1|3zG!s@5(^D-o~la&ag-8|gLNNbfUCi3cT8z9E5m`rh%_ zkLaz?=7fSY{hfuHil8s(Cftc*%R*Z=whLTN;>$w~xZ?BMmTe&Oh7$xI;z{^*JHCeF zO^~=fM!W`CyaJ!(##j9+J+&cU{>h@OV%#x=%afk;!l`0O%%C~wQ^Vjn&f#s1DhWTj z(dsR_6_duQ>aFuI7j&oL?uR^vgR3*qZ-i>jOYqvDaM@*WHM@vD>@J9Ng6(7T=vV<> zTY1jBKbXmeyUR4%xq{Wg@^XrU=8~hY zNz~OUHxci7<6Qjvo*n>mahV^vc*YxiCE(kHrT=NA=Ku!6DF<0poVX^M_&m z5da+L4huul8|g5*l})DDe`?CVs`TF_#)Qs2A(@my5iZVcfwa3*T~u-L)37iNl-$SR z{MaR255v|2W#M=}=P^qYY*b3+HRvg+M2)P`s@G6*)zjTl&?6Lw6Fh_scd$$NeF5Di z_&`?KH-L3y>HXSV`>wyTXpj_Usw`2RJ3ZEc^m->CftkSI?SskPAFF_!LOiAk(8K~U zrY9YrLBxP;e|>vG^R{M>w}+PW^8V2LCO{A$D`wOoz0eUHY!NM8hXRPu@5Px`8T)bR z!*rQx+m}p2Qce5WcNV)+=4>on}NeJA;Sc2mw!aL8W%-5-0V} zd4u0DGmMkdW~Su^Dx|peZRFomz)-y=@*O`3DRwb+ zK}Wo4N1*5Y*%p_$7iAPJE7Ob`Osnz9%QMfJx6UPN0;BK)DDQJj$fB~F0X{UBm#LQT zY9i?I76>out3gEwe5fV^*A~I~1DRc$^7wbQ#{kf+=9>JbtzxE&s>$DfYjHMM z&XA>G)r!C})-G|=ft5XFVz+Uf6th!H!fq`AVhL_vI2lx_w>$S+>G_?Q7Ed-1m{n(Q z=rt|Q@}Ac(ZVMC-I@+n8mJ!kpoJM9|8>|n6;M6)9kBD#-s{^3n&CG_D{O*A&ToL}6 z>_n_~b?mSf-Rrk84|O3PIhCKxz+dGcZ_05 zFPxcVljk5o5Q1@6gTB_XdqbOqt9`+TG5_l&x*fHF=Yjo`j$HItpW~rJPMx0qWm5Or zIf} z_xr2J4{b!r@BQ|qQpyOT@sx#6^9fZQtDO*+b*Db>=Z=9w{{jl~P-cQCb5Q|8aBgG) zEVOakCZ_cMEvwVRrH+1bIuC=bda@IiDM#TG3HTLFBiXgK)TpgKX)o7|)qm+<*j62z zt%ht$JG|JLC49z+Z*o%MG{sSjX?khxVo3{?nKp~&c=zsI)&U`X#=55V^zQebp@9MA z<4@_pG+nCAPcFCo%s>2b^_;l5dH%2vUXR{uCq8509sil}Z^^Eej`t$KAsU`r`4c~` zIYa&%TM$79Y(M%Nb8yp(Gg4a*7NIUI)Xir}q9Z*HLOQo;EL^CHi7nYYH!94yHCUe>)91Wf59&v|TAIhE=B_fOub4SR zX*uGC@+gd+r7#7qpR2=5(B1k%^d}|X(-_#)f8gKe{{ZU5zEl`S+ z`@o_i0FvM)n!O+PL^l#tVie$z{uDatIx5R)(mg3ff1QT&Q{Rv3C-pn7DUE#;^1tJNVnb8y!6uCvf zJ7RUkXZq`y4ZYuF_yY|w2KQ47CXEu23^0IRej^v;6xUf!M5NetuucOqpmyuZpK|D0 zGUmVY;*vnfO3P_?aJ-D%TyWdU{n^SsiRGcw3Jucye5{%EjH_0aX}XFic;6VNrIuJwCP}Bu3GaJAuzEHH1}4-4mP&f13+AfDu5Dm@7Eda(@w8K%Vg*Ql!X7 zfB8C8T+>@yM(Dwnt)sg0(a`<=?;hoX{5f9LooiHnHOwCGbcG+Ki};5=!^9an7{6g^@kGG&WAOP*>y=B@TdT7i%EA9a2a>TJque6R z#W)U(RTD3zO_g3qXNO-wxq}9I96@GD%KoJ?XIQmTrA_>k@*II=KWzey6ayjc`{qVG zOyCF3zgG=AZ-blt+Md)9)dlK*lCzgJ&oPK*Xh9 zWb;#T1=K3X^ub!qH|0@j;tVDHi^+kIS4Ns-PP9$GYyDqQ!+4_~CEh}MRi)#RXoHU7 z+*2Whix_4{f4h+ImhjZnQT>H2I4O2l6?whY;w0~vXcOTzD}n^xel#sJ zhZr?XLl)m(8lc4L03Iyzhg_BN0+0F}ahLf;8IplE-&(qSKwy)O+Q{B5oF{j&T{m1~ zCy!u*9E%AAKJ}@1P)pZ_I3afB2ZaA~6rpYX!)LQC|JB{kqD%+sZ-z8YR3=j{Bv?C` ze73&cRn-t+(5O~~Nh$o2=Rr*lz<>T&T|@W2XN5iaK&g<(OYQAfIoIJcZhhv+Ur|+c z7)aLQ>2(~(yG^S5TrI`jk`fS#zRJZ3Ur`~hO$+{4>KCK+m4eqD=8+Dr+}`*`c}uWjRySYRt!3|a8Q7!c)yuk$6QUISUX=#6<`+O{4e0oNh>id zvyN2Z(~z68PNE1(>Cc-SDnt`_QAsqgI_jM6%se|!<{G=CahaBAmIRV4ZK}?@h@EGg zOtOuTZ@XBfVvLVX$>qeU5@lZ2f7fw58dVG~L-+MkYiZPTKkDei%YO!Izg35xmQtN< z8Nkh8yQLK)_&~uGrzv^j6E(JS)L2d(jJGFg^Y>5BkJy^rf}Slzt44e?5H=br*M<`} zf}M|=14?)AYPI);=Ym-XX^iq7m*fy$sATMIYAuO4No)&fk#Lfm^3>euG-6*D1a)CV^^WBZ7Oeuj!INe?DDaw)sR+DyI(+PZF7P^jCs55wA_J@w`G$~%;+SbE*51q|+Q zcybsU8bWwDDJ3h@lTm*r279F`+11r0_QtxB-A$KJiiyRip2dT(l@8C!yb>l%4)cM zqV?nEyOFP&L`g*Dj!Ixt3qgW?Jq_Tp+S9^bF$`Gg>$9&>X=Tj5c^^kIbHZK<;X+Hn zc*~lj9ho!5gw@=#uL1v55^_I@R4OzxZE^KEc@Q6e+1kvLm3-c?#e141-gNO{m`HK; zdKQ@fjos22wuV6*VB)wGzz9h)mIlLX&b9+bK8b#g1 zp2gq#!lz2~G0ZyF+uz#NW{d2k4Pp`@hXBoT=)IxB@5~cALztW+Y-v*uVM{DXUf|-1nin}<%mxF65pDC$aKbS=-u9lB? zF}e=M&{4{3KP{m%YS5huT4XTrv9=g5D@i~&qvBXIQoFLt6e-A4Ef03~c{g9yQg;MI; z^4uPgrL1(c-^AZoq+Q#aH*%6Q_2V1~Kg>({%?8u${FBgN zds`vc5ZPOI1z(ounO3q@KLO$kZ<9xf@=cokxeT?6aO$Bf%cPffH#luSE6#1Q{UkZ| zSIp&UVJ{}cHXFCZ!J=uC+oof7_mARE_ZJ90{HMMGO|gwaXP*Sv;sEU|8g3y`J#ztP zgThtdCCe+*A5xM^Khd59TN~pfUL%CQuZ20v!iVvj0)6wle*i9HWw}t>8S>PtCTF7I ziYNveB>OD$t-K|N{>wmO_x`rxEd1>FVhaF)5Ij-^`XRj*R+O3SnR>>T-W!c+O*_t& zKh;#Y`^GRF4iElJ%${Kp{TZ0Kozzf02C!-C<9aEhZh@|?Mt;W&wvz&p?Z!@{o~NM| z;F4e{>}Tnk4wCZIF#8D007wd|VJXpUO@O*`xq0fDfBzqh6EE1h%Co-4PBPo)AS=s~ zczdiFBi1GBWh~azen8!URv=U`-cULuOGh0NlgvYZdf1e`7(?>T>M(iD`u*xF+4^~c zR<|0?W>gY?Zef=o3TZpWZT`dP>$SkKle=I;4t25ifQgkkyY}QZ%&k7%&ZlV(Xub-e zSHKCK1cQ>R-2LAP_4Y#k|%G|dGM5IEWaOmH8F!f_KIoz-l5M_TgCDO z;qXg|Yg=+j^1`r*#OFf%YJtS(F{**)66b@!Pp7)yv?=UMyRhzPLrZCmw>EPX2a{TY zq*u_*dTVh~E?NIh{7NvG?S^I-=?#6EY18CV)uL}|r0VnSu3$@g%k~CA2eXES8pTc6 z)NiHj&TZ9#jme5`q(MxJc!$;y^Wn(nWPivwd2{)CFKM67*sE9o2}nFOZM|K$JM>xU ziZzf(HPmM|cdwYQ^X{0ABTj4smmDlCG#G9%wehCaSK&wN2iL&!kWBt5xw59WEE0ZJ z5FUr_!f-~Z?$kE!qzCr`%J81vTN0y|5P_ouLDxd37VD(Xp$2&bKk6}6UUROv_UWZCqZ!@tP*x3HO7-Y4h?gs{I;Sey@vDOKR_TJf4fQ1Obt~MXV)PirU~O-AVC) zQ(8bwk3iDN=fIRf9ST@F&=sB~)=DVeXeqJje;5CuL2KGZTOCY$)Ex%;hPa0cp97Y< zjaG)t^z+n9;*o|hYC+IM>G}G2mj#P?=84}#JdbmQB)vt4>kGb;0J7Xi*n5XwY8&>; z%c1D-BZ?5gQ+WDU4l6)9x7rr+a(s*jwyCe(BBTc^pVlZE@#}6gfyDoVv+savGVQum z0YQ*R2ZhiPkS<8?y%$kIB!D0SA_5Y6lU}9wPH1AGiImWhs+b7l82ozaALgj;K4;bhf85y1S7Prdf463|7z_ssjgM#f0 zsg?%n74V3y{|@s|R9hM;kQKp?M+Y8-`)79J8%f5X$d^6G5V;?ZOk?NYd8$aFm&V2^ z4>x|Ac&A{et?11}qIk=~iJGHr%FI*ezmAQCb)y!x+f6t$+KXL+b-eW8f?b=xvHK_xK%@U9-Y$I=;?^>pO$4_}~1RXic;vQ5IE_;Fogy^5n!|T)t#zKO_m|>&H%JYf4xi@>s3v5SitheP# zDKF3W{<-3KUm>ZT=K1ShJb^H|m4?qyn|%IcJJuR7?B|x_d428vVG7`vtm+gjCc!l| zK9hBxnCuik9>Ts{9~n50SGd;65FdZ%C|56Jb+&f^B274$>^kE+Ec;udX-oIDw)UMI zPFd{y-8Wn~=J(omgs6lQ&Brbawm>FjSAgS4+_qKK^;~stCdLO?u$|kqTViG@1vY z6vyeI5=Isd{*9>H5C_|mJzPz20{rFye--GZUcgblp%T1|?IkB!b}&NT76+{uCJw(_ zF!$G$?|atlV~11En;JLbD9Kmz8?Kjor;jq;c2AI(rUf9rZo9q6Qs2nssVg9S1fM)8 zq@!oPFM&3dgw6O`?p^R=Yp62ne4|fvwsYIg(GE_yUhPQ}4g>8R$wy&~Zq(|zf(jY_ zW9}_l-Zg%cEaHX5hCnB6y*5}JRPUMO{ptl!GpRt97%5^o9j7h1rB)?ReO~kOB0yk? zb(5d1|49uDOcs%ZNvUd5Lh+@&yN4U;ol1gyg+B^x&nr)f@RAO|0iOeZFeQJFu@ACm;XBVvNR;f?8K>oop8Gl^r#4Zx|H_wzOId$5@@1#< z$D3WMsHQg4@B#8{iXC=Cn6J!-4>Iy*ne&5j{G3kA-NPO7>n( zSV&L;REleTgm~_>qTq?VzV)JjgoX zSxb*}a-L3K2HTtV#hGyK-`krEO2+Onoap`ZW0XOtK;mJvi7GwS0$B&oeYq&mt1@4e z4N8H(AA%y3UVs8iIZPgO6DB-#;kJmEGn=!y)3?fi#weo?D8idM3NLsN@mLcnPFnEIQaa$yN&I$XZfL_s+-~@ zSN20xs@+kRcCvi?4{C4w`r>_n%3;YhUP#^FxOU^s>*d*+K|U$Xx=b85Q}O8q4(xXx z4l zVz8^#n-#I5R5C=jJjI(mnyQO3_rK!z?cFm7LpbC-;N+1(FMzJ|(*+GwfPd*}nIHKV zBlj|p4QDjVK4lCGex5QYp3K?~-(A34_g+oi0s!kxAl@wbEe2nz^IbDIsOpd&I6V_N z;BJ)l8TAR`4bSx`kiNY9!|Dp&gUN7QhTfnqvhO`!l)Llbfg0btcb4?-i{}}0h3pbUY*wp&RC5*;M4i`y z{7{RDbgh_d?M&mPbIO-HJ>A@KLlp%OTuk8yH4^uUnq0!tV#k%Mtda`w@T;7YCU34z zr;H5_D>%TQ$tlSr&8H`^AjM7DS$Sevz-iFVc?zt5+&D}0H31b?$|;VRI0}k>F(}OB ztv5QIS<%x9qu=g^juRsqq6u`g+1xXXs(A1@m(uUJ0co;y#EhVI#24)l-()V_6Y zc;v!(6oS)|zTx;y6ZqYE$v{gT|2b&yb-n)==1r=ynz{VhGRt21bGJynzZ}f@h|cRM z^T`1+bG{HPluzalVZ9Q+Ipm=)NVOavHi-O}mN36JY(!S=17x>N ziDzxN%Ym(u2khnduAyi4qp>n*?d%Gn$j83-R8L9dzuzDOl2Onx6U=jk&&FJ;rx(*y z**BRFy__Fe&u0}{{)D4pTOa}J z(Tq&~a_jlm-{2SX0sYo!RE&SqbDz;T<97S09mP+%wC^-ASXTTwDK`4e7AtG zLBa{#^5THRgfu){M%G2=pD7&9!&@A2go#~}736glu`v0>TNlL0pkCaU%28>;dLtS( z1sK%e=jEt#6h5MS+46Y0D|?5g_)`o%pt}GbJ@C8^v+a{-G%zx^8Lu~Y3d^l$%uhAH z-n#W=cs@tERpk&|(wj`ais^@ta8`dBqPoN&SU^k7XuI5LI!JARa#pL)6K$~cz>&V= z@*4@5LgKAD`RdBb2ym;E?m2No`}zxjmN;p9Sv2zawp<6@_QLN6vL?l?nO73U((4FHK-ggDQ?MmG5S2!)@M#G3Fq3hU8nmJUq`=2SV`@#)ykw0 zdO*yq3=Q9t2^;H+Cz-1sLKq6aMDuFu>XZ~N-T|bm_(iHsy3eHCc2^anhwuqgIO?MC za(9=O9(Ql5O+2HKSh7kF?HQ1H(wtH@jbsek{z3b(M0MhMnqc&UFK(>ye}dK-P-FNcI@K>(?q z!85mhng>o8M!nMj7ztIaQnV55EK^=rpICXnVk1}bO-~L-hDpjw;C~dD@UaMYN4|UK zz(`4C>}HGOt_@NUxWBY`7!VSMN9Yo=9T)#$ChqO=_xXB2Zv7;{OL5_%qmkhQ9!X_e z7{gokD~>KzMIrM7jrx)$ONGiR>0)4oXVGVQ$2lLue>0?sD5TqDLYcvrYXt{~-57kM zOU1Zz)=I2Qh<~Z?uasLHDG9_ak~~x;Hu`<(Ho$*}0lTB0l%nAJo7A%Lz3wIH_05Gf zO%w~Zw?uhUj`sNdBh)hrD9Fq)vsn_uZlMy->H^#xeR6RsoB5n3#G4DB2|V`+6!T^m z$XB-GFnQTLG?d19779Vu#MnN4w?f4pY88&K(u%r{_=JzvckcNqGjM9L0x>0m2j6jU ziog1Ur={-{PNP}0^(46%kFf77NwoSy3v9;wx2y*LQqC-+5?MGzeHP>*6mP*+#W!RI z>A3~?YassPvv+I|R?mC9(>G%}-YRQFD7a`vadi!X9^8%RV^zCtVHjiUZ{9a>nbx2v4uhD6a>h<|y@C8fPcwsz0mpIC zh3pfkC`!I6eMi~7@bo1Xk!>z3N<(dI46+IG+bS^Mrf-fqIu2@2RywSHlfIm%dthSH zxj$P**2E`nyHPKiYyNNUq2GV6GBKoJOOoOLmw(=#b=6;#&zgS~@Lu6VtucG<0m_98 z5VC%%Tj9U4zhYi0Brs?==|_+bBuGEz{d%2~K63TltPl{C1E+Z{!f8{U{vwXe;!I`E z(5~X^2D6U0IOPUHl8bU35|?J#WkfSIA*sW2gR%uOV`e-u<<>Tj3h6Q_=mi!pf-=YE z_UOsePrWkAU>|mR0d=Gb8lUEJ^}=t_@k;w)yeN)P%s~BD&!0L^Y8+DsYH#okRVSxr ziv80!h@8fy6VnO}-NpP-YH@M>+$CA8*dW4`E{#E!aJi+V=7;kc$9k5R93~7ynH3Vl z!~N#RbE<4h;8Sesc^tf~w@n9k1t!6i8Rqa)4#eqIaD>oB0)=QY{LPtdxdjmIz|Gw_ z02&pWQHO3Yh^fwi@SwhyZxcq=g5w;KP<|7P=rS*)!`wYvBsOJLY>b#Cu#+f-_|AsU z+8XDp=Bh}`0ZFY4wxz}`uI157!wU!O)6SelN4q4NG=WN?m?d!ENi zI3HAC?OZ9>WU8MUClA_!`yZYTO|Y9ikAP42_hw3sHC_deB=_GjL9n#U9=O2rf4^~N z9bU_xPO=3C$vV@T$^&n0LvQbfs)nG^6_RCk!UT}`G=)>pDB;L52^ z;52>u*!_+$aFvDM=FBPAu>uQuFRcmVwPO6 z9SOio0*t!3tWAkU`%N6hKpX_XbN{`?@IPHF(!JE*`(s=Efc)5D>c!ous)zX2y);zT z{dlBIO(NDn`^)cPYI+y0>yzKb+VFpVm9^q|g zuZv&BM8Y>;^OGOZ$r(K`QWG*VW$DkiM!z_pw6|T(ZSDR%Y&I@~A3qF4KOFi1j_~bH zloet`8KeVBUxwUmXMJhQO*s1dxwUi(kOy8Y)@iX4adWyPI5ji@2~9Y|1oEqN2cNOW zE0AsC2ZB|_7yWy>f36J=L_pkv0xu5cHjvk;3!ViG01JTF@h3Oc8;Q%%1TAbXYRn3y z*fPK7w>q4VRI%__NKz`-=vCkL!U2P);hZ+-VxQUCFW>ZX*W^!Kl-sx+hTUc!e$S#`n2rsQ~Q%+IUNOWmpulzaQb$K|U^a%*#OUJI$&Hg^N4sYO`N8-sn&DR1q?;F@ zG(a!as{s&xXBki%5IOukQl1M0sWZs-&R=Q-DL~*YT*`Bn2E0AX2>yhfoM0&lCud~6 z1x#zTj_LONX>(zRjJHZcf}zS-5ExbhPMO}-`-ea%eR5je=c`DielX87?z)0+$3HM| z-sbjrpTe= zz+;nAaulQlT1+#P`h-`X0LiM*+TcKbV7uiF9yvu&eta4FrVcKBc%w`9jkbU}^o^DS z&5X>%-~Esq{M>eA%z_qm3@jAp{uuB0X#IyeB+H>wl+zzgn%Jtp;#{p^C|p^HDeT~Uq-6eTNQw;iF}Io?7SDLP}w}0V(oKTnd84g@YzUx z3(FuVug>iA42*$eO8n`87gF=TIfwr%2oxp8j%~MX`dRjRKGM$ z>yzH^zXHCsXlBfwK9h3}gfR3J7s~WjTdf+~dB^vtkMH*SzgkrfiDRhSx=^k)^S0T6jCUfQ~b7lG)EDzd63)*=UAToFKa;ExyUz^@aq9p%-} zqt3sWJr6QMu>Sv-Z2tx4tBD4qsu}gBL4+(G&c!cv>bWJ|PVROmlh#oJWBv zWys~B(NR^0?%1ePw7-iD0S$q1Fqf>Uri5phvKk%@gZR-5uXYj$L*q!Vu&lg5CN%nf zp?KX7JkNL84=)80kfg90#|#aYXb=&)Jm_p;&n~ZV2Di4(cVHhlCXc@1u>X4FGlX1P&&-U``q87|9GTvv z8hCnj)50DhDWZTN1hI8tu)riWz3ce!EZwZP429`5BpL=!wjqeh%RhY3$$BHtf!^#c zTBrs$6}Z-5|DV^IOwsZXODPwOYRTN010eo}!_#*RviyP837qVpab%_t8 z?KtZ(yBO&4fx*$iO9W3BXuqd1Fi8%b3-r%A?{}16kXlyuU3#~q^EN{ifRX5PHr1_ zhZzo54}S=hiXP@+ZnW8d9dv)RpsEQ>DDCL^Ca+q#Kfh6Ay;{(CbfFRuQkGl^u7Qj8 zi)9$Mrwcj*SHglz^-LK5pC#=7Xd`=FAR)pv9BydScJ8nH%Y)*KTZ%Ik0&ZX(hj(!d z;|&H`>lm!lufn)%bv^N?=9JgpBH7cN;jZF-rr{Kw%U5w%@!FrNmu2xXV|gm225tVBxp6pd!yc4MyS6UnhHNlG`O{?%m~*vt286z}~o#5Qh`w11#xJboh_Wf5V?JC8csvqP}Dp5~t`o}FD$QBMn>3oxU&TAS&zy?Q zq)l?SOLqe7~4xRt`2 zy2_|p0&m6|4NPgAeqYlT#VE**%ezCa6d39nn7WK#3)LwiU`iUY(fGCFC_}08(AS~t z0jW;3G z%d9X;p5D$pTtwu(+d2ylFuO>0h~9_Fg0QB&BRm0sr?0 z>jrNE+OLv)PknPto%XZN6%Cet86BDRyy~N6gXGe4k1lOk1 zH)y6Bxb6v+q0_g&y@&tEJE3~)TK;=k3GH(VK{{i{L^_kbcPj}o?Fz@Q%D?)|Flndi zdWT^jtY$ALoCPzCtm(G@UcXootM5SBaxAWq0rJZ{0OczaUeJTPY_K;n(oliaW4Sfc zzQ}5b!hgzjo(@Q&F&;Lw1ioOx%Y)3cH=r&A>WO71I|(47R`wS2mlrZJ=j*pg-J4j? zD+GvuM({_ppuNB74_8eHZ*+Ba4M3JfdBxMl1~q^P>j^iPAa0)7IpkkN`2Wpu0CyF# zr|8xKmHFc9o8I&a?i+H|W8Ft8LhUV7EeRLX%d8wDAPWU$DeG51S7gn;ZM74ASjn;OpO_wTNR;|R4hP>A+Ab~q8076@49JLNHq8dlXFaswbw9$!49ru@yTRIS zXX_Hn)(*Y;eDIydm;O1565fq~MS2Eug0>4?vpXx2oiGM?uRM50r}6Ni?~fIebg-0m z6U%;|&0S{{3$m$T^uLbHht^1}o}S!OL;`J8Q_?jIBvMMRwNrE$&7zbONK{c2A5O_=vyrn)v_!Qk{2O#xfzqr{} z&T*8P@(pwk@RY?Z{pj#POHtwK1N576Xy8Y{!Tt;Jh;(_S0ckvSlOt2ryjPwQqm8oxE!P2VSFdoB z{4^dB3QYu_`#n0_1cr`VXbu#MR|5C|u^s+vrLb6(`$%3?|H@TWww$}vqJS6BT*LXZ z+(D%ztnzaZ_5-Oa>fxHbz(}0neUHry%*HsJSd6MVrjUnIAdkR)x`5UkrYF#RK+(Uy zPgiQ|)Bc+E;Lh0hxpy|jBSZG8BeQSR>Y;1Cnhd~rF7L9tk@yVzx>F;Ir1A`h76cs= zQA|#mIw7xg1Gt>Nb7q7}b}k@@$EED}!WP^nZ@zcf>eoZi7zQW1AAy7$4@ zEA!@cKn0lTWD~OWk!{htTMO~Au$PTndXh^bOUjG&W!g8-CcT4tOp;}iU+9o)%XC>> z2vq`}`?ETi2<-H}MD}2|(YBSU07x)Qsea--=zOos>_{O6t6F?cf%RY7_Is|MV}sz+ zgnvSiG3&Kz_lEZxuthxlD>ex+WeEG+gFtL578bI&ar_1|JV86GBTYNg;69~A`^%=l zukTeV1}zXaJtIC1n($!FrvgaU>g(jzWBrpNCnXxLs~W0~`hj{fKVw|-32+ojY%bNPiN=f+Z=%~SdJTYiMAdbIF zC?0%EOi+!OaKC*-f0|b}CY7+!4;gRb3<2#|qBa(ZTd?RU_t`v5r~zmf#@xMU;Hy+% zTq36qarp6rYNVwF9@F<-&N}jp@0$1EA9iMiL(N>2@`sK%`vjJ*_~&$(Th(J-M=H)e zkEb=SUA@)kcO=~@-favsK&a4Od@ug6X{ldi`bTA3b{rXW z=^Y||%v{Hp*UxT(2k}ul@s9JRA^d%EUICpww!oa4`it}S` zMHi~oQqki_T4<_ALbi-XUOA7Mn2j;gLYGcM2gb}tXk`?{WZr8XGjezRl+-re#-FM; z<}Z8f%*y-L{un)7Ic?eh0F%-HtZP%dF8Tp=o&@+13=^;b@co)i4RmRJr+ly>J2bq}>fs3k@?n{#>~L4kYkH~7>y3|AalghudPNHM{wtGp!tI6J zm-*qKS6%6760L%}4{=cek9NYvCa5vhJcW2Ngii1o1CAgxk85NZsevk(guHcTb^&y< zN{W{rvrw)2Zm=vHS3lx4W=0x{fC+778t|c|d8@^(_H0^EJxXY4eg;m5`DBd7C+a$b z6i|9&pIJZ(h;`m2#LmX0nseb3Vs9E4KF|Un$^6k~V-uf*;Qkg`3$03>!h&;I!{PAf zCjZ_+KF2Hg7UgbFR~!2FIK)BZ!D-R&RfnbXd9)}qTHL*sf=N&O3S~g#6vrb62yORXHjHZ=OfWy7wiD%qIwT zyRO-x+P?L-m1zaNSuh%*CP!L61J5}5>!bRVfT`MuPVxM@1)z(qKN>FBy=D#VZraki z=B%MS*0cbOW563<-)QbH^pHzr z%v7XR-AP=ZF>t{kwGO2u**#3)f{~X15#JPgdV^H<7*0GgItaUF77@QRgzgs0)*+u8 z-<(zuZvD zjvFK$>`l1f9wiQlngFIK&3k1GeiYu=_U&hLC(*N3i03f2-+5xde|>bg<8d_5Z%>w8E~@BoYe$2X2k;+ zL8E6+R;B_j{Iao7i(#V?_^7lLgxjqW(+&uHzIuT3^KCPlW!0owRSNaLGH_xC0q0jlG?_1zX+q+Nalm( zI17~tgXCMUYNk%r#~1T{*DmnO;~Cv~g{kNibk7yi%GSK3_=sqo;wpb;^GD-vAU`H1 z8HrT5%Nl$`Rbswsd8cze0HEO0mpB} z+7A5peH%e`=BQ&W$z1PkS>o6szaPFtq^G~d_$LWPw^(FAAX>K>p0Xm%CNpNOgSR)p zXAu1&OP_=r*(H7Ct;!f?(+ zk3DO@2h#H+%9E+t=h^Ckhwr3NHf=Fs*)K{TwcF@i#R5P0A;z15C8q!whj)I?Mg6T zyt`vxh~x8*dJF*K%=)}={*^~`gUSXSd z@eezwrhn8o0uxwgK_?e8YFz7oPFB?$1}dyD6QnXB4a2?L)e90c5s-xHyvww)56Zil zpPC*~sYvnFVZ!s@1ZlOaIB|%ay->e;fzB5g@m{}5SBom|9a@{4FR?{Ll_!Ijk-(TQ zaOCqyR?Jq!oGizY&NuN&!fpH(L}jR9awgaR7`8&$J>MxvUltM}fCz_J^yR3;r#GTKae7wIv`G5~0ohszXDV<%%}ci!PL?-X($ zHc8WiJYAEDXy;6q>u-y^Y2Ds-gRgM--mYD10)})ag+YyLJS6r`M>o$Hw(?HT8<82r zw0sDVy2eKizM}w)t+mV@!CT2m;qCrXLJEm^*FMFnZ`po7?2>!+YEkg(m(kmx@LBVj z4sIZ>Sd4G62#CAVw?YPk>gG_vIDxTG>DyOnpnIJZ6ZUb65yw)oe)M>_+4nFQ)7o9E zNJ-`KOx8%+ABf4xzMl3FY9FKg%mJc}yx!kI zkJg&+FmvXetF@t7-Xs&YHCU}~XF=%E#kE^P{ss)s(`(ZaXl};c8VuR3^oy>K7p0Ae zD_2#*o{^ADUQ*<_xGtRDMW`WE_vX@LRNKz@P7TKCcnUUFt2L$a_Uu)mx@7LCenuV| z>{KuaENcZGCJi5wYBbvA-IehjyMv%m#kXL+la#|sw`%o8{CBk0n|3S}2CG6;k2lb; z_5PM9=eedCKbhL23iI7y0wvCHTV!u1>$*IEQw6L~Xfi|ube-1@8N9@|06$b8Kf*Y*$zS z%;W}=fA#RQdC`J1Xpg*0O2|-MUcOrGe|laErJy7r?KG~JMRxeaQI2J_9i7fJlCx{n zrmz6|p;}``)06c}I^daJJo`IP5-eF?{n&JjHr)5I)Eb)E0c>#pAu~#lKZMgbMna}~ zHLoplji-!_IQ&UQt)?IH(-aZv1)9JsqH9&cu#c!=Wi8DzERubhT6j~Kw2Zja&DQ=F;KGz}D)cnfa*s8GpoD;y(uS_^;J;+MTUKz8k9 z#nF_EpUem#Sz-%7iY(=sd@&ac|8<>iU?hk7o$~skzZgo*T)8;%q2-`Gq)afJ=WK7c zeMDW6gTDz1g4MYwoMaf40tB9F?oH%+Jy$M4=v(t6#c{Jf=V{NRRC9H;*t?PZ7o}qb zW)*$-tHjE~TzGk+fOH~c!ved?%pSbs_5_d!*r?UGv9DQ%cTDC-8#TQRZ*vRtXl3CU zp$78ZWPhW-`uH7R#U`{t>HE+sTAFvLkO1gDw?X%}ey!50GiA|p{0)L}d&?699ctbN z5k$$tB6;w$>~Ix_HT*zu7RU?Psyfn!?1P5ykuqFR{nE$Fw-vVw?UBl1QMrf+{+4@h zFu$5%-4eE{PxX&}3IRhrzw44dk5-q43VsJ$;3$TKF}V>CT_A31C@ZL_7}&+)Zc9_X z(Z>O-2(7%jrw2PAtb&SgWiS8iH&>8blVnmHJ4UWZ=8AYyg@n1Eyaa&4@EX7SEul}8 za%nr%kwr3qFwk^WjuN^gr*(>sqc|(;+EE0S!-I%d<}Cv;$}_n-lxE}D`KH4Rg`7J0 zPf?^}rRU}0Y+Q_a(OSaK5^&oQsc}PeSNYfJ4ifXc6-v|xEQ4nz`}2U3$xe#z>bgVT zFE;8gLql9h+C4;CZi7jOxkr9AjqWH@4kh%uv77$8u-MX~56L)w1v>FmeZu~`=l+kR zIw=jfE8r=m6h`R3unq%YFDW|YM;P!!rmeaJR%4d?Z#|bVeH(fZ=mIbq82M@kw=q%Q zu6HbBH5aB;y>-su#3L=ZApEI*#AV_2QQtO!D6$kGA}TVdBHIb4Bpp&T%{nyQUCNB} zJ^n-bAnEfcmc98a2qo#J-v$g#+Y@1S>NCwTh&3M07Om4|1;SAM)uq+|OzWlqs%@xp z4#9~T=)4~Pa8!nhX7v6>DH#i|6&V*m&vU|0BOtYtPEU-+LZh^Aegt=yhq4E{X+hQI zqLLcueS_&UVbjt65s=U_(#Pp@m4sYX{@pV?k1w^0hQHKb2;r83#U^fC15i0mK0t~OxYCJIgHo!!T~W^h6^fpf2NOuo&657ZvH*}w zFu&>gl0YOmpC7v<9eb#(h;1>#=`%a;c7zE_tWb9&fWgTkz!tw4ufW2d791jLl`7@m z)Hm^JiF8OFJ~ZromSQU7`-Z+Oy!6HjGg8R-mQDGLNb7W@4$jnNUbfXZl1iO+MUYiu zPC9iX4f{bVBriy#pH&FYnw2J4}3yv0Ii(H*70o6wGjy@NA==DyoU@p}6QUox^C#G;88R${BV~mgD zM+A3F0h$h9BtKsM8$1%o>y_(kOPQrbd9Bi5g3q}Pl7IO5ySXI$rXTy6fdc$g`Cmst z1#YYnv`sMa`WYl!2<|(3OYL2rZD(ESmgvadt2d-p-lyW;0Ic+S05?`i%`9_N5(^uk zla$@a=;k+yp&Z-!ZA9O7(CzHSPX?{t%f&eTs3_h3_7f&U0lhrM5GteRuP3gZ%iTo) zxRFjka%8My6uc5yaTg3Tu)-X)+A{(W>rO9Zt9FQQA=GfDxvS-Fhp7A8^`eDsz))1M|58mVQnY6^!)o1ortQpAW#yNl#e&C9lzOnJx%(EDWXa1?!l@AM- z%Yag#e=iEN@mE!d6>|CW)Y|~~j`oUp=C9J+U2<}Qcd!}TrLXLfy1n@AaeF3wuzgy* zYxYl>$WTXgx-oO2dd9DLo%w#{r#;u)nycc|*;QTLe4M;XXArU7cbpXO`r>BQj-l6* zED(I+y1ebi#o0G({7lbU;b992|%)B zq|zR}N))XYS}C`%El8ynhc>j%{Jm|b2n~m{1@B~}wQ#9c?*#!%lI4TI)?N{_yOV3- z8j(Yzew%{Vs6g(gLRzrJFDg66`IGzFmzD1C-T|1Q%nS0}l1RVwJ$~f(U#=D`iu@bz zn`*i<%GS)JbO$d2Tj^;^=(N{k$*KLVFw9Qw%i=d>SFJJXtT$IZ57aEh@Cm?HkrjD( z*PsXPGAj?C1rK0h3e0g(@DWy(^CfI=mLrL>sZi-I0@pNCUp7TuRDtQ>2}pEu<-PO_ zPgmKmQMrX8gEYPGigN`z!uFh|?1yKn*<$V(c z69c&2Ryenp(dI+`Eqjq}9jgcsQ4tRa+#5EG#y@>d&SvQA1suPvVz3?Lfw3vbAOA~- zEm%?&^>FgqSQ0@v?(8+;HrZ5%eGgPpS4MF!vYkKNZXHgBd(MZK?K#JGj&8w!SjTq-+FFMWU3q4%w<};~9mfXB zK^~-kgr!->8t(A_`Bs0y)3r_R=0$BrS!Jsm)_oR8c)JvQrv2+hiTUBu3D7Tz^dtwy zsNlys%Ee7tARaDKR=qvBDv?B&%FG^C_=tw#=3j(2^>9+_A8Hs(rRM9`W#pEMV8FxP zI!@FA$XH~Iz+6%(r$PAe09j+D5QLCbY~CnJ2C3p-8QRn;$33UgVtt0_X&xPn^P@a&&N+(*h5`x$s*aC!bh`{GmLuEt>Gl|l| z#MrpGPDgd|lYyuUl%!4Rpy_4?R`F*D0WCYB8m{j>rAG_-`Lk*``3)w^fMmI5X0?#8 zL+0Qbs2$IKh&?&L4Aua5UdNP+00)jvtUay;_&auvPmq{dCqXeiBFLoki>Ti#EA+}G zc-Ysi1SeT3DffUxf?`1&vN*vpie} zF8FN3W=C}E+>n-SdBlE{rLvYl1Y|N^fs?ULK!WS0BUs)EUOE08?~DPL)j_;N;tJ5& z0d8ZktJ-6=D+xiYHDf!b*D&A4hH}=rV}YM+lw2gldV5sZfNIpJ0b4x0b*aqNmFV}G zq%RAM9;jne@~A8O9Mb^ayHyKslmnx4(V&Ikj{EdqAHk=)dKtiH%Pt=y7x8x4300OH z^EI|a-<*nOpFTiVfFi=%<_v_Jnz>?01QK0s+yf`iec|74mFZQ8-)DkPHfT}VpB}&8 z&NkFr^U0;t9y|F-qOQq!My=X#RIc>(mg0c9yu1@v#gbWTOv*>-uXTdT`+T096~MxoIwKYxZ$Rl0X++cB9uZ=TjozWWssbl#Dt zBhayhdLGX)cRx%{eZuzPB}8LgFivDZOXS!KY0N2(B(^)XBn84Iu%^J{{TpyM+z_g% zuDK19$;~VPKC@{!U7)i&1;?63@s}?mEOEBzcJto4HPK~L<3GpIAMbC(C?Z!R8y{RP z=3KMR2c=HmT8I?Yq;xAUsY&6h^hcL|(9BBT!&3@6NLz&v&|n-N}j zYizbOZ|0)e0X=%#O+A36r$1zuK^A@Uw7Cdyeo&1p1Av_w>V#M>p6T+~b@@|{Y?@u) zsTI7=C5goV&|qwt1NHF4aXHWrHl=Z}ovto+;uW@w%~e}32oK*V^KEKm!(g+giO&`* z9I};LbSm|sMOPmg^vvU@{d$DbU$E!^`Ju_iEghj)1?1VLoo&Hz1R_Z3ivr<%Qss4l zIC3s+*rXBx98fVq<1J#|hlXN1Fi5oAj|tF)`47t9y(vLd!#|fnmi@h9J!F$#6%wgE zymMNBF!s~VTJ74;xL-peEOZ-4kKE}vJP(~84{Fp8msY)=iWb#_Aq3smmE#V)DoH=H zUA>x~loPLT|Nh~tqM}YWh=1Or-%plJUs3qag}|$dI&nm`b3=a|ng5Ft$zo!)On~e# z`hn8G2V7%jx2Qnt8UDtle^v8Vs5V{+(^dLqX32vmQ?j`TTSsd5Jv*>4pytqm(9Woc zdGBYb=^=|j`+tu+1M}l}{|3m2J&fQUE9uK3iy0`9y`Kgmdsx@9!6{NZ1$Y3_O5?q= ze%v6a}K(9Ksf9SGijh1Pc5Wl!5nIJB&61D`^WB4ntG@o$%T z2-^MPBEGVIM2|ge$(sn$r}+8qEbleOJ<&`HSf$>E`L@Db6$5~~Bn{Y-7Iu>XJbKA`To@~nMz!M-_Gw8|nIQ~5>E3UES zFMz!E<;$o6&<-0jeLS4?-&!C%IY3rt%X*%k#Qpq9-{I`3L^gX1NbT9zQYlZn7KO%2@E%o2QB-geN@B;t2FEQ zf5NZDW3ohTo!q52u8yz#!CIt1YYImI!hYVDrIph`MLO?%*G6qgvdP#`^Ly;}3mw>l ztPnWltxRx9)bT*!yW`juF4^rJp@W;aMS1`aM^rRV#a)@iVk!c#m#?@}P1Lih;=~kA zKAe)ugUGOfD=P`ljm7I+DjV9{0FP^C|77*o;Q`Gx*ZXoXg#)j0sFfqgiOUxrpdNxy zh9V&M(!iuX(oD(>=ju*qhlH!DSrzrpHgR_o1kc zM=x}F7H_JFer4W`X(+ADMlxnS>yqQJ3`vuVPd>oG!Ot?|6fZ-&4P*+p+lsx0jtqHd?$kaqRn)jV8W#Z9H1+Kfm7b5TG)3^>v9 z28mNDc<455v`WA+ad8Y6N@uHnZVeW>AoRK%YV&a#xN|K)ih^6d|Ip2zOKJ%1T5eW` zkLI@xokt8leSVW+k8I5qfY8yln=dh~DzgUyONYwuoC?w&i0QdY3U4$+VeGh^RH@vU z7!l;oajqHs7iw`UqdIOdO6w}J%ptW#Y}K9XrH80TWCZRKECcxShh-|p;wjEDB9f^I z$P@$e0xDpE9u>e5*8C8<&cSMxGMpVX<)6EH&nEQ#u6~-;jM6f42D?3OqyJ~^q;Rj@ z2p=$ij0Ww4T7mH&mTfL*hR}h_{F)Sia77+-49TAZKH|mrb4?=;&Xy27eu7d5)MeNnyBOX#EWh>78>bJ7YUiX=TS>Y2l|7o#KVyZQ%Dg(J?9sp~1Pg z;e?w6KTURIe^Z z%o5WdH`7|(ZPxo?w~PqFADrVx%_+MGuQflfsFtf7Ecy)8f7hVmXUdx3Lm%F}mZhVr69z^<^H%I?5cV|{20S;6=65e z-JO*ZK+Y@T#6fV|eIMc7gG`9-(N%XPtpg0xPjFo?JIH zT_DiRice94#vq^jaTb4z-_4gBoyVHt9is%!9dX8p>_UB+NPyD}c zLv@MEacyjJ*zVUf4!FPj?i%{+-c<%8OKY{8)h6ZG7{A+k59(w{S83_b9^;x!4Xk@< z!T{Y?XeslaBOGadetWMmv@AM$^?>>S6@{6`uF_WGkUcOwEbI!VTdpCuj7}_FRU7R+ z?L{Ay*9xNs`)0S9g6*YC#{S^&X|~tv1+&IK5UXVtTRCcWM6U!;`Jgy3>WBV`RQJ}O z9rW@#nG^avR$e~~=u<#nn$-^?XO@(r3uLNa0Q;X)N{YNLsHS|fQ>$=a!nwukwGtIq zIoTBb_(TIc!8-sNk?~rudhf#QynQJ`Aw{1MmH=BQa=X$CImEV@k6*DtQyb}94x0g> z12_#{KCLz#cZ{5AL_VckstB-}+x(abRBs zI=m+}XWvP0(qWNG3l5iEAj%2y&uWlu}c_7yKpWPY#%bJve_8- zNjP$l-h3-8at80!u}RX^zMpVA`hC5(%`)rFRexXeDW8wF!9PAUMe>Wi6{+ zOIjq5KdqsjqJb04@k$+8xtE62F%L>Tyc@qV#c-gFYijT=K>*ij7On;|X{*{PJ~to#lDd?#Wha6hf;x?b^4#pJWi%%%M>ERtHP zbwLfq-8R`9z}CN}xL8ISd2B8Xz^Q)``%3KM;HF0;`%9jRoC*SxeT|tH%~;K)y6ols zavGv~(4epNtht#AcC9a)L45d#zvEkdz^+3Q?}4cgEonv_Vr9Hpxw$;c-Jhs2xDdzQ zyXLiKd(G!Jms0g%EgReNr-xl=!kH#r7Qr+y3WT@eR?n{s=uf?d%QfcZ1+=4YOKAN+ zXS1l%M|Mjs`Gm=8-03ehCqznMK;P`+TW`#fR(Z=*2R#^ZSp`i!+EXO@7}Dt zvG{Mzef-q!HGmapuA+j902R8O9Zn{9W77Y{yg$4^Bvw|)CR=5{yyYQg)gosk=99w& zm)(Yiof%)l(s2%jvRD}Jr{2#cpn0bvM*MXg)@ekLI@#3bBYnVrg()Tm*eRR~WHkRX zknP<6BJI87ss8`}f068Ck8lXd$PD4wD+}2l+a)|XkLTllf7~Cp+x3V28J`t$BDP?usLf7 z5eL#^oZmaxJMj3|lJ^^)n?+m|=2phDePs;CXgvbZnvqjm*ih4@K1bpDPQkgb+lmTs zA^MaCn$WgkYH(i}cQw@0#Wu(DEP1K!eXyqUbVw-%K<1vMxZPLfnPrG-=`Ow3Cs1-PSEC-l-!4> z^vI=ZY=|GfHmGN=z=6Y}SDIsH$4{!A=&W+I2oz-Na8|4)88_|SwB8Z}o@(>--RYOI-HlKa=k zmmi(xN?e^22256|klo|X1l!{q^$fjla)W!U8ClCs!V;q`R${WApaix7qL-aGP$7MY zjR|Z4lVuEZVj~KVM540cAENp@TE901U6Z|DdEE&7zbGRj(g`;9G6z-5C2)h`lk3tu zT^<9ZmL1*SQQG$)>FOl1-&L|Xd3J3H8zhUtS9Ee;y)*(!Dmd!41J6y@ z@OV?^=DT4EnEN(W!`y4LZF#~8sb+ARmW}8JX)LQ0aVN0V^tCAk@uqEX(*8Y!{VJ>$ z5{$so9`mSHi>itRoyg>-W10;;PHe9?&QYWPsw^i1R-%Xh=b7P(; ze5Ca_P22ad72tpC?;Q+c?0EKc0>As~yHsP^9=t$X>LPycI{|w-4U`6ZvZ!Nv9=mA3 zDFaJWKRwWfp}u1&2eAWA;k)ihW(Ery z(|2tTWUmSvKKalXxzE%cP zESYcDu|kz675N+N>1^VuRNJ}1DYNuQ_m}2B*?3?cJPy7zYe9)u!akTBjbHLw`nt0{ z`{wQTVwlrp8iDY&c`yyU>II}EzsR$n02Ru1nPKMI(3-wz-xIdGIbo;sqlTl~h zj##;g9qr_(=UP=AQGL63Acmsuwdfh1*8^KYca7Au3z}YVS9J4{VA_78Q4g8y`CN*S zIYi!Z^J?w;_qv+TpbNem1qRy{oIq0mEX9gsQBk>9hLcwDRc}7ePdz;;sFwgn;vg)B z1WP0Wy5-VePoLT@VIm%Il2$z!868)5-Pr-Skq~DPMl(TfmH?G#N={^Q$RA68?)>Sg z@8JrwP{1r*lYBUqo)()`AF~OeC7j6xIojjnyob*#FRLic2Cehz%~AqyaOVkt_^IcWX?3g)gXM=t?H`p@v&Z&}KcOUR#d;*;-G) z1)9d^SX0q000bz`uesAh0`^+1oN3R{mElo!vD#V#W4{mxV2?R{I5t9sg;7bZh9&M# z35F)V<>HJ2PBHyegb$nX*tf4zFw<>i=LbHzj<4Oe21YmTpAXRj9(2)XH)1yj%s(=V z|BYpwkPpC4LHiT00Q8)+1&@cp4Qk1HCbbB-pON*b%8OJeN_-%&UU|C6gynCKe~a@w z;l_>^0p%hQgggSiVDX>q54w+`p0XloD{b%a-4PFdU=7Fd=J&Z_g13*Bt=+QRSvmzh zbjw`!fa@NBfB-EIm=t4~L1L)m3{h|3hD!Tm&@hiWmHyW-ibC}N?R}76#{s@NAQ}h+ zqwm{QKuAn==N#t%wuJvcj%4zzkKC|K2VLn(+F|e9SQI2odU!$+j5ZSw`?&IF-TA@M zQup!WB!)M%2QFz4=A3OJ>imtxPc2QcRPpg(3JwDY#On=`RaL{|@q#x$Q^7J3Mib)O0#LW6_k#cr%f>)hKG6`@1Y^MT)6D@R<7gTux zysLSB+J2y7Ix0wk!Bzp`D4=clID{T?m~AJ z91KyKG#2HLor2ugeTzXo5D1`Wmw$AXc2z4Edw*tUmj~pRSPNu<)8dNso@s>1(R-`~ za+Di*jA&_SNo;dVe$+mPFk1fzY`mTb?qtpV5UJt^ZydOH-%8eO&1&Ska(@wwf9Q}Y zzd=H;kLBlG{arBG%cIyeEdU{KA=h&**gR-TOm1Bz`92zcaZonr93B9mDS}@N{>Aj>&3mg(+Ym? zCJLd3i(#a|%p(ZXqM@PF2MMR(_6q?N3~cTm?-GJv2$BfgcY6sUOFQhaJ_^iL++J`Q zCC@$GcV5n~j4AxjOzsmLpe0Zv9gaWPpUVx%mLjZir$c&Emuo0mSy6+SCUvKKWO3i} zId{FMQdmT|76Iz5ZsB^Ua+SBXYS_iqy0&<{p{WrlLWvmzNwaT5zd+BpR>!tIR0Jtc zkJ0l)CG4F13A_3Ex_Zu~MqzBdJc%{pNnmO4h{2aos)K8BH_o1sqxK-Che_Mf)I3 zd>gxh@hv*+X)h@Ep(+=FrZZ+q*me?HrxO=?k&YSw+dw=9lm$d!5qesJw^h;S!D|JQ zNcv+54NB>XsybzRu8Uy}fG_l%4xROw*g*~Qsyax6j>=$Cq>cpdDwfRFCX?}}m2PwS zN5%d(J5)tS=vImN)n>B(&KK>#ksD`|QwDajBZHZa1y3Ne`5PO~8t)M@GIB>;2)9EF`u z*mqaW_A+fsBJn|B=%rKt)4iE@&b)GZY+XP$tpl)_Vt{dCV$#j%%a;0`*3@|q#1DVt zntT6KISja+ad7HhO#Ug5(jv1LXU`0* z2DNj;--bTo(F-`c6R&=58^v?CDK0P11N2We`E@S|fIFM@Wa*yslp1MA=sT`=!3KgEGs-um8@dW$JOf%m7QzqA?{|3qZ8>~pcautTcy&Yd~q(KsNi#h;mGxmK0dsE&?-c&^qb#&gg+&RS&KU6o!=X7b<_=gHnms z_}X?~{diVhuqAv_hiWUGz5%3F3og&&l7nzNGO&-RW3_Xh=7}>D{8t)+54!_yiVZ;<4b51qN0UKSm#>Qe&UuY1yAlx@phwW2<5PL1j1sW$-W(Ru&^}sX2**zk1a(d)Ss~gR5vRNk~l_*gRhO6TDRBLU@ zX`KL`0xpd5K^f#RyXb9-AL_K4qxU$@?B zt^LlRtYxb!T{-3A)?CIbe3D|ocZGS!Y|I zr_lwA3zX48>WQ~Ojo0=YB9*Q@G<19|=idUaFbu~Q5+CfZSJm~uXHDS)1JA#p1UZr= z6GreG)TUR)h3=S8ry0{D;_cV zVPR`3djxdCH3ATnxkc3jL&qPnX}pjd(xgxc zZh|_JJ;6@_#ZU_?mdo5)QDq67$39eDc=ewXy~qDTlmE*GNHR@2cB_9a|Kra72h_RR zuCFK8ESNxJ$OX$Z#Z>RDv4S07s&-<{JNH5_1gZ*YGHN11tpg zr)UGIoIV&(kKHwY&cf~p0%Xy%l64(QSo$k4;&>8bAJw!i>90q<^dE})zkXDSDu;1V zHJa}y*+bm?F07!(&^z>AEmQc7bq49h*||x#WRKC6I*#AEj*K;sVA1-(zp? zA+c|4e})RS{!UD=zb8@|L4a+1sR>gJyha@!G^V)Vxmxhf3(o%-Wn`Ln@`*^jVUba3 z;|SZ*fS&A~%O*(XJXc7r`ky`dA;SjtMMap+^Cb%6CbB*nQ&OX;;oFZ@I&18vaSc{;uNdlboe6Wc>bs#UVfJ~IP`^Xw#i3Z&_x$Cgk+&%>VTdpXUjP)B%cB^*jg4BK|w^(5gQd^J+NI}*VrsS zkzHmlSJN0wCND00pKNSw;>!*aFnDxzN9(I1F8%*pax+0^h4EA+7@6H~M2T7T*+839 z{H`<&Kp~X(TSe&jV!K=bm`HDC^(vc6%^%|YPN`0tOLa}#Cawl4pGYb)A3H-)eDQ!M zXn_zVKyw?A1@Hggr;2uL_cyanHrQtI9+Ei^J_0a%5PXzoVjQBQ0~QzXnb#~g zP9?mbS!vn1J8}U#TXfz+IkuZ1J1QqM%f>-0Bcd&5TsgX_OTk^)PnFxWir7 z7srnSONZoA27`IK-#>S?4-V1*kJ;uveLji%F9^%OAkYQ40Fonr((zWd-i-J0S~hjA z(#DJ)8$}INPN3mR)ygpF8);QzbdOz;YCoL-_nUG^T=eG1#x4ZWmR?oXW@iU%k5);B zi8ox5!AoN6;;)IN=uJ%A`OT$uu^H1gKahDnlfV~CixXn`zkc9^Zq7X7A4C?5q{H8n zi{naz_6#0*{kHsaDZRUl{cqbA|z z-lMa%wOsVwvgI^~11&H9!k0JGyYWGch|R&PETavc6X%1X?LPM}OOi{T+Gr84S@+5> zctgigg*NwhS-n;#;hjeJwl2$Tlu4rneVnCF!PQ=K9Q?DIX2D9L!RLyKg1ONx88z5K zzU$y*f40Hs)Xc9leNZi$)4&UmT+jpHq#(Y2{6V3*ZQL7rf3= z*pA$;&2quuZLg0)xBaxQM&N)U@*tBaFMGzJn>pT8=DQ@nGcD~9y!0}U;9F$TIW2dQ ztZV+mlkll{8n0Ee&fN&Xqv1!Mc!OO*1ZGwFPmv&_S`rxvCKgq_JV+~;ac=z7- zqt5&$c&8z2av+s{wixx$O!CYxqw7p4=;O!v-^#b7l_QaBg^O0de#d3u=PEgJ?pgF< zN?5aQopjLd={>n+;m!K&be2LarROIHHeLiLKqrSyENS$4%WEJUQKtD_{c5^&J%XK)3GE!$@CpL!D>{#-ov z8JBxZZ0NrgWhHy>+b`k?1fsq`1#aJCv>s$BBc=K(|I-I#xBwQx4W)YVUckQ)1m5x* zH>F}V`Pga87lAK#%>@>DjH&|EE&R=nP4NFNSi{t%yMCI&dwwx)6Jk2*D`N^{vDvn$ zPmk;BvLcrTwtgjS9f~~6Aok0M{51gLm`;9G_NU`Z^O4(hr|f?x-1_qT%a^U5pXf^O z`{TDrEadn;<52MaJ@}x=pZ~lWh*S=n5EIqf;H>rqw>o&gkJCt6DOZE4u)rZDhjZZ2 zUDK_*mXhtSSE^p$G9Tr%kr?RI*4@_IVn%?!Cta_oI)r&G9CBp4Ba!~lK|j~pzF;n@ zJi=3vg8~XG2(A+74FuN6nIV^MJtK>IQ}qnyPdJ)+Yrs7oj@uBEEMGUX`(>)sf%a4OcO0FW3Cw;~SkvBT9W+j-5@&hPJ-@ekoMT z{g^d$s)5BG15V3R8+$6};A@;!cSe9b1*lXE68{KSeoNYJEs`wd)Ypd^U%$RI+~gTu zfs5tk_49oD^(eFAV|PD(5E-Dfnwu)VO)y(U<_TX-X-eK!RHW4xYCu zQL8scsEyRI7TylY1^?>eY0bY&a?t8W5Y|rnrd#fY0Zn!Fe%s{5{V8M0#_O5%{Wap> z7$cVJ@o?zZw`A*!vq<)LhF%NPqq$^+wd^ZH&&p+<+{Xo_L9QJ~1B!M=Q1*>~2e?$N#0hvs>_u&0V$)d3(wx^*FD~17q zPx1$AWxkWIwlK|4=O#{=&!J6f$+S10Ov5(E;e1}qk{qk=oj1ucx8}5jE4oeWh(bad zFzaYq!I5d9ypRq+EvlNe1k|FCn4CT?S?#TqW+i{4Q-Kr9gGkZ2ijgtoRmI~+y=-1n zxz!ym?!Z}PadN0S9Fy&n?$`PX?VVyREfZBC8h*=MN>)ytI})OOyt)V1O~rz*RN(uQ zG{odcdV33{sph524Qt~51w7c&CoK|l?bzeuj+h!r2YMADX9Ty@38x1C=lS8%SnKdf z>j{&lCq|ahv*4ill<}}Pw>jD6k{1xyUXwgCR})v=b%864%zBQ@I5iv`ayR&GGkl2v)r$Wdw;r&cQ~|2ZZi^zNVoW@-`_$YoV@CdJbE|_cD)v4$OpdInv{;qi>@{U_fCK9A2>Vq*Z zHdiBl^u|V$oIAV+ znTH$JBgOX911rCV#rp3 zf0J{I+E`}Ut6NgIo5@*WchhO?-UqqWZ&emQ$}Q!54{a>W;#Q1|<4}sfN9(4wdm# z&PiwEM@9KU@pRpHbuSPRrSz1#2e!uYzo1>Tz7SDLBPpML8wfq$ zRId>AVcu73E&p0|{_QiquEHkv=HjK!?;&`hW7aHT`=F)(1q-FX=OGJ_0pX*~C?5U! z^L1r{(5}L02x&zR9_W*GHdBSW4i0zn5^G4YVi>Fosia;BJ{*EG@>Kz@Z9_jvtxgij zV~ss2GrxIGp7DKasHl`fHRQI_MCT4H82Jt5r`Ycape}%#Ok&9ac%;r-n;_n$#3N%U z?wZklI;M?1^1!r24-gy|HDz%BF0p3;B%kW1CzEpBpP6c?yglH_ZpU9TcJdk_s7%YI za||PRM(WPwNKj6K<4#%?)F^ASRlS{~dOhpOwj9mk&@^)SF-C` zI{nR0q`(F1zZucm)@@T6T)!?gMkV9&t|3@y?2D)#@}TVtdw{Nb)$-`{ul*#^+ZH%0 ziYYHPbPqp%?iKYb@f@56=Z4kk_0)#oj#mpcH-6E;3iJg42xpyWVC3w^*{kxg!bIkp zm7=e64UfW#IkU{QA6y+%4wI|&Fq&*KRb4Bh^0mAw)VyqkP>CJG+;WH2#5^5Uvb{gMSbMPb8C&ZZ#Z^QL<>B9=j zNtFbl{rzPo#*i@`AZ`F)Bo+6FUuh7^PhYpbFoA>^HMDxttDnzB9DY&$U9S%b_(*Iz zQ4?hC#Qr?QNDb1vou_=bHdbYi2(QY2o)`yo1=qG-~4?}>m3Iku74@k5Ae%4*(jA>?hF2|zNnR%7Nap`nbV zKVO+ykDRd8Va0EURwJD3MpSya!LfMT$emjFWMT}4e9cGwgBk6N*a&F%l-YzEBqXk1o^&&8t#~dqnYjhsN^Jv6f9>j%A5rk9a=!Fox}tNT%1?pF$NzMs zcj8WpCW5Ml&O!LTv*ZqMnJE4jDvVKAS1fBh)*1mm-g`M zzWVZn&&Ra#M+?Quy`iU9xPmVxYsm9QmkzlhEo|$1I z2POIEkqxY==y5UZENg{&_F^P1A6qeub=_)0GN=(&rA#6I=%r|i;q3Fq%u)B%T!5L) zwNqB5C%#{Z0V$dq)IJaoRKww=(l>9g<1HsX;}0a-D}7Aoi6AWo;Y(9fO(m-G?O0IG zwA6b2GV6Nfq+KV)rjkse9>0%le(9h-ixmPzoF;e-`OkLZIsI>^z1)|N`S>w*boaIa9n!KM9s zj*OcvFcN?t_H3*`WZxLhplUvj?@0N(_9ngG{|R2|w`TMV-|-^-*!AbUu=@+ZjOwnM zUIh9v%)$jaX4zakAU~hh9BG&Mu=N~b);l;cb@`xP=QiLK^GP&mR!Tk!HZuqGl`op@ zby2!-Az5@&y3)PVO%Uq5)%p-KbjF)I@B%{Fo&*@VMvnU;GN)R`PXHsH0JM_~1tDu% z2g9NDQ9mW9p{5CR4b{=uY^Jq*4fun0OX|lg7-NOr|F1-9#N2a>mpNSm)IMe-#xX~%`={)U`d@LkxmBE1{f>ivLDifWS4*yL+2UWcNBz9#=; zK_hwLCs{lLkG1;0-qZhRvZkj8*xiLsM%yQp46x}?V?sj_zoawaaf zSmNtJa!(~}pF~jfzxNq(aF5%GI$l1$YlFXXSrFymr>@Af{IuVUa%Ea<^JWvdR82I_ zV3#Wxv|a3@0rQQ;qRfX#5VXj!KDSr_IeZSeo5^R|39?g(wZkD558PRHMhRQZd5wZ} zGwpDK{w#!clEma>z?k>O1e43=Xr|vB=@V?7_?=%+@VbaV1_$6{W7GT2^sy&~pZ3pK zfrFjt&k8YJd5#7!FFlEg`h0M#6m1vNy8;ZYr5kZUEeSK#xrQu!jG?8-HO_(JQ znTI}nKV&gv$MtygB#-r`x8v%xR&hAQL^;l%@>v}5G59ucE;lIj)zHcJvELVn_(x`9 zuFK|eB^P~hxnvt~&g0RATL?u$e^W&+3c{ja@B81y7~nVSw1}h+Bu9`ZtH;GD;9qnMw#7VQR7orS+V5Q z?o_-HHU=zj34HvWM=JhjjRxC;&bW~faN0McgYF+R^xv_q`0l-m017GtBU8n%VtlZg za4jm{X#0uVMD!MX1BlNRDHe$RPcn(IH7Ot$fcgmJL;)D!@bp1Vu`-$eqq9Vw(RzJO zPTtS|{npDR`khASahvWe@0sdUxZVcs;^6c{UjYwyu|;-QsTzxQ_iLtg><)D#?l=#4 zg3$}Eb&i1Sa)3TD&F=I2Z3Z%1_|+PuogMZ)Q*~?02L(n~opaK6 z6PP7vg;@bE{tEY>E?T+=K)q~w<2kq6@W?&*q}*17)-G-105HR=N|mr5AzenGz!;~4 z`2jx|GdCn8i56FLThyMDl|V<$yamaN8&ZVJ*Ldi{_dVRumD^hrpoYAldnz8IkVqss z(tg8_8&>9#`mtj_rOQ*3e7Na1e$9_0G(}u26_ju8Np8nlp^k@#5!%%o+gcPopqOtm-TToy!R>bq+41Nq=fD-xQ^?dc#5#}MXTx-Q`m@V{o#NdM_suX2nDC)AOx zZdW?yw`Ps2#4;{|cB1;qFtyR~@NUVJyZcbE`J)Gyl|t|44BfzwL7oo$tgoIbZMM+R zBT`LJF}fur*X{I#)!GNtF=rNVsa7L!W%23|RB(_I2Z4KjPM>Boau(7c&H{&O?C~&l zBNUS}rCNlEZH)TX&!Rd9TqxqvT?6JyKbziOoR*iGTKwps9En}^0KbHJ)7UcIy3Z3~ zUYe?O8B2D$q)eb3xu+bqvM^U%={6Zerd=&G*do4f0n`0a^hkKt89o><-PI-P?}WnArMrqu+T^t{;X5Hi{;; z>}84G12g{R*lVC#oB1M5o6dj=V%+`gWb|gmBXm7_G%IBCVP+UHaS4c_xP5tGfPV~y zV$m_9Z_*r`i_t)9XP5OR45WLI{(W6|3O=+7UPfdC!+I)8SrA&s3v=`eQio5opB}pk zf-%x++gz=yQ`oD*c;+n%0@IM8xjO2=yTe17;uC>VTB*oYbptaQ(@y%_;5X$(v$AdlAMO5*X9g|Y*|?=? zWD;hbp6+S|bkbFh;YN^O(<7;7M)pw<^Q7ev3CSLzzAjrpPzdzgp|0UlU^HTQc=FRW zVq+BznmIkAC&FIY3ayAA4h3F08X;%EBTzXfmmB=$Ipk)plGgZLuUS=b9p%(-YAV@r z+dYj$iB6=W!Qx<5Z*R8#Ko7r;%iKfOZt={>v(@r+v7G`g3=WR}3w&%+i^_->h*=p+ zP2nOw`8^*3-tINMnZ~@lU<&9jQ)<8~%nw@VE2yWU7ex3HKt^Xqo!MZHBW5Osw9qv1 z7;sme(v?o_aw0K4QklIi!8L=hO-xA92DZbH*!E23@t@j+OSlZZ*>V=1E%$@gul;@P zc|zi=+f@GmIjMN`;`mN!?<-fyyP?0V>)Omr^4@@sN(yFUW4CbP>?AWZkcn%WgzNDe z6z4S~xoih14~xT~rvg=O^nOs{oBRnMJAX)XWpiw~Fbk}#3IOz+sZ7jqgN@44vDZTL zfoiv1j~Vm+)cA6b-D3 zCWA{qmN!MZk%d`b%Cn4CM9GT0*E8wre&PGWdWG+>?rrEe?bZtbWXFirIpiw)8o8Tn#+ZC89gy`9G%r^^AL<_9)xV z*53`1l+20^tmZxKv-p$Q2vXXdc{-a9pG0U(C<*V$U6C2WiXJ7^yw8u4_D`MR&V<(h zMOmk_P}AU@ml#%Q`CCkEkqtZb*TRZ9fR5VW@Nju{3ibERQ-yzfD9U)|gSpN0L(lS9 zB%izyVy$pmJwuJet43_IviRrBK#;dA*Ep;WgSuQnhfW_k^J;n%AAa2i{i5KK_T0eM z>Y(TRa&Y%2E$G$xOSH!lazk@QJ~;BhQ{=T;H&I@8FkU~Y#xV}1&y0l-+DqpzZk z6@XJ+AN{OwL-!uj+FNtEK@{+3H3FmnICkf1Np^1Z%{;WQdNN`q!utD8;>3*Q$_-We zmpY&y=Le)ZQ(17m)B+E48L`9=HcY?=w%*Hy`^Eaf!bKtgq=2;s(;%OUh9qOrODUN3 zNd@;W+WseR!<56sZ48tz?tEP&pFJlJv_ykr?$5;x6sOQDk2pP;lvrx_W#uqR4%<6M zMn%jc^{15NBV}PvhOJ^8bGg|+j?E|_@jpt7k2Zt-v%nr=TN)7BQ&?LtspiF2`j`0Rsma3Y}BnJ8~wemV*}&KqM!g~gCTrmq%Nev zckDn%i;Mq{5)*b;xk3DrmU^rH)7yC$tuF=!v@XJ>N9K554EW!%JUPp`YhX+N_+=H~s}a*+bx*jWB;&aEF; z8Q&6@zq|d$=G8lLV`W7`wcCojGHgU46jaG8Qu~M5$bpI8%)Mrj*RLmD_fL4r4tg@% zEuyghAd#`@H_y3D26WI-Eo${Y#NxXutHqrZUb4@7vkJ#6cChn^+FzX%`gr&@pemFG zh8c2MSH?WhCJ%+sb&swHJ~PXU-5r7y$#)(}*_W4FMe!>V{AeeEqRga|SWcB3V%Mvs zZoL8DoV%SDUgaTv^0a*SBB7t(brvQig^20)gdry(WlS4H3sd#r^B4G#lJ@ZA+*uSeKa|#vx*bRAvFwNbTIL18k#k?3=zN~J$m1*#7pKNm?aVDDp6$F9iTO0T@XMt? z+28jiBU5^Db+ra9e4n@TOVOLGqOUIIO-9n4*V|v5OD$ZcU64n`8pnFIOD3o~KiDaB zV_l(rBx-xp;n$kOW-t}%%1~fnIT4P!KzNEbIz z3|m>T->{rkxdIou!9oblxRr_%II3`O9%(qSPxhEV3QoZh7ANXe(%jR&@{Zw+WBYp! z9s@6}Z%)b>Qi2N0E0nPBUxE=G-^HP+!^7TkzUYBm8Gh(k9&GSCJ3JQJG_26q`phHh zIT7)%^ijv7tH#MXw&73%MDQ(A#4f{tfpm93fLhJU?Wwd|)CfE&2}E!*$>}D06d`2Q zTRTkl7WIb)Dz2At9YagVH`=2btl{04H9{a+ZtF?}S&Kpwfwqt`Ubv%;q7vlfH$8H+ z>dHY=FU>vQK$u~-!aZfoGDD^wtDZl39O74ctsm; zI-wvp&qZ-rKoo}}aHv8L)HkW6_%lg5943kX-n4dckUu3m-dznBr|;`tpT}3#eeU%^ zydY5b1tdj_g^o3OFr`S{5k7NUnoYmiy&BHqP`BD(`sBd<6t&(;PGdm~HT8rhO{tcz zMN#>g%6^Nh4ScJ6x~D=eIXq%V70L2ot;LrDhLQL2Y2t)S07l2Y&^kK>jN!5aDfHzX z-3YQPGjHdi?vF|T^XZGgDXj1bIDiG6Mp#5orn$Z88<`~&_ruq-#)MD`n9Z-2VZ^>> zC;U;5h*Ij<@A~gMnC7BZewLqmC|7Q0p+-wy ze1hIqv$N-Ehn2*Ke7bWIcrS+ocd~{N$N{+aZ9@FdYfBa_N_`(5+c7`s8-T~m9_(w( zx3R<5Yl4|!62b$4{JvW$@JXSqKR?={KX1)~`r)Z3qo!T_p#BkO)LhONcDPVO_Lo_L zR_zW-Nd~R+_dJ**bZ}4$Ts7yq)S^oqr~F`k4rd4j)O_!lSBn-giA<^%KN!oN9vZ7d8Qw;sI z7F+KURX;%nNSY zV1YAzl+a+hYcoFTm4E~LB91%>s!fpfi$ztA^5xr(7)$a6u}}K}$T%)_$Fk{u2Um3M zyvqW*o2lRnU5JK-i?w1XzYizeamRHY=hi7|dym;u{e@3ix#OjMg3lNg?IUk;FRGQv z59Epj(Bjh4cOejy_x6;mm&`9J5^&6AB9!ji*Ubv@=MjoPx0U?NI-{m^++r08vU+j| z$unY@MNR2cDf?!}EK*oZl*NrIiaKh2?u0;gw?7Ued<6%{h->VxW}iH6e7-k-9lZEE zUBe25j5O2?rVwbp2mAMTBggmbwAtW7H7GjR{Adu*o?a~Hdrz>dJ2{qiTw;q~&+7?P z!A#o{h{GMN)9{v^F~e2q=KClynun+LX{RHSPn)l>;F(b(f&(=VGU(2xk3f)wXGl-` zZFZnF@L+0s-3oTtv^vL@3UgvS58{sS4-c0x7h7s&5p?Jp(tQ-|qeR*e2c$}?oZxJ)=jcVzQ_ws%I+zYa_xFj4OIL*v* z0zXON`}b~79oBLH>wC%b+LUX`j`7r)Y=MCBor2KLSQzBk{Yr?7=Q1{^Gqg@3|4)yhW6^V*rCHh5rN=m`trKiHa^lPNh z9-4xE4X~YhvA0&tb~$iM5h8+VpxYc^fhWfDee=ijB zE#b2DNEV$cmxxgq#L~{SiHj9{EM@L5Ald%AjFV4a|H?^idm#22AsC>OSvd-Wucx7x zE>Xg&Q1q~-rWqARN@jaM?XwKO=B_}az#*czAxqZ0@HZm1D*oupx3QZ$g%SZ*k>qVZ z;g}Y2)2x1>*R!R9Iz=9@3cn%#l=Kp`ts^CjVJvY+bcO(_SGBN(Q9Jw&j~EAU`mMhz z`i2Xpjry_#?zGyaTJ&=9yI-$pxE~c{=g+yIb-|)79oSG8@jtOEJy>M=iaapoffvhY z5Ac_dO=?RFGr>#|K|iVu?e0s$xj6^Ia})C)@i=1Z`|BT`0Oxt9V1X81*l;U?cK^U} zdid+t$63-NR6beo^wRK^m1 zaBw)WOTxpYxvAY02wpbYgT%Y~_LA%2L888a;%7+a34v!uDXen?o9v%U$ja(2)R#zf zygSIWAUKUWYkJS8%K90pdKtRDGwhvsaf_d|59Y!am#ep!@c6k7bjeA673g9&0;9s|Fm+oW0VuItu+EgzwgcHf@LH+36KpS>iojRIv> zIE0;jRmaqYO{Gkq)#?$pJBUIknUhY^OMGe~a^vfqucL!K$PMhv}O1(H`srvW!SPkv1sY+=!Y3FwMXZ&%Iym@ z*YTUUzAC(nP{N5a&d?HkhLq6qY`Wo0=nM7~S4ZNIKv4W`2?XKOOQc9hQjtj0w>43M z-W7(!ueBZhPM>Uy6AVO=j>{htSlMmP20t3H`*pjjheHE8xTwtf*BUgQP+ey%6> z0D59C!A*k;H=ilg?N{AvyAV|O+9+AP!Y&l|RWsS{ji#mg)~VIObhKodPOi^nMqFig zPuz?0CEU;6^c{ii0Y!W-hdPsILkPU5!U9{OZyYo71QYQewA7&R+FE-oWDIvu!}=R1 zaz=mk54SZn$p+HI`h?!%$&o+d=jNtJ+2F-43Gpz<K1dE?ce>?C@4ZAe zom9Y9zdyS4MjjUtoCVs3DUc5=y18DvFS(JnDOT`(rg5$KO=CKU zYc5!#eO$x~!h)2EVIFCC-Bip9>vnBlI9EWm(QkZ~{>wXeA=Iq#lYTSA{@gVn4O|1w zDI_J=`YxHBYVM4|v9m%kPZj<)bL+3B@}@P`i{@}-~V<>eVtlV{YH zmZ~>ps7|A=-Xe{DIgB7b5G)BOa&*DSUyIN?Ua(43%DY^)#?$sTAt3laJIposbdSOT z{niCbrVK(_n&rA1uLCQey=E7Vza{8;c9F<|8mt|&SCq^(`vZ=|qOGSFE>zoSN8;&2 zca?XZ$&xc@FfnIcv}C4)^)!gX_kB7Hh?B)bU8>gK22(S9+M-EdIxT#~g?nw}9Y+!k zSC-(@Co*~A!-1|AnDtGItitIBTMn6PTtD5V=dA6Zd6Zu&Uqtq-I-!oYG<0*74D{|p>f@;Vd9F)MP_P8gEl9H&k-q082{t2 zryWhn-D}J$3rD;9`Yw?)3_Tlx{9YBNh}~UCX{DrUxBn)!`teK*_rU-IEGzArrqgZU zk1EUX;n9*X)Kz(6H#pK6nUe1q$fdV#Ee*Mu7#K{&Q&{I53WLE4QiF@c9sAn@@hUsx zuxrg{e^Zp;^>NSbuY1b28{12>Jmy6IXkH_ojTKE?OZJ6ua2SY%)g8k$J1@^ZmVxZ9 z{v<#ILmXqTJl{w5g@jz(aApa_BROD67PP1!7P9XR3=t0=Zto$z3g_m~x2I82QPC~y z{pP7yGu;Efq@VcJ!YJ2enEt?(R@_hN{yn8I2}V{*HH0NNi&|%N&G{O468S_`k(F98 zyZvBqXn$ub9iaGRm!AKVPVuYxEIf3s#4*e+9Js+G&P z+=e?oPu)2?DzB-Y+9@x(*^_YP@Zlw=0sb11c2wF)`4c|S;#9nV5ZH_+7TIirfri6r zO_=%N?d6RB9BNws*P*6)HN+%rx0&GCouH~Ty7s-3K6gx~AM)mV>`DSZp7}=eWtFWb zgE?eUUyA2)^U(LTT^P1LJ8@Om%AR@qq6wsr1fNPz@!6+W?=9t1TjJM% z0g08-`(3t%({YsC_3AyXV<|JNLf6rn7Z+rHnOdPs>N#cOxxlV ztN|=nJ6nxNDXjG7klpK()-w&wq$E9M%j;LF?wDeJ<#rWXmrBRo3avj#OiEA%Lk=Dh zF733YHhqS{jB*6FGQKSdA0Pi&@G=PRl8N2*IIHMg5Tf$*Y4StJUE??9?);L;>#h}U zaGj+l&(bvllNOJ+9(BkdvU3|2x(!|7{!t${ks371Z9VX@OkEYC)v8uCk*vRZ5rS6? zu=Pim4p`h)CwOF6sXZ=?;%>0fDXHB5JAg^me!<~OCT3$Zd#>8 zc_6@W>#j27!UfTzKDYE`#`__Vci`uJL;sN4GkZfs+Wc0jtjlS$;jXTtJkE*NZ=SE) z_}H)B{xao&aHrC;`$W=h(`nUC^9WVbcdBkB!-3sOIN=lzFD^9+iVaZ*UyOz>j%PIF zn7fb7!oP}!skOGG1mV)g1xMK(rGr&a{ss}ejAZ7oQ&)Y=X;@**dB5=)(~hX<%PheiE}^H54@5ekC=RTUy$*U{jC)4|gmk z%r4qMNH_7T=LMP^PgB7{?inKphX<8%S@@l6`fHB}*)pcR-+tA?LB?5FIL&>qfQiIE zXAssa*g4WU*(b9U{%HI$@#0-8y&#F+K*RFi0l!Mf7?Icwhf-M7&zdwU#z+}ail&uE zhk};HxI9la6w?LREYxg)@|hL zjcc7N<4Q+v4mlsJqw?0-lroIId_R1Qsj}pq+}!fUEXL)P*ZBFPH07En4>v#;Y+PvJcqxl+UoOa)oamVdn|r$X{lFx6ce_ZE3eY@z)sUpqTMd^)O07~aH~Qt zD?jCZ~P0Ci~fR*8B5Vsl<-~S)+cKS;kawC6x3yLs-US^> ztIiSDN^Vy;s|U~9FmC-nl)ZIalv^7$Jc2Of00N4@&>aFwj&vj42oi!yDLI5R5`!qA zAS&R%(4eH0N=jMiP=cs*3?be5?E%koJkR&N?{mKM2S0zDBWKuq-`8H(y4G6v{9NTh zlJp>L4cguj)k6sM06BFXj)z*5(;}`fpw<8cx?5HI*7b2gH-;{1bk%lkN>6?YO*V3P z6cCK7$KR7%9LzKxI!%U(u2xLeZcfSegz>Og?T<4PUw;)9G^0BXR~2UF9g-jA)%=zL@@8P9M-Txvnc!K#o}W@@3C?xbp3W?05YS9AGjj9@mBanv|5g*ZZyN z-QG(LX-cF?-bL*Y{c%RAgz0Z5*K<5Hn*MR?Tk^-lwET;}M%twnz+*zsgA>+|XSiAB zH!M#AM`kuO*rbMyz?g_KMo|2UxxjbPI=^DyZQYkq|d7gM`y7$rv|Q%k-r zaat;5H}?Xk#84otAeKQ~nXpf65ECI-1Zd8$Wif8li_LkQ{Jmd$p$eea{wC+mXYixo zSIh-pN>QZUZOe%DYtX=zUXqG`tgN6p;3nf3#2M$e4@&%y0gx>%n27Mr0^A-1g~i1bi%HUbn8 zt^jQH99?78&#SzL_fnB$@Ujz6`usZE z7;JOLQO!{Uj2SZ#QrnmskxMe#JxN?tD5p>KUEZjTg0v;{E3!ZS>yQE)wnGZxWK%(Xc5} znia88!)xPwt{-&ha#J}l;^|rTx{>Yp7PqQ)`MXG;Fj7<>mB%Aj#ZQ+e+!rjxK(YnQspV#zEMWGZcIYcl)|JQ^c53S_e z3$6@QX|L}%eM^4Gfp&5Fa~}gNnQ{lhZ#_Rp&kAx_RJ_4V#56rKKZ*&wWD!O-@W!1r z5~c{0BlrJ1K^+OylxX*UJPM<~Po3B^l`Co%ixQJHoZ{@DjOnxlzrGSQl;V77!M{_ixWcJCxmD^wT zuhZ%Rv*_Ad#%9;v`mVJlbmK0zc~hwK17_jl5DiW8jm0d&<>g9|b7-;)dPbYvi?t%0 z_pm3Fhv_h2y3Y5Goj~8Oz&|v}g)?LgoTQo{ZS)a1ZY z+()v;!k+lv)Kaq*&$-&9Pi3NguG!rB!Arb?rtG_oN z&4D1PFRb5uJ%#A)@3)d`A5~FOI?>nLYZ3hOj3N#a<_W&AgQq``0~w}Hk0BI)NCIbD z6Q)5Z;=u6%n4c6z1xRy)zNyCNl4Gp9a{`FtAsD|PZ^HRg3C7*D`UUZ{x(A2q*jKgE zvoka0k+7Ajz3>SpcawS^%e*0El7A8A?>X2RP)=ETS%$Ot@45A} zF^pv408KF&-I{2iT8@9A65$a|1s-Rw$xGU}rcgUMFw*GjzvkGPss^}iperEuBrQD& zmb3i?hA@%@p1F7zdx8;wBP<||!>~1=G-2Fhu_qvQ;{H2tXBrPxs%SFurLRp+7z6VE zQA$SEShv>)LB6atIIHnkr-CtDBw!^RitD~xIWz+L^Bin5t37ntr;HmQYw%Ak`H{1m zfp7bHe$g1gw@RYXUPC{1SvA^tjxB%BxbP>^%N;P=VcO%~*dKxpYNDlnn3TjXFK_ek zutrK>&7#%=!+m3*?)FOo59-Mgc6N59EA4atb|<8f1%tJQRWu6QkIeZS%bo~4|7_6t z$`WDat+zAGEITnlZ?y7O4Y{^EZ2pYba&K?7*UVO5&b-7T?;^v~ql1&l%{r}b*K(wU zmRZ7QhS;&0EL_{9_6Ay%Q5A59J0n-KnnQ46Lnx znE{$;#D*H77=t~rg9c~LPz?u(<5Gr_1OuX-dMD2wm-y&W?#|(3_DHO`toIbfGx^}u z^JmVP+-g0%F>@Z_D;rf&;ayOh6Zt{^Sysi0*G%a2#FSzCdm#k+;Ux_a%kTcRB%AZ_ zcD4)U#()TG&Zh`l!>figj~-`a6&3Y#OKcC0?K3oAn4C=B7>OwAS90EIp?d!L!t1@w zz7-qd?2twFNNQzwnv6FdqH@-jnlg*RxwN4Ntcab~gq%?H6}cn?Y6{<-B z=gO`R#a9)r{<^%f?ufkkLAc0=`N3h}!#9%>eW^oi$8CPRiK*%3TRVQHHWB@dLLh50 zr;xH@Z*gOUANFBa>Z#5cyG_J-q(Q^*Pf$xBKghvqfC@4U2EO7&V2K_aJ&54hR30{= zfXA~cl>A;Ep>yZ*va&c3VxbK#7Y@uruPCEnteE6p+T^4pLERb=A)$u{Pm|KL&+_VD zO5o;VNs7Pl>!NI24Z+2;N= zPq%Z={3mGoTm{r%J$Z%kC~9RAuqz~|r>DD?w?|Xr-rr3M{kqwhao{eXBUglgD&wrD zg0?XN=gY3|q>-CUZ#o+K66dPv8y8>yG7&{()Lb6={OgK(IOF781okRqAeK2RJKOnV zim;JrVtTsJKC7RQzVb-vvI7{%m^`cT7*%#gM3y#vF>de+ksl%hYjdCQ|GYNfVu-{# zZJa^mpf!`;MuRsh&RE2QkT7z75s{3FiVA)?IhEA;rCZ7z!NEB2B40al_}+Mi)0Yt1 zUl%5Rvpf+pz&-Z~P4 zBSOM1UiS2Rf1xC|_m9Miwaxmptp3l2&j{Jq&TTuIQ&8S~`ik4qry+X^zL%``B9%+c zVC=P!zK3S;Oep`{c#Y|Fz)Qurm31YYDCBF2wJW`VM-vu{Wn;NP8_y`|K6JB+`0!0> z8GvFRgTDa%U(kT7K{QOZ@a;2juQb)OA;YLKgllr(oMeL%%?O|p^-c=@^3iR>Vk#<( z)(=HgNH8MbdVlB;-8=IfFgBsqTHju6oA~TdSl9YLz^A~0oUw}BT(zCe=yr85mp%0i z;Q#zDCdQu5XD6r5biaSWjCc_85RkE2u5|Q|FD3N%^=pShKThA<9HmTYSUUyZZKir* zg94LZ+^3CSMGhEmG{WaY#bZoiq1L)IevQ^?!E;ph$x2FlFf(XpM8nKh!P_Z+Z9VBD zr^&P;$$C|JUhHQ_<~;4|uZlXDC454j2*_f+*!m4DA3x*z`O;{a!Tt2{v;_*tk{1L~ zpg1m5qr{%-r1<3NJ)1QV5_`goK)#!mSTak-M7&V~cXA;3@_)LMV=*n@)n9FgbD(ku z#i9kx`9K(tDK4>M>^feounNNe2fwPDmt3LI&wXxQddYTiem?8#SIO7NSK*)gzIM%A zaGWu@PH$?0&b#2~T2OL8AR#GX7^neVUt80h4&MCfH|7;_3DAH={ff?=dAei#O{4+> z0z!djU%z?NhQdb?WpZx)AR1C%AL{JR!;SgbP_q(1aH__h#JR#GHDojo(WJS9Ef?EV zc5~T9wCE*b%oeVQpr>kn^TkcT10lq;=U{K&ih35^pV&Vh)^tWwQ&SPg76xwW%{!n< zfD3#Rl#Dn5WvCMu9*S7uuX8C-Xd$AEMmW*Sa0GM4MSU{3%a{Id6mV%XJDDpZx=7~p zm6es-hWoTo3a#f6CkS zTU-9iS8Lun7;L3htgo++v8VDmBmj}|U8+FJ2=L)S#W^ZFM_;Osx+S0aC|Fx=Jw`?Q zxi2nL6}`}uHY4oO4S^V}c;A?pryexmTvSvfH0P;03e>x2{f#0xZ+@$2^ZNXdTe}riPYfPSeBu1- zOFej2ArM;>VdUQXB-6B=1dVk)q;v-@-12*lMe_sA@Cc|(fZb&1=8e{Aw~v)pZZ!Ht zu)mqWa~@IguP+qEby?mx_e`-0`29~`L>mWhmV$FIC9xEk2m^Qt>CxRT<0L7y}1YXK1vcXBpwI`SS8%O7Zaki?Ca zU2VzP+<-!3oxe84j)3c{3c*eYIxb;IICE@>} zMbCK7!kLsXxSsq{n2T|vDGob$=_gDNxugcp#MWMwP|%L^vdIYB--HQ)IVkFN+qRLc z(^{~)%a-!-yc97@JVDzGXw8vgq;<(lo%F;$P9zY&b-Os+Zi{!~zcM1K8e%PN@AOXl zl>Q?I+;-EUrWLi@x_mXS2i0$IR_6GHnnz2JHzn>^M5umlxdh2a`Js1WWklHOY3`8H{X9a_1|amKpA=#b~c%5`Oy_k ziOW1R$zXV1dDa$LHX1m=Pb+&?DV}&DOfdngbnt7Z&JB!2`WygMPU%N+k_!_D$S*0Z znCqJoQChA9lKkYMp)dyf_7T`@vnYOO^@296ND(32GCxvP1|NB z@pkk7x_0{d^px?pLb9Ugjf%H!{py~8a`yCK6}KNeC~7+KAVU6+rshiXQ*1adhtsX< z>CVBR5;-VS!!+pCy6e{}B`?|b2qF#CNvb7_SKEE9n7%;4-od3^42e}L_CU%JigPKF z%2U|W`g-!oHKKnCC5j++2YcH_P1LNcrRNa{%ZI#DG1JSvk=50b%jY8qG#Hqzm+Dtm zA7q?4yr@70;|L-Gg@FMI$|tzL8o*T0$pl;oU{U~p@F@WH8u-DkhdoGCL9O`A;cns$ zQsxGFHsN$K_yRIB`w|J7As;025+*`QAL%6RrCPzytrql--?E~jz^hN-eX4F+!SiY} z*^{-9KQ1-;bW-?)D4XY4LK}*h;?OrhEWT_y$|R{a(t7K5ERG)WZjMWiRzUt*xK!He zTzdz-TTDg3&CF@S;V>2A?Z5(Vg{hnKO&_;#Eh%3aB(;BD}Yb}KwH(|FUD;h zTHkjv`o3W{uDdy63}Z+A2W3!uCmTcPUXUW?JY9+wke9Wa=sbx3(8Em}Yl9)wj~(&x zmkV6-sf=d=wPbhHfZJi&zi-zfnOC%yK!0x}!#18}3%t?h>==6!4Aj1E zq~N08!dj{pO#R={2-3)ryC8}t&Lpq_?ee+L=+`#F`JsxotDujMk5-32lletNf&UKj zd6N=ddBkSeKgzr^G@f}vcaZsFrOCuy6vY#~s7G??;_S?pUn_UIdMP-vVoQiUrnO(q zX#?BRFs7sDZr>w@O`WnDy!PtMU)9kqD=#m4WB!{8zRPH%$O@uhy~ht*fc~q7+h4h* zZ?PdsvZ<=71`Ljc^)U8tpZ;5=4^`pZK$e z@TowDw!V}{7@^bI^iPev8|{AIGImP-o((2qZ)aWVx^jaJxSEO^A* z4+Es}Ur`m}(#S*yXzogX{e$Olcu{6^$~P)`J~ZsV7DsAuH98c$-ZTvez50mf@nvUu zAv2dFfpO&_b9M1+lPhnX*GS+czCM;Vtl(2ksz!&Sse%a6ksQR7)e{*+|35#L$wXPR z)i^OX{Lv%)XCi^u5&mPnwV`t3w!rAkDt@$cV?j}-xQS->9s#pb0ybPLeapca zjt;m=Wz~;=e(F6eS`?T74U0qWT|5^>vgh53N!*e@-Dx3fBTMfuc6>oMmHrzj0a;|* zZf7zzj7W(BFN*l@bF_8_@y=vErkc5fsd2siu?kc^qkl$(pxh_Dxb&WM05cYa{;EC` z%+s)Gad3OYzxixx>;)midntQQ1ZjvEWRFp7m~A+Kc;ldT_`h`xkRc`fSh_&zW6BGm zWfM`7e_R$_Sf$%nwoxMYsCKymqG0hQNozU<$aLvfSHXeVAB|!6o;hiHKQozER+_v6 zW|;)vvW#I4qB@42&_duT1T=L|dmK|?Wo7Sd`JFoiu?Y!&_e@Pq&H4!l2{DA&y!#(? zc+T?+3(`^%*Z^i;<9>F`^&6%MuUe#IpPW@0e*{|_{5bZaKkcjv%}4S0Rbv{&nj<59 zeyXY56tQuS)8S`*=Zv>wz2vjwdlr$fOCJk!a_-08`&dvreR0-MZb3==Lt*4%%Y#5e zmbpwhx#7Zou_q+(YqU=L8X8VNH#jR7=8P*%*K3D(usagpo!&E9&FEKrZ^$jS&FGf? z8qdm&4QO!WMeZ=l10qZRsbcQ;nyzqRE9Z=PsbQn5-}qLCT71_<`iaE?GP=@jhm+_;4@41ysdJ z2Y<$}hv0cxLxrAhWl;JEG*HVXsF6axzCbDCU!%{A_8>vDzc=Rc8((4FRBe#ONo(7p|7( z8g1TQW(XEKKr;UHTcl<@uR31;A-(~1BXq1`ZWl1@*QmPd64}U-hcnzy8#-=|v_iPc z8#5C#8@}8tlOq51EYhf8>;EV(+Q*t6l$*Ueu~vT=n}vCi6!uDf|8@E@)-8|E{w z%Gv3d`5H=hm@l|+1m7-u^7=#XG*xf+uhK4W2b7VfLjU1g6P;&-bs;m)H@kdP0u^?1 zeIB!9PGx!Zd3xA^$xwPO)8n4iO^W=hO0yYE-vZqOBgfrLb4eEPu={vM>hSFX{s{1bYyLSXc~444hmR|{^g9qs zSxj(pl68z4u>~W6hv@*(`>aUlEhas~0(c#4dzaH~FItv}Jz6sA5@{N@uExJ=msDZCr^ODw#a5Zcw%WnwWmOv^%z zSW=CQ5ErQx@k$)Zh@eL#-^_t3-+2*g2AX@uj!hubX-sU1pq{g6vc|OROm`T07-Y5w z>V7m_YkGc|NA>Gxhejz@n7vuAp%j3^esNv6MoR=d^zgHPW8&bY-nrT$XO~*z{)~em zNJ;2~1dz3=@N1UyC_^{F)b&D$1pobpQWC%UK#-lbZ{;GJXVYN_6-LH{KHv#DWF#|u z5sqz^!%uJ^`$xbLa326T0;`JJBk)#qv&MZ%pT4iLs4%5JX!8-W&yldb@6(eG+o8J% z@Ml@I@PrzAj(0uh9_A10SwEh8!7|=2J@eFH#?waS9)1(D^Q#Q$#Rtvt5gTWOqBbnijUjMTK4^$x0`J8| zmv}_9PZ9e?!f0q_-;G9>X)?lTRK^|Kv0E{kaavTI&TB?i^CFYEe`-$R@Ii#pfnn;V!~v$P6Zd|fTDja4eAZPSv4N)P}k4)@a%4x&@cFy-~bJ! zf?wKj&)JRW#WVp)-z43xA_);GCVm{#B27k4p6@xn;|;s@t`TTZno4X}$@YaT;AFMj zNQ($1Y>yWh-Js4str-rtv_qjRc%up4fE57$yE{?(ya8D+SZ zWVM)w{l2s69I(@31UM~zZQHXN{gD=pQXTSQ556|8o%prwA^2-|?pms!dzt9WRk~t1 z15G$#`U`B}zQ=3dg7x)|cm0QUg1@X}o=zo@p5~FepCz$t{p!FlKx)sj&%Is)H^M@L zs2%Rky;_7D85)*Tc0KKu`IBh~6C4`w5d9{wU$+SBR5l{0jEpbE`tymB z{Mq(<@E{FXtPVy$jh9>nV7i7FH3z0F$dEbA|fKUatx+< z*3!-noFP@bbd=j((O>8r^H+mB`LACwO{?#Q&j7j4@N1!HbyQsCj<#3sYZUC`=`P9- zo#mXTySA<~c3h4(JxL*%CmJ&v`71C!dl|;+dJ|wmo&EbmXhi!IlBiwI8U20#H)`wR>e6um|+= z`<136nc|*X9re-A@}rB;Bk~~ zi^W$4m9virdiw9MW{Ug)89&%RN@C0^7-px$3&~~V)9nor{*)`5hCjA`xvPJ=--{lh zu8vU`!YTGu zzI*DO2G3?kaVgQV#Z=kc(Yk$p%H4b-!%XhCPm}{c_ZcU&d#d*jE2AFnug)B>wFRn$ zgno|2P>w34IFqqZ!A+9R(kTdT5D=^3L|x6#`1rUokLT|g2@xc1)P7c&r_$V zKg@FTw(T4o@gg)t2w2>h@-uRxcD1Woh%R3>YB6S|bJe#qh=lp_Dr`L1i&ise zs|fy9*>5L~egY`_CC2+3--~LMk{wVbgO-4`8~+pZV&=Ty*GprPC-@2W4a+$Pz4rP_ z^p>xds)0>?>7kPB|9Y+N_UgqBl8xF#mxrJAt*Fo4ik$Y29louU`gw$eP;PkGS>SV7 zod$kt9P76gxG1z2Or1?^7_UEAqz!mU*6^cV53)xV32UP3+dLCUpC5!s4!9ASS|#3`n6| z_XUs^P0ise!NF(bsC{DO_hw0S54H_zqF~xAaP{W!DObI%i~B7mC5U#*Ia2Csj_CC- zsLKRoRvlYF*|{beJ_MIakN&VY%qwYVeD!I&=Ou-#=~DQO?c${iLtpZ%Pc1WO=w|x0 zlwz*N&PgG>_UEG0q-3Gkwapdgy5r*hu_~|8FHG`&eM&St(&IURZ!>NIBJ+XB;%zzR zr8g2L{qE9BEW$g@KH{JDRxeDhtMvNo$hPagmhHSMRj;sci1nE&yXv*(YZ>Z3+{lUa z0AL0bb_xGfm6bTDqPN7tu9CM-=|koJu&jbikf>}X7;`H__#{79iB8K%lj;?1XE5ji zQ=l)$u3|_Ckuo$i|2|mZ0|K!hm}QEu(`?8kmWjo?KtnY=PY##%h{2G2bU>5CO}g&n z;e8+9Hy!A2-nY=CnQL9Gs!E+5G4QwwwA6q{fHC_TJ-A+RXIB|Doz+-t{d^!8FS5;VT-?nkBZx@HSeX5mq(O9MV>dyCI zuor;F^mBnDq)!Vk$qd(|doR`{;JOXJs1lUudGlFI#vd$7G^eb(G{)7Cu~H*yfu0oq zZl>QY`YT$Vn0!K3=pS;0uts}9FM|&~Ea_4C=}QyDlL)*nMw^%F9*RKWD!g>dw5Tu#zLDk2eMYv z+^DBO+H`!*jRL-*Pgw~&E##@D5Q4B4Bef^{B{Thwi^O5Z9n~z0Ja)Jpj*p-GgGgn-}B>Lhm-GwL*+q+Mq%zSDQN^9q88EF9AvY2 z^05YPJc6{BN5^0BH*5)Wt)!#hrp0jYflmN zq49+6cnGk+rU+*-3C+dlbZG+|e_4bAZZw>^OMrSJ^>kQ;=^H%VkfwsD4 z`#&{eq!8O&VIWBmrcHoE#Owuw@~(1OgdDy@dI~rO zal7OPH01pD&)OMNEunOn3qU|~8CXr*N)vJww}Yh+sbsEj3izyi_A90EDJ*-~jnFIk z-{rPrL9Z;wikPoaB|AYU>MG)cw3bkqa>8ZGer84_2dR~>vSjYQMXl|>h>FYhd^w!; zlgNDEWekWczTQ(t;k#q@;2!3jK$!v`5mNDOy=`+Ow(FNqmH5V6XK zjzq$PMpI{K!NckwKHPALijqo9UxPu-m5L4_8l|Ms7#zn^q2ma?vGKj}pxL>x6!tJ|)rhsSfbB0Q+QfW6vvKE3id4i5?*CxhA@p6SHIDP_g)e{f*jfv7-$EeQzxc zN@3rdd0pEq#b02_^AFk*vc=i9SeHr0c=I+pedD>aIOuPa4GzwOA87U;Ewticvf<^+ zXP?@GB)jFuaifM?zzWSC>JCbR!Phs+(C2q4E!kT>b!i*L;{9~Bf_O+={u-$26O^?iNxr=GOK79FNwbY2nHf#yyrm;uj zSmQ6t=r#kJY13K5;_I@&0}}xUWpyVZ9{Z3eX~t1WDdq6%!L_%;Gla&k+L!C6EtFAK zwmDJWie9wwM6kPm*|y^~jlH1>26BG&@^%1XY(=KvP6|L^92-iHjS|>Pi*9CD!p4mHz|9LiQfS!n^Q%>5)7P zQcLmIN}ok6vBDk;UADh0a`B>->`4j;fG?->_~LYRZu(2BJr#Z5%=Dm918=6DQ^Q1< zwo(JjfQ0=?b_)`c@0s%ICbEEruk4P)L{JNz)H#TJ{dx03pAz_HgiCg9=*)ZUC|f5v zY4zTX-OF!Y7e2SzuFb}@n48U6oRy4!+aA?Z%l>t7*`~9H)npEzgE0>}RDe0YN)@Zl z6ldeY`ww(k2kTM4*09)y_f%us3a@^-k-@vA?&g0PnxV1+e@O)_^8AJ~$WD-9CdoXz zIBjfox7~BWYgb&YmRhlA_~T3O{RG_hT(oh_*nrt{2b$xvJJIflJG2(;AUq*{j3vgU z<@$gC{P5)6O{eAc{w|NzV7>SQDG4qC*1s0GsR>u`A_t6YOu`AR%7b3{OaG%q1dIH- zRm4;&BIPb^sE+v?L|e}ZkH4xdmJY2ul|4>0mc@U4xN_>b z@!Qtula?zu3o@9APN3-0HT(ge(OBC924tobs6%RmPc3#>92L>C!=rHjvP*Xi!*Qh; z8Vjt%LHoNzx2GLN^JIQVW;~>V&uymK>c1ZH`dsf_)1zkE8isAAJ#s=^)aB>z^_U>3 zqNl%0CDR6+phvU6S@5!10us9`|0p1*ZuR9RHwFmcr36tV-vl`dr$&62MN#jrAe87O zW6Xex0!`c$c8ujbPF?REaeF*26_0}{L+$QW4DiLq+5?hC_Q$LUBX^4f>8l98zbXrp zGM*nx^78iMinDceAG<@X_HI+&o+~O^&B$p$^YzuprI%-`L7>F`Z5WV6&d&hz_obhp zAv4&wYG_d(R`Jf$$(1o)$o1T$D8FLsVngM*s0fh|ZJDp4~TkQF4 zFGzZbBKf{)t}ZlYyuIz!QY%yW1~?`+V_erSpV}oSBc0t{Tzz3}{g#UqPm99Su~+w3 zjh*&5lW>n4nSjk^-wklCG@NCi^tca@ostiTV%gc2TQf%g@c7AYeXmg4C;g4iUtqmslFm4PR{G_K z_f()C*Xm>gj$&TWuu(4LZN6>L?@t|`J8>cb%WDhI_3iUSxJq<3%Ra?4lAdD9z%Z3u z(0^9jXT|`Sx#OJ`l)c3c1|-Eiq7xeGFlm3t!HK`exIkeO9zl#QV{0c7(jbTXIJO$k zzwJ^5qcv04_oso>NB@K!aL3^jU!?EysvPC~tk{JCqU5sy-YGZ9Q@F%;{w_3JN#%%P z!_3>EVD=NDz)f3qM&^n2X$Sgq1HN2Qm%hA5#FIQ5ZIYk>;=Njz>}FxPS6YcfW_dDoV$RGOXNCWcNwF z+wn?U%_n6wnv|RYq%p={wu}(7KRRH@5)qgunXupC)A#d80h1% zD|!Mtuk7SEE<^&V`qOI#@C=AVf@i?&SLtF2HXPSfhe-(>xqIq%DlPYYz;S*JR7TnS zq~+e*?z$ShI0r7-{#&~Dmac*lJ_t&>XYA&q4o(#K)6!MCF^>rYC1}2lM*kBj#e~Z) z6|^FtO#?uWxM$PJaKI;JvzC_=YMv((BsUYF&EUEC;}^2)M%{mk4pX0dV{t5#s*LXl z8v(@Xp9|-2AHg8$B$KEZ&MONUaXZF>={cF1im~uybqx(Gj;JS382ZNJQxg*vB_t%q zxWnVYxhyd_%~Y~=PeaFN>GV;*$n*hbgwODx8x3E55E1=xjL-gZ`p#Up!#y1F$8}_F zN&>J{pwVRTlg6Ndk<&BnN^ zV#)4Oq8_$5gQHUM@Q_I^&9}-H;}$o5pA9VHcV!frOSu0P>C$|~ZPKvO!@K@`| zLMVuj{VyM2$~ZPPXmF?&D>b#&_)jd!cQWldf^&>7Jc*WTXrG?q`*u(rk^vkJ_osb>O5= zG&5NF^3DZ52uFwPGRh%44akhHD7b|x^eccC@PpT9KFif1^W;z*2|a7|r0<}#TP+9D zKcnI>cwo;$#^=k{kE zs>x)8XsLNt2R$Bw@5-s+mWXqMncYK#9wFq`WhnvG2?5-DBsGmEYAhAs@9Z!D(@!%RB;(LE%BK)al5*iWj*UCB4B7XvN?!p^^h#|Bd#3va zaV3EkNosrq4JqjI+H~X;#FjD0^IKjL%g@L4l?XDpE@j? zr+b>Ht(Ti+`dr()@Y0OMru9cX%*6AwbdQZypN&c2bF!Ao>?b_FbFRIY(kQf+{T!Ea zj!C26)**huElr=_H~#_&{x5K@EjOQZp)Eb}>@*n9HD%zZMqsj20pu*pNQlrsDu8eL zQ=GxxXY#Qbh(G;r$zGGE3KCg)&DXd3ICX4G>UuetuZA_2G%9)3{;|WLf@26_mJuo&-KUq<)hYm$JJI2T)K^=HNZLRhxNMD`-LoRKAbJTZzXLQRlUhGCQ@2vh)hFONCbB7JhuTjUrwafP3eTw`Rpem;*k-JH;(1 zIwbg)f!f3pb8b-5HHGuop8yq8mEikmoL?#-*kVDvI(dmVfRT|8Fz2SA!E0NY1nXnb z^q&a8GmsHd#O!A*o@xCEODgxT6L7&+;BOieNFCWR4KZR4%CtAgYHL(Pa#(n*%Z`}+ zE|mfJe2iw79+d%NUS*;-M2XIg5m&1`&HI3O_^F`u_WSSjrgOSCwvY(W=qV&^VxuPl zW@!?Z3Gta2Vn>i_>T|A)cNVjxtSEMcX_4^ljh+|ZRvg@oub$~3we6})Y|woLI0{4a zn)a&ViD?G=+wmt}ZWsmjn}XHF-$B8cmTC!OKCXeiwCA~em{ts*QZV}bjR7#4{i(|7rv>e@e`uWNs z;K28i9i2b-Br$I^*r}O#PSc9-Yi*a)CtHo!*`4?WcHn~7gvWkWti_1mbNmQ433-2iPu%vFM4=s0H)r8xEwMH6{F%)B8K`#Sb26iSzF;4QAbr_embHuwzpt zEz+Ximn*@TN{>1*j|KuFE+Mz{el|!SICvwyaaXt3(jpJ>S%&uA+6R-=!%tj;O9`RI z8t?hz90G)cxzkJFgMRyB`Zu#uPS`doByYt*$-i4BPy?A9IdJI-ukN zfY=@Ttgp}2ZXz~6{nR`2!wY_RRH}yYZD`J1sd*dav16DxjQ-DN|A&&54?nklqcuI} zJG1$i)A}=yQzO0?XOZSQlfTU^q=EQf{Ktr@rTE~~0|)5$(L2;KCr>7-+&Z~Lva+d) z=l+g@B+C7UwnW*z$~@CA#8VmD1aifpuR3&I7o&6W$vBYd1TlAZ`sRR(^PYcJ6Y9c7 zWEuYkWiPK+MWYs^?p^$KZP*zm1V~bI73kb*`o;c~_()j&sB#Pkh`G3G-fTL%K-14D zAXD~$g#>1#bJv6I{*os?c_b2~Q&@bySRn7dzv{p*t+~vSzT_9sV&cZBL(M^KWq61B zoX33GQp@?lA~9k6{ZwXN0k#P_EX9IzUpn35rDySmSpY2Wk ztHD10D5(8?w%OThD4_D^2k$&h2GT3>wo#xx8@mee7e3w>x6I7Uuv4c_@yW}hf#C{@ zzhr7<86W@*nbkE#Yp5BT;Bz2o@tl~zB@zw`6PQiZ5e132+|NYQaGF{+qWThssZVph z;MUQyli=ZwNn*C>8-x|02QxkQX&%rq{-rfgqbmxqIYEa|xzZ5|O6tZDV&Ckp4Sey) zHp6BSQ`YrX%-9zwd?v3&0nLwr*zNGi8FI6ZHTXqyS>H9f)cxPOgERXGwVxN&YoX`^ zgZ#64TlYfFBIYynsebse#xb37%Uut2V2+BqtRU~U5i&b}Dk1`?Nw{hkYX)C}f{U7! zc0`IIYn739HY7xXlD(HYzNG3h*>Cg=)$ z7uAh%xBRS}_ZwK$6khf1EMpJ1!{nt4(p%YdT<}2xePR`}{^9%YBY9`etjg!KmAdCD z$ZZP6g)mMujVp|jpFC6R&NWZyTm@xGS01b&`=uz`-1_#G0?7_5?Q zn-*+-z%bkC$4|eDlP@}>dd;$~>~aPi3|^(n3My47zsnfKPdUZ~^}TwxRYICR#4!IjOXoaI`?oYRvP$4$TIGeM2C9I_Jl2kDr}3!;J1 zGsZJN$h%Pcp)Vd*^sVx)K#E8{2un-Olcu;PH)8uII>9ob>wshy?=E3OlZA~QOAD|M zfJ)o)w5sK$3)Z z+p3_bCqZHiNQAiRp#t4#ssqEw76tbM8kw>K284yKvWJOj>xd4ni3j^oqr;$#;rzfd zFYSTe^ILi$IG^{~SM^>v&*P&rGfOSOjODYxf^7{xe4+6%3frs=R!F`|8ET%svV%E+ zf}zu<@FA*4k6*({eJjoS86FU312pozW+q%LWMNi+tE$aeEx#mV{XqU*$XQ5Z&epRt zCWaBhw|5932Pco&P`%#XUMx^lnPT25g8x6h_jp)wo>ndDI~B{@mh5*X(XD@xp^YZs zL&?IGLxNNRJyaS`^Metg+1|B9(iU7W;Y^2*l=Vj+f^P1G8z$m5NHGw(k5&$J5iv;c zmlFi~#55EWrlp@Ib@>H zcI?#8LkQ6zMNfOz3#m;*rOmz*LurdZ8KqRkL5zmcF!%1F8`iZfodl|wxXb1g2KB~6 zLqxz$y8m)L2~ZJgtRX5^jq$&bRe zUp{|6EkZ;LIU@Ed&I??4`i|=|&;G?Fw}}if5T4ZA5p&(MsFX4m-*tlkoX7W7ez1Y{ z3qGBnSJ?2iw9wh44v43CpQ^VNGInge0>!KL{lkY|z~RrUxfB+}+O|v8eGtquSW}uD z8{3f0qVQydZPpK{TCN-#-WfN%<1zHQ@3+*{JxJTN2JYz$-*rW26%(UJ*Z`*66xx>z zfBjLfumQFYNb3ey=RLPs+4!&~+an}R^x=$%SsP`R>`ZtB2}Je}VyvV&Jfik$n((2& zFfc7*f!)6;f&Xyndf%65Jku=n$1@*ZR3z}c_Ud?hT%u`Ld+5?SwHZrlEQ(H zxm6_$2AGNe>0_va$DaXuC_{Qlr`y0x2pXav1b^&bS5KFoOsQ<)U~T7AhpdM6&!0#T zBB{`-bz#f&Z5GQbCsWh*ZBeFr=a>hb(sJ0s^^p#e#8?ra3@Gd zNhy3Q@#JB1A}3VFw~3W*{og42M}GYtex`g*<3+>5tp4sh`fr!m)B_`$c|leA1HFd5 zT9;MHw3@3dKEA%};3Kwxexy^28|btQG=834UJ7{+A|b0%x`E%!+VTv<7W&)2>VGGK zS!^J!Rn+yX&zsyb^1pF|ox#QBAkR4#${GU4PC0|g+4T&hLh0yZW;566#?T{$( z_Neq&2bl|ZDM3gpvLP>8Pv|hhg|_CrFHUJ5QpX(6eExKTc|GcpA%XsF4`MWNGc8kV z)d2pHq2+`uY5Z=~fveGyj8@v9tmADIdG0ts4XvmbV7B`>J`Leh^wWbt8U!T&Z_B~= znWAFg=Bj>S(QxIzKPaCHDka6NNX}OOIyZOn-s9otlHMQ7RWuxV?V%%mbd9TbX6pq% zuty4lm<#^mjCu~E6@X`qPM!#BJT^dN5_;j9*dyf3O0Mw3Yk<{lnOzXLgN0~0cJ6V6 zdH3RR=by4!-%HarPIL;jW%Opn@O^7Bz3t!-v3_oInVUMktj3jfTPgaS#F zpaV)U1|D|iZCCgfx@r=*f&c^3r1g98>{h2bQ%ET`5);Nn-?HpMRb`L^F#M(!fY-{Ml(xI4 zrwt{{m*DE^O3PXe>>VI&vK7?~`8O_vEiy1bNyeu(H-#4i|DP-~q~>ooYKg1Ii>5AR zbMrG2Qk(6tN2ZQ^)BXhT;MqnKrq*2DF^z^Ckah+>-gBUM%E%DEA%zb!jsueECTJDj#iQ(YaD)15K?mThG}|5(qU-dv5A_rT&c3&hUu_gbrZ zc~x-hB^H;Ko}tQ)W0JBYq5)!TI#yPZXI~D}6+#>O7_0MCApQSl!E4FU4T-F0o$00d zCbv$M6E2zleN^Gn!R0Evz!ud79K0r$HcI^NpNwvuc3eH!aCEulnq=c158n!cq)+0X z=^(a-Mgd>dd#DX<;0T?vyd>6n0uucD1q?K3Y-ajECeW|<-{?%ujpeAEcn?#wCFTUt z2Y>phZvjIfB=gK9NI4iI?mEg9Cv4*VyMCLqP7GkOvy&JZ({S^zECmGkavZ)KDpy@i z7arbaud4Ag(ANh=ZA2V6r(Xo<^9%o==0?d*=Vs=q-mUD$Z>kZ85zXkwVF`6?h)9_V+$p2ai~U=U>1( zHt`v^Gdez$lOcg&r&tajxU6n<@rI~QlV8Xcy+cR}9w9?Ot-J_OaK8L_E7#wp5%0z{ zRGGJF@aS@@`=(nr~e0YwQHsIL)pVVs$d@+Ao# z9PPitS)Y;S_bZpBr=MC`WE0wNoj)i_$&L@AfI!y5#)-t7%B};m5T4qU`Z^1}H(9;D>$1q+v2|M1tZU+1h+Uxa9Y(b-s_*2hn|w5`S`AU2TW z3;v9Osi_)JGe5j_>(*F{q6W|v*)bo(u*d^2bi=vgi|Ieomz*e$K2OtAEGB5#9@LyV zmfr;cB@(nyouQyhe=%#uzFo?QVx&_kjP7F4Cfvuz&)8dA@LB2e{ECN02+6=o7fwXl z=0Zj;-<@%icsK-?0PJW*n-5aJiHOZ7fYd?$CLI9=0*eZC4J7!~GHgGn^Z%8>~Vj- zbYA24`{Tau`+QvgT#vK!e4Kp0-_LlxU$5uO$^Li4vuZ$}efTK{P(;nlI#fq_UDc>N z2h$Y~gUn<%bL-N@T&zZqYa7NPRdmrk!)WpgUFiSM|3dL0_R5?zyiV}S=0f3EN?1{g zHt%UMhr}0ZVMpgu&-I5KIXqTzE1L2&G)Z0VHTL`7w5kldZV@y?!@~;@z7DA>&)oMz zS&*5>a7MMK=a;CCj*ieJTd&4UV2)iB%wUuhg~3+O2vs8ZU^g=9%8-sq;I+PT*0=x* z^F4gP1iTB_6llkk|G#$3a3H3_ymX%-os@{Kf2lgxjqtj()_fKiRbH>?(BZ$&_ovf5 zFMJr+Q%?j$PJ8-B6~Gw|6%68MV9Jl`D+?zYO%!{2`zGsBrm&Lo?@hH5d%>iGS@OjN z6Gv8$@q?v>1$1m54-}Z5Va$x-+cVCa`K7CW*#=hshhL5>t13_J`Y>F{Lc}Qp1$!i@(0AEx#o1~ z^k8!~LlgA`2bwKzVB`C5Bn?*pWRR1IS5bk|)`uTeA!8AMqo{%4G6>t>P>0eqYEMD} z&yA}(O`YleIZnfKcdggva`B8(ER-xqh1Q3-knXd00Py({4NW2Lxum$CJD;R~#SVU8 zQqj;(P>sH|d(8LC#CrAh%BPJU)&y0WJQd8FEMb&ko-f+w#d$GR+W;pm%&FBL{+*lw zBuGO7abUF<{`wckg|W8dH(KC>gM%RGEPJ{QzV>VegQ<)A(ctM6fyeZJu+hMj9`L^) zDFx8l%H;H<5JJz0DDkKSVc>JNJo~O+)Gph{F`TcDY{MB=r>=@TnYn}njOnX)b;UXBHVsk8v%X$#B1gb87*!T2~EccYp%=L5)$;>S+vYHQ5=Q#k;P)Hgir+ZA3 z7h!HGVBX#?4Nn95t4;`c&E5V^a1h-Zoz1-j`5eSxFkQUA5=2IJb~kiZ!5V-RpYD2~ zKq?*-a~3(iP7Vx)^NLWg9w-I>N3gybpoL(^?LaGPOX6(2%ohY<=(y15V4f|*XV*4~ z=3ya6*K{x1ya9}S`E>!)PV1cCYK*$cH)O?fiLv9!Fxg z1TNmoG`?J)QCN64u&85gbJ7cqE(uo9c+%CGeXL!6*R`VTwbs4fMozx_?IL||bHV#R z_IHVp1Vl0RXc_=Yl7%(_di~d@;EMm@0eN{gsZxKb65)DqM^1nghH3iGpI`=y-XHMj zl~^+*_7Gt-%D#gZi}Xc{j4tNpmA_EcP)G~os8D^`EZx@n1HBbJ81w3WXjEl>dV0rb zJ~FvobNwAxlK1N7qT?7p#jUk_r&q)R`I?jHd&KHXAD@#jE3)W&Oo(pIXr=jBk?}`i?YNMxY3K3vRRwLfJx!v z8yHixt@L?HxGd5BaE6(6vwJ6ZQH~q>7MGW4y43I`pI;Ela?BHpRtuyqx zGYTDzfs3dF(M`TEn-`Yjbv=w8x%;%k{~W2${Z$sv!7uY!*aU)X>Eukq*_Gsfd9Ej2 zHq$}>v6bHbuTPG5dzgqO0{oO;XR+R5um}>1#wFCV{LE7vQ8bq|A~PEi_Z%;I^7Z>9 z$?84-7*JZ`|1~q}5L3EI#4_W{%d(I(6LYxC+m#UrHCYwq#AMwstFQ~hPT7RrP;ija z@2^t4~~BwbR(hh4k)cWD6zYbeC`Kb;U7Jab1X zPE}bLpi(KznxCMXawBzB z3mR1^x*|Mo%Uiep_?cPjbJJAjF*t7J32BP~t}kN;V3CKkb*rhVFyWFR#EKD**81N9 z1caH5Uwtk-2~~}LBU3+8P$SIau$PX4I(^Ko9l=kAO#uU+Tp)3>fN|cI;<2N~Y3+sJ zm3Byp;evUI{PQS*5CzP`|J&|;7?%}8p^nv;l|#XfBmT9mRl2XVsgm6_^@j^Ar&h)M z>`l%Fa=`*$0l_08qWXh~`+9C_4#y?_n5?p}J8eI{Mfc_9AlVCA49+Pkn^;-B>9evh zi>-gP{fs>+FF!5nqiPg_e{CJXR_2J}qq(^ygrQxa5_o^p7NGiEr>~{gq{TqD>QBS# zug}kmfk~GU0OIb~7a#s9{O&Nx`f6e-I(w{8OO^-3HnaOLz;^Hcy;&>$J7OnNi-2zU zB%)8Rzva&zX1UD}{q05OLk|4?vlm&(^ly&`O|$ZsAJ?dgP^ta8SB&dMe_R6*1lz=v z5UhwSIb|Itc)Kp!CM7sTt!3d>(Q-x}_aW%}ks;2#Rpb?7Pn=U#?(3o!`RQA&D6-g< zV|lo|#X^X3!R|=JMMxJ+1_#}c$+42s%o{AZ_2GHyk*=*}ZpfcIoZ|;;QFBv}&#BE) z?eYDLphyZe&m0Qb8D0uW@D6C(e~}_eTBx6!zI_ulUpjv@!!=h(&-K`?h=qju&xPfY1j!Mq zxlgk%Lx@PqT;wDxuq2|EgN7pdQ&7gTYry+~O<+XzQ7FBuglLaALFEOva3Z7o> z!C%wqsEXM{E@zQ?y-W3U9&!S4f)&%AEX+lZ;(q{`k>Zo_9Q0fprEJ*^dA}+b_4sR@ zorA2nWb6e6T_p%UfT@#?TwgE5x_Sz#=qo1|YdAz&EPj;C1zK5oMTIo^jk08BY)Y5B z=M#61vi#9cPX&7E`7!`OD{A_L6t*UOcCv+F7>zK<9@eafH>A;lMTb3K#)YR%lw=HN zdQ)zg-}!g?J?+zJ`aNfChi8TljTxW^)|;NICRyPE3S=uJ0((iN7BfdgOG*2a`K#S3~$ z#`d3@kvu<2BM>erK%c-ou;h@Z9=gh3?0XrR@fBNz?pu|NJMUjs$K|f6H$A?A6av7% zou~IbDRj~aY=65F%j>N>nvJYVp~lm+HM)Ha`bvD7&`o`F3mQ!Yo+|(&;QJ7qV9}?? z*c`Cy#1GxTgIoL=whj%%8IKD(Q$)vwraMN`!YJ}~f3wSIUo0Hz8A-`JaSE;(8b%S* z@*&5o9@WCXqs1KB`OWSJ!nOLRVcv#*=~Qo;!J#g6!+tYEGp!_78mVATx-^cKjAE9u z@uOGNye8$=uw=v4Sn{=mwK8XQGnh{K&q6ntn91qzK+ozpi@$Xi(2A0YU@-W9e5rU1 zY$$^4H5;va2;pQW2FITZ6n(O%UFB-JiV?U%_ z75$wk3CSs z3mxdD^{Q)Bx4+!YpA!po3#qq$w^%v-keUS%=^FYuAizX`Zmw_L2-pd}%?o28t^~eo zoqzN3E4X|0BKOda8P~F_gHYX3D-9P{E7I>>{d2PNm3~cYuh_@JieyCImzT8WZHU9G z_rTm()>9$XAz_;yg#|&gi>LR@T_MOM59~c2gRHxU8(%|N4;t|qf$Ja*(o{KNhgYD> zD9p=vJbd4IJ%QC|VO5>FI*HqIf+M$lCBMwuGxd@e1+uKNd;Ps*USV{(C$oCW3|8bJ z?Ddo%Xq0SobBJ1W<1yTnL29@rgZ&l}AA3WXZm;S%VwG%<)>-SZm>5ISGeKGK-FEWo zLF;)>XQyfKZLfoILgLAthFHXfb1<*lXGdm;IOKbdQ~as!u#Nj7ZXjzYY2`k#)Nnrs zLv&Nid5t$vy~#hJcC{n%>iiXS(?@ZXubur~kT{nINR-AJQl9e-XC-;gXK;`_Wm4PJ zLZyu%qxljHa^hXI+LyeWOK_h$FHR+{A2GAC6z{lR+>Sfy$!v)s@Lj4zi;e1rB+*zo zV}fdK16P=hFfRTdo#BV4d8U)kT@8~Jy@{YWCU;i3)^hYHXAY^XP0v+N8bUBB?c0j;S#)vLI6-$ErNjTUz@0d2TbsK0ZM^XG^Je@i?P@^C%9CE_}E~MB!3iu23PwA9w71 zz#svWJeUo^*-@ESRY~baHJIoC0#G&(sZ{`y(dioCtt+{^*X~GV^Skty7r$g*_1mO5tK69k2{YVBLwLFR7>=yhz3sC zDs%DNk`i1WAM7o&lhfmz>crBs2Q-tu9a}Y=FYNTDFsUXx@cz7BRZWq#=Qf&(iC9Po z>Ka3Wv5@a^><>EeeofKDz=chY9o6h++bfkqie#iq39-eqNA5j^l;V5_(pz-%jcAcY zLO3Q#e94L!Z7{o2a~s`MM)@h%A*J~qmH}Mvv5@ZAurvPJJNs^{x33AHq6X2xu-DH% z=;LP#zf5ge~$mw{>OYpLMOg-4{t;6E%`E^5c$gysw`Z`MwU#likDVgiDRd z%M z@gECQK|wgs(4XCYEm4*AmD=WIP2IulD8!~FL?od$+oM1LBPZ1mOLj^xeT%P@L69qO% z+4N~toH?3-tp@9ukr1!O|>N&>ZZLRmeaTG?6Y%OHjm{x ztUeiXn&ZqC!`EF!Zn}UB1MqeJUH56vrY6d8df2I>2P_2G$gD{cwh(vSp$rbIz6#^X zvNthh8&8q-M(i{vZnqhaHg#lv6~DWDVj>*F2cYQllkZh~48ZaF>t!SgrL^BAskW58 z>J$nc=HsZTG8ZmwaN#i?a(mn2@t?#-WqkhA|!bz6tuw&KZF`CR)lWziE#GmQ z$Ff=3aG_q$c;fGa+O(YoVj4s^>6XZK%?o}lQR#6N@rz#n)OIghZ~KE#{1+v^nR^}n z@H`WdZYY|yd{9EfBXK7q$YFACNK4+qhNX%V@Zk1E@mtbx$?F_|$YbC+D4fcN=0IT$ zkbWui7?74f_bI11vk3tY>L%ZU)8zNBdd7>TFgp9IC2BwVT20f_%_i`YkY;z!O&XWo zj-at$Y`&pm1S5(MKR>xJzxO0Bmi9Yc47QO?&50*MA?dpVn^N(CTcYOaqaL;_Tc2w( z$=R#pdNs>rHrTHxrSBe54`uW}us9Y4*G!v}vY@K`pI|B-8_1bcm;Uv+SN_M$3B=~X zFa~xN2xj0f5=8R&YcM1*i>?ri6#AnVLm`-?0$<7MDT|JYiZwW%la`TRJ!1A;9>6ze znfP|1lEjfvYD-vNR;F@Qk$!jOVRja6>p8DwEPVi*V#=FISOja?d7NXalj}ar^ofG@ z0a3GoANo2~Bcv7;4z-E6c84N_^!9`j;uBbReBS~gGtKZMEA%RdbWz|3)%#&TfO(<~ zhzPw)Oxo4X(!_WnVW$Y505|; ze=M5=v1TzpZda_&O?WdTk&k@K_q0y)=qD&LDUq96T7lDizRBA2d)^3#jOH2qiqmDk z+Bdv6qMD2Br**^9#{%KxDH(%`axr0p33HE3QmyBUJ)eX9m-$a6NZ{$xzcsml^E@aO zfZO6`{V%Ad?$rq`ZC+Va8Sy0^atHhQZuNoKybTX~N<@lD`l8Ev3HXY;o}2tq0)H^P zBH1$S!B0G*?c~S@OV_|%el+!%oW`ZXDm{u_PdScF%7z`qw>!^`lIKhTMEqi4-8nsu zds!=k;z}TH89}GQYZ+^mTHZ1G)vxaM9_ zQK7tt5mqnwHtSaZ9yFv$wN%}wZ<+_-g>`Q70tjJKpa+dAiTsIpA1i~wt`iFk#)tcF zNm|>VNTAm{`ymAd1+=lF!s@VSG+Kn4^f^$eCk>z!-kSTq4^D>!&fUMAsj{*i&DSL) zk3E3J5S{-}3|}J*t+bitR&Zd;kJ79NvMSDh2%>r57r0#ge)a;~R&0Ll!tyt}TV-Bg zbsp#ex%CwmqBovR?hPbEy#p_kYsc>bwt)J)zN%s%+|_ib7)|lw*m}SFq*hMd;n1P( zJZ@dzI+$uYd3SoIf)J9&OS}sLaw(qGDafyeDEm}&lQru~=ur>amSigo7&}gf9Bb@g zHpvO7uF$832e%F>>X1{i+>ez{^f-0a^Yn#ns{&fR|U%QgFreG!Yn*CdMz`0N9Ot)3v#j$iuG) zNnt>bgH z^=WG5v(hOi#0aUGF8%4@i2*LnZ2c-$esGeEfof9`1WIpDw>t<|bw|N`61V z2xhs;nt#NYkOmLwFTzjr8G-ygEN3lW8~Rxi7kkmUX(!3CtBN_cOE-{70qV2-xI#)J z!}CkAQDpXnCqhSZPkwn33xGRHkG@)%2kzTuKc0so$rkf#>pvzaF26V~Z7Q`za^0&A>~0l*$K{}`B7avHAz&T+S&HkGDHRrJ|+vakU~WS8zC}C9rsuue#T`HKC4ANjZbaZ z$Oy16cTjWQ52GNn;iNBYe`&2Jc9r9FkhExPci9_(oW|^2?X6)oPl^6aT+ghH#_2ww z+LBVG*%jLLAP$jXVPJe8;aMMknfFfC7h#3eFIPQ!mqh(N_vvIp_R^>Cm3EzYnBHU* z+VNUUj)`^K=K_D1U4}9`Fn|_AC3rCrVzZG~-vG*kp=* zS!GHJ$)m65Ga+xFu;hE(r+ucYL^SzKJSP4m59m}6!*az460~qBz!1*;fQj>hQbSIr z+UvL3$YxtgbW=`d*&K)Cp90J85Aj{($AG8-h5i|Vsm#MJI#Mz|H)VqNHl<95Q z-m+eMyZlAn|(fy>noW*b! zBr+qIK+9ZKm4@%;{7USKw=Ze(1!c`=Z1VY1!tdpoyB@G|FW+KJX)S&i)py%dka77| zX4cNPlb?}{?jHwdUj!MdT4#R$s z4Nej-exLpA1>`izZQ78!qE(kyKtX-QU9Mc&TvH)_x*n*3y*-PR+H8>EcuSZ=RXu6L z_TS6D0(9Q45_tc3p}i8jg*i^r+S$2^m`8(q)|onbX?56b)zGRsycriy%R}K{dV%a9 zN35I{5%lj@0&pWUiKri+GNW){5EKKv5Dg8lnUQ4`<(-3FqENe#8Z~Bc>cd=DC1G&P zYp1X%RRyVgkL+bbDR^y68obY2_ZjmG={qBFT5t*qELAL&imd~|`>@A>$zBAXBWEKy zapL>O4GDW-*y~#6GNK-xTbm{mf2XngriDNJ{vn@UN;;`fU^YWcN?wdl;IBi`Hqf2^ zwRd%~ZE znQPzqxzjrK!7ZGH(RQOFgYb&m>Nb<+E(7SBP_EOhu*hu%_?!QDBcO6?15s`5gtIr| z7$@BAP7&!A4%j;w0^mSpXX}19pd^vSVldt2W z$7G#iL)g(`5YX;IfhqiDL6*>(V4!6xLHJbi(<_owO)E-AZlo)l2+(59xA-!iJSPZ_ zC(w>|lwZOcP_(=3z$o)33CWjJTDxf}R;`7T0ir^r&lfbPmryW0<~1-h#{RxuYVuR= z6#^HJh@7^$FkN7ig22`i^!eRD=?el>nt(l@|2y6Q^A>y;Tq2i(DG>5nORNwLyvHjn zw{*NuX14PlJ8>nx;`xJ_-v=`928zSsLdcLp!Y6XB4KMIV{(Ztz0VS`DaJoVw#6^Bk zvH=bb;4hp&-wW__d=h-?8{~Q6W4}AU38#PYyC5SiDDwX4Qy`B%9vP{#^|ccdJW-9E zxab`@#gdlw@mcxZgpD=|Fb9)AJ;aeF4yJar*d;1~z9qAf3~>zl;|7uG;swRHc@ETT zvGe>t$Jcy(BTBjFau(&vDK4$KWEy4XlpV%wb$nEP8zsQ+1L`O)=A(Y+%7D!%`eyJn zZ#gs3i~72EcXjG5-t%?RxseFRwK05BH!l$|729(Qe+*7}yNrRS7S!^t`_B8z(CwYm zele3P5K=VZL|E zQl3~nv}OJZ%Y6CiOvOI-#|cjR$;hh7_Q@}Woe>vmTwFFE*!JvGlwIYgbl6qVz=Do$ zpOi6?o%QJeZ6@s1gl*N9bKz4zVg($p>Rj#G`IS{ymY}tr8|hmJ)g7Fvc+hws*!r`e z;7JH=b&BDI2wW=agq-;Xj9LQ5h+O!YBy(36D=)7i5yP&4 zGpXxZfNL_RO-Z3bc~)7qTki{2iq}jW?|2V)Dzm~UkSIshug8{o4*Vz<_M+F%alvg% z!bQ-DO1y(|asD@0HL_-}04HHaXxv@dOXrH1zn}cvsN0-EuooY$9y-Y{JF`x*w0_qZ zp>uURb_bCE2rVt`+)vppD@WgUn(s+j=5D(Hmjy7j)}h8E2x$0}Ky%6Hj-WeJ3?L(V zgJ~U9djvVe{#-5)tW^bm4HzEjKL(v^B5-nG6#f#(4qhT~bpS6>H91|L3n0yoUHt6l z9S47G#Pq?Yv0{RPQJl1IC$)v$OLw|o4GJFOKSW(*2n5~Mxpl3Lzdv4s{ul$Rnu|i8 z?c;e8{6d2p02!VQ(&uTUu6E0Kge$7=VC%cCJ@1!2RRQOz)rAjr4G1^~NmzY%#dGH# zm}nSMJA8=f28JgP>K%bZK^_YhTkL$Pih74)EHlSihwlR$#2bMAH)9_J{SH?Yf#@&tw?zaxR?rZUBS;|^kU9T07A-c} zqz?mEMPVrK%|!yvFte;N%I{fa1nWfZCw@+sQ+ru`5h&?@caDee0AJuX-!*5`mYB_y(r4o#6jRh_Bb$S_l_I7LL^TK|0WoM7}KhxNcJnUF+sO+z=q z$I+uuGt0rLK)A}2R~2Q!DMb?mc2df;80B(Z;OEKsu|>l6P~8+xGn53U+3kQq?w~$} zM4qnu6JD$Rfb1`EUBJex;bhu%%HhS{m{I1`k2bw2CvTj~9##uQ3QtC+N}Q*1(l*Mt zNPYA?_YPhXdxWJ}+uMhlvGZ^+D#zdK?CN3@r`qn@c)55PqbA6!oV;aI{y|lsTo2fx zvY^JC4&tIW0J+3XX&eOl!x~cmCbVz_zJl*vGr2WY+!HOPtxatSRcieLX0}`)6$p?( zhp1yoz$c&NAWd&&PZ9y%i$8Bk`%dW|`xCcFonbXMJoTU}<|}|z0{V3Fp~Y`3RMH4_3IpQcMPxC6LQc+h6~7)m@wgDCD?ip(rc~|}CjRdOWFTWl zc$&G`^zLti{XBMeoHqe#4c@(UDToU%1Q`WVWODm_&yHm1=p^RJ(*W}rghgJUSMh2bp*dq(64+P&z?z0 zqNCHbNtS%Eb4+j}9>HlNVCy!Ul=sIjI;)5L5Wo>*26%W%Zj<+XZdF`+pfb(ZX`Flm3sE%bp*W>f9Yj`fBFFF#Iox9!Jq6qWKZ)N|I$Yt)zL6<1Ucipf$?Dydv zzKYpl?$wga9#zO4LuC9qya_p9xw*Jhz}kx{ipc*ig%(pJ)5*W{q@d%<+Zbg~qr*xv z&vDZRe`9G#(_RZ0)7rAsyLy6a_KD$&Nw4b_Etg~V4{^Zri(ppzGicw_0~*016$9YF zz??T{^!+obGNcjz>*;}p90?*s4Ep?uD>%7P2tpN$8-GBZn2g@kYi~L|(8Am6e8-ct z8?gn@d?yocg5j@J& zqH@r{m%X^r84L$_Rp_yR9s*Jd(Sppm%%MMp6%n_b-LAh)(ex07KgS(!3Un z>yqi#8$ax~KtJ!+)2-uZ3Zci>`R`=<nk30q4*X*8QnJ z{7gAPxYFoj{qXJDN!pXM7MNYmS?S?iCf zdUQm6WcjT`Y$+3I8ZBV$(Lb~{Moqe<1gfNxJ``qs$v)98kC>KKlu^CxR9aUTz85aO z5L2z@djCsFW~7SB54wfj?~2N8DNIVUOWpH7&`CXmLz7#xZl=#g-3u0}EIeF@dJ`Wi zwzIQ?qO^j!WdaZxP^($!fcOe8hWEPRBg}e5Yst0AvR9oEnv#+rTE5 zP<01O2t1Tw!D$qpdsk<$CU{HO8x1AVT(+$5H~}{8bJsc_4qw3G&?yPX80y5@ADuf# zaj~K?SV^QL;&$})-Jw|4%TU|Hj^gVz9`HEZ-iVE5_`!&r4&B*WUl~6xexG(8=YYo- zlOU@zIC(FP$z^Hf5hznBjl@VMS=C9d0xU{?D7_DPtVLCdkMDF!==HJ>CE+ey5v6vH z6#lIVwgBfWEy*l$1NCUWp@tPD6^b@}@UpX1)fo9(5b#XD-8z?^-Px@=-O|_DY2U8K zt}6!ejX>bJzfXorV(hcMsX4wGP-Bzajl7P0_oUm$JU}IA&)8xpWTdA?Q9uGkUFSMy z^PT&=$L4*VQOhnqcnxqjo@uMU$P`U4`r>InPs4$Y5}qE0>g6_bpP6>oRyk{%PX6wU zIXsq;;lHSuyTHuKSkR%ika9g{>)T72Ls`GYXPse2nCyrMh3_x$*{9{Vu5##PMK8_e z_PusH3`EtWiN;4Yq+ljr?n5I`C;Nzf0em@7@YLpM59g-Hdg?=>y~EbD^J}OtODnWp zRyrn2oJ+R*X3c?)Y=+Pb3{WGBw05TTXk+_Ixfy*=k~-v*>?;?aEjZsjq)Y-7O`y5t zA$6cTx{|SRb~}N10Ns?~?!mj0N=!~4;8QtGHsA1r)?|pSEdSExEhr{c5#(j(07xCk z2o>;`rzdZ-1~ojM;uw;U>Xck8I@NZJUt3xXLwxbUK>9mDrw>Dkx4MZwM+6Ilss)9( zxVUoux8}SjJK(5SlFq&&FIGAFy5~ru&I_H)Iv4VW0(}^?@(Qnrd3v`ngm#%ep3+sVhc4qU^?3P+t8LEJDMNoV=xqn1mt_JAB{ z4~VHU^RE@@ir$NOir@ZK1KuZ2B$^MsWzqL*3VzsM86no-&cs3WOYGECbp!O6B z3urTFXfK_gwkjYb7;fGAmCm*5h)L(wIQAXd83yC^1&jHHmfLB#l35RGX{E>Ls4VvE zv1M_uRAn)?7f^uuayBZjN&&THF~B(kQH=m~GSl!JY!v_O1(Z41>wd)$nBH+57Iku5 z!)V3#CRy6_`qP*ST`EM^-Pc#B z<=ZZ}Z;f;YUf6dJ^LQem+7{12{ab;bWmMbSvYF+3R*2Y(-ph`Wl1ONU>`>%7^WkBZr z(hb&Pr866#6&Gfj z7SrNC9DGxb!l;}ni@yqI@6V~c|CjE=DjKu6^u#PLT@uXV4BoCDgb1XuZ94V3am)_k4H2qyo&08obROQQ>qt{Esngf1#1 z!97voIW0v8f>g*rDPkWeQL7|{KYKx&2US`sWQ{Jovd*V{^K{v|jI@!%Ix4I>zW!QC>(cCWV;VV}WowDp8RT!vj)=RMmfNY!7-YhBAQsyLSyVcb(naIl4x=^j#Sa z$%WIVmQvz)KFfNGNiZ3`m{EFSB{V}xp=@Bw`Zgw3)pU^cThvFp1#^MxHy$uYDjsBE0Q&EzyHHP+oN22^1+4Nva~VaF6R((T>P_?ayoh#~2{CF2jY z{og-hjyf;1nD}1@GS6S1-p`I9OmtpHa(Y(Q%f*$IPPd;G<4d+*-jE;Vo9SrCYh97f zZMmGu=Z`T4vF`cLUuCai=z#anhWJqJ-S~lK3tnBo=B)rt^7nFCqu)qGtMrArE;4Zx^JH_F zAgF1@m1E6YRMecR7LP6N;^X-p?t{$^!pfkUg5IzdjD;??SQCU^>bsnEtC%VpY(?;V zGfR9mMN4eeF~Ej}2cg#=7@D|!xk;ohSOh>}^o3Kx`}XrU6zueyB*N2d6_5wk)gig0 z38oSErS$1MBaxPJK?B0~l-$W8g+-`i>lF`1haVrr3Q60qP>G7=M^s0T{)+x^{qe3Z zgdmDY!~fjUSyjw=dnRQ}w?$<@bE&1JeLy@ZkB~$llRn2<2FKh{#TOa&x{%vl&~o=V z@A3$xm~{ud5^j6Pyuv)cUs?lL#c+12H{9wo6tvB$9$|=SCev8%SRlgdd4IGuJo+lJ z;&l(zZ?e=PsTiEyHuU7H7MM_Te#JA{9_(0%>Am-JQ8e`;Z(MV@{qmk#B6Vw>OE$Rs zppi2AEyvpi@%(e@Lq{@P%Lxar@duxV0qkn8zLfXeB&ims8;d_d3y%q{InO@X7Pj>b z*GlYgKFLP_=M*)xs_=08)$}39Ir5uXfp4U)SpNlW;goxrJ@$fxO`_#D$%+@+KMB4Q z%}?HBFWaxH>&d$G_)-7AF0OM>J1`}0YoFCH6EGI8-8{Xxu8HsR9*0s_8bw!P#B*l} zf%E3Y9HkV!n6o%zQlXG6clliIwp$&=^Mslre`} zT~%DZ8gU&;@_i0~WDSZyK=1wvXdL#h_E=f#(Dl=a1sc%T33n*uhFq&WIQu1?cJ2y#)i~ZJ&k{e8Ip7J zRw|imCzO?>%$$t;>bfaTh!C_PQC4{saz8q<6Sx~zMqno`gfV680SZ~!s-VVMy+1L! zFDe@ul(4jCepo#Oy2Fh_HZxf-PSzn8JB>Xhq(7u*EHTsU$499sVAo@~WEAA%lFiNb zrDcwH1;5t~ZMb>FQ#=f!-kHfI2C24~`_}o3wPA|KOm$4*;qviw!)YA5^HI7V9%Rd8 zvR;|oKYGu%cLljVO4KGL9h=2xQ+rb!Zj_zz$iIJOem*8kC1?y^d(C~_{+sTDrhP5@ zrkvCqzn^_oJdK~#p(LQ7HG6b2A*CWNxOV@oVTa(AoEMFl+7}4j17xGGAlgXel-<`w zohSysA%4V2M>AO9lTA?28l~M2ybFIO?okKSEstEQQzXxKXde($_DA z94LM3X5mr#a(`XmgdFX=J8Gl51dQ!2qn=SK)XyVmBm_lVKW^-kk#AdO<$PNC2)~tI z8~yTV#+|K&Q@5zHsK^FhX1YLgK1-jrvg1sN(ba- zWNRBV|9Huw!8_!ismQ^z<@wi4_cB02)o5Q7l=G+n%am;7IoKi2>6F!taI{r-KQc~@ zx?#|eLy+F`aISp~1pd0L59Y>xba>;OG}GHeXXY%csOSVX zbl{SyV$FTe%O8a7V4T^oC)mZt>=3iOs~q^OTrdor=_ z(J*>$RiJcOO*qGJBoWu&4}yGCpu)>J8Q`XMySZ?U=3wbTp2G|0wDb5hlheNSSacF3 z0w6iuTeQ2%h-C*R^yT#5&trQT#c>Yf?H<`ZKfseobf{cJ%0$olcL!HqQQf~f44o^t z-1hii&Q42{Uj@|}(foILxny<{_F zbv*s(KuuY+0!r5zpYZ~A5RcqN49(nRQDw0WQ=-VAuJC>rH4Ex#eHK-6h>(%HF4n;jB zE8RF|vf^5U`nq&qs)Ts3IVAyc+q20N48?C*f8;32iGm`AX%Rma+JoV3^2Z2*n8O46 zKZ54(_$+VHj8!G7ki%t_eHkslT>ZyyfraYzqct_c2)u#GE<00uA>n4_N6*#XnduCE z6r__Wl2lY2+rp8Z$9AHxBQq>@WYvxHGK8|Gj-Z|7ns)z%kuV=q>|4~!;ZOGT~9KGCg^Uo}k%9zIJ#ySS$wIFjNqtQ&v z-fG>V`8&b&xs!W}FXu_QVmcD^KIHdys{>5z=2cV6WE5JUD!e!LwpaR z!RkGgFAcUtEr>%!IJL`E0Aitfb0>Q0G90l>3o6WI8ZQ)m-AN16NEC6YPfCgrFprsW zrUmDf`7gp_Z*;{xEcXtYmmYqqxE%lC%PCMRGaa;lkAHMpH zb@>1A3okSM)}|s(s>KASyDRSj6ea4j-}pJ_ro-U5=3E}nQi1DU%(QDv4x=O-*0XWE zc@ImU>JU+644@tDg{;?#FgD6j%#XQvE2W|@?AmvaW_jv@RQ|bhMQ`c`w_k1>>Mdu- zl!c8p(BBUHxJi`3N(zodD!-IeT}SQQ;hb99!x^lE7P98l+}xGeud;EOQ%>>Xi#2m< z{rZt0{+%c2`UIK6K)UDfMl3LqcP{Ve1QwmP$fZ-`Hh3Ayci^(Igr$C1hW#2E2SIsE z@8jW<2~5xzr3Tq;hGux$4K#ElWvffsO8i&AiRz zS7D`Q;jy%^Efi}#%k84qJ|wI!s_n!D)NvIxb7T*r6k0G-8)g0gkkq?WXfzSP0wqmg zul{kUf!*b}X(ozM_Rf-#v2kdi*Z&^0xBPDJnHz}3+;!EB1xb!#vIoMmf8 zl9}(?s9{G(g;K|wtp^jwM%cwkYZsDu2R3J&oTKaNT15gPC#IZzJ=Hz37DtUDEj8s9 zm`hoiiyU3V#Ai&A+V%=VHXL6mfetCy+CL|K$&Ks|K5>_OMWw2tYAM$Qnosbl1L@#r zoRgTo>0ms&vK!Wi9ii?b4sLDK4>AuXUi%ml7>nUc?73`gjvq{aBSCN`@@q4EMce^R zxoZ7V;Frv{b`MctWAnL4IAwL-+?sf1v!IAAy70RJE@m5{vxc$n=^V3Pu6ZU|T_lw0 zQrC6G?-PXpKt7k6v1jJmuVeuh1&DzE=Py%{Vxfc8It zLaS*DtdGwYqlywk!V--6q_VybmVe=gZaO zLbAb+KL7V{$gC_uy7CI3Atq-UVgEDyg6AEO(Us0Lhe`(PFT$?74|?%zwg>c5-gr=Z ziuW#n6`F>o3(rIFB5zOV$tazBH;xN$FWp#$h{MUpLDv&j`TOIh#0YvDk_Kfj1aIro zpby>DTzfPbpmH`-KTz-2SFGg;hoD6E_Iej@jv01VnDnl<=y`;+-Db<&74@)O9tjkj zmJ)$W0g|bkw|_aw3|5`dM8efv{e^gt>VFh0WRvLO3lSY{HcMNU#G8r1JWu zW0*N@YRB(@X^0vK2cSrgjddpDCP7pMhB#~W1I;4WkuSj48;6erIpqH*u*ncKRi9j& z_$%Zsm`MggJPxQM7X*%o*RnRd3O=YMe9H{qT}sLJNxKa23EivKll=+Xo7uU~CCj#k zj~;R)^(XQ)oQ#fii?Op8#Mn+=U=-L??X?HLjSZ5d2Z|Zv?Z-bABJ_6=9><5sX^+$+ zm&{t^TLq1j%z$Qz?+$!S<0m@DGfchf(xDy<^6}%r6bR)3Vi-dF`rX$4N*1|-YxCu0 z#qkUZK#Y#W;$cS2rXTqWroJQvR?F>_V7k{{KI6`yHeqm+h%}p|ApMZi@ zG)W8br1=Zk3!K~yj(?gA2e;r}OO z%E3S=4Oq;{$pEf{;&ca1ME{WsZYLdI@hNJd#%}Wb(6GE^tSaQ>EAw4JntI*YSJ+z3 z5g)ka{}~4@(hi&myPnW8syob{Oi46wj=e9hKYsIc_Cq0MG(F*UZbPol*vI}q4u$Bi zOSejf-3%ebjCNe zI|_xq=9YfQ`%v>ip?~H`%bBBi>9_m#U~fB9`{rV{emj{TL4*IMmbA!1nUao9Qa+kI-+9ifM`Vwx`-%sH6cRTLC(6Qm-YdiG2J%KOMG%}I>KXXIH*zlp!C`U{ zBl@W|3D!(gpZ#yPC-4M=ds-Jh_!}@7li@Eb(s_M-^W2TA6Ot;|gmiXw?~uJa-oVd& zoyb76qdk=4sn0n}gTFiYsZ=*R>eE%J9n#pJJ|~nPaYL`04T>ztC~Fv}O#*V8^W57m zO@-qCx&wdGWJ9)&#vPjF<+R^MS7kQsWrWPK`S~-$QScK>i7R{> zNh25gcDQ%ELM(n&#}7^d^J2y61RkwNEE3vzIUYN8G4_V~jc!f1G33SpHz^*g-V&mW zu_7R~wu~nF#=jSN=Pl7rb}19>cj0o(s{^eC-RR84|A(`;j*7DD!p2cRN=GCW8M;%E z9J-_%=}-g#Q9u~FyF?nKVWbg83F(kfN?=Gq8YE>X>H3|~=Y5~|_uuz@E?G-mOPKpU z`|N$~>$>(iyc)5iFa{V$k*Eq=CdE4NUN<%dR+xLrP?hH8FELHLc(;&Rok`CkTc-gc zMP<*aLf<>=deW#x%e7HNP^?m56vQLuJQSRK7_vI&D7-j!U-ss&ve6M&$GfAOU1Tjz zDQ}8f7pqV6)IOI)g^+Mu)7PC2)`69LSI}XG%YvV(nl}jNDgrx@4*!3D$14V` zVrkz%jw+Mix#Rm;Iz264rCJtz18q8tlNDcnqCj4IN08O+xFznT`&+TBkG3kfaddKF zAMZauUlOq8wh(tSg_5E^kN%*KoQ)bYRcN5iC@FLxK`LMqUFYJnS#nSbQMSGhxboT^IPV4S?9>%BRdB=#Ww4M3F}h{>u(gWQ6Yai z_5@^>^P8h7*U3!y^Iv3vOfDB|*%wOQ62{{*@>%^VK2i{7_M;cdiY{4XKO<7#kD{bCxCB=56p# z`?CpRP0Cm0@dh-JY_2KGH&Te>uNIb-$3J}dLZ*MNNo#myO7+pBN4iEv51G^Tm7a=- z2>C^M*4KC0yD3)42_My?-;c|-IB+)0%A0$u3%Kl$phng7MyWcz(EIUavohbZn|M2} z$y!CnQkOhmFRH7aZfTieB7;89GG^Zjxo9?jsS26Jk5>msk<#0FKH` z-O7+{jj<>-7M2XiCcxK7!Q)BG|4apUYMo5{x3l?p0KrGPtZ;kmMKeB1KaMh4CDV(Q zk#R{W^%Tw$8E;>`eoM(rb0zfjxc29ZGTpZcY7?pYN=WCD8J~!2kEWSN4xiiG7fenX zmE61C^?L$u$bi{}7EQ@;)$QBdO17%&?ed-*69KYH=m2qDqbE!{vZVV4o<0n?Q5>Xu zdqy6Y1~xV?H*)Gv9+n6QXg<{;ayc|$8C%@Ix4KI>`5fiqt4 zu0RIv@|YmtYX^}TX|@()P*5TiksveC%-S{dL%}sn2hI{+K%7lC~8!Y4SNIwIle&1N?e2E-o&*hL7ZzX^4JWT_{ow zuzUVakr95pLzkhrfZoVkw{}SQVwX_qQD)#}<-%QERXvOqb(=~lWLB2qj)BeNTZng+ z#=(A){4babhnWuqv$kBs-o8%{BB786&yYv)|MET%x1c8wnh}tY{bNCZfwUYV%Ak9O zPSK{X75w586LrD=*yds$y z?+w96O3usTM?@S)8&NXEb7U!ZgjaHVGrF_M}s@qjb41x0w>14&1pr* zf+N~6$O1x^bebLah0_+v!}?>jh$%i}x3+ktvZj0c)LiLlAhY#X77i|U(v4+%2$#Wj zJfAp z9zr(w#Z+xvGOu~LmwJ|1u_@u(Ys642MQ9@O{>yoxz&stWd%7vWW-7S`Dy=f6w04tY zsw@>IA4j}d$xaCl)%-|K@f@6PD5dx(8EdxVZqXFVlKI#XeXj0qdg!)8?wPGc8?paq~B5cXrAZr(8+j;LI0bEB4;NrJ|;0Nco}P zK}(?g&jxCv)hsB2T!5`$4c|tdt}&`Ow5(3XW*;-!LaCcw z{l^iv2os%a`YlPIv`1+RUsIG8Me5wVv-@6t8#$Th)b*T|S=)E)KHlVIzxKoAyAwB(;DIPp`?^Wi(q-E}_E!m+9oT#0T`TzeAl7cJV z01p-mV>5p`+2(+wR44_PisvSV#_HaX!(&PUNme?ZlNd_OBmn-HM-{y>reqN$DEl?W z4tQ|?^HPZA(S;g1Kk%H!gMU@)E72}G#x-jX4utINNEuSPDa=9ej#KrOH|9Xqel-GR zIif<2bJolMzIk2S&MPdIs`gac)Q3@^x+;PPUp(1tCxekukg8f#&YipS=DCRx-d9~K zGP`+?(IMRI2HTdq2(#1+I|@ZQJxyAbL5;D+M(vcUNS1ZTgO*Am`GxZX|!cryto0JXo zil!Q6IIa%(ZXw~jp?g1LcpIePGTSZ$w{IFtNkZLcKJL6i;pXoCRLFB-v*kGneE5PC z%EDkm6oqdeqwxBvNI-)i+%;AH>$PO;&@#q_V}s>6k7JQz{f2gFtC({mBkaNgwiHj# z(+=Tkcy2}mm#DBQ550^*eFOW-r{f{&I2N$~v}Yca7R|{eug+=e8mZ_1dqo?evm$d6XwlG-J<=C zvbtdv+V@pMCC~NJo>nY@B;PaJ5<*B6SyYra#VdT9 zqg0+UYG}mlXCimz8Vi-yhAt#54~hG+fsYK4SN{q>NHsJc(ik2 zvUu*-b0#|%nu+z^DqaBA6z~7tTXbdqt5{O&Io{E>L@l?NGC^ih`UKvJwg4vY{CP9w zn3ptyFNYV4?_Q=?($%$11etUcx`u`IR_RY7ew=7=c@Sfs6xp+9<~gOWYOc*exLJ*K zPTRfEC)=-NBaSe+M_!wL)s6Y~osU}QA8Yai*w|;Av)*gDzSYFJmj5(pw{-W_X7w4> z0Iy5SWmd|u}D5QO1%&YZ8DV3Ny zQU#Q_DJk+V&r3>*8378cuKIL}i-%U6cXSdWoE2Z#&+14EKya;1$cBhwI8Utkzul65 z6=;yKib2m%zVyqj|&5sK8>bRaiIN) zyg59YyZWF)G-JWDtcVyQ{{mm=;VtMxZqp$j5s!iQyDsp1VOy)Mh#-x(w@EEzzRjm# zuNZmUx7zX!-&H>|g^v&iViU2FTh|Nm3)W~Fd$Fc^amgeomA+~9$UA%9Q`wj$^Md41{Q$5wcws4e?}%oHBy$_948wLd%HI%S(zlNm?j z9$oZRUDqAMO%2Jx{#XQTic^nJ}3>G_*|jbmxjK zRCyPDbdzd*YtPLkoH>$g=;X5scx>FuJ%y4|2PS6?-snl_m5PvqsYeHyhtY?;E6WaA z7YQ}mGhRq7-zNlM$7w%NszZ9oGP^n4)ACtNMc+uC@ui}op~z&+;7N&PiPv(}3@>;W z^m8xh5Az{~^?#3C-`f6zQFHD$Bzs|qGnM(DK+Dwu;HE7>^6CD%P=3AMcvOC5bJC@t=!KG~VqIJdiy;a!# zl!RS}OSiSGjCc*ng3vF|);BjOr^!ba6h1iX#|eRU85|BDRkE>OJf*3WPyTSH^kd00 zgqJL5wVSI}OujyRU=Dp_s=96SVDP9`zc1l^U{GKKQfG2N^OdUr20>zBUCD8k6a!&v zbOlA9Y6~UY`JmwgjIv%wIGeo;!NyFup6U9vKA2zD>_Vp7FU5qV=ZD;5vDxxJI?gcM( zYb3i|*g@%8Pt=rsb$^tST*t>j(j$L-F?NCEWGT~aZ82!;=qbl9RtJYzJ4R&!fyO-^ zA4J;LVjBbB+P)Y8_^--TMW1yw_FeXMw|3(7J;krx7tq8Wpp|c=L5gW~@oTn!%^qY` zkSvHv8cFLSL#`;bO2NaQ>P3f0Xm=p6ACDr|onO)A$)r#MWU54Nl zl@z=&`T>)hmnb|L%+B8s4!Oxya1kKw1T#gfO3-v3C=21H^m0d@+f_o8H#7^yPZ^!+ zEE;IC3U|QuThmbqr@uE5donsI>EDUWSZ9stzt@#^-F@dOYUp_?S91-G|M|(2z{BPr zQ?fqi*ocHq08;x(;SP#8pP#&_jahfK)b4DL>Xg_}Cy=qiDKQzJ&I~kG2#bL(=!|rVKWY?syeg zDj9`CjA?SJXD5I&3jb$mg<}OcJx_DH-o`iW;KWj`rh?|c}?V@gj8s%c?%_Qs2r;)Tn zX)WY~1`d{XHts7TMsB0;KbUjg(VtSLT|OK-N@R}26z%@|Mwg=^{B{e@b0yqKOHT5P zx8>rWRy{$BTzJVPm)evD{CI4)La5sQ)yw8wV|nm>W%c5~QVFO2aGbJGv<9Dk1?b!w zD}u)cQspayyzN8!J$_F)pc%YWe+K{P>$D^<*x!D@ux<#ss_D*VEJ3p8GL320OYq!9Nzk&I0tzmrQfzbN-d}I z2Nw)--B_V(YC;J^j+Orel|9^^$fTBbgNN`sGAaM{$B*|bqTguOOvo_**oqs=4IZCJ zI*e%PmE&4H{`GQAf&nC4zyR@d5TpBAwFAE{60F+a`ZcxRd)K~ZHT*LlLDUV?Wg2>k zaP$qq^i{~`y&YkCA=U6351576t|$4aV;Z=_Mg2=3T0Y*l`+d*7YFg7>OM#7nfiLDp9~ZSSy0R5*YpCAfj^SDr`O z0Mb1I62-@JW4Df)gNs#_MU)!B$ph25E@0dIF)sPVXn&l9k_(C+w)U8&?1>0nml%qv z-YZny5%uJ5)AlCvDC!5R%9rZH!YMBzR6qjR`Si~ zNyB|=V;6yM5~4$rg?@^5GgX5IR9z5TUfA0=s1xUYMMy?%IHX`2KCQnZceYZ*n-B&0 zI%cl0(zknl%A*ZvHhfI#f;Fp6#n5+upw9>SZx0FCATBR@lotHZ=8J z(c`eoPQR@}*6re>Z7-$wY-~KXoAc445eK(Wr22I(j_)tt@k^j@)LMfI(RH^_W4Lyl z(-IH;{hq~L^2JTpuvNv+7GdKX=_;wbG+uYP(ars_@j>x`ZgHN%EL<*UPSBn$Vg6)X4z4h^M<_}sMgbVvnWRaDsM7knC) zWwEkdxgxD~0D6o}^gNo-oM=~s<|gV%H;JLsk)mSs*$xxu3~$FJ{`^%ugIre@(=dT!m7jv;&(HlLxymhBX=QFQR`2-jZi<-JV*U{ zPwCqF>ux93m&m2D@6yYQ`&;#InqjP(PPOZAMyq^I!+xW}XotUwj-%>n2ZrFDk39nY z7?Q-yIdBlN86lL%@$PkcH;nmyKuDxWye25>691`UbhnByRqSrn9hk3)==Faia_v>m zA`757yN|wWYh{$3$z95^souPX?6rc%5{@|YJ?PRjR8!-AIU;$F4MymQ5_hAt=|63N zH{Evr;avfDOK6`b3pw3!U-mDDeA41G_ALG+oKoR%US37R%q9AksG`GAhk{@yG zoAMv3pi>1qg-95xU}eEU<^_*h?u?+Zg|$phq5nwR76^?$GjHVELD zuLwpH-;enuNx3kQaL<+#TxPG*VBZq?EMzs9_$DeKKX`(Joqn-c^N=m~>A*8p-)^0Gy#g9=HK8D-gMO&UFeLTYVA%HP2obe zltTf;sV6C)BLRp9{`#$QF@}%QsNLecXh0f>e~ug>uMMAxKaA7O$hw>|)qSL+@NzSg zpZ^8<^tIZftdOwgiz__?XL0OF2~( zFFAyeIr9&L%%$GGc*#`;rNgAA8mVjuFt%rch>jkCyq`8IEN9T zG%aFrMGfX7b%+Od3CFQ&0b<5lGIMFlwXM0>SG1)yn6x9BvL@yGM&hc?`X#Hz2~ zl1>Sgzq7tjoL^;A>e@e|Q4$FW&~BAEalB*-xuEKPtiZ<3@P;6kw7e;M>Wb-Ynw&oH z&O_bBr@THzn;jiYTv$x%uCK@1BV+-9!P%PouwXm6^Um zel(qOa)S?-z>deZ+Q?VY#JfVpZ=s#X^PXL){hf0le@$I;%Z!irC)-;6Xo zT;sp;4%4+a)VJD#Ud_wIjVaJF7k1}0VUh}~$sbVDHJ>e<4ajfpfEu>lnTj+Mo*HDj z6i(;9CxUEjeCsr`3f)#?USOny?c$m)Pl-oC*=WY=Ax2YSq)_B^`2eu=JBz(Gj~x}o zDi254H@Tl)L#7`lHYj{>M-7dscl!CN8fkpcJ>Su*7)!!wq>Z#`{aB-?vsW4uMoujp zxuj{LC!I7}ZE*u}8q%(0<^bLX%V@vprf)DI(Jc@o{xg4Lgdwk~$@G*yHW+ZKFVb9X zMR~Y#RG;AExGID{#AK9Px5{dX%WhveJ1yltQ?IdUp!*ka6pZ1t$!r z9~zOELw;wuY)p}$)DOCSy#oIhSDMIHxTWfI=11~jF_gZ{QdDmnyCgr9wx_AVq96(3 z6!En?>q{Gp_m#Ei+=`3T;|_&4Jw4_nUGxA5s?4>a)@hl)keje-K@o{h^RB@Z3E2@>)Ts&K;U65*27$U@}Sf4 zQMvBaCU2;@>y;Z-LdS%cO=iVMJ_YmWYgRYUX>F&i>da${b0a*tnk?OfJ0{8N9*>}N z`a_L&UQE8q=s%&Nf>xg{y%{d7u6cb&Oo-_ACoR!^gOeg4T|}Dum>FpleaNslI!9hb zG_mMbIJ5*4s#CvP7$U@|98aC2zH_F(92x({pr{b4E9$0#Hly2A`EZ-oxC9CYGO;?K z|0CLrAT54Q?|&bZl2r{upH`#mF$#Nc5U;z9L=P6`GP$rZ_m;Wp?V-jJuTI=OsB zijZYu`x`hJx1a`&-<4F(kZY_k#UYtN4D2Or%EEfGc=xVtx8CN_9Kq^3ahzkEk&+q! zws$h(C;(akst>X7LkASSw-DYeIBxIQ#8Vu;{^><}_?b}!i;p}W1@%k;OhwY8(~kKS zIkYyt?M1%Seq>?smnorFv)kZXepujsL>VrHqUW;>_}VA3_gdp;=kGhD+-vNx4|9&e z&rKJX$dLl8Y_Owx-q&iS=`uO1#Hm$-QeNev50*<-IJ?(gZ$4lFI!p(vwOrWVYTfLV z{J}khbf5G2;hOo`)v3eP>Fy5P<{4i8Oof4mwPqAd7ltB!a;)LxR~E4UHx!E7;$?v@ zbBv4k(!d|=KwbJ#LY^yFk6Gpd50YD6#zi)a7JJx?>>64d(>@%b}7hsW8&HA9i9%_lbBIqD@l_B=ey%wm3Q(r<)E z*MFnHK3|4^*H?)@@3*$JOtx)R^)iOpbTdjz(12xp%Kk0Ai#$fv9R9ZALu)48 znIyX(JNG12w~;n$F*{6^`N4P8lX!bb?Rn8& zyng)k1zs4nv1CerYUkcpMYusr7T=bMiXHe8EuC6Qg-e7(x<>Z8^PO$VkET%OaAD`I z%GLLK-gE9cz90W|^s~339{v$gDuyIBr|++BWde{1xk1d%pCYZT2l_ZV3)hHv+{8Jaygiz(|rPjkGuS(*D{gdF> z#{(JoE9kZRH>*M81A5*)NlLe8_h|iOz1H$RFy&^k8bku?3K}FTtkoO;NRm|u?7Xlg z0MRQ*ZnD$UIG1R-*$7ARvEHZbhi8RXVdH3m>rRiN*&+&8??LV}dG8pY->QjTu-r<-%f>G*P)n zBK6M>-V@wk!895q0fWRGVK&nl6BqU@#Lyvn&qzq=nooNAmmm|;S?YH;090gK3SIS= zOLc`Hy>%T^>Q`EIv?sS1!`7tCaw94nUQ zZk)b<;DfGs+qw{NaGdBE7HV^c<5Dc|7bq(h6{A4&1q+2$|5xh@C$5}O4ZBHOfCq-v zNT77$OgBNhkmRmN0NBmg_p1An2BB+1#Dm|SII&+n`e)IU?xCKL(1YvrR0h18yq_|1 zPf%ID$sr(PTb>5J)Mf?qhjdvCW%VxU#zK-(gGi&aOO5yGYyA5TBBMB7$WJfc!I&q< zwYi9)i5>cI8~20fY2tPZ17A3^3aFfEJ%UVFSEjK$aYS2?tMk;^AQ)3KwQN(F1RM$G*@?|fpUK)-GVH45ye!R=y6wM$4dHqQN046hNMN zS3$@~PVF7yl+~Q6UVikZYV2`$(^gB-T#}?ViZWYgK6p-3+de0<=Q(`?gkn{5gN&OIXvwF#_4|4VTXXkC7TDxEjGYSY9tic<9s(U+G zxUQ!GIALLPqB4w-knq_Oua6W+>Hlo+=LC&k4Xwc;Af}iD8kaQ2%KNao=_NK4)e^-q zo=9a96*&ItcVbmbgLAvl@klP2U{!x95G+0m30EbIE_94?lBvs^%q6?&L1KR*7go=G zb7<1R??=l)Yu`J8^iSF^V~^v-{|1ckem|7oI@tmFF8=z}rQo#)$k!25X3!;;FeY=y zKOlw*!y+O2_W6f37s36hW!_m~T}j5pLuh&=|M-Yg!Ml!*sdpdKMdPl5)=nz|vSO;X zW#%LC?NM-*rU*d##u%h;f_kPTRTI)Bhi8sQ*wW}YGpbAVoEy_Hc3xH=iF>Z}yDV+D zKv2>#|B>_VkpqLyi;xhEP7(|6gJS?b(*xzten0Sdl{y!I$z`vmsIWBZmn&X$X-7b6 z+j#1H;z%?D(5)jaE`Z>(zCla8;IG#=x3cW~6h5N987i}8*eTY_lY<811&ur#bS~CS zXx?DJ4D@$*_}lFCLU>Y$iGe0wR9zOdz7EpBBUzwt&~q>OZBzV&ewVEi71ToapUUsL z&S)H;)@Xi?Yau9>j=LM#%h4f>hI|gh?j?nqPFpwtp>xTpuH*EMQp%fDF*idVRI|b# zR}S2>?;R$%v6!HLn9MNSt84Ui0M)(kki{VX&o+qneYiuCW<7>dsfIg7vdVPU%vH%d>gtj?5+dZfMfA_{mHM&ptEBb>RdXFx5v9NU^(kSujJPHA@!0g zUL6lEX7LSWHmnB_6GJPkDmW{sB=bv{l{fSE*_$l&l8Srs3GR`Qmg{f5eh%<;Qw&C2 zMFjnq2zboF?=Zq+G zE#4sw+4;z|86=F+L5>YqW|0v2l!Mj!rI{h@G$>8nQ0X_ToB)eBtu6J5p zd&B|UNpxnMrA2v!Ppen)KQ98eWboV|xD|+rFf6EnH0C%s8h^GIEb$MBeZowTCTIuk z{wQ2Zcz+9b=Ic=qo!c$)l|ODtNqjD{mU-_U-=BuX?io5qa~4|e15mswQ9th#RQNd5uK`x1V56Hs-kyRu{)@uMAPGv+S2lc;1lOViw)FTHcd!m*1lz)|O$#ZjWtp#K(P;mXA7fZ@Yvm&VlY< zR?kFbgI744!f_=Oc_|8QwuOqccU>#6Jy6s*ax}909?U4f(YhXHY~C zKBhSTn@CDVroAhf1Z^I`&uTF~XeQ@brg0 z4mmsf_zI~H7yJNsQ`u8Z26F(h`T5e}_)`J&-^B}p%-y%MNHQtiGaI+2@&gPKnm(hK`)zO%oX7`=%S?LrO#2uON6mU+xn9+*22J&|6cCA@P77{4g@z5=c>$7gV{- zopY$#dyoc#@uB1-$iiE{U3 zjrMe}L>pdhxc?qQ6nO^q-Q_px2CS0!fXw2p7!9-(d=6xta9A48b+<+h$XEB+jW&5s z5fgdi8&eC$UZ&4+5wZ-yKXD%6g&#{6=Jpj6UJXFB$MD&+*x^)2yQAh)N<;?US^GIt zP6YLk5<`Cj$Im~&@n_W!wMS8uDo)s})@YM;QFl#@`z$^=l?&J+{M`p#9gU?vyUu4y z5=)L|M@<)Mzomgtu+?8&6zDQ#RX!!f*f!9#_v7ml~GCdd=5qmk) zXc$jj^<*s<1lwBdt2gtwF- z7t{zqK=(}FWm!Hk`T{Z94PQ3{9i9<<`{xy-(qw3PlcX<{7_qwCTHGK%nn};l{e zSt8i~H7lrt72uQAgOfnzl#AvwAN@|?9Dw|qq1%=1G1w6trTCqok58aOj$xVaR6dfC z_f&#VheI;tNTKSekNeJR8HS|UDF9U}H-t4xmlI_@t(u>7w$bxnYQ5CVYvS1lfZ~1| z;Cu@Dw^Wd? zoE*}5VRYZ2{k3quaGV)DW8=)KPZ`md@?T1fp4-1H4ERh>k82|z_NpPN9!v~j#uXOZ zmqUrzwm#l1-p*{%tjmpjzP>I`i7TFWvwFMr@X1=hfH(zh?T;WBm+6P^t=YUY1a;EK zjj8C90w=T)FbUQt6dlG};ZRT_@{dQq{ydZ?O~weI{<>IyhNA8T$uWpKy5SLv(!rQG zcVYqKw}PV<0HtEEAB;`%Sf&-G{Drt(&c>{Dpo}2^Qt5%r$XC=6w8|Vt=dnc0?XeVl z_G)L+=eNM+xrHN=3G{9c2#5(6zF(nFvxj7`M?sL{imBFi!IAFWcVX8MK_uexcxsU< zMY%<98=Iuj`e)Rp54^@YR!oB{fIIN5&%-z2?k17Iu*te37Zz$dj?!N_x|kB#ct85P z`J37z;ASmg5AQaYtY3?J7>^A+3>F>mZy~=mDid2)d}C&soS`B?CPE$tf^XGUj;w+d z2%WDeTU06`*!jw%eeNIv)NX_*@8^Qbp_j)+Q^FO?99<5nv67W97|M5RQE;6i7 z1j8N&+Td-Gs^()9BNB3+he8q_Po+?@YaAw|nv2P{A)LpkD zV>@Np?cps#o^cjP7};A?RF%4u5|kvdv)|YP&;Q_*G1|<wZP%%25m4H1972GJm$LXd6`t^dG8x z045gZoZ&XH$sKQMxiGc{k?<)bx{o;8{}vm}a;g!eT4k$i@?nl1faQNjhRkx|gs!3U zUu_Dkg5m&A$WzmYE>Q5o$ zTlbgPbz`<$QL4=U>}UEW$C_%be+b+E$bpXu|Ik52j`v}giN0m-ngFHCp_*6K=ORt- zi_Z=K#W=DPD8@xE5aBH!>q2^Fzw?;3v-^6d`l(F`NNJEMy*miGF;pK3IS&DCU2(%# z8NnZHA}965n}bohRR>OMlQ!|^gCrbm9ORii9Iu=0AyWc~uR|(~g6ZZ(;oxGJa5-mm z1w3b3;U%G-sp`{qpUK<=KHUou0klu}>lkiwTk#Jb%6nibu_FGe#PnBQ!7^nWAFqMc z1e1&ZY40S`T)8#RWv$2%*j8Eveqe37HUF+B$`C%N44EBNfEiRQX^)Bv4aJx33KVDP zk^!H=F+2BLp_uYz6F0x5&U0y|;I81nt_S((>r_{sdUagA2CisvCDHjy{JcB+y$yJu z=YM%0L=ts*ZPJ^!thX4og+U`9!3vX`MoELZTbDLdqru05iw)8&Ny8h6+c0ZNe{LPA zdHPgHF8>S4YyBjX_pAlKr5+g>99ODVd`!$iFW=krj+vUwPl=Dn@ z%)pLmgYo%KlB)g5ynH51l7bTIxOO~5Y&g8$p3E52emO+Fkn@@LmCGeRl^F%?d5;Tc zH!UldvRYO*P8-2d&4UV}yp5x$V6eloMzkZlMR^=YTvINrP1KLBtn_`6|N7nZ;`w(% zT}OR{Yx~{tfWG^EpShFuQu@of(Q}*U6kfxiHLExJ@asN$f4v^>fjs|;eV&uj*ZJu` zvkX}NtM`C$_K)q*Fd_`2%d)-&*O-Pwv~qua#I)In?9~sJ`^YuBb-$d;FFu%dYu-6L zS&Z-2TD7K#oW4okzyp@&+B)#JfvT+$UJ7A&mOE1QECv{abP?HNBe=L+7DqsICN_C0 z&&Tr_8zrsOC?7`hoQcXRWXG z@W3suHn`kf_yWpA!d=V;dp&!p1M^y_Ly%Auz~Ga@kf!6snjQ*N*>6nF_E+Z7a+CY&iYMTudI+SUOzVsoP?dH4dG*vHAVHT z+NlgK*nmD!Kt1{Omp@56YBWBlxqKIYK=qQ!W}w*UtnBf~@2n zb`%67j;{IUn|mu4gtG8~cF4y4EoPKpo>l=EPd!p4Nj98>GZ5d+c~Jp}J!dhpL!JmV zbWlr9ppaP(BHjs1vb~epu@m{((yUXToKU&0{oHOV4}dBs;Y5P!wQEa0HPp|{2y?ulUW&2;cBJ*>>0~u523)r>C|~H80P~LD|$z9Ywqvz zxzF$<{=vEh958Bxyh`#=2pM-EV^V%#cq*5OHBC_N=xfEQsHw8LkNHKL1I<1V!ZPyb7IZuEe_#00ea#8jG1&+ z>g$ci=T6bpi`jg2T9=fRk!{!8G28L_yn#~LR>VPwxK(GH|2=Ua7ZXtbTM1H25{cgw z_Z7$8YI|)rSxvW1%heu1q-HqOc&qM~?VAdH;3Spn)|Cruy+8kjyJt`lLSG&Qn(mRq z=HT*L#*PeN^YXZ|p*jHI^avTW3QFv4A*H7HQHoK-QR}ea@$}854F+9Ru&Y(Z9o8=rwXK<|tPJTT+~NK4A(zT$ zna5{V_Zf;3bJkAor6xLJ6!#IR$1-ohP~VL-4*{7D!xdCzOI_42>9IS8|0CqZO2`eP zp7dFuFa&NDOWGgdRhJC)d2E=SJYYrq_w2~`U!*q;;By-V#s&k3R{!}tZzQW;xXdM( zw%6(#fHDbBP2}#5TIX$uSPhgHfU2N%G+;N3O>w>6St4hsUt2uN}G_{fFYQnn4KAX5`R7Cw#y+WkBzG)27-j+wGjOhWwSv#~b0Dtv)yo|XUmt1V z2XxPzpTh(k^X$kh*#p7b3+zI0^_QC_vX?PVWk^L=m}7MJzV-ay)ea^M%-hZ?zUOv# zLzNz_;mU)qPIHRR3dD7GvazRrfW0%WNT4X2QHn~QQrOE|fkiD(=Vr3{#Frps z@399YyxNqhcRt33|GvOmefKlUX47SFmGe0`LA?UI1G`FZ`VsxF>VU<9@o&-*Td8OA zQ4D)*i<3XAo(^OGMxECT_J_36iS$4X^m6lJ!>JA0y zXV)ee|0OHl{qZDyr2&3JwD;_tDjhjK&{TTwVmt%(tt;RHteSV5*{< zInm={f?srQOK=4Ru86=!H{OeSR@&mbe3=o-CmV$`MAa`Eqi1$bBEj3ed;4tfkSun8%RUjFU5$(DaC*pWu9&W+gX)T0be-z5< z-KGS|07qEVJ(XQOv$?RS2+ib-m-?3|qHns#W5O13WAgirYn%4~H}8dD$FPHPp$*vA z-T0M5=0s}DNd{2?$nAOAAx=-F0 ztQ(Pz3JFH_-!Ps2J5>8Z zPU88;s0>V~s6W&VI9McBCYS`O&ePxz4*l^)1P!yoRPlIvXb(Nv!7(+sQ>>UdYI!RG z(EeuDrGQ~})(@?%3_)w(jdxElHOg_8fnH|&?V4*wI1VUQ^GwbO-7TRRr<6MV7n_Ot znySb0`7(~g(|2D#0^TGAm;GpPQIayCr(~9HQ&S^u{U`z!gG|n&>+>E>Gdf^&!{+;Q za+#n5L+7pfw33*M(Zfcb*}a0Rz>m-aIIJ~~>f-Nvk4IK`dar}oWc-f-qOx%68E!PD z%ktm;*Ta0JBeI~N*pNW1oSieOLN3iFau}fSR|D}15GC65IG{l$zX!!j4kB$o$OM(c za{&gyclD>VIWcmdA}+_wNR>cuMFXN{u47LuR&yg#e@Gj+fvF-@y6)zk%6unF(QFsp z8e+u*73L;IT=BiCDPSO~B84u*Uem$$5I0*}ObGcp>i3(#$LaUelcS;t)oQcdYio1< za=At&HZ}(B|@h8 z|Dx2GVS6E$rX$2E`v0Tst)sHq(?3uIk&;eHK@gA!8EUcJQ1gg!?VW!C(7Hns^|3Znay#dXC*bUh~F*%i+5w% zb>%0w{I^9SFeGSoK3vNzN%$HWl}3)~=sm{2;=_h-7}v3w1^Z2&$D!x@h~4T4T0JX{ zb3D|;bdac|t);6*82YJia71cYoQm06ui#ADO@GiBg3pTi^0G@z=gA4@ckko^0t%n# zw;MWmM_jQ)oMA_ewRm8|{*t>9r+xS@8g#T_v z3H`xV_j0jO#Pd1YGv57e^K>NxIYSRfFUUaF%c*=znBe;BA?CtBjDP^oSEdD&Ucq7H z=pZy<-DvxoMSq?cU-(7$jbl2rPo17xHszw6_ z2=S19U~cgcNhE>g8F7riI&-;HlwMkY>?aq)xwG>^g4m%Jkw&vtOVQ;M9irhM zGYjh_#+AHAxh-yy??ng1xy5yR1=P76s5AIW*`K8v*>Mxnd_+A-oWkikv++$SFM+q) z=+G;Ivd`dDbRp5LZ(T*y;4&_F_LuA8**n`Ty5o~`izF}RYASN32n@L#^)UP?hvz9UOkZr1A|CGJkmW{UL zz9bb0(r;-W^nL2BS^a8o4K6W(2MjBYDuGm)P7U)*kCfM@8Kf3f_5EmI5M953X~C%4vQnAr@=nAe+4j*31*-t?33PWB_-B%PGyn|G*0SU&N9fAL zvYEnmrB1zh-elZsGu(6PFzm}N`;QG5|FNbE{zHqYCH!bIQiFg%r`rXc=}73`(y=A+ z4&t8PpFI)~U$SM$L-sXfVn=hMX-gNFx#%(hR<7D(=Ryq%}PyHO`;{BgrEUOk}afR>BmvCk4$ zjqJ?wHVNJ{J7eP;tonKp-OA?@c7`{DH&6U}e}09H1<*VmYdofO9ap}1Ua09i4i)?3eA~C7MM^9Lb%SNu>FxVl4w{hPGtuZUw@ARla`nj!&25H= zAD=#aJT0R`2>BwP8^#wr$X*8|$p<|F)u_%4- zMO@OB$=J6Rro3HLiG0!p{0zpVu>YqbAHw3Yh4+8|e&bvzQgX;|q+jK8(hnN{{vm2m z<@j77ab{-bU|f^H{~Xj3(E0-e8zA#K``0>#*ys32$#(qyP)^#bgsX(tCQenIIljbR zq~uN<%TYdA!O!6A{~$eG8b8P}J5<)cmfL)jO&bCYG8iOyeqBO4e_cYyEcuzE>l(#Y zTFH+YFiH1}jG!CBrf?CW{y!%!;9CBalEu8TqFbI#psYDcz%*ibU9&f znkM9=zvH27jps;Ber|gHORz6WD4h;w@l1NzZ_u!$VKI{SmjUjD>-)EuK6$SGW%(Ku z?P{5#@}Pmy1nXlfBX!)V8~|mJUE|B}nIx$|d?Sz9Q5CuM=T|jzKZz*93jE#rYC)OY zIpci3s0}vlqr@jS{jsCrYaQ#aG*<^SYL=ROXLq-PkKony`&R@5tTC|;u{El#x{}0^ zSLP4Vr^p=2P-E!!>?r}YW>bFyZ*P?qg9xN!d8s1bMatXqdB)uCmn{tR_>yiz>_XA8 zAcj~s<^ZMv{}>xDn*oS>zB z9XZ6~Xz=``q3wXop+bBau!|5r_W?;cto22m@$Fk17}8!@HbJ=IHOVdwDSj0qN?794 z;%i(iVTK!dx|X8%;t zmHZrPpE>ro2NbPJ$faA@_vZUF94EjOb1+LwRa=jm-lGOX}~U!1y=^D65{>}5;7ou`%#g>i*1xZy{ialh*3)}rR8*zEd=Mee3T zb_9LZOR!7f&pb`FOR%mUzGGpLviFfmX)UpC-0j@fkjg~;+`{G0fqA>K7kc8>yk{bB zyFK890ND&k2I`sR58o(N8doLkWwg+=+4SN3#6Xl#3m=D<$FB|51jErlz4dtZ=Un$2 z#m;N@4li-b`^gON-#$Fp|8*Gs!=gVFQh*d|kFKJm+YXou*qk5P>Ko0J>L>?HDEy9JkS-;)PuaIeO#>5K$+?na; zA7eiK^!R^2!^ZKJfPE8b3ya*1yOs|goCKNIFhg^bEk{42xQq7g1w>2sTQG5iEWT{O zH`~F*th5l`$m2K#3u6K=%7Hq@VCCQdJdHzc6*$7H=QgMeFb*C|D-wNyFj&-}DnMu& zc8};7z@j1J=h?+%I-8nVk(kG&%n2dU-?z{9KID$5A#26tJz%1vBR3#Ma+qe{B)qOs zHWKtLDG3bTi`lnuS#!21G>>xKtcY&#|&`i$!S@rT9G`QZow-DBjz$W5f;d1f#3flSD5rFB$Mwk}z(?e|=4q zx3yf<>!uuxK2&>Lz(UuW_svkOV@n9`LG7Qb=@41HrJN2kG06*MQQI3t90m!o#Qg*t=q~2OK-@OFFqHO2g<}4pkn>=}U@dvex&my(w z(@_E{76|sv0qrVsS=O62rPh3v4gJI;?L+fAED%lnOycmWDL|CqTx{-b);1n*(obY> zPCPS#*J*(KdPxMB{CRN2?)eEzVehq8a<#x5$+rV#X5kJw$>3^*;6z(5fnz2RdWZb& zMt?mt$^Uq0{D9ml5jHg&FjVbAoRC*5ju{_@mmC%>Ugv13{WltJ%0q%E=s!pk>Gf=^ zUBwOxF_nA750gTERMRB0Va3A#PiFixSElg3+-&k286vX>wfM_obwo*(1WI|09>4T{#7n}6%a_GSw(-qdD=hocubQ?mXnH!T`K%q#J6T27m4vOV?h$ZI<>+i9H= zZ_Ok-zT3>)y$4~@A}{IO7}(yvW9QkJ?%(cuH!{a!!TS&~IIlWLDq|mRUHwv@>eGHQ z=1xvtDqNJzq2;d~)gdnk_zJj6QealosauT}V9K89G15G2Oo<>rJH$S4jDoV;(dp#Oo@3K71ns`Kr23O!r32$1o27&x0h2XDeMz zwdb`NT$FGz>Qp@rtX+SQGt^0s7z@jY_J06eCE;D1)=26;MAx1@9sq7}NY_D-W1TP0 zbKaB$>FW>WmUVE~D4is{+2#gO%wmVdUz8c_b)=gkejI*j&AAWSxWV!W+438Jxi*2-nb|MNnf+doR*zbE9hd>RoHD-!R` zAMEeuHZ&NyT3`gvG)lL6W8swZ`5rC#fG$$IkwfePMmO5va!D(V#3Xd)|F>qgsH8{qV!h+&3jRD5gadRGC??K8awc zGwjjusy!#5qvOXt#`+z)wjKFp!_qLX~nT&?5E#NzI0qZZujM%bFy45teBsQB#>87TiiYBA6~(GBvh=l zbaWL=C-^}U2Tvu1Z|f{r4=_@g{<=UKt6zS5dV^cru17BNTT1A#@sX7K=wzkE6lKMly>F;auUG=AwkY%M52_R|rR4TY<-=NRrGnvhM*@6iS=}~i4SQYF*xUxIF6ntXIF(;@yFMD=Y}Jd z?j|#&r}f$x;RL{cY@RT^q|*;XSNG+EwUzA&A%+Zj*IlC(sh$AzV90m4AL{GWmd;@UG zl@jw)bm(*+9cq=OR1tus6kqRLSnT%0`Zuf)?gCJ>S2UPW|M1whs)h!o+lhp2r+dL9 zJje`x8yQVPJq9b$q{Dq}2Kuuhvzfbc-V!P}<6~*A`PBxrT_4`ezBH4?(Y=%Ai`mtY zNKnfUy%dzPRNh5z0iLB7r;k|8dQE@#$A*_vYbSS8U z(IjkBCwdst0L!*>Gais7)gw#X8BU#|@>pDdt{}?Fjc_h6q(tM$)>Z_|Wyr|>VHvspH4_%@;ow?ihkFG2H5n2yOgzEoIPJfFE|6qh%87Fr8pLD(myq2B#M* zL6RWtz@02}(W+~M_aBN7Eqx_=NdZxBGbE`=0aGb=X1=HV2kdfm#9wJY>b&@;LnoAp zoBjDFILG3P^OD|vV>#y?Iy%uPd}HUF`>?<2N6ln)x>@JF`^ zDBm2OprrRmM7*5BbcN3fzq|!iY0vQK=!Mg=H~~@kOL!7-E57>>yG`D2J?>E~ES*ZR zZ2Qc^w8^X&rCc@d13bs8i&O>-G(G-JQ)xi*T!8DUhCSPDcPFq$3HPzd#0`jh=VvD% z<`!HoTY0B<{4vh#2#KZYrCSPA6EK*B$lSk)gZRnz^-_!nSWqJ5-X-I2iW8}$ zmoFK<3v_U56{@AJU{c2aZA`#YbpGQwu+lXARUXXrA!vfZ;&tP;%?(iYJ%rP^@uN-q zWES#4%8edST=;<|#nzgLG~TJ6 z6m+vOjQkrl=X!*tU5yU(98D8v%t;Gh`dwfOVXO=1zGRJq&n#FaJ|Q7(_56unyl)q2*MKBZ_*DMsqW_Og z#eTI@JPxv?5uJ~h00vBu7gatR`8d+Phx0;vt5R}z>Pe&x*00E78A$N~7XW+np@CHn zS@}M|{u= zqbD?DX+fdcyj3MQ=XzYm`Eo32)tdA`XDAHlZw)p>%wE2F>Bj=F4Yyb^O;dv|0a5B( zdCc&;vwoY;M~Mvu58LNyH+MGf)NFS9hPBjYiW5=3xjg)@F$Q>_^brtp-u_l*MvPqA zn>3;Fi<$^YRjMA*9kgC$)Lt*VL%uxZ z(TPTKlU6oA(D9};pLs;^!5jMt7~PR@g`Kze!26Q^dP8+?W+Cf!n5w14{p=^;n7?!> zBDFLgfAruYvei4QQc=Yo(h=IfKJgx3c}2pJtytizqG_~%4h4Zm(E z6lzOvle1qZ7A*TbI85F?+4Uh1lY+vGJ|Z>e-%bP>n!job;P?K+%==9X!5J_4fwR_Y z9SdInofiLj!%8l5ekX|TR=+Jp3a`F@c_RZIgdRy-(Nu`~aW@)JR-GON7OP{CDiK#t zb4=O;y`io#$ws#`zaf|?JKjV8yTn{q&wt&v&ps-XZM_xX4e14K*UTW ztL4UrUYUwZi#pzMydq#zc`d{YGMDV_?IJ#1RyI9HYAUi^zYVL2-bsu57O4q08@PsF zY8{TKNZ_%yPpeRZ_vVhr->j8jqx4@rkFy!*cdL|6AY$MJEU^#GeU9w0<59%Wo< zGc+7XdE411~3(i5lXp`G$9a2M#TYfspT@gn7>UG=dZzhI@^T9L{W+vLeOJpI#zQ3 z>#LOzqAe8@xBug~_*ML2oO`22`~Uq?F!paNrOy7$&X{xzomsT*coN zZ}Ky17TfVtl6t*c)}uOwe|b+Y-Uv1MbJ`CzZkIGGL`vYa#hDqOvhoSkI$jP16y!-K=ffnF6L(O0;8M$S-( z5f{CXBcbAqhZ^I}POgkyTm4d^2sn|*6knl}-&;FkFESoi0X5lRsxkiWUzA0o4Hg0Z ze1B2$SEJ=u*YjsiG}%IAi2|U5kOc%k{GZO6twu5#=wDLy!3vO+K#bV9$enu(4xJ02 z!Oip7xSZQ9s&_7VhcNW@OIcD$>Nd6OG+jlm_1QUlJ#4+V5=v1~ecw%i%LL@Cy>c;hvC%9=ozZzdUpecx8|+)(h(?P+C!c_&fs(bvm-#@DfI( z52b2xnlu6VUsOMj{o5JaLXlfgKxu+GJ=2pu7?=K|(HrYvJFE8;rr#eoKRU^(G8vdk z(_FC#h_E3N<5J;Gg?}lI1Tf*5m>D;Diwc?()SXeG-y*QSLR?NeF11o+{S2OAuCO4&; z2B%JF=#hVNqRo}Ep!?Ya^JH;tfL{t3@Ut*}|9Ds+m2NWxPd;5^*QSqxf?{i06tJp| zyXTRT82IxKFqL<4g7HXgTpktQQCHtXH3=ode4V4X-}7wN8<=BUUXMH<#c6+Ic&L2Y zP(gLxizAEp%dnK)EgHJ?QiZ~}x38=+4()8SiZ19EJ)ZEe{6PHef z)(d;vcLZ+~RPKvXJ{&$>;BAVE<^0-+saW3i;%f9meO7VTUyDD-bIji+&)dNd21?no z7-;$!ST+`ivfWQz#ro6shCWsGS-MA#8aDC4?}@HQ=LQKprqh)g!W&e?VKUDhYb$Gs7nkLUX7`;CgdXZjJ>uvDo5_Qzn=`8tAOQh zwfRM{t63U_#lL#Redxxm;q?uXf&Amq`LhY&*?ZmiL~|5w`}5zCq!&c1l?LB{qFI%7 z{rD74rjM1qjTNAY$#pTSMowOWNHOyX`5a|hq0`4nzf?tF9wMa?z zXveI`-xl1@tQMwRs=GQ2iR3a@Gw-+F!5x_BXi-XY z!~Tn4<&NCqj9Yto8Ryww+8jRT4ApqIyv_s3L#zYdasdeHxG)>}mLqq2rU+^Kz+cJk zRq`rwB-ZCR-0+P1K@|JC{Q_-CXt0TgVbRbe5?>gPf5>c*-&#EH0f6OpjjAlO>9YOa z)x8HXLq1Elx0~YN#<~i&*qQUB_j|m@g>Up4kNa%K*P2H&9S|heNuf=i)$Wa&=M+d- z-4CLI7Nx1-3u^k=k)_u{(qhRV%Rt*^6BFFeDo8%oBFTBVza6l`k0<<$u8z@5xaqkn zwJwY{NE#RF*#%;l{c%p&;i#Emfr*~#4T79JCqf<;vgabMQA+!e<_)m#%lXSmTXXQ{ zr$EA&4o&_C^&4kAcm%I&z)ZJlIC|=Jt?z8;r(>m<0R&Uk#z`St1WN}&0`e%&FPpjj z56%X(2($GdQPRD;R3|#C(U$E4TgkokLNSl($H=*ES@raIA95EDG4u`ft6|eoEA`t} z<;$qq){|iCg$3RVt;0bTq}l8m^wuQ=tBquZ6sJ;mkpn~KmoGZMnrkO6HwPS??v zNXG0GQyuBL_$MRxQ-l@U2MOOF_lP~>zU|E%9g%_vzQK3gZ0krra_v%A0qdlxO0Jc{ z*%#prsr4tXlK<_++v2l>i<_8)qN>cd$Gp#w77A{Cyi|$klgK@9JcZ`2KfQp@f{Bp) z8em6khZ4x{ZZu z*m%ffU(dj`man8n?*ehB6Y-_RPSKr#L?;(+_lbQT_rr>mQW-cm%&Y1(=6X}vvE**_ z)=0O_(||<@?{7ESiNQi5%NyW`MX!=@zZ86w1ssgxq>vbU;Q#Tc5E%;|VUrJ3D_~$` z5p)_xgjqjx84ht&@gGm96Fih{%cRuSxWRKA=YaDNRK7jKDW1mat-V8P2E*dOjGpxi z@R8hCjaN|W5%2Gw$rUe)L^#5mzUYwYL0 z{Ty0b)oLV+7gC4QzDXu5oWG1xxaR5n6byyxv|VH}&Q`ByR5kA9WYpn!G8?qyO6*A@ z$6r!-CN|MwlH^zQ1fCx~VdLLNHpJn8r0|i=NMv^+UZ@|AuD@#dsB^eqUFRADl-UG{ zQw_67i#fg`DQ#(8E4|h8f@^=EvWdq(M5SO<|Vg3$Tnq0B4;5>@_ zsv@y);_gC1Y5zcP_P=}HCJl38&98-`uQTmkvSJf8dyV9WhJW3fj4)pqjzb|@d zzbZrSh}RmP>`$H2UIn#bXLrbf8F;jLhc9yD*{gra$dmQ68PM|dc zy0`}$JSD}q#(X0`rVw@zy73$j*!m+9Eba>}&~>(5F~@O;Jl$rt9xwiTy7C2!&vj~L zx$y*)Bue^89xb3*)Wfv0)K5;g?H)95JuV_)K5@eVu&rKQnZxq^nuf&`&?!76ZT8!w z5KiQMoII#D8dpdhv!_EHKuOof>0&AxLzf3M$Qcps^74u(&P}aZ5!I0zY;qg#Ywxk^ z?$CO#84rNqTJt%OTyhg*_M1*G!t|!PyX^qj10|aE0UFv*4-8Nox;6HMA<@glN`(9qIpna=Qp7UNM z#X*raO()bW`a|q`+zU{*+qjC|uEi@BXfhJymOa{SZ{kc9)au8_MY#^S(atPVPGC@f zwl%{^8Wp)94QHZu%_ed0^W3wp1K8T@KdgEZ)0*{oSaGQMx$E8Uyk6IY;}BmYtDs(s zV#{_Kk9R~@MD1zVU~YD0c%G!FGJ72Ml717X>vxw@;-A|DhZRf2hmM}(B@vM_=+Bi} z)v22Av4M+bekRMJ+KOMWaQ*>9Y9Zc0>cr=CX@~c&%hcK%8_7A|CtF*4-bvOh+~3Z9`3(n-jx#?ia38zX&)dpxfdj^=_p>+4ixX zoiS(1yI!suJXy^9>$&v!#pHZ^V=Z6f35F zJ54|}v2N%vQGaAWsq=u5*=PA6bf`dDHP>xUu=l5OsjlHwn61)wj}x3hvB}KFrm#cB z>Js5nOL2^-s9sJdwGW zM%tpH*5NeH9s}-K-=d4uca&_cVZ+ebTR#9&40^IiDFKZ`#gtv;sPM4EGi4$z65~T^MZNFN4vwtu z3AAS8<^^=!P1>$p2@l1&j1)VeV$lSXq}Za-S?yXJfqn)fRrG0LkWwlk}OW{=qs2VV5E2m*0NF0x6nQ*^5-v)OD_ty75Q;Nnt-c)IC0ps6u=vt#pd`rVaJ6nr zPwMe0%2?}mcLRDb1nQWQO;(p3C%}Sc_iyBAp%g{RpG$S83qnlAHo~eT9o+#U+sPyi zyWsG=_PX864}IOT{+`a)PTC-%vQ z`SloAT=wTrzP@gzo4&q~s`+#M+Ti$UxxZ{(NPO%Oz4tle_3PNz4+f+=;`qXEAobn# zg#Xvml%qCaMZ|Lb2nrJaaAJ36-4R6b^96f;gqC$kJ-C@x&1kcy~*QKdsZ(5 zRFAfgoZ)eGXk1g3L0(ob7$cETZBQFuxZWQ;y7q3X0;mGu z5UG5QJBAns`Am;XyA`}@oWHm~_1NNCLVGD0^Lguj%r~m1@;xLDY<)K6pD}D5aO+d- zEp|n(4H@-*AYoDNjOXGwtgg*jUY_^R@D;sIlW*yl0znN$2DoR*=fmule){yYwnhgT zow;FD)n#uFehvVdIPrfQj-H?37B*+|*MmrX#K!`E{>GTXM;}_A{%dj%l0}~6_E@tu zn2gvcow-Q;mxTu=*!OKWG0}RB!zbGbbtbY8bZPQimsHvbqig3$E$Sj;$pg;f|F{br zgTV6r7(UdQ&`AnOThSLh^^k8R+f?u1CbXrWX?onzpTmQ@Dnetc(PTx$DEkc#b$8jV zrpRMO&qTS3)f*XTYieBmJ_0u<#C-}5G=Eg1E#b`qlTW;HA#dN+PY73(dXX@^eV4ns z{iQd)RM$=6J-I|r6UfVa#A;_2&ZB>J5Y;_F7~5Bcl*4RW?j46SBvTB7YR*gb^4m-G zXTMX#(rsNrk2U&Wb&zsf3RISBfM^d)XC{)-ywp7lu40I~1c1Q4s!e7~kDrfeIoi`c zn=+OZnt%Q3Vq8|wrDKT|64o zlSdmiX&#s4@+5@h-}4kbspj={kAxkK&a`5oU!9f>INybiVtCsI!gS+-v|mZ$a_{hB zHT3QoGyAt-9uJs0LB+DQfB!o#g~4!|>)ZE%*Tk|}gg>%!Uo0LYc||S*eVYClq=aHd zh@7VyQ-T^%$zXAw{QV7PVOLGSjUd-^5pr42nOXH}SYV_2XHW+@W>uqtKmwslM%RZ_ zZ6Q7^gtsz&tN5dgnw9z%y%FLzdH*tMv)5GGW{imFNjt3uZkHw&bu$+O0YqfC6#;nB z$lZ$5V#cIm`*>AwPw&=!Y6kL7JNsoQ9Vh-Npsg}_ME1Y9!lf*WX(WpN8H~n{MO(F3 zZ{O_QOK5MXzXCcdSZDm*QWIrf#fZu#h)hK>zI_K(;^g;N?_PC#YBae5_l$`TW`l&D z0Y`y<)MhJw$r1w58e8{2v0hsl+!A))f2~nFgKjjvIV9@$yx9jogjkIIFb|iDTeeuS z*#}F74Ath{M&c1>z4Mil|oM-_44fB|10^p725|4(z^d>;(W=h!W%RQW4 zwJbTc$CsD3O~n*O7)D`?Xg4~jC*j)i;HH)JZMg3@#B(R8)J#`p4Z+M_IGreXnwqGx ztrK{%y(n)qzX&hjfS z#rb2JSig%UE7Q68bsa0x~896Cn%L2E^7R zouak>aQj%UaYB@%F>y>ZT~6Lx*-r%z(@u|AGB5{35sbgv^IxXt&6~z__ zV1>v+RaM40b>>2f-BtDA8E2loYAXPfTEYl>g;ddbDs3W2?CzmC!SXgE>Y+EmA)TZ+ z_ouGgr_#$nV82gr>Wq)oiQtpxbvTVPsv{8q$x!8ojyBk3;IssaHKe}Z;RWuKQlePi zEf>W(P5cH*vYFDxC~tMWa5eVxHIe8>l@{`6$`Q z?mTViV4M`@9|)34DaP;9N) zB$Rz#@FhU4ND>5u0B8Wxvw91+2(6dWc=)LqtAhwsu6Z7|`2y_OZ#uv84eEB_o7DAl zNsL6U1;Jf!+IbkfUBST2;=R<=wO}m-5I2>kuY-r`a1dxHunL}%KGkBZoH^0$Vr<)3 zCL7$pmXYalVi=QbZ7)D|r{l z;a1>-KA#YjZx9h8{q4m+gMiU-)e3>w+`>>AlZrp?UfRsb%+=Sh87a237#Aq~NgNf^ z#2iEYMGboyn#!BGdzF$==&o@qqPDw~Fe;{jw3rs05UR92?F{&H?qB8iT|nWF zQnv7fD75}u2BIsne>-M+!7KRx^|b_yO!KOobe>M)!h_Cg1BX+I4z)-~BZgP>m8CN3 z45nL8{|eZQ3WoSILj>6BQ37;1B0*L6y^rUmV8upF*{O}%3};)WJz+#`F!Uf*qt?L9 zDu4Pr=v97HLX*PQbO0=N5E~))qm#E(6jLrpar|d!xa)_@mf7)wmB<<6dFTm|sVs%# z_rFYFI$EEXc&$yFo-vh}I8p6mU#~tEzDdoPJ^?oBNjWQ_Lw zv~ErJW2eeG1u0FC2E(Pp@*lq9BLVPLU-l#?Y3PyBEo9CAk_QI}?mr(O@JOs+Jc#^8 z!hMv|fY67UnDt$T5gx_bV;fFB!9)pptC~GZUYS@uN~f=mzoC`8&hchb-=ed*@=y%o zRix9US#Y^g)1zf|jdJ@u+sVq4G1?9FTyyrE%M~d5rg3Xc1?NHmLBxH!lVmWPmQsUr zD5CBnO`EO?#Oxf;PHjW2?*)hQ%wQz{8E7qlLQ&QdQ~c$%p%@s4>q@dx+V%BnfU)H9 zYut2YhvhDWh-!pGRPUnz=s6=Os?K0_FoU366;U(ty^IFS);T@o>)r(d$G>JxdiG4e z3ic#@O8`y|K#Z4%a{9iUrsozxV9~X^W`=T5bAlvI0F1QYfPQbS97$32e00htM3rot z&V&0?%XoN*DmfI`BWF9k+!;0SU`^rguR-|teCn{*Bz zR@4z@mv%73#WuQlS>&p}(V_}4-YAMn1$RANm z&mIP2LdeBiP>ftwMtrsLFST?d6+ow#wgz#Sb(bqxn}!+NlN+>X1*gDpIaOcd4-;xG zN}W^0$j;$~|I?;Ye}^KnQxGt>=N1on7OxmCbBaBJ!4C5W5-HvDfm#1CCI-*%|JsAC z1p~kw)!yEomMH3o-{V~dCPxvst>tn!ItuF<{_(R$#Vk^l>(OszdtmTL!3f(HXMT58o(JMdJd8aYmqh&SuWd; ztoo;zwSg!eOeF>H0_%PIBwUOV%-`sNtE5z96xGaM5w+)r!ynb2F3Gh&^RFxYCmWS< z+y!Q|18`UOz1)|$RJV7UvY=ipWvteEaq7;XJu!lE&jh9ftXuDN@X1a4r5;FFeiY_q z^(zfl20uOlw2)dM^I+5U@nU%s8XM*ER6XvMH5~xWAGy>sF2j`YaUvt z=izI0chie8^do8mg6>-JrlsE{wg}FNIq2Cv=HR_}T#?9qA4|HFhM-I>2v-GnaQb8n zSY|B(u+E$Q?Vf>+%lQwyIh+>{{r#gaIu-)))KV0FgZpUfx}u!2IY2P+?wd8!rG_1$ zDR5nB&oFRr=ksP2nid63Y&cO>t)V6-L6U~5_G$wOJ6KKa>|a_y66`Hrqj!GBX;@H> zkQsb3oF^v9H>wuMa?cg1cF)!8?G1;A>K~Y_GTyfEeOA|^bgs{qcCZ@@4-XA}_RubR za6XR%K>uD8eG=a-$3XREd+99e+FYBPSpt076H&e<#L%kbF>vJuT?iAm(#exVK}3|` zP}erpKP#epXLL8Y_tqTJySL3JcEvxrZkn84nG0Hc+2i^IF9-3GqCVX2LzB3YJgf|U zaSs|=Tz;rqpmrl5y5!=Czb#e}GYg^6aE|=VhhciQa~K*2AUa10zOzWm(M^DxBbq<9 z4iK1haE6PXYm#~i!%}2=_9r7_CJR!}434$~uv7z=4FFppiz_e6Y~{AK`E$qyy7H`{ z8IoLpzn5Z@5)qP(2gSk_9#8kPGixhL)hbvW$58_*59SSeo!APhlOu-56UyiPsEo`` z5bIbGOH=~;Z6aA^i$#EgIwm6?yXh~gRUF;ude73F#Z-p}%<%q=5t}3TqaDo~_+Zb+ zW@1Wfjw}t@5)tC?!E$_T4s`}-#*P>6$r1Ev&RMdJBlzjheeP|1|&zyE7~Nh(~yL&E{yctP`To{ zg|N%etr7ZUj!U+w_WI@Lya%0llat@AMLvjJU=a}lv9&BaHr45^3gfm7?#BqzrN5-D zxzXMAzUL7U31f6cwKyX)ZTXk$PZs7P9N&3%t9vFlhCOV zJhneJ-z#gP2(Hhnb=f}fY@k5n;ZCTnJ+CDvE4m^wAuFqj+{m=i#UYo zo1}3hxt2sdKfT-8|4I;R%o5{@7*PNAbTByoK!=ll`hBD~suo4m#~3W^@vwDM6^;;{ z(|4`b?i2q~ykN{J#w$W)9U=6XHNviIb0yT1Hu?vvz_ zhKUTL8Fe%)al?e%_50c|vGxkF>9{gd3{z4_ohF08?nhG%Dr1wKRV~#Uf*GAP$*3vd~-6_6}mTati9BKp5cBOJOX@R9}s_+39(+^co+eafv>`zIiK#{n`GItdF=GF-*4?dV(Bd6j@4K)poQBO zJ)fiX?3t9Iojt)`8>=0OOtW|Qcl40eD~_THvpI;ufT zZ25YkjGNYh&8tx~SykP>J4Y8H1zdb7~x)N+}Rhd<^&@XEcapRG3^-@!ROJH+X_ z>mFU^PCkXMOGbocHcQplYpyfIGCYV?U3KZQD;oP*?9^mHIjFDCG5vK(c`~}(%#&4? z@KFR2g?_Xd`h)nnPc0vITkx6B+S%EEejgYL-BQx+ID3yrh+`?-68Rs1!ax@00_i*8 z9ureDi@UKmg8Iz${RA45-((Gs2vsc|$bEZ5JPB<1GVQ|9`uS3G+CP5!y0R@NXSHin z^l^NQzgedL{9=8m?ntCRf^+Em#)*ithpmn#J`D4~?+e&L3iCrZGt;ctxP>UFskCk@ z2_xgzFON@a(OwB6OJN6Q0#?ECn*8@x6*~Y?aEFY4vAuS=sJG3y&nkOj_fYFq!5Fci zR+EuYhL2}L%3c~-JKwoXpES*L=T^kcKscq`5vuLB;NE`2)_?tha-QQORtUR`W1tA~ zs=Z{b8_>Qf?TG)rdV4gdvWDYTbV`aM1sh#a)Q|DvK+XG>BqlzeE}s**I3d^ohrfhlxurE>d6rj^h7hm_A|V>%hm4uf8LtR?gjGXG0&kqr@#lD* zfkZ@>T%XQrIpdwmvjpUJLSE^Gw=#RuGf69eV7uD8^fYc<%fxn$20|mlOsiK*N7kxR z!S3FeE2jYiGz9ffrp~+=%H&sG)jh=d`6YTw)X0wvXKT`)Z{$qqG#L4bVyNQ&YN(zN zz9El$DbB~p>3IZ))5#*=yC-6qWY4yqu-|<8f{dO*Pre&1ReThsA4*eXdYpm&O0)03 zqkWD%b`yRB53*q3JjcP2(h@)%f}l@(wB5x2du?3@XBp#=S!Rh4B+Sv6Cm<7W};8tyT1bq)hanpARQm^bj zNQfLgXzrK0op#ZegsaXF;^h1KC9hl~42^a*n<*qgu(gd>4JY0cwM(oN!Ks^v(APq2 z&P7Ym>=&aaIU1fEmFZ``sjo;(KCi2AL#=$RjR5p@nc`p+(xHZ0+6c|xrz;v00a}aE zH}2(t7J~Q4`P&0j)iXfA_*!vDphyil=6@GKhbwj(6hNCUkTk!%-*pMF4Wwd0Zuw#>f>`f#(Spw*Z|m#EvL&Fog65M(@_qbTS~2~*qnY90$$kXDIHQ<=)BKxIuj6(wE6e;lfh9`y2cvb8=>BDVyi!YsCvP z83Wa}>^ESBC_gR#2Pz;cu{L`c9VjYtw7?(;q1TfSAR!Y9@QsqD)iY?wn|^{#n~w_SljL zo%M|LD8e(L zu_oO!zV-~!+_>*JoP&KzM(ZN5OKE40QSXPl)~8t?uFL*EjJ*d`liB(`jG`b&f{KbF zMMOo4fJmj3 z-21K1FIaEfl0`s-7uyEi!V|AumjGcQs<&Q^?n)m8U74UE1Syq6)s#A0In>twS z22)*+!woW~98TXXT-I5`Km4Wa+O~eThcy0h8S}su_aQf*@yckI8B5#E%lBbs_D44E@!y7spCzwB-z*H_EvOYU$ z;JT;*&U}@29vR{jysk1W6RtaFewQ5se0t9p|L7Tcn*GYdG-ggmt7`rTFyuK|az{Ef z3Wn2%7=K0Rn((k{9q#|xeGjft}!SY+hVJY|z^)9Q@1_i|<_Y|9h!A0gK z?IELpfY#=X(pw*}zks-_L0*LcJgdOs(uXc~lABwb)UGaZFVV@b!K7y{u<$;#;xdAZ zSFns{qwTc}qNYkl(GNN2zn;vwrcDt+_j@d|0q9T+#X}%o1ppeF_bRV2Fu~wAhaNhf zGX7sLRrdsW9P}I2HGnay>UpD>z!AN8q10_EuFXD-XL`&(twg9a*j3z{BjP<_{-Tx zoNVV+S{(!0?Vvli9+yRq-FBG}{M_(Zek5D5?>1Mir{^s%7LHL13$S#RT8ZJ;^cV0y zFIS*Y694_GoxPRVc_iSM)zRA*t!-^TyUU!vw|%gr5fK0Hh|+x_vP1IM;FFZckYC-F zT5JSu$TQ^>cTQxGU!u=VtIN$K?sG3(XidEzoSh+V(x33MDE;ike$;y>YoQP1J&*Gr zc30hOOs@$e#`vl)y(oe*;V(aY*iJh>D7UAUlr+YFI{6&n)ef#81@$J*1t^|K{n5jH zH~8(oRBt-_ubQ<2r7E}8rg{UIi#`P2QN&hAYAxrt5a8bcbPH+DPtNIL2SfKtzL~RJ zWRHc-ph(*J#gQU-Xz%{Z+3YQBwZL!H5Iy&`e{v$|Yy( z`-f@qs2$*!~bT%TY*>j`~Y z2g)8hGpheS9_lx2E_&fFE+V(3<;R{+xe6&7I<_4}G=~nPB#c8IcIaWX<%E{W%rd*RHm$8>-00rXb%DhaFph zI0vL|XR+@Z7Z$sqyw{!dPp%~Njyyd-!D8S!1*Jdtm786NEWQUSMx|3=;}w|AX1txrZqcB@#3EoDPThO2O~qrvoi3=K@K8zH=jBF zg5|$n1U0spzx#|8=*{a4e}3-!t0b>?lx7Weu~cJ=S@JF#Tk4ZZTKB25A|_`qzCS&7 z!|h|Gc30s#a0CD=n9l_7@@jbs%irO>SZUnqy$`VkV_w??r4$b4T1Gd-=v8U@oT=}e z`x$`~(+n)-#PxP)#$Hq0sVn44I6fWNt|`$0j8@*-8uY7}pNRG5Dpe}8=DlZN=9B>O zhy(u1X#I>HvVAK`J{WbJ;x6?6Kcthb3AQ_sDTTorkAJyGA0wrTia(H$FxczRO0PPj ztz5StGWA;P?o?IPDh}^T^DyHv;R!H-kg(-$aqxK29c|#@c7dCeq*k+%o^*--EOEx{ z{+Rk<5w&*YQBO5QD-9{>2vGfTcOJ;M&;a>q@1XoxuUyy_snxxyHP4_64bCmV9(!06 z2ajRe)<1MS6vK4weRr-=l=b zd{Xy;cp(e^(h4?nuUFAOm2-4&t>WXHN?Xm-6d`_2Vt72TP&_liIFKyxIlTqyos5O6 z{D>c0KK;Z{z``&Hc=gUn&tMH-KS0SQSjBMzp<}7u{7bW(03gCWp3fJvrFQAO(zX%B zoo~NqKrZKvg@>3pGE1`D{_y?EsXu1E(ERUXkOl$>-~bh!@tW?>1AQ|^TGs#^bnHVg zW<#D*%8g6ZhE2-nP1MEM-_$)!Z2@bDs+qEu4f4z(`@24O zr$nLfGj(hfmtlQ7f0~O@{Wa@vQ|vO|MKR(}8nYI%nYi{I$Sq{*+V!ZYC}iQo%WZ!WWZ#Xxq0JO zvebDukE6%y+{c%IeFg8tM3l>VC7AC2ijh+PQ`6G4u8E}ZpFKeLp$E8(Qsopjzh^uN zsLl^Lvoh7Q`ZMrd9ZFnMrt#dL^lt7^(XTj2Z<|-D|w5HD{r@DmTHW1#C_b|+;sIf@ry%y6+<4%ixP0G zcnyPN?r`eio31YAw}AtB$+KrVZx3O}r1^Zg=u)~~y$g8V-o_GRSY+xU_~54!@B zdyjdbXnBN#jk)~1{MKn8ZL2r)K0!s{hZqKTwB-#hC#Cc=MB|*9M{PST? zaq-AjgA1x~xBsxeFkozwpMeGPY&nUKa!GL>ON^&MW*s)642c=K+as5rI$009KJB5n zS4~aal%8WeZwZ6lllVDVKIf5AC3;HW$Pu(i|NIaXQdU-$t$NqW?%i47oH+!4W^|mm z|K?GLu*ae|0Hu;W*1uK7)$Am3v_L{+Bud499l1~ z!59{1s>+Dmyx#+QOvD;Huyi}Lm^@DLXlW)rf{sPpqRuBE%}^2UMp zg97n<6Rl5=()1YOls;ivOJwD$aq_)2{@~%wr_qC2rn)@eb%jH94LPn^X{t^uAS}Y% z@=)JicXWLfl-R&9aRv!o3T++U4myjNrZ3p*$=e@d>{x=yj^1Qvx$f!o{Z&V2XRD>P zN*n7AkDYJfA|vTx$o+fj3s?HBOlkEyx)Sz=G){(XLbQH%C@522S6uN&%nhmbP4wHzLvP=a9!II?Y@PXJ!K-_CTc zaL${u1wfaS0_i(DJKI$-REXbSa;)HTd724h5|nPGZ>llcphNO1#}Dwgx9#!?=-Bzk zT5D6y1(cIU!IC5;6^+9I<{+$-f}&gD&P!4SM7^(P;7V9$;am=BiD0*MDXTQofOchv z#g4Ub@gVhGE*LP=#^Bhi9+kR3IQ|L9%xu@E_hwg-H zTzKiVBs43H$|Pm|*U`maw~%sa|;kB54hJPn@;+^h}~`>}N*Scb~01RC~+J zqGtj_-PXPbYB|ru<#5~&qbVE^7K$e%`jUj=Zru{+npPSJQWTN+CNQBFEfCbd`XyOV zR(uBSt())WmX=5vap~hOIY{tL|L}EQ*<@evyBYpl0Qsb6^P=IBy^>BkFL2EvM}Ex5 zMck4r&5eT@HY4h~idTrA0pe%PbhRBNM9V!~l^scbbdc~Ca4pnA=a35CYHbbhkeKRd=vjct{a;XBVq4}OE;6A67deE z%)Mkd={0o2$VGDS+(P8qOXc{mras^ByGee*4DLO9Sb^XW&GP(vZqEa0vaA;xXoNon zLNxBbN!Y#T;xx?AzIX`c==V<(e9&m$?LYb_4G-zNKOeNW^&E@PI95k*(e(*A>IjY0@sXIYC zAK$63wtc4=90}se^`O<2nz=fmpkC*hVmCGow{IA({{Gv7n2^8&82U|OLP|_V_d$wq zrJkMo_s4OM-G_qsOB-k;7uN9!nrb1IPSyr6*q-_Y#hTkfLKO)bkd;33K=*Ifc7zBp z&+dZuMF~x%O*25e#Y~ttMnv9HK~>znzZhvcR!G59J*-v0*1pSxi?>Y?gR+z@EXHmi zj$UT`!(CRKxO>XArBsKMk%Qgb*zm->cDwU?ybXQq?|07HAa~KBs!7tjlN_{Hv3e8m zO#ygwyQTrMx#E@YMig*sjE_)AtL$bDNsXsTgi@_}dgBFZYtGO7(XS{+j}t|m;>?VK{FK?F2epTNodrlqIe@2v02y(QCfZdS~blG)FLQ;HHXIF2)suMpd8 zL*qv3P4jAZu8wstxkmMn-~$e)chS9W*b0^tP-VwSzhki7%(9wh9gj4utM9_46A2H3 zLijge%TsCWbjzpGiSt3yrEaNq!g}tbM(xqB_~`0~%5;g~0Xv_zBEEA$(aRxuU)+2D zHZ_$W)K0M+_uqJ$X;{0u>a5i5Ie1O^Q+LEYxbY2r%uxOxN7F|mQNBDp%~JR5U!i3M z4J*d%NSc*G-`GW`h+o?b>+CzXa0@xJDS#p79MsS^N>URxi)&dI359&6a|5=#H={uI z>CCJq;E^`#+z$5SZj&-hD49%@EVt6Q!o6k+5>nh{^UuaJCxpWxUgu@iO;m0|QU~ z^0z0CP~V?d4L7!{(h6n@MK>O?cmDllh@fH?DxT7*mZQ#bUmgY9uiXCevGZ*m>PBdV zujU;pAZrz{um4QEO(5}>FE#A#9uN(vn=A5cm$C}c3ITx%g3{7Y===L-8=N--6(eQyI?%2K)_UHJ*0I&V0<)32d7h41mq|nw3+>ba;exL zu4h(R&cSI43Jqg@Y|KK_ffdKNw6fM0(F;VK#*?Gza;f(NS&cx%PAEcFa@^-T&dfZ~ zOeB9&8zMD@al76)xhT~AtJ)BJps!!aTM(h#BWE|?mF((nKz)n_WDQ0(VDCJ|2JU}- zh3$`T$~*&8g>RQou{sS>p&YNrvpC5E(b0K(;vnQeJoeT&`5L?zU~`n*{r{3u;%&Fyl*7%U1# zc1hzan67;A-ar_-*a}C*#+f&7yq4`=3Q{i;!&EOVVv`6@xF@^A1huw9Dq?sB_rJaH z5c5-a;8`IT3fzAc3KxMDVRvcT=SF&2-fP#c;o8siVNRt;!}YdSCTi^8fU*+KuNZOy zP-9d4GQgOxUKYHxes+fDlQKNxQ#xyISmjS2hyJc0cvjHYBLrmmD>S{oN{AKd?)1-J z>lx#Gm(JCn-r#db<|6u87KI|d+xKc`VXRt@9N5({@4qRtjas+k4D1<*+9)4TZAfM- zT~Wfuw<_cmp*?Qp8$`J%aeU97L-PjMv?a zgJqnmp4(GjjkcA84;3l6Jopb0!UGE9`rn@75U3f!hWjK5w>G$P8^BIWgt~zj5PmV@ z=|7&PhNPsMtb2W|ly}YVMA#?QH(Oh;gG3AHZJ_SsmNyXrm z6nVds&Q8NB!wadzasR6(j^123x#a)X)x^?B(1}FdluaUqSE{yZVyq-iZ){Ru6B15# z?vYbCo*qNXRXa=BKF9m3BASaD7!p+mi_c!aexocTk*GHxQ~^WfHzt81r>}qFW^n3( z$x=_Pwn+}+N?F-07svcyfeklw(LwWM@mz29_Bd;Q!((%&mSZd%BzG;t=FS_e@kSMt z3dFSooJD?uL;l;hENmrnww^Ox!fvl9ipA}J|I|N!{w!ru6N7Kr>s2e%-}^1_+KVt* z->8`m$s*Ui^$L7XKt8KCGya1xNJ$*EFt>%>YjYzu9SAXomn#{4&*FAJVx zj>^U3qa;o*q?6Y2T(owZWFNU-Hc@~+#mHDxi(7k8_Fuaq^fZ84V&2vwz`lpryK*`* z--Lw;aiG04wcmGuJM_i#!;K%ixNsf6btUjf)%Fg;vGcv|ABQ*fsY8o-w|g2Mzb}_2 zMr`iC?lSV~S2^TA=Zip$uW2N zFzXFpuATK;P&3(B+eW@A@}oK45yX-2ex)b1z>E~8X^b2_xw0Ffvp)yPEhu`oxNeoX zR)|G)T{KPTM+K5pO<1zCvm7tqIM*#@J%Q3z^dNoQn8!p4O{EiASQbkHSTxQZHcl$J znFNCJaAy>J;U9={LdHqvlE~dss8Q7O71hI85Rt_tr zqN#Xv8W}ubNJ_t_zXf2sjs5j*{^JZ9sWQvpNm_NMm?RLuoCPj6^2l$EiFsKZ8&l~~ z2#+p^zJ%4J`akRN8Jr#TMmeI6Yh*+E>2>(Bw}9j&;~%wCG#m?rCDxiw zi7zP*HV@2KKflbn+yD!qNSaShO^wCJUj+S^vWr*b+3_8@rMg<(%@$xZg7Vh3 z#T+UxxjCLYSm5e!$^aWKr~(27g{70rCBHml3|BL>E7lU0u9^Ug(RhvjAQy=Ge-(%Z z?`vyov&;kBJz9bezdM2TL8|ND=jY-4a{qjSSAZ9ye@JxR^JeJ3KNh*YaC_0&8LvT2 zZhK)peDq9yFPdksf1Aph9Yt)!xxMfy9Na~t+8zm&7Px$dIi=`RGBdn;mmR7I^1asT z&Sd3^O`-0_41<=Q;Y~Ntx{k0!ME2f#c{hVxH;srGH3hPj4PAM7{AbGAuNQEFob46= z%JxGaa&oM=;?mr{wVVb$WPeGQ)L5lsthLZ0`q;(Gzem$hR|$zz^Hfx?e?ou;C2RbHtlILTi4v%vM*-PfQub1hncZm5{pl$^zR zl(}i*qufJ2ZSRI>m1h{`Eiq++f(WehP_v?ggcW$acv5&huUi!a#cYN9;FU_a{263+ zVyP}9xw~xWi}7d!he+Q%aPf&LtOh|oB2=sLc(%tsv)rbGjwz$Ju~y_cgzob z1)R8IBGSAz+l-E7SNJ{kJ=6MQHcamW`$K^G;aL(G{#C-`;K>4k3`dmPgbh4JKX>X+jimAz8-<((T0BY6{ zmMEF|{ry_J=`ATi?&Jw+Ei+wq#?*bur4+qFfUY%Z;yAAnl7zbrO^o?Lp^#bWc;_qU z^-hWJk42FnH1=(cD{QTM|D05AeG{(qp=4OnQuk&z&Z_f!QAue(j-4W+M-NASmcoBM zOF;n%3GN#AWux;tJ)?j)rIAh&`{S|U_wV21QG}7QW~TR9W;uxp{{RFNl~-3S$mJXW z&wfJNFaE07L(xQOm^p%-Rg~p4-@#IN=FBCc()Vt}jdOz)*@1FqegXGx> z8!bow5o>G!P)I8(Mn-$j6cuhlTmSEeHr(^-o z4Dy>^TmI;kndFt=?vhV+1eU2Uw76EW?&c?>g5ax}G<#s{t z;Vr@pgY$)MnmQ28aqyN3LPV_fUsMELqwpgaF(jwIy49o0pPc~N#8Vq#Ym-BL81TvW zA8BMxh4=aa5*q&?kiqH3_(yf4m1^tsIA#CHs*;^=}i5tps2*~?8A1+p;o8ym`IhzC>w3TK!f z6&(7@_2bjfWn)1!DrVdFm(0=HfzKgll9PoA@7l_!yK8xMYh*3H#{AfR@d&nN5l@eN z`^N3)X$>nRKE9a7!TNz5TY4@HHdCR{>2#lwX&XmCb3J?8g)o`ljkyB>#ixM-+_ zs8xpUfN253q+>F_v_s*J3Mh@cK%gEu|8&?&Y-zfJ#uZ%CE^Y_^kgkHB^V0WDk*m1jUPEG;vd$f}gyN=$l zk?ECD6hdoetzDIRu%^0t=qX{)Tw!nezS^hH@cMBsbmeq_SzH%It~-x`=I$YkzZwcl zLwG;dqC(#5U?+i8&2YY_@lad4Ur_os$F$SkXU{&*5qCG!rynw0x3;m33Ftm7+N?EF zm)-e@FP(C|Yk-g6Tq>MAC>1u%-v~wAaOrKyo2{6+{zJfVMVD|(OpJFz8tt6>zCTD% zXyXAkZO~FhQrjB&N|nXr`oJv#G0SSPh1*?SUpq#x!Y9LqHom#rq4n~dJl=bVQ4~lX zb^_Q7@mD-8;DE>oiHh>`95C%G{#I&kvp+KCX~LRR4)`~iB-ePr`6o+%;nvXQsVV8< zF~5WA1)>n~vkqg=jx&#EO|TRlC&xxh4f@LNHxMK0nq=F< zh9`5J`%BYq5Q*}Nil?>2E+{Y)uOw99c#;>zwjl9XjpNGDmAwWUYU}yX;Ny_nU(`P| zh%XSJmSWgNJD3Tmyh_$+Y@~@Ho8CspD>0LxwcZ6BAv} zsh93*t1^k&ql%!7a&d7Pa*-g6I4{WACDjNCq$jfrMWnI69{dc|yTQR~_T%m?QLE~Q z)*s{$gHu7iCJL;+RLt39d70BMC9`H4!j-34*aR^2DPALRLzO&t#AhRZN1Mx_zdK5r z109a+Un#Ko`|m`+l5*oo+?__1b5h>JlHToPqU7@JE(>?*yOZl(G)sP!+Rv>_0i z2F}er)6UUIehYT!w5)f_@<&HU{(5gKk7L;g2V>Y5(-H367=^sCSBw$PemQv<)(v_skwa>)8RAKbJtl$5ymGyJreOh(z?qG7j~Ox{{VpG+{J?V>h-|Bz9GJb28(!_`yCayv_RkI z*O_CALLF5t0@74^XFU>ymltST0DAT+nO!8%GMRDwoGtGbR6X9^696F%|8b#_ygfw_ zNspEC&yZXymZUz}`D*avDY2kCW$E`SU{ep2JAYTb)!)fLNkkQ~nzlnJE&Gdv(2CAM z?}652&t-LW^}eBx7r2#z8M2jJl@4ZRQ$<)Vsnw^~Jp}VO0L1KfWdmC%VpHmTX+ZXe zTU82Gn!X)fWa5HtBMjE9b)~m3*O=eLVHg(n3O$;SEFX=4FiBj<#aK&+$GDdm&`Hp> zcBlN{7j4rnv^)l6IIx2MRce-VxwR%eWUrmehzq!(a$`xsN!Gplc zJcsI5NqbdtOZ_Nx!>?Lyd3Ls0 zb#-z^Mn-+K60f87+YFWteIt7b7K3#kP}LKwQu*C^S6yXVVuGENsJhYDg~M^S0hiWqzq^PDr(&@s;vA$y zhng5kHn$c8gzV*+U#6&HJjzPq4EZ4;LKWr8fC`GzA5`(u4a0||yDaPmXJ@Mup|Sy& zM?}bYs0@$pmdOA^~*1B_9r?oXz>A6X=kg#a`_;`{BcWC_H zgh$qNs;ph7?mocCIzeuUm8iD7EM|QadV9JU(9j+GFipUa;SJJ=B0^GEO#E4McUwIQ zcFI-Q_PYhYOLU~(d|Z6OmtR-YZ%yW$~}^9m|uEpfZt!#$BA(0#mzsB{-oG!2Ekv`(|Q zEN1x`fAro5loxdU5r=O{OAi7{mbU=mB|Nq16<5G6 zSQV0A#zG~8v(Id$j1>#c)Goe=Qa_Ngb-`k1#Gu1f_rvbcq|tiZ?!aDV`6q+L#igh2 z?(Tzu2O55#$7tm8f9RwDQX-ymhv8i)JK*R!A0_wGSczqIry+L$P(r2~7d2VS<_7Zi zb2Jlsf~Yw3XsI$4us0=`)VAs7#OwCw1!1`|g{S1C1h27xmZbB$PQ#?HPMb=5W?e~> zt94vll2GzZy+!8ff^b}5h?Hv>sNu3te%-E$$2VQ66?#kgBUC2<<>WBVnyiJwFPPA))=!B3J{_zVaL( zjK80uo}h`5Z8e`5%Y`KF@sFLySR-V}ypCS!DX21NT?zunMm_+xs^!tu+8H^oEEL|t zQ~JW>!~IKk&>Y%2j6R5gAg*6c*2FJGMjo+)7UpGmR}J)D12+qx|8Z!k5$|0$&ey$e zbGlrlW=$g(i9zh6%7H%yG3UOsIxOYW~R7!#-p|0vWshYb5 z4Y&E>$O3LamI#My{|cq&D2jDg9Zkvdpub52D67;b*I`+vFqoO?TiYHvQozEae3$uG z`}_M@d$8?kWT(#=GQick`nRs;Y6c6VP|p-Z{8K)uhn*l$5CGclV%^vJ$?wQuJ=DRf zjGOJy@l5;$VcDHY{M)H#)knX~UcW3LC~>`scu*pY%-tjDaC7Hd+S*FkASvdj4m_Xa zN;h+ok_Ys#D*9>7t`R*er(kBScX#=eu{@Jep{XR|&?B6!SoNgq?H=f<*3S&NPM^=s za&S1W5f0gw(Brx#OS64@`R@fKMLuoR-8O*;K3Y9#bFdPtPhz!enI6C>dFd7c$=K3u z2@wYg!K=o0$aSAV;*fb@Za%SOblASXzQMLT-$bxHP`O$j2n|3RcpF2-wa8lYTs@tJ zqQ&h$Q>y_7(Sl@&s8SVPI7J&AQ{;2FGmqwXe9_zckX&_DaUST_Xxt!IotH2En?buv z&aSi~o~nVT))f?R=}Ny&ZUjHQ?%xm8H&ZY3wx>z#jAr|sTnA0K*Jl~^)WLyoI^ApDueMI+V#3>ySxw{F$#NLwAztx89?n)h-94d;RYNYGoa zAkaq9!gCcfPpGrZ;XhL|CJ*x2 z5(NVLM;g={sj72A8c%~7)oASjqJaUy!}0$8nb2!siSI^5%Rm$W3TBX#QGmFkQCGhy z*@6OH()?n@BMm)Z2?^4@pg+QnON6@Cz8_XmS~7nxXvC%Defteqn`3KN25 z=hTre02@D$Ea8CbuMVfn+l~iRz~kHNsG&P@H2yu$g`Au)X$zlOb}mnxWM zuo8J&lpnA#e`#)t@4@P2qs`^>Evg-vY*a zs#%Dl=hlMAZ|$#6X04`UisxMNR#zrt)^L0-p0W=`sUUYiVPR?SE#Jn5nP75UyiGF0 z6Yz$@N@*@O`oX{0*oV7cw({kWHZJ_2F3YY_B%|p?>N-tCgj$;^tWU~O*(;hxTt^3+ zP_Y1*?v{nPApI(g&cd+ge(BwehyB+w03Rc4H5nBE_ie7uam`!CF<%Q&+AxHaX!jj`P zk1GQLBxP@0!|Yap5e`+urxj$q$|(hxrEBDX4q$6CvE)00n7q@opNAMinJq3vfyuGl zqJp;>s7VV@%ym}EI97`+9O7l}I!auq-7CTuryU9UScz}|8p54eRLj7?MyUgx>FCQl zfza<}2dZ}7YX^uc0N(A>Ud@2GU(@*L=waiX0*fNuoqKxj zgSKLWnwqhOy`$^dIHUo;nAR;01Rei^k~^E^xqN@hw32n3dum3N7_(PwdmE*M5}ErZ z3h&A*JQ7xsA!=$$PuS~bM(3)tIV;DyQ__S^9idQqjaUbwg?~fFfJ>h0^Ctmg8vySz zJ#tb#@A{#jAc~sR)7;Q}W_xWG*Y2H#csK#X1bqir7y0DKh}hM}zs18-Zr9GgBWvm& zC}c26di6;%EC1r4Ii$;R+lAeR)OG4Ui$2b4n{0`ct1uugtLr_+EcB&E_Egk#(S7#fyeIQF7R?NmJ+0iquL z=+fRfpOt%iG8G(+*u&A<9Ct)d7sON|ysHp}mG(&mcb|+)_}qLLEwzi)+8iZzz{rYX zU3Ih$b|~=|?#y?B$*D)o1>|*hWBvYTfbu#x;EvHg`+VBv zfecU-LA&%ZYo+m(t89+38Emq4P^{fN1w>;ZY3`1XR2}b$sbgn19$-S)Z85|TZmAlZ z6~dse^~}8;L^M?IVL$)wjW)mG6aD-Q7O?q+UxrUOcwX&7TJ^_?6DGvE6$Sk zKRl8`=&A7J6%9T9B!}ux3vtIgNj$J-L^cuD%6DY08)?dZ;n3~_K++H~1E6W0cKAH6 z4Lv(Pk7$6sgJRPYd4!^9X0n#7fd3cot`GuRnF0WXsVG3-zkiRz`wZf6e1I1eCBE-2 z*IIado0?^JZ;$I$#*J4-^L-d?F$Ctv(h@Kpy8*t$4R5MbeRx*!T3%h31{dj_YzbLw zj+mnC+ew@>nEmkW&p&?tvF{*nW^JqxsBq`&d}EV*mkMfq4gw*W${H91Mk%WK zfJavXdZc48SY~CVs0CQWqH_6EL|2!F&GQ<|uZzr#xWlkow-pxg^(}F)y>UtB^t2Xl zbY1#GJ`-C}l8;Gq4MCg4d}2dsEK60(ZTjYtX>#>;GD=0;CDhf;E&kTZfdK+#){kY% z=P4+mSpwp#faaCACs9)TR+FuRIYFh{K$9}N$RQqJYhVdWZ{3G`x>)^pY3&}B_KCN% z0Z}#IO&`>jhlbG;aKs;ouMq26XB8htA^wuXu%8r4a{?UhFY+fA3HaOOA^n6eKA@tF zUEz|IorJf(?PPX!B27vvf@kyZp96Y@Lv1R#yFX*IR!O^~=o?Q0PkrLUv%tCts!4_uWnZI5r4tULtY zZL88eFf^oW=oK8VN9ZigXvTZXuF!(Y#L*)e&N*37gG2N-o#DF4Bify|lp#mHLNf<7m~LOa5iBWu z(`QYuRiBh7%om;KwPV9I=}~#nYYvqhf@ctihN~-?zpoJkL1lV;3_!O8AFqo6(7*rUpTAksehz_X5t9jV5dnIgSd4@G zN!*gwO_s6kcd2oGRcMqp`THt_Rq0Xsb=#lZJRdEMPaec4?G9cPUmr4TH8)plOtCYxk_(SuCosj^88l*1Q89^)JDafjx+?MM+O9pk#o=Ot5tldKW+^3^ zd$*OCnf=jy1TC>HCPxjqde&$Dv|Yi_2sJ4b{0D4yzT+ob5-~DQ0-!kv7d@N=92a!V zReIx4SgrVi!-v?Akb5?@Oi-r)>aM+yL%Pb#n)IzrNEwGY)GiLIMhtcx5-_Z4Jh5vZ z7N$N2*VWeMiH_ENh8Avlpk?G<(H0v3iPjwqou={$`FgLcuCvH5goxhHizz+2v{$NS zgnAo4&_Sme)*vGL)9HV1($%=quu)U67D9t0x5YcVLaK1-cP(+l| zC3~(jbrvY+qtB+OHZP%HYpSVqdel9hsMKp%CdB%$K*{*|xPH+iLx8m_@QQFUykpPx z+=trL4mEL-x}*^B`>~)sdFp97@JN;cs_F`aQFXI_+>Y%c9HfXLwz~2MH^%u~9C!EQ z3v2`>a*A@g9EYS>W5I~mP~#5TaGgg#rf?57-fncPyVkyNOyyh){43y?i#7hUba4uE z@o;Im_x;Lmvv1iIPvKIN2x@NO` zcN28RSI5UbW;fy`?h&~iE05%m^}?H4Nc^W$8mBH8oSa|9h(96>49ZHPQ1?*DW(E;> z{6&0TOXbQ~ZpbY4)w2nL{Cq%DU^gY~Od%5c-^Q}v2ZXR}$ha15&Oik`+Yewx@ecW=L(j9AcqErupcDq2|Jn3@ z`by^6C`P{iUHpCw!S`wL3>0gHef#c(Y~&;ZacUj^mJ`^r%xUP|MAq?^JqffqetN^{ zK*ys@I99J=Ych6p_a1qC0q~&l$zk!ShpFl~N@A|_(PO;}1!SRR1DX`>Mlxc=Y~;+j zp}WR}!BUN^z-Xi@2$d$k(Q#y*kf2a|c&Z&6x=E zo@2Ml^8-a+l#Ddp+qKNtt;Lt8VNByw*G)^gS+vQcKo#pTA-*$_T!0j=mzbWcZ8@$# zb1VJ_=CVoFUS{Q7gc>9U#@}jSnW4M8M9+^#)=e5Mpz#Jd8@S56P46K!3|k4_?=^K- z+?xNZoR017M=kJ+5iWn-o9)X?VJg19bwH0J55}S$7eO=Oyg0x`6D+cY(18Qoc!g^a zNOtD6iY>53Chex}isa_)|7fma4*bJ`997*qtG^i+{-q^YEkC;cBef2t7= z7MoR(YwuW+u-J`+h79c?DWl_(vfN;KQ{R~A<#%%ehx^s;EJ{eAjQ=Iz*bYV+6A3!{ zUUm3=b6`0ye_6voUQ$R+EhKX%wz;rGqat~~VerlQHDKv5-7yK2Xh!QcIWIzfo&k(c zJaGuVN_9(w2g13L;@P!$CId8xIXnXf_NdgH4JIHY05?R$2!7}w?y)dwu-SET%(8qv zllZe7KTTZ3I+o0N6v__{0_Y<1EEpOnryJn~}Q&0uLfW z0NTW$C+0NeN5h?Yg`dBB5jIr_1e0+BU!JA=Q_bVtq@7uQlX85dTJD>I2o>lYV-X?q zdzW>0wFx!h1X0;Y9Nbg#-QsqucIl~bPRMmJ>b1S@gHX^gIbqFIK>tey9n<_=T4ePX z&+IWoxBRDp0X6V{N4CH^*D2p^S8Sbo!-P3gHW&9I!W@dt2GR*k-6Hi`?mZj z4Xo4E3Qd#hInRTGhXb=mRS!)Rc?7{r8}i^Axe+z)UMl*uUs(SY8$dK-F zH}0^D(BRmWsgbwf5Mj?jqMK+&15mDoD?z1HImWV^N@_3}v}^Myhd!xlB;z7FRN) zLTh_uvSLnPef(fda9qd1Ekx-{5OXzCCJ=ppoa^O2*jys#JOJmNNk}kVjG0DPc0OD1 zPT4GYX?t@z2n;cGpyX&E)W<qw&)uHJcmh`L3zC@x4Zbb{oTPtIn^)M#;mO-b%yYiQ$EWZcy$%dNRQNW@C(3KRmPxN>olJ+5(8@~a@eueM97uEbtF4D=6$;GQ@ zmK|eN`DJQnrisyctLqqkMS2oF*J0OWds8gEN^b*7Wx5Cx@e3syGDbVj7kUaz;ui#P zz2;#qdF1f#n#S($84yu_uMghqR1buOLP8?Sz1_a%6BzBAB-jS}j(?6zz@Z`5`g-l~ zVEUSoNWVJSHPj{2vAfzZrp0&PlXYz@R zpyk{K>l+X1;~Pu_>FarFP?A+3)zKulp&jw0EcrGgJ-qQeqI^rEQ$Et9rllBDN@tqC z=8JThWqrqKuUZ5C4A75DyCya6%BH3%fS^=l-%$f&L5&T>5Q`IC%NB0nI+5GrlI`(gP_%a*{aQpf8a*URNEykr4o9S0e%9APo2%~_a{{ysWdq*!X@p$`ziO(52_ zzp|g`Vb_xygQCVI_ocR_zF?&To-xw_VFIIG&Lior(w3<1sZ~}Ztisv~R=d{>;W@&L zD-P<)S+7R@0UpQ$%%1Q~M+ya=-f4u7BT<5yJLRY8m9QLwk$dNWc`os7lqJVPL~B_# zH+V`!(zEiw;88Ambsz$0LS9v+|W zPfe@r#N?6u~ID{S5Lcb)aLFwG~fB;_mvNbVT}3 zf0QCzQ^qH)tj+r0#;F#}yRfAY!3=`Ayk&;l2)6tT%OP0QwW6@M3n!+-A;O>L!St+M z1*;s=HAPrA-dDDEnh~PmiT9~NDT^{wZu|d*%AEOsjGcKrmHYShiAaTtCTv5YP?SuO z5S7fwJWG^e3uT*!ZI()gLZ(oL%rYdyHl{@8*oci}9=1Z;>{+*SobUN|p5O0z{yYDi z^E&Q*-|MqJYhBm-B2?E-cBbQ^bzvPf*O64Sdv-qTvC}>Ks?b2TFv;IKh6dWc5K~S8 zRhan3Oi5uH{Eu{7ds)g~Im!<=$Z%#vs@KR;izOKy&uT8Ab1To9e}t%T2T$HU+9D9Z z+?ZvZlKGZE$%H69p#H!hCWfC3*6v8B_N&EqVF_^38Da~8-?0!b&=2CUHOZJ`sBm4I zmZ>{Q*e7zY)G;JGgOMU$lRg1hLW8=|>{pTDrN?=&VEE+(dG${pSzyg1{WOXP-8H$n zWfiA~JdbSX+t{jH0WBo>Q4~=L>YGyze7WdMt>u+1_S@+{Q~D{jrw}16Smy;rE}QN; z-4|Z#E)b{H{HjF1HSu-qi~c!~h4;uUixRt&jVQFmIp^9EUlJ?1|2S6L>^^@fQeiY7 z=S+|b>Yg5~xZ5j?6Ar3~=1e6eqlYwK$`T1{ZR`(Z>6fA_@4vd3M8p`(Q|&6!7GKXJ zm3M5oC*I8&V9|W*OBZtq%`3R!^?Lnio`IXPEUm5hVErr6Gq~JVivJuJHy~Qug2y8% zfkiz$F|q1QQJD2dKPYc!Lq0Vr3OGLa{+lc({|9$Vyk56YO~$wB+^5NA=(W&?4co@- z>xT5LAIDP6$CbNH?Hu-O)<`|=c*v_U8Y!%iAeP{6*$Rwk$C(5p-P zI~le)=Bzj0;OSZU16|pbY55TwRuG*iJgCtb`Rc7PlR=-7+EwC7Ftfs3m8Q;xSZ}xR z^km1@*uhDU$$M5(>kl3?R)%A6I)a$Xt@=T7!hd)Y0E?F&Jdb>E?&pl?L)^7CXU0671XWU-`g|?hJ<`>lKYY0*8U`e+d0C3ql{wtG0qVqvz$n}Eq8hXI%w}kY{>AlqIr>sI zUr)@ZbC+=ZF>4a%H($J@JeIzJ9uIs$@M5@i48<&pqFPj|Xt^LEf%atpQO`=Dlm0Q- z0|ov3s;{3qR9J;>&eQreZxZ9}{{(OES9h@|ab{DT*}A!A3$*kMnE3^@%1XR_jli}< z(wcNCiZJL&zq;0v%Wq|E9W@opWVJcU*L^{(>(_K*?2=G&2<5`WdJ9RZj+05SJzrMY z=+(2$X|(#{($}1e16mj&Hlo9BRw#iT{L)%l8Wxo$ha+Q6)}inByL> zHsTLl5f^4V{!_)QU3P0ub~1w!rCvIS*UJ(3Ofvnqm?fUiT-A7)w zza|}e6v)(XCbSsFJNqp^>YncJ_W}{^E@e@@8Y(+0iy*%9<+~+B5NE6ReR0Z(&6nhCWOKI3AY7> z`jRRe_|@#!*0_4`7P}b4qdQ}2C{ef0PoBkoWE#=j0EVH@K3chVvWKR5Kva)8V$W7B z0QN$teYA!!kG=uz#TUG&JXSSy+xLC32-!?h9@BO^!r#AP{Svq2J|b2y?<24q~e8?hP;&NqwE-o zHH(_QbgK+&g;(ik$|-hb~ikGG3 ztM#t9YU6#(>xy9x00(m$kJ%Z&SHqn4tN?@OOyrg@#`=^JX{ZVc+P5AvI z8%gPCuHnHQi9;m))x(rDMblSSUa0B^RTTr2!^jFve6~511UD-Mu|wyLn2#L!)}8E? z9_OPmrdH|1{yafA=%)nlzMPc=YYFUqJqxPTs;R`K=>TWX2Bue9tHo7O$;09GVRDx$ z=YkeS!g;1y<74*}#T2*a?wfOLPXaw`7SzE;*zA7s80YKOJxlAcSS+mPLocvVR+-Cj zm^Pe}Iw%#kykH1HP#s;4wA^3R{xB-nRB1>qdn+@=6szn^M=J&^84&vyen7lXPa6Wl z{Bj*$s?ZTKT`_m5Da}rLA|7VSpD)7I%-$ed83A%K8+D}p3sx!ut=_-BIg8KJ4{AuT7F#N~n3Tss4fk$JmUvg~TZECR zWDk@Cn4qqjEK{Jp9Bys|4W1(=2ayu|eu--j+F85lXD0Spg|v6rODClg3}4}M8R;6R zESeju7Eg71S~}t!)YB~PD2d4?)+D>F|)%5qX&dwCG2ZlY3_!6W8 z%yL>|pI|E4`i5&f69TVeXp8Ios65=zbZ6a-B;+R0|M*aR+uPfVuIyVaqdL*u7ikLc zA4RiJmW$s!kN|B}-o6z9N{L!R`4%z+5wKTpH3)Z?wxM&L_fWC=4dDsU>zd->g!D%JZ~e-N`Xl zS@1BxC^y#1`N=!JiimyUCT{)uago+AuLHI35YvY$*AiCAnYqjJvn)(_zrhy#qM8e@ z(7T}*e9+gCRY^Qfj#L0iS3J!I%~l3$rpYIL`mOT1Jt~tV9_IeRSDM|Iwa}j3nyG6W z@?0#hT8Wh(2HGhb{^l%2*i#>Ec_BIN=^6t_0D*nivYl{oVCw`5f})vfyx8!+Es@n9 zV*YZgzKc3CX+VxtsN|s0rKAd0r^M&`ZyYTBz4Ws@Xz zybMuB_Hc5B)q+=9u6TOjwU>p30OYwa$l&b!#IOV02WTU0USt#`y4hU51d~pFiJ2TJ z8WZ}+ywU1ax810U1@67Z?yIO1N9;l zR!#i4Tx>=b2w9NJN4ZM5L<(Xx*OR{@Au%6aKXc;0O}^D19tK_6w#B}y$tYm94bx>` z*qRUOM|{}!4rStUuL=c$L&asX;_nz{n6+Ff6@}Q1pXbv2DDtIL!Tadis7$@+H*L<* z`i09=)JeMj;lruVjx8J>D0G?3XaCgl6N5}87%UtG7qsgbS1(QWn`FkfT^)F8l20(n zT5V2LP_?U~xo!E8^3;KE1a~u-@JLMHtdnXaf}*I{sn*t%VTXvv!$SBNh+70SjLLoVQYTIZ*trNO-axK~=#f7SB@|?aBgob83!J9Cro<1O&Sy7D z3td7<_Xk6qUKG*gmeZH1{xXqYEn5@MiJVw-q3=S!KGANNAy1mGT3bs>C5>#vvVLoo z@uK6ippIc|8t@ibQ7YL zzl^5Om1@kcq?^0l`GE13FJbmJW=n{PdRdPB3JjdpeyhnT(XG&L6=o&ilI=vo10bo#JewsXEhE@Od51Y)-q$-^5W>2E?nr)LzKQb zAPnP-CS2r7&d5k@IWT=#sUZzo{tTCSv+e~;;Y;@{`Vx-T4uq($$Iu%%T-0(RURTbC zplqay2)ojIl$W36lz1?AYSwnB@;a^A#IWP+o?%brI&$MV^r4OKUfV;&ENBy>=tlB1 zKczSk<>TGDtU!Zr5j` zP-Elc&H8AI{j*`o34&4G!-`va4^70`i-n6}jcnAZZ}+T6-lRj#R~y0I)oBY$U5v;t z^mDo|FDgb(_D%%34=a|lyU<6Whd6W=yIBh6>{5TS=CjeqzsUvdzPn`C{Z7AM1pL#; zKsuHsJHp!;VaW%?c_+t-JgJJszH6Vp>oMdwzpY}cyX)GG)@RDJr*@6+I2RnK;BB}g zkmlH(;g4EeiikKwRBKXz?f*q6AZ`NJkNn=0-xYeS<}u)?eKZi<45Gu-><6JJ_qez% z8(~*H!MLx!wxp^sYgQ7I=xmSE;LbmqO0clyjWkaO7Z(wii25ck-3`ub%Ym zi}9K3t6N^c{5>LjQf25RDOx~}X!T-v+8LI~S&P4DpGyaJ2Ftkm(LJgOsi$L7&Z|0o zTh;52MuM9L=%rFBGCyPo1YU-br&B@Cei`c7o*|>BatI4cLWqmW1?&oOFCS5Wo*7bf*h>t18)GiI|ONh|End|4I?2xU&^ZInB5i8FtU~mpbT4%WQc--~CK>Z?G>N zgw!j!Uh4SGjOx8;)sXz>Lj75#$h|RKq7aoB%<*oHOsL}UiE>qa(61`wp|^(34jtd0 za+p_lUxG;;dFP)WG+?u=#Wst&-mo9{lobDBc7=sdT7*yyd77jIKrP!LbwG%N{M7!T zw4jaRPCqIRafKFyzccbhk()iq^l{gm019@u2pOh`pqjOK^yB9XV79$&?dEpg^4`+% z3imuOBWwcAb#qG>xju<<<#)+xxqraz2bmr&{-S?w;aH~ey{uN^5T;aKEkQsMnbb*Z zyONA-P=vZ)`oUKynD3_6O|NRK4j7SF@M4`7I*JMGGH>a+^G4^ZsE#ap^_w_5JJ`FF zi%%qqcIdz8Y7Qj^brgRg{W2ccoc(0Lb@@8p=sW+;zUK1uK6dhMtLEoXUjlf(H*ui43zprJ=kqy)5tmpZx$Jm82> z95_+2PWq^Mz(H0)BME5Occ1dJ9-a?RcwS1jEJv3@le3XvvlS17tit(+h;F7<88%2kk#!dOS(NbG}3GA(Q6D@mU?{H0#UBn&7&4BGZV(kuiVUJt$D2~KNR z&LGNm8G2OSbRI`gX{l@Bky4iXYC3TL5!TDt9v{=~yp(6{W_3XUj9m~Ra*>fG?wmUR z@`!sc0(A8bAQE|TN9+;c64sL18?jKK7F&w0O$o$qZ5W5<`#CHd)Ay2!g>Qgrr3C48 zV1?WxElb_v<0bi+&H9W;0$+rqy*)@SxsEEPmc2CwC!xH(z2*5!$*BDVPtoDwO%k+U zN;*N^v3q%zXNW+GOf?M(A8UNvtw{gl)Y|1E8w)rfxt@e^&-SuEIgIAfyYh3KeeMbQ zepKg?wjbmlC3|OIAAHmtGaYZs7~FjVuQ|KvUn~-wiHv}fLA#^%((`Csw9^%6M8lEh zbl*iw=#3VQ>B#1f|Bvo*d3qUYbHv@zoye+Gg;_p09Y!jk_z4V59@i-^9!W2G1?-QX zw-Us->j$xhd*{}Z9B~?>^c$rY_Y+wNt-m;2X4#XBAkg$w$~s6wNqF9hX<0#`b2sV6 zuV14|8DAnQAgcIWgN%p9E=mAhL7*crTTV$Y8LUXG%yW2$<${YcdC{~CFes+HHZGty zZxbC!c8aZ&I!Ew)AuV(&qE8-DOWkRn)U1i#R8mkB%5G`z4xtz)lM`8Bjt^vssZ=jOrh`JjM(fZ*MBrT157cQUU5g| zqvlUv>uWN8>e&4>hUEj<|7b>MmjtsPm_js%`}B$Sh9gMr_JnycxFmCA35r1@#Py_S z>qCjD09I+_&1%l;JDBt`vwK}1@VtPEd0LBIW`;h_$009{pUmy_WxdnvMq@)8<;2+B zy#SEtSe@3%AvtA^^-`y-{!rC3QPmD04V&0pJm#pNGW`4|em9XSrbu&70V!kqT)0KlI0vt44Fu*!&v*lU3+?|*D zvdrmxb~|1GI7CflhGN=Z-4m+)z(wP3GHvR0U(T8p*X724cDCW*rG*0LhD_uh4{>+2 z>IkYO2x2K6vu4YC3=i!_L6I+fpGHA|97g6H((}JrwJCXXQ3l99J=V6o`Ohltm_q$U z_Uz$q4q|E@A=Fb}K_DJ>BGwGnuQPL^fmaK|(epu@%&@wue4}SQ9hU;@#JyXadD$1Y z{i2{Ih=3{jw3=ty;&CWS@Lk-tbkTfx+4l7*L72nNTroQ3Yugx-}-w0VoEETPzImMI=fISk}r)FvS za2~5?5E=;4NgD+5wf^ordft#s5-}kSjzlt*GuWRXVS2MLWie ztO9x0HYPaMKiGM;h@Z>VT5m?Fr;!IWGC8zW<-XAa0Y~bqEQ2z4H<-#)L&gdCxh-_6 zE3`c~i?%m>hWZ<49S_l6S8}Ex)~qT`T(BHsKN1J)?yxVF^Fa1a*RKy9;NX&4^LeIy zhKi*KeO1}>71+TATQi2eCy&!!{Cs2m=jbzuPE&z79z_c zab_;ur-tMBaTKz^cTh7{tlPa>j6iHMl4d3a^cJjy6I}kdl3WjIIT(3i&ak}+cBUM* zSN2GTbajKoV4Gls%{NgXT(s~--Hz0upmPjKdnD^_u`NBs*cjfa9Zt<SYlq`DZP{hV}lJ=9au`laoS89}k*(A~R{ zZ2|kz_K4jSh9P18G+o3^%_*3CEI z>;!-+B6Bjh9W0!v-Bn}6eIR*6NBm|YO@f9jgcpMp!Wvgqj+FLE-9LV2CC0IPJK3E} z;LDr_U_eG{Gv;#Jlf&RK;XansDkddWg&|H7s*J0SHRzsm2nkVro_j2f;hJ6sqvV^q zveshFcAZUi1?;^UoJD2^GQefH0vnbX;?vyD+O8I+;1?(HzX)KEEqdXQ3fRP-bkt<* z>7)P8Cu08X^KnGrG*zqSR7cDGnk-usUWtaw<87V`&WD^$PM_tukia7J{oDZ-A=I9D zzs5sRvmag>VlRBSEuSZJK${*KIS2w;5Oz1J)8A2NmAbBR~>0)yk zlG~oeT(4tZ?Jv{*7_r3LvH&@nJAar`D?+l#Z3c&6(WBKx z;6}H4nL87ZR#V80qs1T;`V=zG|K+RMk- z;tCqdM-=NbyzEc7j6zL}3Sc>Oua<6M-#+jIt(@Gdi!EGFO;>t;)8keL%R5z(L47NB znL3(P)~?om7~y89;#7I}4u->94IaDOwMXHSkXG*N6!$>xrJ!iZu|4*?=%!`-Sj%$z zOgV&vZ+DkLmxF0KZw=Pm+ ztji1cR3%ywxTH6Pkq(hH(EN{`jNv}*>9L_)UG^aqojENu`{s!Z3YIP+_l_kY6Fg8l zPKqQYQ%cTPF{LZ|Q|;#8oW&N6WnWs#M367iK7j`p`?~1*)0z-gN8=$wgwgltk;B{(xyJ{`<({W8b+ zO%phB6|!VxJ&XnL6Eky(pu!px2hNPsZN%M1^s0#Vmvo=3$NqnL6u@*qfR$ZHi5-^i z2G5`LFDp?WxLr(oj686>X_&RXay>OY&jf@mW8%2Wk+MITjGs?f7LD zPk1n~Jh*0Y+Z?0B?=`8K_mnJ0jzpEIrz|MN%;7fd zxHqf#oV!5n=iHN-`?>W7f6BHMp+7K*w{NLNdA^oi}He&i~VHEf`}T! zm>X>?r(`*-V3y%1pi&$ie*rIs;w$hJm=qj9#aGLoxl_AWqqFl4PKf1Y4K1ydWeL@W z)>gI&9>26E#6X7t_&utM`~ZNb`$&mI_}1#tqfgwCop_^F($@I%|9}akqE8@n^zh%z zeQ(x*Y+t8W-N_obcHV;mPLOoN@Wy7a(g?w!XFY+{9O}f%%F0+Vc$+!*UtyQ{tsTkT z-IWftZTGGFYDM5$yD8^ons_ZKa8c7DE6s?WV8;=IK?pQQ0+!eC$T-sdn zg3dOr(?3vTk@b~+yB>pgn+Z4!96gQj8WMWXa~XBY<+6WmC~!Z;iT2-hUewJf3kAeK zHQ7+?(@&NAY*IiOo8h%_L<8o9O_g($sJ@oLDx3XeHbOfMmtK+)v(sF^RRA-9C&W^* z*M6V98uB6S=U!o#EWp=R)Olxv6xRE5v+9#GYlRF{&mkZsE%A~W{P-Q-(s+qeT!%tp zWl5jtTBgw>TI1P?-%ad+IJ<~Hl>&Qo{$l4w;Jf*sJm_#*s@Ef?gJP?1K-zErVby*! zz-|p2u|*{NkOSv8jiT0#(5f#sz!T+5A1x4Pl5l@>#&Dz`EArwb{HdT$&skE~)~mpA zY3Lx>>+ECr#lXRm2i`=F&ufBYHm7&~z~dc|p8#%N1t60Qu!FOjBxwFy?97nSU%F~M!;G&}o_G5M{F$sV1>LTxQ`pW;yA}8| z(c6FK1y-?>#ESf8e-?gLoD67aV6u^)ywfR|ClwUj*He=z7q@f{f>qq{e{+HMAOCG# z^e#%yz(5&K0VI*Ac)8Vdz`)yx%S8AAqF;~5D*$fw zB5%`~2m2i>Ki#y(^`*-k0$?2FP0mtX`)z52%L*&A8EzUOpkm8o8nP;HEK`7F`@ za)3ELOm_1T{?lj698yI5IXE1jZXAu`jkHjVY)N&PDzmcK=b48w7iX=W+IfE2;em`r z6n+e9Ep@bA``|QHa=(eNwjrwl>b%^MEpR#{P_Fm4FDZ$n4MB}6S(n+iuF%@ zCV3TdicD#*TCGylSBV6z6gT-WIDr`M$ac~aZ0Xe(I>)o`O>2#stTIe)Ermvxoi>-4 zAELZ?>&?X&*gVf3EFbaLkW>~Hgql}-rN6(m*t#r70f?zR1)yzV@vIV`!eO+A=RO>! zIH%&sHb_KKkyQ%^V;63{vSjDz;>9j;2P^0GcNG`9w00E3l zi3*ewI#Up<^pQfIc1O)E{C|b{gv|VgWp8j~vTg%;4gY?9utsqX_w}#Shg2&dO{imM z!cw=-{Tz^B!9w>C;J2=L{E)6>Cs(xO5aKxkJ0<|OQe%LHT;-2gUp`>?$sK_5b~U9wRt_a!OI2cUci>kM1vvf{LwD^kIv zRTBZ0-?;es@#ME548MJuxaF~%f6miW)8;D}u?|7sA;1cqtC=-qC&jy)ej{LMNlxom zb+cgCz?mOe9DX)bwDIg85oX!!3iC9s7@2A4#X)-2kLBW{7&zGT`aEUOh@@g7^VUhv zLk;fNkSW(QD(-GUm>wG(M=TUSSX1y%b|KwhSCwnrQdtyhgY*0GE9xtSanZ(|2YlS= zatN`qO$3LNJD!TxZ<=W`)SlOf@qCe@rz1jskag_rluSbq=nkV2uqp`A(J3Iyu3{I* zPxUvj{=w|JRZlJ2R|Xz+!>&X)O5>K3Lz_~cs2o4W z5&@)ym_#sqB_*YjQ_S8u5E~l>j1pvcP!`C|Q>DvUf4!0iHb~aK^bqsZ5c`$Kb9OuR zOC|ByquY_{hWe&nXLDXhAoOOgAl7Zts&ESrt%O{9y7D;=g8|g%Sio68w3VWzwSxZE z((9#46(hGb$A$GfcMDkFHaNl-!#3yo%S>pRAcTP|Y&W>%nCsmi5Zs5b;ENC!dY8uo zrX0mQbQr9!OQ7hQ_Qrd4tl`&|2v18YS`+w*?nMbzgZ|POW8$jSf%kl!|MlR&D^|s* zB>yYAT+@#(S!TizA-h0!4=~10;t7rZqs_*uH@xQ1LPT3gVs8^k4hu*@L_d`5Ca;NxYwo;!@IWe2JJTKalHp0xef(4?xfaug5Idg z2wDF#Sza*z3x`D4~6^w%gp-7!cxj!VO_T!mjf?58`bImF*#g6;V-t%#nLrDZ& zjZfqY39VNNr=puvlLPpE^mI&LcvakFjL$tfhra+{q37RUp*%=@&Lx9QVeMxGJSQY4 z06#W5>T2>Yj{bcFDvt=*Xi*YlD7x>Q@CD*y0l(*KVJsqLHF$Bps;>sy8N=bF$3(ZD z1Fzfnd1D659+-QGv7Aq)B6+BY?mHkrMcy*5NQ$jsx;~3IsuLBY0)4B7 zTf%^l4CDvbQ@3F-JUV23ArkFmHviMf=9sNJ@v(K<2zg5Aj#ic_LG0d%)ZbA&|0-L# zQ-s2g$jF+(q6IK`7hzvsU3?452Vz@Yc)F0_H@Y;qTy*C1wdR^Nb5rWh(wn9-fWyk` z9$x&34{by~RR}hc7qZ@sivO|7%CxsFsRowpFgotQX6WrDu7(L)4DU0E{?@U#vqq+F zACoPT-2`K5hA<%aegcpp&+LJAJliwSw>v1qAcb*rpt(DK%rz0aY~90NIdvgjVZ5FH zNMG~ItV@G#s_9Hmr0$CAPb^qW<3#-smmNC%H4>JB9Qh5qkRv~$(FZH3Nvuo*l^-tJ z+(y!m&aH}x^*PfSqt#gFT)EZ+ULuZ!XZvM*Vtb7q3$;!$@3uIr^y+_(dazyi(&+bv zbA)&K(zRP^K4FzC!wf*g3Kt_x8fAay|KE=cJX!$&R^Gn8slQ%4gMt3{`4{EpBz5Pu ziTUQ{)&2(-=RIFiEVr0$erw%wM|(K_QHjCqHi!8kvb`}$Ht@YrQW;7`TU0JPASz|n ztg|>VD1T$+*3lp+dGOkmr{gh?+emwqm7CxUfnJ$G|9K1zl&{8$H1a*T=qWtbyg5s2 zJwPh#Pq*z^NxQUoi-&*SI~6K_(oB0cyAP5+g3p%UfQTai8w`Z`s;h&SP7KdoBR26I zM^JQ+aEyIBkW%QJmK$1^YrxE9jm<694Prrlmudm~r zj?Mk}cldKiefzkRR7nAQZo|E&VgKEUacvv&HGsPxTp|(bU(+szkD+WNb_V2b&dgpA$En)BMyT^-N9Y=|%6v*I%n6od+X( zV~(Q@A$I3kS>TmhNA!F^M8Q)1F8Gklv|KH+KJs5jEn zAFmXcmhz*Rs87^`dK%qWx5Y_;!;M{xe{!glw{MUiA=F6*c`2JH;;oHP2*}L=8@XUi^w&X;{5oP#KIJnndoF!g$8Mls>~TR*x0sS2-N z!wky9`h>nWi~9fk#$~DC#zG_Z&TsGPIPAgW+UmfO5?$#vR877hOe;7sjbsIgs3=xl z6AhaAZJ*n48zzIuq!gj!!>IJozU&RDA#yL+Z{~N!*zR9g5M;G{S|G4h>e$eq0oo&j zz^uXZ!{pAoH5VJCdV9@7cs%A?k?QBRx_TSHMjVv1S2;&M>mVsB3y9wpOrwOm|2A}+7 z)w(=b;6~N2r%!%QN+!cOlQuZ_Qj7TnEsr*(W%)lryRVJIsxXpz?t&2;1n$u|a>m-7 z{Vd+v5_{n4Ld>NMzOzw{vEn>=nc^vB~B=P_- zW9vddt77>jxmSM>iy997*5NG)IsHtb{55;waN~TT@a$ZYgXKZ{>N@~Gy~*dmwuw)w zs~!qN8n~a)FCw-R6{_bnEeM5UScau@m6O``D+P-$T{5b`p#^-BjW)GYPwqI{=7Do`HEcux0NHokA8l}-V;VRcqJ1^r^YF{v< zJ-vVKBz%Ene0B>*S(4d60To++C4a~))vnMjhMB)a8jLJkA2 zPU8asVdVEX|M@tw5Suzr!ArOl(P*S-Af7)fbtrwZ!}NJ3B09Xt4b?e%LIn<$-eHV;z~FrTCD9(bGva!2tnZWa%1Xu#nmqC=r{z(kzL! z0Q}OL(tI68TmIf$*Mz%(ljDP!ut2AjiMdJP)SA6UC(~sEjH9P6tYmC8pX`R`)yei_+3GpF9PxQ15FuW=9YlKcvCZO_CmYiRhKW1pEzOsK6khBS>SaJ^2#I>Bvv zUilKIV8p48x3Vcy+~Mok1>?v8_6|&3L)-cA6%JxU{NJ|>X4ApR8RPg?jDx)CWdWeAO24dH)`8FN z_TXCCWbV1k=dHwzQW#R?Klq;^c;aWc!XcHE32A}XCWylClF9eaiiPKpKEEA`iOyNl z&wwv&9Q8u**EXcI^3T>}mUXD|j$|2)%~+BjiQtZ=a}@|*6}2;PQpRo!r{duTx_s-~ zWBE@@O?9C79~KEGq*q+{&bad({%Z?bU@(b*H=@+d`CXj+V)8BTQ`f( z|MTv`T!W~gs+SxnmumXJ<@9$HosBEh=iI|??~LjDT1{rfl1zQUpaAPSMI@(&Rf-dt zHaI33O@C{NgLi(=_?Pf3W%9`O!Io0v@7meU9#fwrYv?xEQb>@^ns~Hz1N^DSsZl3h z$sngr_c{`K{Y6_#(ZV_Ou?eks_NZh8?2F2}nfUM3(&|s&klpK3VUdFD{7V8Li35Du zNDOwdEg=(H|NBU!iO}sC+@Bfc+nqnaNnFPR?l$Ebpc~kaX2W>Wls-oe##j7tihk^6 zcb>?}93)M{!PTw&PGpInjxW;5`Ix=|DROBi5#?X&Vrdb_Il|olNP0bxSUk)GL(C!l zSEiJ-%$6lS5LyU9x6gC;!^x`tBTh7BXramuWuvNjl!UE$JjuML5vWEqYS~WD2Qdb^ zx?#baw&=_0u>_CX8M5#7%g(+xD^v$0c(!Zt!$dE&DdxBfb4+iTh#mwfi%v*(61sFR zeJNZ2ZmZ-oCw7j$!58N^zwy&*nft=QgXH+ZP0?+r>ZgUMKzS~*kBLw|IgliaFa?a9 ze@xI4qG?IO`rv98bg*WBzlQKei1yR5n2!{C=?N@?v=_xvO{r;>zaQB7MD(JjOcoc* zUc6u>M!%u^Z7f%iv9Y<91H@6_w&G797eppN*V8h<5rOPxcHuk`Y`^g6ZFN}J=hV#- zcwK)IKQGnPaKu2o>lUritaMdpI|s`X`Cgr^+E|V0ZxmSGx|$>q&RbEV_*?4{F#6ju zOy*2~OuhXzY@7e3v2h*ZmGEip>FOko2N#yBoOTz0< zR#&m+I-BSKor0ivKdR(pt+f^mFcr-{Qdnf;m(a#c>+{AX>RkFGFqhNAzpbM?W3H|x z8f^F}L4|YY!d&ndjb>)B$^_Y3cwS5fvP$XNT=}REHS0OED z!*A~jN(ue_)m&v_J(uj)6y0pPWzTs6$)N+9rQ&P?IN@x3_Y?f)2$^E`d6a9bpO*t% zJi63s)!R4@MLsF5&Pr(Y%>RN)Xwa8_0LZ!VD2#Az-6!4;8J;t5UaLzN(=- z8PQx0ByJKKqS#Sv`D6B(6Tz(rn-xi!fWE|Ee37-t?EM{WQqSG)Lhj6BjH9mV-}Ry1 z{kd0alC1g5t}>@PMcwXtDvg{IN!xl_^k^QTXht|Mx5KmBL!W8?jW0B{FmjUq@Qoup zVtO|0ib$q?oV=LmrnBFUm4pgdc~Jp}1!=@BXZuT^w$M#`|My;x{cD<{qVh*7JMfD< zWVTC*qsY~Rddhx(bii5pdSGv_9uUf!U_K9V=c%Pj5sZ?{q3HGTKSf8$; z(f%twUf*kp0nKk$yycSk7=Kv7=9WtaL;Z|>-u@Tv%KW(H{AYly6#ifofZftj*H`C7 zBDvy_mQhJrHBRNoYCqq=^qsIX{lol5uMBJd?$U%PhjI}sO#~MKJ$^E7#Gw)JOl2hV zQCjc|h&Ngvpn|37g&9c3vVWta<8l2;29alF31V@QROUx&Tx$ zH6`V}yWW-6qqRVTZ3L`ebo0IS<$J``h6BH;aNPD7y<VFua!RNJLDH=2}lkM8w3^LaG#FhM|&%v29giyC2+tYu26u=szQ7-8n z_P5dQr6oA`AK0k_|Nj}W#;~4HK-Ls}2^d)lmfn!eve$__AXwBI-p| zMi43?KLGBCj=^bs2c){Z#aC+pl}8$Sq6%5joua>+>^r61X;VpS@j|vkk+lE$-2?;# zvTFR{&AXE-L22__bpi!3vS@-p(ar+46R?O$3wjg_@lHG z0A9`LIemHBG5z7(=H26G#JM1SYie%ZSSLsigg?(9*u}~g$C)a%9<_a|FwE-i@|Nul zSl}K~15K({@lr=m2Y14NPkmV3m!kK#9&rSP%zSV2^r-UO(xIRM=}AR&o^|R<{1iZc zof`sox;!#(+N1k=lKq!nw|z+D8vE;3lz*+SzJ`Vy7G@ALZs6ds(BT2ts-=H(7c9NE zZ4muKIZ@?vKk0UdalZp?e^|+$SrY0|XDy1y92GZxe=367cgrkSQ*!AP- zspwj|xfR%gSCtRTYa(mHwoZ#$x~|Ab_4N*Np7kjhp3d>Em&c_p$4R7oPalj1{kaC< zk=?UErqQj4y3+#Cn3xwadV4RvBi7mBPQ566QD?AyFVq$W2RD-U+BAmn{DhlFssH{0 zd+dLNp%ipOPfUa@^iRvSnTGs1+HVWHl7ZNpiF9=4X(WD0zD z=n?n{Ce;`zG^yF5v_yd!BZ*-BjEH!7qp9cORb^ZH29%$CPi&eVja7Hha z-=x~^Rk^;K76o5bgOZpHl8z}eo_*Ic;^CBjXTtEeyuq+NLKb5{Tv_}hii!~gB(6c} zoc#l*lRw;AK=c3ShmKeCNI)qyI9ZK9pxu#NnTEW;Zmy)>+@{$Gq|X8=MR5V<(Y62H zY_tLP{jjxe#ubmtROSfu&gRho82oEIXfHq#^CuWDPtG!jZTciZBB0W{q|Bjs#<}&^ zj`r9|vMrbZ2EU!UZFD%k<>11?0wP{L2Qek5!2;enAmg{MPxxg)SqmkbP}|MSGVRP< zr^q%nIWvbW8hHS|xzH*rDYwdFoVqD_;=}5$F^MNyp9~)DE&$2T+2^*o*xLHrxaBAq2&+-$HM_F)%{z7GA%;n@kZ- zhH57`I&Wu|_lkR)xC}O@#mU0iaP8Vhb7(8PW@p*X$@s-S6y)h!itc`w`U9%QYU|dc z_ao8G?qaAT@izmg7_Mk&+-U(>5Fn)$K!O@$P{+vR)poz^2Fz9j2bdQ(sG%U)c-%Ta zbRZ!TbV|V=N5(ww%Zw|A8ce@Y4`HkvMpdTf|8;{Wll~C+B>34t{v};N4$(@cWRiF! z;=`J)OPki7F#Gf}0p^$61X!h+8mw(Uq-9@1 z_K5PgYA7lL(n^?jB3oB0#Z9LcJd->52TD8?bwPC>>9SY85mgXWUusok_(kdN6^ zzhRR9p)6}OU_kIFO&m#YuLP{=Xt>{xjeV{F^Y4&vAgj>(11u~NK3S<%_&nOs$FXPx zD#Y*v3?!d1LFdS+@%^Pq$gn~@Thr`zI~dD+0?b#lX-nAVG|r+6tX-Ah@>)Z%lvB0tv$*&?Co{M%Xg(s%YGWhb-Org zN&Xz<6coHkXrehO#<6d9IccHg!>vQXa$GFI4G^MLksh^aT$PqLDeT6!S~dKmsY&AM z_XvPV6jSkOT@oDq=MP#P8}hx^XP4b!TZS06pHGx*ire1nXU!cUO58oRb4NOU426nB z9K-u~k;bpnpA`v_&xZK%&v?>IuNlx9@;aM2)_(hIzr2sRpq*c11PzazC^h>&5^A9sREyqJm{?f zA~ZH1$k;y|AU6v0p#2H0Pq(#d)P+ary_8;V%n~?mSXo~A=sJF1!S4Wm*Wd52E(0x> zi|lSbEpgQ0G{xB;F`D!FB$No_&i&?08~=VdSBq=+Wkfl-4GBD${*CitLkr|y&V0MX zNfTA5TRGB1CnUkJPaC>DoP>Qdo}oyv?1Q&QB!8 z57pf2F*UwZYwDnEx8)7-uwi{ruiU4VVfY*gGw8p<*C#j6A+Qc{&b{I`DfZ50wZit% zPOkFNPQDPgVxr*WVQ%#TRJ+z!4&(R!{nz22TV~0BeNsQ~15|*%%YE)z))mRVk(o18 z#D?!~i3Q`jio*R<39fZJ_K1cPvvc;qT7}(5=;1r_-O#I#E>3nLBEwabbylzVm&9!+uk2G8v_c`~gk*%nD)wa(aH>96p>-Po(lf@# z(#AHjFOVA+NFoWWWfV9;fSz<-C_RpO4R5DA?IBHW@&Js)6`R95mo>6n4j)gaW7RyM zQ;ZJ8KwsPX^mX?kv9|fEnG5b&_Fr%5o}k|OOLVU@d05)o8f&i#FF*S4OPGx@#MSCS zZi3bT=BZ(D>IxeJSvDX10WP5on#azF-$qRc^soE@BTqzzn#|K#DkFO^_>5D4ZW!Di zU{wXlR&s7`YsyE1TIReF^QsSoM5Mbhfpv@r-`>tDTkUzbu1so$GC3NVY1-th-7jnzx>l z-84e<9_~Vx_A5VcPcDCY+U>LXGH6R7R=*FThEId=Ee-7#$Z+W1E`}e=w>wu%xmiR8 zP|(IHPZEot)JqMK~<$L~cu zImnl2`0Gsg$~PIEsK@s%NRS3FUvE7>`Elo||I6)n}64jX` zyB(9dfkb@6b|o65C^Y;NTX(qkojMbM;x`dVegMih#8d%mZhAny3e4^ew|Nl~VSPPl zn*v+U`*)!-q=3o9uy-V!w8V+;x5&dY<7Qlorfl_Q8pD)3Qy6)XB{LGc9V1h>H{)mE@d|9q5n7QVHZOBM(ZI}s#4scPT~X)ooUI|i z!dg6$0V!kF&A&#-WMQw$NDmlo3;p&8i>429$&|CYjO%3x>H0YCEDtyoVKWMh(Z*ng zQ=9?)TI}x}POjs;MAE)|+HG9uWMN$>BlO zgoCKU3b?>lRtDvvX+~x=$ecGswZZ1h9S|uV*45od%Z-dK4qvlZpg1rItz^lqo@7Lg z4xT~@N}m7xpZgV^^cD3+wia?@K^3pGow&Hw@8cv@Uq1XH`@EZBr-bhp78Pysp`iXg zS59ov2Z)yYAl2_spzY6#=Ddp^GNZzMm0+xZFoCjItm;-LA;=j-dnGFteWc z9!CCmX}-waXB|X;NXsI23zbabai;@d%NQ!)%->rta@vb7vU__WC#?6kP%M$W@QO=( zfbfS1`UscP&2Kx&x(_3b(tTe#sI8)dP?~!vN*ej;{WqFgcBP}L8-vL?f{JFx}=1Tcrtl|@1*)& z;>?Ei)YSjs>n-D=-n#eUp-YAaB?XjF1VM7>PyvAQ@V5hd+_$0`#$IQJnxQg&S%))z1O;~Yh7!t=Vh|^H#NDY80u@Lrsp@*?Ca%G z%ra1kzlNyYd)ku><~bvg=%waL@9@q)ywm$%BnD6;Pi#gXWt(d+%2@qG1CX`_@)pETwe{9N zV1dpET|els>YsQuxCEx3KVU|HDL_q*E>h3`;V3@uydV2gCXKW`B$0TGM8@V z05CA+CNd%y-4ZKGN$2zD2FRk43%#9_6BDBb{k+TlpfnAr7f~-Fyu{_;uxeh zV}lqL`j(aohgL3ch)wiLVGA<+!RFSEbDfbu;x#ZxI(RZi`oA}JDg`XW{O#Ouxd}j= zGl$PDbZ55BM5{Kaw>$t1FPWQfNOVjBfDamADNjJ}Sc5%)S7JW7o4}v-O=I>)$_crY$%t z740{v=V3+sV|WP4apULXKu}9)4-;}jU=FjONrE@%?95y*8!f!;CmIc=(DkN`UmZzy z?@V}50EU6bUWA}h>A!#8QtB!l^u8IW$iPPFuA}ZVeQ1Hkl%TS{_4FjJV%T6#Kw1>D zngblek|;TAO^(DymeU+6<;CXeX%n&kgi!(D<#dsa!-kf{s|z_gW} zA)8tWU%A=Sc|>z=q&1wj{L7!Ou~cdpLx-o#=RZjH- zg$nd|`w##6gP+Wd!2~$ArBct6mjr?G_vSAqcKtoeVk!mP$})5tQq1L|bl)O0C^^lV z#sOnZhy}0RsJRVp>+cHU5EHQAPWipGjsxw?z6M<5Dp3_gu>^B4Oc|N9q# zwc1*#%xW>ft@Y;?o`OJfvBm(*h?$rk#{Ba^z4=Sgm@hu8-3%~(AYA~S7~waI`bh|T z?|oirR^F%tx>*7kd~669KVjidC<^^TR*xpLs)fzD-k+iUYYmcYv#}~@Nl_SPM*PDO zfC7yQMWB}hJqsBmLu&MF`*YAbif-Ndy(sxH(Vs+oZU>ClF3hw^F~B8<9v2vt;5;|x z(D)>fLDBGtgq#4OBaeaY zAjihIa^NYk{&*w_ss4lKXD9Q(JAOqI4i7(A`==f>RRZ`Hnneo;O>$|Qo+1d13qbCM zc(=1&G*DfrsluNie=Zx(#cO{uc?}T4EGnoxWr0gVc^E?q7hp<^ zfS4QO>qOJKkss(T5G7eKqK?&;_)m&1y+8bKaYHFnGV`D1ZHlUnM6eL z#a1sFxSov^jjGjjd(I5Lzm!3Khte9O`+U2zfL|6(a^BF+hS1T8TurIGXa2k0e$OBEK^#{pxF;6pQ31JKBK z^A$)^9nW{5j|Lp|VjhYe@>+8fOIdW_|M{^WH)`EBjYJt9o}JEWNlmG5QSi})Du^)( znIQCu2$noo6YjI9p}XOM>$ciu29#bcaA>`(U{Xt$rxH?4uwFpW5>FP*OQZ^E;OH+` zax_>WHv78~gchhr;NCgqkxF847yLf{?sN3(S8(hUKTLQkxW z(9bO!CS<*q547oP*DYFUYI%<$*M{^5Mj_R*Wqhn0q%edk&#Dzr+O)%KVacO(NKRcz>v zV0&7ri-q$*fpS4$5?tuQDcyA&uyOhY8tTlY`>0APs0FvX0=m)w3}`Z9$x+gH^*Lzm z&+(E26Lp08P$pEAjIX5u4eq`8jV)R)=NJFb#d&4 z_2c;W?i1@^`KB-x?VX{OxQYh$h#jlvp8!$?-sax4A-w(Ihhv8m^t5qHo*@xf9?kz4 zOi&RoiH!v@{s?~5+XA#d5-?OSr~>`Vy zyGNCC4J;YK#E!>i;O6=E#dwF;Qq1!m`VL?CfhN#nTeB9zP~7A5@Gtr<$kz%7_57u2 z5`$n z6aoYoL?TC9;B5&8D0=)lc;d!Z*FS*KNif3POebZVe+Y|IO;{8Xbshx?`2$@YtMB_i zc4`v_uvj}025g1sHAA4n;+j`jxNFD;G9s9ZU2k+R_IE~1JVURf7o^xXxLUn`vQddi z9GnmO7wcs)n!sWzBbzkrFi-a$vmevw=v~eY}_d?!5h5G3!yrec?~i zHyHZJnybylxo{a~_{KC$12M|MNRzmA{78%b0DP^Coo*GU2S)`$j~t4!YHl<A zhfWkjiK*BNMJn>o_f$YBF)}c)z1o*p(>WB)RnIKFs8b3b1jIJHfK3xzz5j=rbYiZJ zlsYV`+#HLrLN;@r-TClaPx-Qo{+bc6eFI}BFyXM!*uNHR-Bu`WJ0xL-XaVYETmV*i z=~e(=FLQ6!I5T;70RwBfJ9fm43-l1R-cU=L7#;aWXs$P~M1H>K`6uYKdja|(Wh_~f zClX{2j9;aKy_?u+$}-a1a6dmmy{PV=;6x8T2>V_PkYsgsP(juvVhD_YjPahsQa=u@ z&D!n8%bH1A%$ns9uOvabx;(N)X_kDI@(sxD9FH_C7{ zr|lLE&aj+4eygRn8vIzOEaP)^uJUKadtpW9Gb=G>4?fr^#M%UZP<{|=vuYYFM2am< zDuj7e-tQ`e9J^T*AN;4x_W7V!6Ba`<9t49lz~u-vIP!qp^}D}6)-|}yy5!50eNrDB zbijt>z7O0ta4I$7je@Qml;kkZ@+^($52rZVgm7`UVq-0Ka#W5#u9hubYs$ZPo zP1=m(bt;p*vzG_8;~i+;-OuPv&}Zm&=b#P`4JHdLB)Sdnh0l%0LYZv$B%;oB%Y#fMO$C!!Xz@up}H3e4q`&AP5FL#TYw)H>C^{A7LLCnxPz5_^f4yEFw|$gQEk zTj|S_Kf?3h`yO~mE57PKS@x~jpeapn&&y69>7%{?bWgQgy+=o8?=%K!Jh~# ze4(&vf5m82ZZ?uYQ3d{P-mrzX#TVllzI5KE+S(7ISpuRFrw>$c6Nry%*y&Y`NhWLt z#WrS~b8Na|?nwN=7keOdry8t`Z?Tmo6(SE6)?dl-Td(y_|%(hH0NG zDcM+540l;Mzipk*epCFMm$PMbmS$jJTy|Y09ABKj;hfk?cuUjDWAUjO-+T6VwNY*- z$Cfr8`q(sww1jC&`L9$y=SuQR?cqtX80d9RBwAYBmJ60UU&Or)U-F@sLQ*qnTbGN6_37rpqeV>7#k9U&c<3%%;T1v$cO@AN(@_8G_8Z=(oZ-i8ljlfSv@&5y$_>$ zhwKL;8clwf;7Pn=r!5~k)6q5Zw)%(YHF7#0 zoJ*zPGlDTlp$cUB)6FJ?`-ry3cBQ`T66LyU=!exeuFxP=g0FXt8eh+1tBl_CGI)xE z&Wtg|VFh39)s;fyl9YoNF(QU4%uM=dE>&iS-+K7%(`L;Z<@O?i(vUP~Ny)u4X=Iw* zz`#JfvETUfAurJkWGWWiN%c)petxeP9W}x`)d1(f=ETT6VWrhaK5~3X(fuP>^)X~aY}V`TF)=Ym}X`Vdy4dBYAo1d z?>*ALrM#h@_%W%*-GgQ7HZV12bG1bIPBcxuaDtSyb$-3XCajje4 zIiwY~iM(N(?tfezcAur+QuL+)o%ZU_#zYOA@|`@c@pyXB7e zwqg2%?yuCz)~-OTsof5Ce!rpJUmttS{RqJGIazxx;}1RczcT-S-;{gnc#IJ;#6Q0@ z*k$ZLg!o(!1a~Di6dCxg%V7%74Ao_Bk6Iz)wVNLKR=!_~vsFE!>i_AG7xJ7EjdUO} z&0$;P4SZZUfq?Ijbbv2E5bIaT-Pr(G2@O_BQh(9pEk0x-L~zNBEYoRqWzdh>BN5;Zews@^q+C=3SP$Jj z40%nkC1O#|NHZ?B!9vMHvgP9yqbdU>57q3uO*g3XY7Mi3a8E_&NM(v=ROQOdTjZL@ z<@UptXE>qlxNML?$nN@LPJ<*qEZPFhyN@@PX<*8yq;-5H#}fY-*c%GY1i3KLLL($5 z&`1KrmWR-N1D&wKgE7`XT6-#p)O9r1pt~i~e}9na+1nO;{ZprNFJAM{h|bcz*{{t1n2^i)Dzn~p6@zj1rw934p6cE@;w8aBeE>z5yg(B&1PkhKS%Bj3bOxX z;I^-VwNYYa(Q^y^9C>f{X(VrJJSR&a=7Z7UCwvTB+pBt{%uLWrwhoeT#oNiJ6qe6s zkJc!ch}7kj#NyJdA7SF2#@)Gdr~k?D^`xEwxA{lg2lhIq`T_Lnc?Ie^($!EX-!*a# z$}p7>P9m{+Us*zWnW>E!zG}aoX#tUM$kSu(tkqe<{g=V+ov6gT;XMEW*3ZW*m322oyxuQiHA!3Co@$_C+uhQ*3QXYQp~A) zXjdyl^~%_3&q>jMdCsx+d2PR3kEZ+K9W(w%a#hV%&gzMMw^}YS7>xIi&r)bmfN-2w zA}M#4hOxzt8eJudsO@9*BXvToM-R^TGBo&qW*uVZrtHg2A%H0#fdo*+WW4VSnD)`byW6BL|!>(DoW>NLB+`~-bcxs&5mU&kpMvTAQR;zD%Dm&SYG zp5CO69BNKZPSVW*;O&Bz#4q&jKnYx{G$BLl#*kdD(`^EDYA@YrvFfjODxkFJ)C$sj zF_26MDa}OthQj7V8#D-6%?c(~#78uUS}=lNY4C)!l0d#*)TVqKGfw&g!h`Cc=H|F| z)X}aMP&wU-;mUV4ym{wG^YfgbkCKrIOLy0*gTI+mh>VKn7StjO9MVW?Rdh-l3v4juZ@1{w9J2ZZVk^j^EW1JQ#{94;XglY2Ey;Rw^ zjtDP3-BEqidzVv!>zS$Xm(!r+uisn>vHnraJm|XWMax8BKG^9LrWoNHKHcP-OO6S( z@;Lux6OtaqyFEM^U97A>8TgDJY>sBri{=x+jLnmgC18D8{&Zn+gsUX&)&bVYM@Z^x zWNk{y@%hyyRhHzLRBVbW7ZzDTA{++tU~C2n4`GL~*jsuW(UzwL**rN@i%eM{D1D6vpFR~KiT$k%>|I{_)(;!K z>-+nQi$*+G+sc@S$ZBAsSq9IgPhHS)@ndCV+Vm~UGo?G8t<1b>CRfGeK@Sy+7-?t+ zC$GTWt~p0Hzvf?s7GD-6T@`^ALK=lHgXP~pupB%1@WxbyQA%7Y0V~%vr@b53$u%B& zZ?^W3HF>G47Cq9&cN^L1Z-g8F&M{eK99n}|M|tEa{t?@dgQLih*SUS7B8gK z?9VBd;`d0L*s!H;g4z9{oFw%6s$XdwEuKBg>R@!YF*Ke5Wvh`^{0<-}dkz%c=VH(@*N&99(QdwpAwP=0WBfoMil+O1GyZHDeSFs5Ee0 zYOvrwxggM68WclE^;k%or(cPtlcf$U5fQI2RaAP0tl!

L_+y_h<3pyAnzx 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Μq{34`` z&wp&rtt;!(U^;o#|L*MtBXD+%+hVBt7nN4Dd>>_GG1%P2AUi|w)JX`fu}dhr^3rnb z(7O@m4Vdvh7)}*)xF?F`he0q@D*S!3*>h zXU51K_kKCjdE?IV@q8{B!4{uVSXZX|Ru_cVvY~tC?nsLo2wPzudkyyLgcsgAZqFfy z5iZd!2ex(+DeX5tLH6`}LaSS=8}V^Zd%9Pj#&u3?VaXWz`s35M6_p9e#`$LRX+sFE zRHXIWEE4s8wS3dg^22~!jpq?BF&){8o!1LQO0RU|TXbMwtWDg^Zajq$vvSW!-yV8> z`n4e!4#~UQ?2