horde-engine 3.2.7__tar.gz → 3.2.9__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.
Files changed (239) hide show
  1. {horde_engine-3.2.7 → horde_engine-3.2.9}/PKG-INFO +2 -2
  2. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/_version.py +2 -2
  3. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/api.py +4 -0
  4. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/preload.py +49 -3
  5. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/settings.py +7 -4
  6. horde_engine-3.2.9/hordelib/utils/gpuinfo.py +155 -0
  7. horde_engine-3.2.9/hordelib/utils/nvml.py +190 -0
  8. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/torch_memory.py +18 -2
  9. {horde_engine-3.2.7 → horde_engine-3.2.9}/pyproject.toml +1 -1
  10. horde_engine-3.2.7/hordelib/utils/gpuinfo.py +0 -205
  11. {horde_engine-3.2.7 → horde_engine-3.2.9}/.gitignore +0 -0
  12. {horde_engine-3.2.7 → horde_engine-3.2.9}/LICENSE +0 -0
  13. {horde_engine-3.2.7 → horde_engine-3.2.9}/README.md +0 -0
  14. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/__init__.py +0 -0
  15. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/__pyinstaller.py +0 -0
  16. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/benchmark.py +0 -0
  17. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/beta_models.py +0 -0
  18. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/comfy_horde.py +0 -0
  19. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/config_path.py +0 -0
  20. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/consts.py +0 -0
  21. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/exceptions.py +0 -0
  22. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/__init__.py +0 -0
  23. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/comfy_patches.py +0 -0
  24. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/graph_utils.py +0 -0
  25. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/in_process.py +0 -0
  26. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/interface.py +0 -0
  27. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/phase_timing.py +0 -0
  28. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/progress_hook.py +0 -0
  29. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/execution/sampling_lease.py +0 -0
  30. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/feature_impact.py +0 -0
  31. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/feature_requirements.py +0 -0
  32. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/horde.py +0 -0
  33. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/initialisation.py +0 -0
  34. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/installation/__init__.py +0 -0
  35. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/installation/__main__.py +0 -0
  36. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/installation/installer.py +0 -0
  37. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/installation/manifest.json +0 -0
  38. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/installation/manifest.py +0 -0
  39. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/integrations/__init__.py +0 -0
  40. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/integrations/logfire_comfy_internals.py +0 -0
  41. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/integrations/logfire_setup.py +0 -0
  42. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/metrics.py +0 -0
  43. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_database/db_dep.json +0 -0
  44. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_database/db_embeds.json +0 -0
  45. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_database/diffusers.json +0 -0
  46. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_database/med_config.json +0 -0
  47. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/__init__.py +0 -0
  48. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/base.py +0 -0
  49. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/civitai_adhoc.py +0 -0
  50. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/civitai_provider.py +0 -0
  51. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/civitai_records.py +0 -0
  52. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/codeformer.py +0 -0
  53. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/compvis.py +0 -0
  54. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/controlnet.py +0 -0
  55. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/diffusers.py +0 -0
  56. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/esrgan.py +0 -0
  57. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/gfpgan.py +0 -0
  58. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/hyper.py +0 -0
  59. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/lora.py +0 -0
  60. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/miscellaneous.py +0 -0
  61. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/safety_checker.py +0 -0
  62. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/model_manager/ti.py +0 -0
  63. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/__init__.py +0 -0
  64. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/LICENSE +0 -0
  65. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/README.md +0 -0
  66. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/__init__.py +0 -0
  67. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_example.json +0 -0
  68. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_fg_all.json +0 -0
  69. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_joint_bg.json +0 -0
  70. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_cond_joint_fg.json +0 -0
  71. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_bg.json +0 -0
  72. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_bg_stop_at.json +0 -0
  73. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_diff_fg.json +0 -0
  74. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_fg_example.json +0 -0
  75. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_fg_example_rgba.json +0 -0
  76. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/examples/layer_diffusion_joint.json +0 -0
  77. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/layered_diffusion.py +0 -0
  78. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/__init__.py +0 -0
  79. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/attention_sharing.py +0 -0
  80. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/enums.py +0 -0
  81. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/models.py +0 -0
  82. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/lib_layerdiffusion/utils.py +0 -0
  83. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/pyproject.toml +0 -0
  84. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/comfyui_layerdiffuse/requirements.txt +0 -0
  85. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/LICENSE +0 -0
  86. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/README.md +0 -0
  87. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/__init__.py +0 -0
  88. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/VERSION +0 -0
  89. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/__init__.py +0 -0
  90. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/__init__.py +0 -0
  91. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/arcface_arch.py +0 -0
  92. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/arch_util.py +0 -0
  93. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/codeformer_arch.py +0 -0
  94. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/rrdbnet_arch.py +0 -0
  95. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/vgg_arch.py +0 -0
  96. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/archs/vqgan_arch.py +0 -0
  97. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/data/__init__.py +0 -0
  98. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/data/data_sampler.py +0 -0
  99. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/data/data_util.py +0 -0
  100. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/data/prefetch_dataloader.py +0 -0
  101. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/data/transforms.py +0 -0
  102. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/losses/__init__.py +0 -0
  103. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/losses/loss_util.py +0 -0
  104. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/losses/losses.py +0 -0
  105. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/metrics/__init__.py +0 -0
  106. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/metrics/metric_util.py +0 -0
  107. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/metrics/psnr_ssim.py +0 -0
  108. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/models/__init__.py +0 -0
  109. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/__init__.py +0 -0
  110. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/__init__.py +0 -0
  111. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/deform_conv.py +0 -0
  112. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_cuda.cpp +0 -0
  113. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_cuda_kernel.cu +0 -0
  114. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/dcn/src/deform_conv_ext.cpp +0 -0
  115. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/__init__.py +0 -0
  116. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/fused_act.py +0 -0
  117. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/src/fused_bias_act.cpp +0 -0
  118. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/fused_act/src/fused_bias_act_kernel.cu +0 -0
  119. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/__init__.py +0 -0
  120. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/src/upfirdn2d.cpp +0 -0
  121. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/src/upfirdn2d_kernel.cu +0 -0
  122. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/ops/upfirdn2d/upfirdn2d.py +0 -0
  123. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/setup.py +0 -0
  124. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/train.py +0 -0
  125. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/__init__.py +0 -0
  126. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/dist_util.py +0 -0
  127. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/download_util.py +0 -0
  128. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/file_client.py +0 -0
  129. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/img_util.py +0 -0
  130. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/lmdb_util.py +0 -0
  131. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/logger.py +0 -0
  132. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/matlab_functions.py +0 -0
  133. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/misc.py +0 -0
  134. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/options.py +0 -0
  135. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/realesrgan_utils.py +0 -0
  136. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/utils/registry.py +0 -0
  137. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/basicsr/version.py +0 -0
  138. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/__init__.py +0 -0
  139. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/__init__.py +0 -0
  140. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/align_trans.py +0 -0
  141. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/matlab_cp2tform.py +0 -0
  142. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface.py +0 -0
  143. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface_net.py +0 -0
  144. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/retinaface/retinaface_utils.py +0 -0
  145. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/__init__.py +0 -0
  146. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/face_detector.py +0 -0
  147. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/__init__.py +0 -0
  148. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/common.py +0 -0
  149. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/experimental.py +0 -0
  150. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolo.py +0 -0
  151. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolov5l.yaml +0 -0
  152. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/models/yolov5n.yaml +0 -0
  153. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/__init__.py +0 -0
  154. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/autoanchor.py +0 -0
  155. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/datasets.py +0 -0
  156. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/extract_ckpt.py +0 -0
  157. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/general.py +0 -0
  158. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/detection/yolov5face/utils/torch_utils.py +0 -0
  159. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/parsing/__init__.py +0 -0
  160. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/parsing/bisenet.py +0 -0
  161. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/parsing/parsenet.py +0 -0
  162. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/parsing/resnet.py +0 -0
  163. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/utils/__init__.py +0 -0
  164. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/utils/face_restoration_helper.py +0 -0
  165. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/utils/face_utils.py +0 -0
  166. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/facelib/utils/misc.py +0 -0
  167. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/README.md +0 -0
  168. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/__init__.py +0 -0
  169. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/gfpganv1_clean_arch.py +0 -0
  170. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/model_loading.py +0 -0
  171. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/stylegan2_clean_arch.py +0 -0
  172. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/facerestore_cf/r_chainner/types.py +0 -0
  173. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_controlnet_model_loader.py +0 -0
  174. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_image_loader.py +0 -0
  175. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_image_output.py +0 -0
  176. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_lora_loader.py +0 -0
  177. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_model_loader.py +0 -0
  178. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/nodes/node_upscale_model_loader.py +0 -0
  179. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/__init__.py +0 -0
  180. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/constants.py +0 -0
  181. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/context.py +0 -0
  182. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/families/__init__.py +0 -0
  183. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/families/image.py +0 -0
  184. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/families/post_processing.py +0 -0
  185. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/graph.py +0 -0
  186. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/horde_compat.py +0 -0
  187. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/patches.py +0 -0
  188. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/payload.py +0 -0
  189. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/payload_pp.py +0 -0
  190. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/registry.py +0 -0
  191. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/resolution.py +0 -0
  192. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline/template.py +0 -0
  193. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_controlnet.json +0 -0
  194. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_controlnet_annotator.json +0 -0
  195. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_controlnet_hires_fix.json +0 -0
  196. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_flux.json +0 -0
  197. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_image_facefix.json +0 -0
  198. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_image_upscale.json +0 -0
  199. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_qr_code.json +0 -0
  200. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_qwen.json +0 -0
  201. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_cascade.json +0 -0
  202. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_cascade_2pass.json +0 -0
  203. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_cascade_remix.json +0 -0
  204. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_diffusion.json +0 -0
  205. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_diffusion_hires_fix.json +0 -0
  206. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_diffusion_img2img_mask.json +0 -0
  207. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipeline_designs/pipeline_stable_diffusion_paint.json +0 -0
  208. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_controlnet.json +0 -0
  209. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_controlnet_annotator.json +0 -0
  210. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_controlnet_hires_fix.json +0 -0
  211. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_flux.json +0 -0
  212. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_image_facefix.json +0 -0
  213. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_image_upscale.json +0 -0
  214. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_qr_code.json +0 -0
  215. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_qwen.json +0 -0
  216. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_cascade.json +0 -0
  217. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_cascade_2pass.json +0 -0
  218. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_cascade_remix.json +0 -0
  219. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_diffusion.json +0 -0
  220. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json +0 -0
  221. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json +0 -0
  222. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pipelines/pipeline_stable_diffusion_paint.json +0 -0
  223. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/py.typed +0 -0
  224. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pyinstaller_hooks/__init__.py +0 -0
  225. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/pyinstaller_hooks/hook-hordelib.horde.py +0 -0
  226. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/safety.py +0 -0
  227. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/safety_checker.py +0 -0
  228. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/shared_model_manager.py +0 -0
  229. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/train.py +0 -0
  230. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/__init__.py +0 -0
  231. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/distance.py +0 -0
  232. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/dynamicprompt.py +0 -0
  233. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/image_utils.py +0 -0
  234. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/ioredirect.py +0 -0
  235. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/logger.py +0 -0
  236. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/optional_deps.py +0 -0
  237. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/sanitizer.py +0 -0
  238. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/switch.py +0 -0
  239. {horde_engine-3.2.7 → horde_engine-3.2.9}/hordelib/utils/torch_build.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: horde-engine
