horde-engine 6.3.2__tar.gz → 6.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {horde_engine-6.3.2 → horde_engine-6.3.4}/PKG-INFO +1 -1
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/_version.py +2 -2
- horde_engine-6.3.4/hordelib/exceptions.py +19 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/civitai_adhoc.py +289 -69
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/lora.py +190 -61
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/ti.py +108 -15
- horde_engine-6.3.2/hordelib/exceptions.py +0 -10
- {horde_engine-6.3.2 → horde_engine-6.3.4}/.gitignore +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/LICENSE +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/README.md +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/__pyinstaller.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/api.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/benchmark.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/beta_models.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/comfy_horde.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/config_path.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/consts.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/comfy_events.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/comfy_patches.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/component_materialize.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/component_stability_probe.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/graph_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/in_process.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/interface.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/model_dirs.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/phase_timing.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/progress_hook.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/results.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/sampling_lease.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/server_shim.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/shared_components.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/stage_graph.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/stage_payloads.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/execution/zero_copy_load.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/feature_impact.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/feature_requirements.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/horde.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/initialisation.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/installation/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/installation/__main__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/installation/installer.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/installation/manifest.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/installation/manifest.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/integrations/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/integrations/logfire_comfy_internals.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/integrations/logfire_setup.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/metrics.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_database/db_dep.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_database/db_embeds.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_database/diffusers.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_database/med_config.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/annotator_provider.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/base.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/civitai_provider.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/civitai_records.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/codeformer.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/compvis.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/controlnet.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/controlnet_annotator.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/diffusers.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/esrgan.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/gfpgan.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/hyper.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/miscellaneous.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/model_manager/safety_checker.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/LICENSE +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/README.md +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_example.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_fg_all.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_joint_bg.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_joint_fg.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_bg.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_bg_stop_at.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_fg.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_fg_example.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_fg_example_rgba.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_joint.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/layered_diffusion.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/attention_sharing.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/enums.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/models.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/pyproject.toml +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/comfyui_layerdiffuse/requirements.txt +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/LICENSE +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/README.md +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/VERSION +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/arcface_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/arch_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/codeformer_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/rrdbnet_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/vgg_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/archs/vqgan_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/data/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/data/data_sampler.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/data/data_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/data/prefetch_dataloader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/data/transforms.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/losses/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/losses/loss_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/losses/losses.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/metrics/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/metrics/metric_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/metrics/psnr_ssim.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/models/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/deform_conv.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_cuda.cpp +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_cuda_kernel.cu +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_ext.cpp +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/fused_act.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/src/fused_bias_act.cpp +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/src/fused_bias_act_kernel.cu +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/src/upfirdn2d.cpp +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/src/upfirdn2d_kernel.cu +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/upfirdn2d.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/setup.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/train.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/dist_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/download_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/file_client.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/img_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/lmdb_util.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/logger.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/matlab_functions.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/misc.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/options.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/realesrgan_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/utils/registry.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/basicsr/version.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/align_trans.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/matlab_cp2tform.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface_net.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/face_detector.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/common.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/experimental.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolo.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolov5l.yaml +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolov5n.yaml +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/autoanchor.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/datasets.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/extract_ckpt.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/general.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/torch_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/parsing/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/parsing/bisenet.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/parsing/parsenet.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/parsing/resnet.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/utils/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/utils/face_restoration_helper.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/utils/face_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/facelib/utils/misc.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/README.md +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/gfpganv1_clean_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/model_loading.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/stylegan2_clean_arch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/facerestore_cf/r_chainner/types.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_controlnet_model_loader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_image_loader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_image_output.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_lora_loader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_model_loader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_stage_io.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_upscale_model_loader.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/nodes/node_v3_canary.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/comfy_node_inputs.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/constants.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/context.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/definition.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/baselines.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/bindings.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/cascade.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/controlnet.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/creative_upscale.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/features.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/flux.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/qr_code.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/qwen.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/stable_diffusion.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/steps.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/image_gen/z_image.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/families/post_processing.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/graph.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/horde_compat.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/identifiers.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/node_schemas.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/patches.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/payload.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/payload_pp.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/registry.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/resolution.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline/sdk_adapter.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_controlnet.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_controlnet_annotator.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_controlnet_hires_fix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_flux.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_image_facefix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_image_upscale.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_qr_code.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_qwen.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_cascade.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_cascade_2pass.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_cascade_remix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_diffusion.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_diffusion_hires_fix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_diffusion_img2img_mask.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipeline_designs/pipeline_stable_diffusion_paint.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_controlnet.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_controlnet_annotator.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_controlnet_hires_fix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_creative_upscale.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_flux.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_image_facefix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_image_upscale.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_qr_code.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_qwen.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_cascade.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_cascade_2pass.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_cascade_remix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_diffusion.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_stable_diffusion_paint.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pipelines/pipeline_z_image.json +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/preload.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/profiling.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/py.typed +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pyinstaller_hooks/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/pyinstaller_hooks/hook-hordelib.horde.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/safety.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/safety_checker.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/settings.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/shared_model_manager.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/train.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/__init__.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/device_pinning.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/distance.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/dynamicprompt.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/fd_limits.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/gpuinfo.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/image_utils.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/ioredirect.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/logger.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/nvml.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/optional_deps.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/sanitizer.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/switch.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/torch_build.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/utils/torch_memory.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/hordelib/vram_planning.py +0 -0
- {horde_engine-6.3.2 → horde_engine-6.3.4}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: horde-engine
|
|
3
|
-
Version: 6.3.
|
|
3
|
+
Version: 6.3.4
|
|
4
4
|
Summary: A wrapper around ComfyUI to allow use by AI Horde.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Haidra-Org/hordelib
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/Haidra-Org/hordelib/issues
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '6.3.
|
|
22
|
-
__version_tuple__ = version_tuple = (6, 3,
|
|
21
|
+
__version__ = version = '6.3.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (6, 3, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class CivitAIDown(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CivitAIResourceNotFound(Exception):
|
|
6
|
+
"""A CivitAI metadata request definitively failed with a client error (a not-found or unauthorized status).
|
|
7
|
+
|
|
8
|
+
Distinct from :class:`CivitAIDown`: that signals a transient outage worth retrying, whereas this signals a
|
|
9
|
+
terminal verdict (the reference does not exist, or is not accessible) that retrying the same request cannot
|
|
10
|
+
change. Callers translate it into a terminal rejection rather than a retryable failure.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ModelInvalid(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ModelEmpty(Exception):
|
|
19
|
+
pass
|
|
@@ -30,7 +30,7 @@ import time
|
|
|
30
30
|
import uuid
|
|
31
31
|
from abc import abstractmethod
|
|
32
32
|
from collections import deque
|
|
33
|
-
from collections.abc import Iterable
|
|
33
|
+
from collections.abc import Callable, Iterable
|
|
34
34
|
from contextlib import AbstractContextManager, nullcontext
|
|
35
35
|
from dataclasses import dataclass, field
|
|
36
36
|
from datetime import datetime
|
|
@@ -45,9 +45,16 @@ from horde_model_reference.meta_consts import MODEL_REFERENCE_CATEGORY
|
|
|
45
45
|
from horde_model_reference.model_reference_records import GenericModelRecord
|
|
46
46
|
from loguru import logger
|
|
47
47
|
|
|
48
|
+
from hordelib.exceptions import CivitAIResourceNotFound
|
|
48
49
|
from hordelib.metrics import DownloadCategory, DownloadEvent, get_metrics_collector
|
|
49
50
|
from hordelib.model_manager.base import BaseModelManager
|
|
50
51
|
|
|
52
|
+
try:
|
|
53
|
+
from horde_model_reference.download_engine import DownloadOutcome, _segmented_download
|
|
54
|
+
except ImportError: # pragma: no cover - the engine ships with horde_model_reference; degrade to single-stream
|
|
55
|
+
_segmented_download = None # type: ignore[assignment]
|
|
56
|
+
DownloadOutcome = None # type: ignore[assignment,misc]
|
|
57
|
+
|
|
51
58
|
TESTS_ONGOING = os.getenv("TESTS_ONGOING", "0") == "1"
|
|
52
59
|
|
|
53
60
|
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
@@ -86,6 +93,24 @@ fails (its own cleanup) well inside this window, so only a crashed writer's temp
|
|
|
86
93
|
Age-gating the prune reclaims leaked disk without ever deleting an in-flight write.
|
|
87
94
|
"""
|
|
88
95
|
|
|
96
|
+
DOWNLOAD_CHUNK_SIZE = 1024 * 1024
|
|
97
|
+
"""Bytes pulled from the network per streamed read of a weight download.
|
|
98
|
+
|
|
99
|
+
A weight file is transferred a chunk at a time rather than buffered whole in process memory (one LoRA
|
|
100
|
+
can be hundreds of MB), so only one chunk is resident at once. The chunk boundary is also where an
|
|
101
|
+
in-flight transfer observes a cancellation and reports progress, so it doubles as the responsiveness
|
|
102
|
+
granularity: large enough to keep per-chunk overhead negligible, small enough that a cancelled or
|
|
103
|
+
throttled stream reacts promptly.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
DEFAULT_ADHOC_DOWNLOAD_CONNECTIONS = 4
|
|
107
|
+
"""Concurrent ranged connections for a single large ad-hoc LoRA, matching the worker's checkpoint default.
|
|
108
|
+
|
|
109
|
+
A single TCP stream to Cloudflare-fronted CivitAI is capped near the per-connection rate limit, so a large
|
|
110
|
+
LoRA is fetched over several ranged connections to raise throughput. Small LoRAs and range-refusing servers
|
|
111
|
+
fall through to the single-stream path unchanged. Overridable via ``AIWORKER_LORA_DOWNLOAD_CONNECTIONS``."""
|
|
112
|
+
|
|
113
|
+
|
|
89
114
|
DEFAULT_MIN_FREE_DISK_MB = 1024
|
|
90
115
|
"""Keep at least this many MB free on the cache volume; ad-hoc downloads evict (then refuse) below it.
|
|
91
116
|
|
|
@@ -110,6 +135,17 @@ def timestamp_to_datetime(value: str | None) -> datetime | None:
|
|
|
110
135
|
return None
|
|
111
136
|
|
|
112
137
|
|
|
138
|
+
def _adhoc_connections_from_env() -> int:
|
|
139
|
+
"""The configured concurrent-connection count for large ad-hoc LoRA downloads (>= 1)."""
|
|
140
|
+
raw = os.getenv("AIWORKER_LORA_DOWNLOAD_CONNECTIONS")
|
|
141
|
+
if raw is None:
|
|
142
|
+
return DEFAULT_ADHOC_DOWNLOAD_CONNECTIONS
|
|
143
|
+
try:
|
|
144
|
+
return max(1, int(raw))
|
|
145
|
+
except ValueError:
|
|
146
|
+
return DEFAULT_ADHOC_DOWNLOAD_CONNECTIONS
|
|
147
|
+
|
|
148
|
+
|
|
113
149
|
def normalise_a1111_triggers(triggers: list[str]) -> list[str]:
|
|
114
150
|
"""Return *triggers* with any A1111-style ``<lora:name:weight>`` entries reduced to ``name``."""
|
|
115
151
|
return [
|
|
@@ -163,6 +199,16 @@ class SkipDownload(Exception):
|
|
|
163
199
|
"""
|
|
164
200
|
|
|
165
201
|
|
|
202
|
+
class _DownloadCancelled(Exception):
|
|
203
|
+
"""Internal signal that an in-flight streamed transfer was abandoned because its job was cancelled.
|
|
204
|
+
|
|
205
|
+
Raised from the streaming loop when the manager's cancellation generation moves past the value the
|
|
206
|
+
download was enqueued under, so a caller that has given up frees the shared worker without the
|
|
207
|
+
transfer running to completion. Handled entirely within :meth:`CivitaiAdhocModelManager._run_download`;
|
|
208
|
+
never surfaces to callers.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
|
|
166
212
|
class ReadOnlyModelManagerError(RuntimeError):
|
|
167
213
|
"""Raised when a write, download, or eviction is attempted on a read-only manager.
|
|
168
214
|
|
|
@@ -184,6 +230,22 @@ class _QueuedDownload[R]:
|
|
|
184
230
|
A worker thread abandons an in-flight or queued download once the manager's current generation has
|
|
185
231
|
moved past the value stamped here (see :meth:`CivitaiAdhocModelManager.cancel_active_downloads`),
|
|
186
232
|
so a caller that has given up on a job can free the shared pool without killing its threads."""
|
|
233
|
+
progress_callback: Callable[[int, int], None] | None = None
|
|
234
|
+
"""Optional per-chunk progress hook invoked as ``(downloaded_bytes, total_bytes)``.
|
|
235
|
+
|
|
236
|
+
``total_bytes`` is the ``Content-Length`` of the transfer, or ``0`` when the server does not report
|
|
237
|
+
one. Invoked once per streamed chunk while the weight file is being written."""
|
|
238
|
+
completion_event: threading.Event = field(default_factory=threading.Event)
|
|
239
|
+
"""Set exactly once when this download reaches any terminal outcome.
|
|
240
|
+
|
|
241
|
+
Lets a caller wait on this one record finishing rather than on the whole pool going idle, so
|
|
242
|
+
unrelated queue traffic (the default-model seed, another job's ad-hoc fetch) cannot blow a bounded
|
|
243
|
+
per-record wait. Read :attr:`success` after the event is set to learn the outcome."""
|
|
244
|
+
success: bool | None = None
|
|
245
|
+
"""The terminal outcome, readable once :attr:`completion_event` is set.
|
|
246
|
+
|
|
247
|
+
``True`` when the weight file is present and committed (freshly downloaded or already on disk and
|
|
248
|
+
verified), ``False`` on any failure/skip/cancellation, and ``None`` while still in flight."""
|
|
187
249
|
|
|
188
250
|
|
|
189
251
|
class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
@@ -298,6 +360,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
298
360
|
# freeing the (shared, daemon) pool for the next job without tearing the threads down. Guarded by
|
|
299
361
|
# _download_mutex for writes; read locklessly in the worker loop (a plain int read is atomic).
|
|
300
362
|
self._download_generation = 0
|
|
363
|
+
self._download_connections = _adhoc_connections_from_env()
|
|
301
364
|
self._index_ids: dict[int, str] = {}
|
|
302
365
|
self._index_orig_names: dict[str, str] = {}
|
|
303
366
|
self.total_retries_attempted = 0
|
|
@@ -583,9 +646,15 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
583
646
|
def _fetch_civitai_json(self, url: str) -> dict | None:
|
|
584
647
|
"""Return the parsed JSON body of a CivitAI API GET, retrying transient failures.
|
|
585
648
|
|
|
586
|
-
Returns ``None`` on a
|
|
587
|
-
invalid JSON), mirroring CivitAI's quirk of returning 500 for impossible ids and HTML for
|
|
588
|
-
|
|
649
|
+
Returns ``None`` on a transient give-up (an exhausted retry budget, a repeated 5xx, or repeated
|
|
650
|
+
invalid JSON), mirroring CivitAI's quirk of returning 500 for impossible ids and HTML for some
|
|
651
|
+
error pages; a caller cannot tell that outcome apart from a slow-then-recovering outage, so it stays
|
|
652
|
+
retryable.
|
|
653
|
+
|
|
654
|
+
Raises:
|
|
655
|
+
CivitAIResourceNotFound: On a definitive client error (a 401 or 404). Unlike the transient
|
|
656
|
+
``None`` this is terminal: retrying the same public request will not change it, so the caller
|
|
657
|
+
translates it into a terminal rejection rather than a retryable failure.
|
|
589
658
|
"""
|
|
590
659
|
fetch_logger = logger.bind(manager=self.METRIC_PREFIX)
|
|
591
660
|
start_time = time.perf_counter()
|
|
@@ -618,8 +687,15 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
618
687
|
status_code=status_code,
|
|
619
688
|
)
|
|
620
689
|
|
|
621
|
-
# 401/404
|
|
622
|
-
|
|
690
|
+
# A 401/404 is a definitive verdict, not a transient failure: the reference does not exist or
|
|
691
|
+
# is inaccessible, so surface it distinctly rather than as an indistinguishable retryable None.
|
|
692
|
+
if status_code in (401, 404):
|
|
693
|
+
raise CivitAIResourceNotFound(
|
|
694
|
+
f"CivitAI returned {status_code} for {truncated_url}",
|
|
695
|
+
) from fetch_error
|
|
696
|
+
# A 5xx above 500 will not resolve by retrying either, but it is a server-side fault (not a
|
|
697
|
+
# verdict on the reference), so it stays a transient give-up the caller may retry later.
|
|
698
|
+
if status_code is not None and status_code > 500:
|
|
623
699
|
return None
|
|
624
700
|
# CivitAI's 500s and HTML-instead-of-JSON pages rarely clear quickly; burn most retries.
|
|
625
701
|
if status_code == 500 or isinstance(fetch_error, json.JSONDecodeError):
|
|
@@ -644,9 +720,19 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
644
720
|
# Download queue and worker threads
|
|
645
721
|
# ------------------------------------------------------------------
|
|
646
722
|
|
|
647
|
-
def _enqueue_download(
|
|
723
|
+
def _enqueue_download(
|
|
724
|
+
self,
|
|
725
|
+
record: RecordT,
|
|
726
|
+
context: dict | None = None,
|
|
727
|
+
*,
|
|
728
|
+
progress_callback: Callable[[int, int], None] | None = None,
|
|
729
|
+
) -> _QueuedDownload[RecordT]:
|
|
648
730
|
"""Ensure the worker pool is running and append *record* to the download queue.
|
|
649
731
|
|
|
732
|
+
Returns:
|
|
733
|
+
The enqueued :class:`_QueuedDownload`, whose ``completion_event`` a caller can wait on to
|
|
734
|
+
block until this specific record finishes (rather than the whole pool going idle).
|
|
735
|
+
|
|
650
736
|
Raises:
|
|
651
737
|
ReadOnlyModelManagerError: If the manager is read-only.
|
|
652
738
|
"""
|
|
@@ -660,10 +746,15 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
660
746
|
worker.start()
|
|
661
747
|
self._metric_active_threads.set(len(self._download_threads))
|
|
662
748
|
|
|
663
|
-
|
|
664
|
-
|
|
749
|
+
queued = _QueuedDownload(
|
|
750
|
+
record=record,
|
|
751
|
+
context=context or {},
|
|
752
|
+
generation=self._download_generation,
|
|
753
|
+
progress_callback=progress_callback,
|
|
665
754
|
)
|
|
755
|
+
self._download_queue.append(queued)
|
|
666
756
|
self._metric_queue_size.set(len(self._download_queue))
|
|
757
|
+
return queued
|
|
667
758
|
|
|
668
759
|
def _download_thread(self, thread_number: int) -> None:
|
|
669
760
|
"""Worker loop: dequeue records and download their weight files until stopped.
|
|
@@ -743,7 +834,31 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
743
834
|
return True
|
|
744
835
|
|
|
745
836
|
def _process_download(self, queued: _QueuedDownload, thread_logger) -> None:
|
|
746
|
-
"""Download a
|
|
837
|
+
"""Download a queued record, then signal its completion event exactly once.
|
|
838
|
+
|
|
839
|
+
Delegates the transfer to :meth:`_run_download` and, whatever the outcome, records the result on
|
|
840
|
+
the queued item (:attr:`_QueuedDownload.success`) and sets its completion event so a caller
|
|
841
|
+
waiting on this one record is released regardless of which exit path was taken.
|
|
842
|
+
"""
|
|
843
|
+
try:
|
|
844
|
+
queued.success = self._run_download(queued, thread_logger)
|
|
845
|
+
finally:
|
|
846
|
+
queued.completion_event.set()
|
|
847
|
+
|
|
848
|
+
def _content_length(self, response: requests.Response) -> int:
|
|
849
|
+
"""Return the response's ``Content-Length`` in bytes, or ``0`` when it is absent or malformed."""
|
|
850
|
+
try:
|
|
851
|
+
return int(response.headers.get("Content-Length") or 0)
|
|
852
|
+
except (TypeError, ValueError):
|
|
853
|
+
return 0
|
|
854
|
+
|
|
855
|
+
def _run_download(self, queued: _QueuedDownload, thread_logger) -> bool:
|
|
856
|
+
"""Download a single queued record's weight file, retrying transient failures.
|
|
857
|
+
|
|
858
|
+
Returns:
|
|
859
|
+
``True`` when the weight file is present and committed (freshly downloaded, or already on
|
|
860
|
+
disk and verified); ``False`` on any skip, cancellation, or terminal/exhausted failure.
|
|
861
|
+
"""
|
|
747
862
|
record = queued.record
|
|
748
863
|
download_logger = thread_logger.bind(model_name=record.name)
|
|
749
864
|
|
|
@@ -752,10 +867,10 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
752
867
|
notfound_attempts = 0
|
|
753
868
|
cache_key = record.name # Refined to the version id once the target is prepared.
|
|
754
869
|
while retries <= self.MAX_RETRIES:
|
|
755
|
-
# Abandon a download whose job has been cancelled (the caller gave up and freed the slot):
|
|
756
|
-
#
|
|
757
|
-
#
|
|
758
|
-
#
|
|
870
|
+
# Abandon a download whose job has been cancelled (the caller gave up and freed the slot):
|
|
871
|
+
# checking here bails before the next attempt or retry sleep, so a wedged retry ladder stops
|
|
872
|
+
# within ~one request timeout and the shared pool is free for the next job instead of pinning
|
|
873
|
+
# a thread on dead work. An in-flight stream also honours this at each chunk boundary below.
|
|
759
874
|
if queued.generation != self._download_generation:
|
|
760
875
|
download_logger.info("adhoc.download_cancelled", retries=retries)
|
|
761
876
|
self._record_download_event(
|
|
@@ -765,11 +880,11 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
765
880
|
duration_seconds=time.perf_counter() - overall_start,
|
|
766
881
|
retries=retries,
|
|
767
882
|
)
|
|
768
|
-
return
|
|
883
|
+
return False
|
|
769
884
|
try:
|
|
770
885
|
target = self._prepare_download(record)
|
|
771
886
|
if target is None:
|
|
772
|
-
return
|
|
887
|
+
return False
|
|
773
888
|
|
|
774
889
|
cache_key = self._download_cache_key(record, target)
|
|
775
890
|
if self._is_version_bad(cache_key):
|
|
@@ -781,7 +896,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
781
896
|
duration_seconds=time.perf_counter() - overall_start,
|
|
782
897
|
retries=retries,
|
|
783
898
|
)
|
|
784
|
-
return
|
|
899
|
+
return False
|
|
785
900
|
filepath = os.path.join(self.model_folder_path, target.filename)
|
|
786
901
|
hashpath = f"{os.path.splitext(filepath)[0]}.sha256"
|
|
787
902
|
|
|
@@ -790,7 +905,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
790
905
|
if self.is_default_cache_full():
|
|
791
906
|
self.stop_downloading = True
|
|
792
907
|
self.save_reference_to_disk()
|
|
793
|
-
return
|
|
908
|
+
return True
|
|
794
909
|
|
|
795
910
|
# Refuse to fetch a fresh weight when the volume can't hold it above the floor, even
|
|
796
911
|
# after evicting every ad-hoc entry. Writing anyway risks an ENOSPC that takes the
|
|
@@ -812,72 +927,134 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
812
927
|
duration_seconds=time.perf_counter() - overall_start,
|
|
813
928
|
retries=retries,
|
|
814
929
|
)
|
|
815
|
-
return
|
|
930
|
+
return False
|
|
816
931
|
|
|
817
932
|
download_start = time.perf_counter()
|
|
818
933
|
download_url = target.url
|
|
819
934
|
if self._civitai_api_token and self.is_model_url_from_civitai(download_url):
|
|
820
935
|
download_url += f"{'&' if '?' in download_url else '?'}token={self._civitai_api_token}"
|
|
821
936
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
937
|
+
segmented = self._try_segmented_download(download_url, filepath, target, queued)
|
|
938
|
+
if segmented is not None:
|
|
939
|
+
if not segmented.success:
|
|
940
|
+
# The segmented transfer landed a file whose sha256 does not match the record: discard it
|
|
941
|
+
# and retry (or, on the final attempt, fail) exactly as the single-stream hash-mismatch
|
|
942
|
+
# path does.
|
|
943
|
+
self._discard_partial(filepath, hashpath)
|
|
944
|
+
retries += 1
|
|
945
|
+
self.total_retries_attempted += 1
|
|
946
|
+
if retries > self.MAX_RETRIES:
|
|
947
|
+
self._record_download_event(
|
|
948
|
+
record,
|
|
949
|
+
success=False,
|
|
950
|
+
size_bytes=0,
|
|
951
|
+
duration_seconds=time.perf_counter() - overall_start,
|
|
952
|
+
retries=retries,
|
|
953
|
+
)
|
|
954
|
+
return False
|
|
955
|
+
time.sleep(self.RETRY_DELAY)
|
|
956
|
+
continue
|
|
957
|
+
downloaded_bytes = segmented.bytes_written
|
|
958
|
+
else:
|
|
959
|
+
# Stream the weight a chunk at a time into a sibling temp file, hashing as we go, rather
|
|
960
|
+
# than buffering the whole (often hundreds-of-MB) file in memory. The read timeout applies
|
|
961
|
+
# per chunk when streaming, so it bounds a stalled stream without capping a legitimately
|
|
962
|
+
# long transfer, and the temp file is renamed into place only after the checksum passes so
|
|
963
|
+
# a concurrent reader never observes a torn or partially written weight at the final path.
|
|
964
|
+
with requests.get(download_url, stream=True, timeout=self.REQUEST_DOWNLOAD_TIMEOUT) as response:
|
|
965
|
+
response.raise_for_status()
|
|
966
|
+
if "reason=download-auth" in response.url:
|
|
967
|
+
download_logger.error("adhoc.download_auth_redirect", response_url=response.url)
|
|
968
|
+
self._mark_version_bad(cache_key)
|
|
969
|
+
self._record_download_event(
|
|
970
|
+
record,
|
|
971
|
+
success=False,
|
|
972
|
+
size_bytes=0,
|
|
973
|
+
duration_seconds=time.perf_counter() - overall_start,
|
|
974
|
+
retries=retries,
|
|
975
|
+
)
|
|
976
|
+
return False
|
|
977
|
+
|
|
978
|
+
total_bytes = self._content_length(response)
|
|
979
|
+
hasher = hashlib.sha256()
|
|
980
|
+
downloaded_bytes = 0
|
|
981
|
+
tmp_path = f"{filepath}.tmp-{uuid.uuid4().hex[:8]}"
|
|
982
|
+
try:
|
|
983
|
+
with open(tmp_path, "wb") as tmp_file:
|
|
984
|
+
for chunk in response.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
|
|
985
|
+
# A generation bump means the owning job gave up; stop at this chunk
|
|
986
|
+
# boundary so a cancelled stream frees the shared pool promptly instead
|
|
987
|
+
# of transferring to completion.
|
|
988
|
+
if queued.generation != self._download_generation:
|
|
989
|
+
raise _DownloadCancelled
|
|
990
|
+
if not chunk:
|
|
991
|
+
continue
|
|
992
|
+
tmp_file.write(chunk)
|
|
993
|
+
hasher.update(chunk)
|
|
994
|
+
downloaded_bytes += len(chunk)
|
|
995
|
+
if queued.progress_callback is not None:
|
|
996
|
+
queued.progress_callback(downloaded_bytes, total_bytes)
|
|
997
|
+
|
|
998
|
+
sha256 = hasher.hexdigest()
|
|
999
|
+
if target.sha256 and sha256.lower() != target.sha256.lower():
|
|
1000
|
+
download_logger.warning(
|
|
1001
|
+
"adhoc.download_hash_mismatch",
|
|
1002
|
+
expected=target.sha256[:16],
|
|
1003
|
+
actual=sha256[:16],
|
|
1004
|
+
attempt=retries + 1,
|
|
1005
|
+
)
|
|
1006
|
+
retries += 1
|
|
1007
|
+
self.total_retries_attempted += 1
|
|
1008
|
+
if retries > self.MAX_RETRIES:
|
|
1009
|
+
self._record_download_event(
|
|
1010
|
+
record,
|
|
1011
|
+
success=False,
|
|
1012
|
+
size_bytes=0,
|
|
1013
|
+
duration_seconds=time.perf_counter() - overall_start,
|
|
1014
|
+
retries=retries,
|
|
1015
|
+
)
|
|
1016
|
+
return False
|
|
1017
|
+
time.sleep(self.RETRY_DELAY)
|
|
1018
|
+
continue
|
|
1019
|
+
|
|
1020
|
+
self._replace_file_with_retry(tmp_path, filepath)
|
|
1021
|
+
finally:
|
|
1022
|
+
if os.path.exists(tmp_path):
|
|
1023
|
+
try:
|
|
1024
|
+
os.remove(tmp_path)
|
|
1025
|
+
except OSError:
|
|
1026
|
+
pass
|
|
1027
|
+
|
|
1028
|
+
# The sidecar lands only after the weight file is fully placed, so a present sidecar
|
|
1029
|
+
# always describes a complete weight file; a weight file without a sidecar is
|
|
1030
|
+
# harmlessly re-verified or re-downloaded.
|
|
1031
|
+
self._atomic_write_bytes(hashpath, f"{sha256} *{target.filename}".encode())
|
|
865
1032
|
|
|
866
1033
|
self._commit_download(record, target, downloaded=True)
|
|
867
1034
|
self._metric_download_duration.record(time.perf_counter() - download_start)
|
|
868
1035
|
self._record_download_event(
|
|
869
1036
|
record,
|
|
870
1037
|
success=True,
|
|
871
|
-
size_bytes=
|
|
1038
|
+
size_bytes=downloaded_bytes,
|
|
872
1039
|
duration_seconds=time.perf_counter() - download_start,
|
|
873
1040
|
retries=retries,
|
|
874
1041
|
)
|
|
875
1042
|
self._evict_adhoc_over_limit()
|
|
876
1043
|
self.save_reference_to_disk()
|
|
877
1044
|
download_logger.info("adhoc.download_success", filename=target.filename)
|
|
878
|
-
return
|
|
1045
|
+
return True
|
|
1046
|
+
except _DownloadCancelled:
|
|
1047
|
+
download_logger.info("adhoc.download_cancelled", retries=retries)
|
|
1048
|
+
self._record_download_event(
|
|
1049
|
+
record,
|
|
1050
|
+
success=False,
|
|
1051
|
+
size_bytes=0,
|
|
1052
|
+
duration_seconds=time.perf_counter() - overall_start,
|
|
1053
|
+
retries=retries,
|
|
1054
|
+
)
|
|
1055
|
+
return False
|
|
879
1056
|
except SkipDownload:
|
|
880
|
-
return
|
|
1057
|
+
return False
|
|
881
1058
|
except (requests.HTTPError, requests.ConnectionError, requests.Timeout, json.JSONDecodeError) as net_error:
|
|
882
1059
|
error_type = type(net_error).__name__
|
|
883
1060
|
self._metric_network_errors.add(1, {"error_type": error_type})
|
|
@@ -893,7 +1070,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
893
1070
|
duration_seconds=time.perf_counter() - overall_start,
|
|
894
1071
|
retries=retries,
|
|
895
1072
|
)
|
|
896
|
-
return
|
|
1073
|
+
return False
|
|
897
1074
|
if isinstance(net_error, requests.HTTPError) and status_code == 404:
|
|
898
1075
|
# A 404 on the signed download URL is deterministic: the file (or its
|
|
899
1076
|
# token-scoped resource) is gone and the identical URL will not heal. Retrying the
|
|
@@ -910,7 +1087,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
910
1087
|
duration_seconds=time.perf_counter() - overall_start,
|
|
911
1088
|
retries=retries,
|
|
912
1089
|
)
|
|
913
|
-
return
|
|
1090
|
+
return False
|
|
914
1091
|
time.sleep(self.NOTFOUND_CONFIRM_DELAY)
|
|
915
1092
|
continue
|
|
916
1093
|
except OSError as disk_error:
|
|
@@ -928,7 +1105,7 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
928
1105
|
duration_seconds=time.perf_counter() - overall_start,
|
|
929
1106
|
retries=retries,
|
|
930
1107
|
)
|
|
931
|
-
return
|
|
1108
|
+
return False
|
|
932
1109
|
# Any other OS error may be transient; fall through to the shared retry path below.
|
|
933
1110
|
self._metric_network_errors.add(1, {"error_type": "fatal"})
|
|
934
1111
|
download_logger.error("adhoc.download_fatal_error", error=str(disk_error))
|
|
@@ -947,8 +1124,51 @@ class CivitaiAdhocModelManager[RecordT: GenericModelRecord](
|
|
|
947
1124
|
duration_seconds=time.perf_counter() - overall_start,
|
|
948
1125
|
retries=retries,
|
|
949
1126
|
)
|
|
950
|
-
return
|
|
1127
|
+
return False
|
|
951
1128
|
time.sleep(self.RETRY_DELAY)
|
|
1129
|
+
return False
|
|
1130
|
+
|
|
1131
|
+
def _try_segmented_download(
|
|
1132
|
+
self,
|
|
1133
|
+
download_url: str,
|
|
1134
|
+
filepath: str,
|
|
1135
|
+
target: DownloadTarget,
|
|
1136
|
+
queued: _QueuedDownload,
|
|
1137
|
+
) -> DownloadOutcome | None:
|
|
1138
|
+
"""Attempt a multi-connection ranged download of a large LoRA; return the outcome or None to fall back.
|
|
1139
|
+
|
|
1140
|
+
Returns None (so the caller uses the single-stream path) when segmentation is disabled, the engine
|
|
1141
|
+
helper is unavailable, or the engine reports the file is unsuitable (small, no range support, or a
|
|
1142
|
+
transient segment failure). A generation bump during transfer raises :class:`_DownloadCancelled`
|
|
1143
|
+
through the progress hook, aborting every segment, exactly as the single-stream path cancels at a
|
|
1144
|
+
chunk boundary.
|
|
1145
|
+
"""
|
|
1146
|
+
if _segmented_download is None or self._download_connections <= 1:
|
|
1147
|
+
return None
|
|
1148
|
+
|
|
1149
|
+
def _progress(downloaded_bytes: int, total_bytes: int) -> None:
|
|
1150
|
+
if queued.generation != self._download_generation:
|
|
1151
|
+
raise _DownloadCancelled
|
|
1152
|
+
if queued.progress_callback is not None:
|
|
1153
|
+
queued.progress_callback(downloaded_bytes, total_bytes)
|
|
1154
|
+
|
|
1155
|
+
return _segmented_download(
|
|
1156
|
+
download_url,
|
|
1157
|
+
Path(filepath),
|
|
1158
|
+
expected_sha256=target.sha256 or None,
|
|
1159
|
+
progress_callback=_progress,
|
|
1160
|
+
extra_headers=None,
|
|
1161
|
+
connections=self._download_connections,
|
|
1162
|
+
max_retries=self.MAX_RETRIES,
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
def _discard_partial(self, filepath: str, hashpath: str) -> None:
|
|
1166
|
+
"""Remove a hash-mismatched weight file and its sidecar so a retry re-downloads cleanly."""
|
|
1167
|
+
for path in (filepath, hashpath):
|
|
1168
|
+
try:
|
|
1169
|
+
os.remove(path)
|
|
1170
|
+
except OSError:
|
|
1171
|
+
pass
|
|
952
1172
|
|
|
953
1173
|
def _existing_file_matches(self, filepath: str, hashpath: str, expected_sha256: str | None) -> bool:
|
|
954
1174
|
"""Return whether a previously downloaded file is present and matches *expected_sha256*."""
|