3
- Version: 3.2.7
3
+ Version: 3.2.9
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
@@ -698,6 +698,7 @@ Requires-Dist: loguru>=0.7.3
698
698
  Requires-Dist: lpips
699
699
  Requires-Dist: matplotlib>=3.10.9
700
700
  Requires-Dist: numpy>=1.25.0
701
+ Requires-Dist: nvidia-ml-py>=12.0.0
701
702
  Requires-Dist: omegaconf>=2.3.0
702
703
  Requires-Dist: opencv-contrib-python>=4.11.0.86
703
704
  Requires-Dist: opencv-python>=4.11.0.86
@@ -707,7 +708,6 @@ Requires-Dist: pillow
707
708
  Requires-Dist: psutil
708
709
  Requires-Dist: pydantic-settings~=2.0
709
710
  Requires-Dist: pydantic~=2.0
710
- Requires-Dist: pynvml>=13.0.1
711
711
  Requires-Dist: python-dotenv>=1.2.1
712
712
  Requires-Dist: pyyaml
713
713
  Requires-Dist: qrcode==7.4.2
@@ -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 = '3.2.7'
22
- __version_tuple__ = version_tuple = (3, 2, 7)
21
+ __version__ = version = '3.2.9'
22
+ __version_tuple__ = version_tuple = (3, 2, 9)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -66,6 +66,7 @@ from hordelib.pipeline.payload_pp import (
66
66
  UpscalePayload,
67
67
  classify_post_processor,
68
68
  )
69
+ from hordelib.preload import controlnet_annotators_present
69
70
  from hordelib.shared_model_manager import SharedModelManager
70
71
  from hordelib.utils.ioredirect import ComfyUIProgress, ComfyUIProgressUnit
71
72
  from hordelib.utils.logger import HordeLog
@@ -74,6 +75,7 @@ from hordelib.utils.torch_memory import (
74
75
  AcceleratorKind,
75
76
  clear_accelerator_cache,
76
77
  enumerate_accelerators,
78
+ get_accelerator_utilization_percent,
77
79
  get_free_ram_mb,
78
80
  get_torch_free_vram_mb,
79
81
  get_torch_total_vram_mb,
@@ -120,6 +122,7 @@ __all__ = [
120
122
  "classify_post_processor",
121
123
  "clear_accelerator_cache",
122
124
  "controlnet_annotator_download_bytes",
125
+ "controlnet_annotators_present",
123
126
  "enumerate_accelerators",
124
127
  "estimate_job_burden",
125
128
  "feature_available",
@@ -128,6 +131,7 @@ __all__ = [
128
131
  "get_feature_requirement",
129
132
  "get_feature_requirement_registry",
130
133
  "missing_packages",
134
+ "get_accelerator_utilization_percent",
131
135
  "get_free_ram_mb",
132
136
  "get_metrics_collector",
133
137
  "get_torch_free_vram_mb",
@@ -88,12 +88,32 @@ def _pinned_annotator_ref() -> str | None:
88
88
  return None
89
89
 
90
90
 
91
+ def _annotator_ckpts_dir() -> Path | None:
92
+ """Return the directory annotator checkpoints live in, or None if it cannot be determined.
93
+
94
+ ``hordelib.initialise()`` exports ``AUX_ANNOTATOR_CKPTS_PATH`` and is the authority once a run is
95
+ underway. When it is unset (e.g. a no-boot caller that only wants to *check* presence) the same value
96
+ is derived from :class:`~hordelib.settings.UserSettings`, which needs neither ComfyUI nor a GPU, so
97
+ presence can be read before (or without) initialisation.
98
+ """
99
+ ckpts_dir = os.environ.get("AUX_ANNOTATOR_CKPTS_PATH")
100
+ if ckpts_dir:
101
+ return Path(ckpts_dir)
102
+ try:
103
+ from hordelib.settings import UserSettings
104
+
105
+ return UserSettings.get_model_directory() / "controlnet" / "annotators"
106
+ except Exception as e:
107
+ logger.debug("Could not derive the annotator checkpoint directory: error={}", e)
108
+ return None
109
+
110
+
91
111
  def _preload_marker_path() -> Path | None:
92
112
  """Return the on-disk marker path, or None if the annotator directory is unknown."""
93
- ckpts_dir = os.environ.get("AUX_ANNOTATOR_CKPTS_PATH")
94
- if not ckpts_dir:
113
+ ckpts_dir = _annotator_ckpts_dir()
114
+ if ckpts_dir is None:
95
115
  return None
96
- return Path(ckpts_dir) / _PRELOAD_MARKER_NAME
116
+ return ckpts_dir / _PRELOAD_MARKER_NAME
97
117
 
98
118
 
99
119
  def _annotators_already_verified(ref: str) -> bool:
@@ -107,6 +127,32 @@ def _annotators_already_verified(ref: str) -> bool:
107
127
  return False
108
128
 
109
129
 
130
+ def controlnet_annotators_present() -> bool | None:
131
+ """Whether the controlnet annotators are already downloaded and verified on this machine.
132
+
133
+ Reads the on-disk preload marker (keyed to the pinned ``comfyui_controlnet_aux`` commit) exactly the
134
+ way :func:`download_all_controlnet_annotators` reads it for its fast-path skip, so a caller can decide
135
+ whether a (slow, one-time) annotator download is still pending *before* paying for it. Import-safe:
136
+ needs neither :func:`hordelib.initialise` nor a GPU, because the directory is derived from
137
+ :class:`~hordelib.settings.UserSettings` when the runtime env var is unset.
138
+
139
+ Returns:
140
+ ``True`` when the marker matches the pinned ref (a full set was downloaded and verified),
141
+ ``False`` when it is absent or stale (a download is still pending), or ``None`` when presence
142
+ cannot be determined (the pinned ref or the annotator directory is unknown).
143
+ """
144
+ try:
145
+ ref = _pinned_annotator_ref()
146
+ if ref is None:
147
+ return None
148
+ if _preload_marker_path() is None:
149
+ return None
150
+ return _annotators_already_verified(ref)
151
+ except Exception as e: # pragma: no cover - presence is best-effort; never raise into a caller
152
+ logger.debug("Could not determine controlnet annotator presence: error={}", e)
153
+ return None
154
+
155
+
110
156
  def _record_annotators_verified(ref: str) -> None:
111
157
  """Persist that annotators for ``ref`` are downloaded and verified on this machine.
112
158
 
@@ -5,6 +5,7 @@ from typing import Self
5
5
 
6
6
  import psutil
7
7
  from horde_model_reference import resolve_weights_root
8
+ from loguru import logger
8
9
 
9
10
  from hordelib import is_initialised
10
11
  from hordelib.utils.switch import Switch
@@ -43,12 +44,14 @@ class UserSettings:
43
44
  if not is_initialised():
44
45
  return 0
45
46
 
46
- from hordelib.utils.gpuinfo import GPUInfo
47
+ # Route through the backend-agnostic accelerator layer (CUDA/ROCm/XPU/MPS/CPU), not NVIDIA-only
48
+ # NVML, so VRAM-based mode/threshold selection is correct on non-NVIDIA cards too.
49
+ from hordelib.utils.torch_memory import get_torch_total_vram_mb
47
50
 
48
51
  try:
49
- gpu = GPUInfo()
50
- return gpu.get_total_vram_mb()
51
- except Exception:
52
+ return get_torch_total_vram_mb()
53
+ except Exception as vram_error:
54
+ logger.debug(f"Could not read total VRAM for settings: {type(vram_error).__name__}: {vram_error}")
52
55
  return 0
53
56
 
54
57
  @staticmethod
@@ -0,0 +1,155 @@
1
+ """Human-facing GPU information for diagnostics and the benchmark.
2
+
3
+ VRAM totals/frees come from the backend-agnostic :mod:`hordelib.utils.torch_memory` (correct on every
4
+ backend ComfyUI supports); NVIDIA-only enrichment (utilization, temperature, power, fan, PCIe) comes from
5
+ :mod:`hordelib.utils.nvml`. On non-NVIDIA backends the rich stats are simply marked unsupported rather than
6
+ erroring.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+
13
+ import torch.version
14
+ from loguru import logger
15
+ from pydantic import BaseModel
16
+ from strenum import StrEnum
17
+
18
+ from hordelib.utils import nvml
19
+ from hordelib.utils.torch_memory import get_torch_free_vram_mb, get_torch_total_vram_mb
20
+
21
+ _AVERAGE_SAMPLES_PER_SECOND = 10
22
+ _AVERAGE_WINDOW_SECONDS = 60 * 5
23
+
24
+
25
+ class GPUInfo:
26
+ """Collects per-device GPU statistics, keeping rolling averages of load, temperature, and power."""
27
+
28
+ def __init__(self) -> None:
29
+ """Initialize the collector for the device named by ``CUDA_VISIBLE_DEVICES`` (default 0)."""
30
+ self.avg_load: list[int] = []
31
+ self.avg_temp: list[int] = []
32
+ self.avg_power: list[int] = []
33
+ self._average_window_samples = _AVERAGE_SAMPLES_PER_SECOND * _AVERAGE_WINDOW_SECONDS
34
+ if not self.is_nvidia():
35
+ logger.warning("Detailed GPU info (load/temp/power) is only available on NVIDIA GPUs")
36
+ self.device = int(os.getenv("CUDA_VISIBLE_DEVICES", "0"))
37
+
38
+ def is_nvidia(self) -> bool:
39
+ """Return whether the active torch build targets NVIDIA CUDA (not ROCm/HIP)."""
40
+ return torch.version.cuda is not None and torch.version.hip is None
41
+
42
+ def is_amd(self) -> bool:
43
+ """Return whether the active torch build targets AMD ROCm/HIP."""
44
+ return torch.version.hip is not None
45
+
46
+ def get_total_vram_mb(self) -> int:
47
+ """Return the device's total VRAM in MB (backend-agnostic), or 0 when unavailable."""
48
+ return get_torch_total_vram_mb()
49
+
50
+ def get_free_vram_mb(self) -> int:
51
+ """Return the device's free VRAM in MB (backend-agnostic), or 0 when unavailable."""
52
+ return get_torch_free_vram_mb()
53
+
54
+ def _append_rolling(self, samples: list[int], value: int) -> list[int]:
55
+ """Append ``value`` to ``samples`` and trim it to the averaging window; return the trimmed list."""
56
+ samples.append(value)
57
+ return samples[-self._average_window_samples :]
58
+
59
+ def get_info(self) -> GPUInfoResult | None:
60
+ """Return a snapshot of this device's stats, or None when even total VRAM cannot be read.
61
+
62
+ On non-NVIDIA backends the snapshot carries backend-agnostic VRAM figures with the rich sensors
63
+ marked unsupported. On NVIDIA, sensors come from NVML (accurate v2 memory).
64
+ """
65
+ total_vram_mb = self.get_total_vram_mb()
66
+ free_vram_mb = self.get_free_vram_mb()
67
+
68
+ stats = nvml.get_device_stats(self.device) if self.is_nvidia() else None
69
+ if stats is None:
70
+ return GPUInfoResult.get_empty_info(vram_total_mb=total_vram_mb, vram_free_mb=free_vram_mb)
71
+
72
+ avg_load = self._append_rolling(self.avg_load, stats.utilization_gpu_percent)
73
+ avg_temp = self._append_rolling(self.avg_temp, stats.temperature_celsius)
74
+ avg_power = self._append_rolling(self.avg_power, stats.power_watts)
75
+
76
+ # NVML's v2 memory is more accurate than the comfy/torch totals here; prefer it when present.
77
+ vram_total = stats.memory.total_mb or total_vram_mb
78
+ vram_free = stats.memory.free_mb or free_vram_mb
79
+ vram_used = stats.memory.used_mb or max(vram_total - vram_free, 0)
80
+
81
+ return GPUInfoResult(
82
+ supported=True,
83
+ product=stats.name,
84
+ pci_gen=str(stats.pcie_link_generation),
85
+ pci_width=str(stats.pcie_link_width),
86
+ fan_speed=(str(stats.fan_speed_percent), Unit.percent),
87
+ vram_total=(vram_total, Unit.megabytes),
88
+ vram_used=(vram_used, Unit.megabytes),
89
+ vram_free=(vram_free, Unit.megabytes),
90
+ load=(stats.utilization_gpu_percent, Unit.percent),
91
+ temp=(stats.temperature_celsius, Unit.degrees_celsius),
92
+ power=(stats.power_watts, Unit.watts),
93
+ avg_load=(round(sum(avg_load) / len(avg_load)), Unit.percent),
94
+ avg_temp=(round(sum(avg_temp) / len(avg_temp)), Unit.degrees_celsius),
95
+ avg_power=(round(sum(avg_power) / len(avg_power)), Unit.watts),
96
+ )
97
+
98
+
99
+ class Unit(StrEnum):
100
+ """A display unit for a GPU statistic."""
101
+
102
+ unitless = ""
103
+ percent = "%"
104
+ degrees_celsius = "C"
105
+ megabytes = "MiB"
106
+ gigabytes = "GiB"
107
+ watts = "W"
108
+
109
+
110
+ class GPUInfoResult(BaseModel):
111
+ """Represents a single snapshot of a GPU's statistics, each value paired with its display unit."""
112
+
113
+ supported: bool
114
+ product: str
115
+ pci_gen: str
116
+ pci_width: str
117
+ fan_speed: tuple[str, Unit]
118
+ vram_total: tuple[int, Unit]
119
+ vram_used: tuple[int, Unit]
120
+ vram_free: tuple[int, Unit]
121
+ load: tuple[int, Unit]
122
+ temp: tuple[int, Unit]
123
+ power: tuple[int, Unit]
124
+ avg_load: tuple[int, Unit]
125
+ avg_temp: tuple[int, Unit]
126
+ avg_power: tuple[int, Unit]
127
+
128
+ def __str__(self) -> str:
129
+ """Return a newline-separated ``key: value unit`` rendering of every field."""
130
+ final_string = ""
131
+ for key, value in self.model_dump().items():
132
+ rendered_value = f"{value[0]} {value[1]}" if isinstance(value, tuple) else value
133
+ final_string += f"{key}: {rendered_value}\n"
134
+ return final_string
135
+
136
+ @classmethod
137
+ def get_empty_info(cls, vram_total_mb: int = 0, vram_free_mb: int = 0) -> GPUInfoResult:
138
+ """Create a result carrying only backend-agnostic VRAM, with the NVIDIA-only sensors zeroed."""
139
+ vram_used_mb = max(vram_total_mb - vram_free_mb, 0)
140
+ return GPUInfoResult(
141
+ supported=False,
142
+ product="unknown (detailed stats unavailable on this backend)",
143
+ pci_gen="?",
144
+ pci_width="?",
145
+ fan_speed=("0", Unit.percent),
146
+ vram_total=(vram_total_mb, Unit.megabytes),
147
+ vram_used=(vram_used_mb, Unit.megabytes),
148
+ vram_free=(vram_free_mb, Unit.megabytes),
149
+ load=(0, Unit.percent),
150
+ temp=(0, Unit.degrees_celsius),
151
+ power=(0, Unit.watts),
152
+ avg_load=(0, Unit.percent),
153
+ avg_temp=(0, Unit.degrees_celsius),
154
+ avg_power=(0, Unit.watts),
155
+ )
@@ -0,0 +1,190 @@
1
+ """Optional NVIDIA-only GPU telemetry via NVML, the single home for direct NVML access in hordelib.
2
+
3
+ NVML is NVIDIA-specific *enrichment* (core utilization, temperature, power, and the most accurate memory
4
+ figures); the backend-agnostic metrics that never assume NVIDIA live in
5
+ :mod:`hordelib.utils.torch_memory`. Every function here is best-effort: when NVML is missing or the machine
6
+ has no NVIDIA driver, each returns ``None`` so non-NVIDIA backends simply report no enrichment rather than
7
+ erroring.
8
+
9
+ Uses ``nvidia-ml-py`` (imported as ``pynvml``), the maintained official binding. Memory is read through
10
+ ``nvmlDeviceGetMemoryInfo`` with the ``nvmlMemory_v2`` version, which accounts for driver-reserved memory;
11
+ the deprecated standalone ``pynvml`` package and its ``pynvml.smi`` interface under-report *used* VRAM
12
+ (over-reporting *free*), which is exactly the wrong direction for a VRAM budget.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import warnings
18
+ from typing import TYPE_CHECKING
19
+
20
+ from loguru import logger
21
+ from pydantic import BaseModel
22
+
23
+ if TYPE_CHECKING:
24
+ from ctypes import c_void_p
25
+
26
+ _BYTES_PER_MB = 1024 * 1024
27
+
28
+
29
+ class NvmlMemory(BaseModel):
30
+ """Represents a device's NVML memory figures, in MB (from ``nvmlDeviceGetMemoryInfo`` v2)."""
31
+
32
+ total_mb: int
33
+ free_mb: int
34
+ used_mb: int
35
+
36
+
37
+ class NvmlDeviceStats(BaseModel):
38
+ """Represents a single NVIDIA device's NVML telemetry snapshot (enrichment for display/diagnostics)."""
39
+
40
+ name: str
41
+ memory: NvmlMemory
42
+ utilization_gpu_percent: int
43
+ temperature_celsius: int
44
+ power_watts: int
45
+ fan_speed_percent: int
46
+ pcie_link_generation: int
47
+ pcie_link_width: int
48
+
49
+
50
+ _nvml_unavailable = False
51
+ """Latches True after the first failed NVML init so the worker does not retry NVML every call."""
52
+
53
+
54
+ def _nvml_module() -> object | None:
55
+ """Return the initialised ``nvidia-ml-py`` module, or None when NVML is unavailable.
56
+
57
+ Initialisation is attempted once; any failure (no driver, non-NVIDIA host, missing binding) latches
58
+ NVML off for the process and logs the reason at debug. ``nvmlInit`` is idempotent, so calling this
59
+ repeatedly is cheap once initialised.
60
+ """
61
+ global _nvml_unavailable
62
+ if _nvml_unavailable:
63
+ return None
64
+ try:
65
+ with warnings.catch_warnings():
66
+ warnings.simplefilter("ignore") # silence any deprecation warning from a shim install
67
+ import pynvml
68
+ pynvml.nvmlInit()
69
+ return pynvml
70
+ except Exception as nvml_error: # noqa: BLE001 - any NVML failure means "no NVIDIA telemetry", not a crash
71
+ logger.debug(f"NVML unavailable ({type(nvml_error).__name__}: {nvml_error}); NVIDIA telemetry disabled")
72
+ _nvml_unavailable = True
73
+ return None
74
+
75
+
76
+ def is_nvml_available() -> bool:
77
+ """Return whether NVML telemetry can be read on this machine."""
78
+ return _nvml_module() is not None
79
+
80
+
81
+ def _device_handle(pynvml: object, index: int) -> c_void_p | None:
82
+ """Return the NVML handle for device ``index``, or None when it cannot be resolved."""
83
+ try:
84
+ return pynvml.nvmlDeviceGetHandleByIndex(index) # type: ignore[attr-defined]
85
+ except Exception as handle_error: # noqa: BLE001 - a missing device is "no telemetry", not a crash
86
+ logger.debug(f"NVML handle for device {index} unavailable ({type(handle_error).__name__}: {handle_error})")
87
+ return None
88
+
89
+
90
+ def get_device_memory_mb(index: int = 0) -> NvmlMemory | None:
91
+ """Return device ``index``'s memory (MB) via the accurate ``nvmlMemory_v2`` query, or None.
92
+
93
+ The v2 query includes driver-reserved memory in ``used``/``free``; older v1 (and the deprecated
94
+ ``pynvml.smi``) omit it, under-reporting used VRAM. Falls back to v1 only if the binding lacks v2.
95
+ """
96
+ pynvml = _nvml_module()
97
+ if pynvml is None:
98
+ return None
99
+ handle = _device_handle(pynvml, index)
100
+ if handle is None:
101
+ return None
102
+ try:
103
+ memory_v2_version = getattr(pynvml, "nvmlMemory_v2", None)
104
+ if memory_v2_version is not None:
105
+ info = pynvml.nvmlDeviceGetMemoryInfo(handle, version=memory_v2_version) # type: ignore[attr-defined]
106
+ else:
107
+ info = pynvml.nvmlDeviceGetMemoryInfo(handle) # type: ignore[attr-defined]
108
+ except Exception as memory_error: # noqa: BLE001 - best-effort enrichment
109
+ logger.debug(f"NVML memory read failed for device {index} ({type(memory_error).__name__}: {memory_error})")
110
+ return None
111
+ return NvmlMemory(
112
+ total_mb=round(info.total / _BYTES_PER_MB),
113
+ free_mb=round(info.free / _BYTES_PER_MB),
114
+ used_mb=round(info.used / _BYTES_PER_MB),
115
+ )
116
+
117
+
118
+ def get_device_utilization_percent(index: int = 0) -> int | None:
119
+ """Return device ``index``'s core-utilization percentage (0-100), or None when unavailable."""
120
+ pynvml = _nvml_module()
121
+ if pynvml is None:
122
+ return None
123
+ handle = _device_handle(pynvml, index)
124
+ if handle is None:
125
+ return None
126
+ try:
127
+ return int(pynvml.nvmlDeviceGetUtilizationRates(handle).gpu) # type: ignore[attr-defined]
128
+ except Exception as utilization_error: # noqa: BLE001 - best-effort enrichment
129
+ logger.debug(f"NVML utilization read failed for device {index} ({type(utilization_error).__name__})")
130
+ return None
131
+
132
+
133
+ def _device_name(pynvml: object, handle: c_void_p) -> str:
134
+ """Return the device product name, decoding the bytes older bindings return."""
135
+ try:
136
+ name = pynvml.nvmlDeviceGetName(handle) # type: ignore[attr-defined]
137
+ except Exception: # noqa: BLE001 - name is cosmetic
138
+ return "unknown"
139
+ return name.decode() if isinstance(name, bytes) else str(name)
140
+
141
+
142
+ def _best_effort_int(read: object, *, field: str, index: int) -> int:
143
+ """Return ``read()`` coerced to int, or 0 when the card does not support that sensor."""
144
+ try:
145
+ return int(read()) # type: ignore[operator]
146
+ except Exception as sensor_error: # noqa: BLE001 - many sensors are unsupported per-card
147
+ logger.debug(f"NVML {field} unsupported for device {index} ({type(sensor_error).__name__})")
148
+ return 0
149
+
150
+
151
+ def get_device_stats(index: int = 0) -> NvmlDeviceStats | None:
152
+ """Return a full NVML telemetry snapshot for device ``index``, or None when NVML is unavailable.
153
+
154
+ Memory uses the accurate v2 query; each enrichment sensor (temperature, power, fan, PCIe) is read
155
+ best-effort and defaults to 0 when the card does not expose it.
156
+ """
157
+ pynvml = _nvml_module()
158
+ if pynvml is None:
159
+ return None
160
+ handle = _device_handle(pynvml, index)
161
+ if handle is None:
162
+ return None
163
+ memory = get_device_memory_mb(index)
164
+ if memory is None:
165
+ return None
166
+
167
+ temperature_sensor = getattr(pynvml, "NVML_TEMPERATURE_GPU", 0)
168
+ power_milliwatts = _best_effort_int(lambda: pynvml.nvmlDeviceGetPowerUsage(handle), field="power", index=index)
169
+ return NvmlDeviceStats(
170
+ name=_device_name(pynvml, handle),
171
+ memory=memory,
172
+ utilization_gpu_percent=get_device_utilization_percent(index) or 0,
173
+ temperature_celsius=_best_effort_int(
174
+ lambda: pynvml.nvmlDeviceGetTemperature(handle, temperature_sensor),
175
+ field="temperature",
176
+ index=index,
177
+ ),
178
+ power_watts=round(power_milliwatts / 1000),
179
+ fan_speed_percent=_best_effort_int(lambda: pynvml.nvmlDeviceGetFanSpeed(handle), field="fan", index=index),
180
+ pcie_link_generation=_best_effort_int(
181
+ lambda: pynvml.nvmlDeviceGetCurrPcieLinkGeneration(handle),
182
+ field="pcie_gen",
183
+ index=index,
184
+ ),
185
+ pcie_link_width=_best_effort_int(
186
+ lambda: pynvml.nvmlDeviceGetCurrPcieLinkWidth(handle),
187
+ field="pcie_width",
188
+ index=index,
189
+ ),
190
+ )
@@ -8,8 +8,9 @@ what the execution backend sees; otherwise they fall back to plain torch device
8
8
  cover every torch backend (CUDA/ROCm, Intel XPU, Apple MPS, CPU), not just CUDA.
9
9
 
10
10
  This module is the single source of accelerator truth for the worker: it never assumes
11
- NVIDIA. NVML (``pynvml``) is treated as optional NVIDIA-only enrichment elsewhere (see
12
- ``hordelib.utils.gpuinfo``), never as a hard requirement here.
11
+ NVIDIA. NVML is treated as optional NVIDIA-only enrichment (see :mod:`hordelib.utils.nvml`),
12
+ never as a hard requirement here; :func:`get_accelerator_utilization_percent` is the one place
13
+ that consults it, and only for the CUDA/NVIDIA backend.
13
14
  """
14
15
 
15
16
  from __future__ import annotations
@@ -140,6 +141,21 @@ def log_free_ram() -> None:
140
141
  logger.debug(f"Free VRAM: {get_torch_free_vram_mb():0.0f} MB, Free RAM: {get_free_ram_mb():0.0f} MB")
141
142
 
142
143
 
144
+ def get_accelerator_utilization_percent(index: int = 0) -> int | None:
145
+ """Return device ``index``'s core-utilization percentage (0-100), or None when unavailable.
146
+
147
+ Utilization is vendor telemetry with no portable torch/ComfyUI equivalent, so this is best-effort and
148
+ backend-specific: the NVIDIA CUDA backend is read via NVML (:mod:`hordelib.utils.nvml`); every other
149
+ backend (ROCm, XPU, MPS, CPU) returns None until a vendor source is wired, so callers simply report no
150
+ GPU duty cycle there. ROCm presents through ``torch.cuda`` but is not an NVML device, so it returns None.
151
+ """
152
+ if _active_torch_kind() is not AcceleratorKind.cuda:
153
+ return None
154
+ from hordelib.utils import nvml
155
+
156
+ return nvml.get_device_utilization_percent(index)
157
+
158
+
143
159
  def _enumerate_via_comfy(mm: ModuleType) -> list[AcceleratorInfo]:
144
160
  """Enumerate devices through ComfyUI's backend-agnostic device APIs."""
145
161
  devices = mm.get_all_torch_devices()
@@ -69,7 +69,7 @@ dependencies = [
69
69
  "psutil",
70
70
  "pydantic~=2.0",
71
71
  "pydantic-settings~=2.0",
72
- "pynvml>=13.0.1",
72
+ "nvidia-ml-py>=12.0.0",
73
73
  "python-dotenv>=1.2.1",
74
74
  "pyyaml",
75
75
  "qrcode==7.4.2",