gpustack-runtime 0.2.4.post3__tar.gz → 0.2.5.post1__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 (153) hide show
  1. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/PKG-INFO +2 -2
  2. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/_version.py +2 -2
  3. gpustack_runtime-0.2.5.post1/gpustack_runtime/_version_appendix.py +1 -0
  4. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/ascend.py +130 -12
  5. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/docker.py +6 -0
  6. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/amd.py +65 -32
  7. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/iluvatar.py +33 -12
  8. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/nvidia.py +30 -2
  9. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/thead.py +6 -1
  10. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/envs.py +8 -1
  11. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/pyproject.toml +3 -3
  12. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/specs/2026-08-14-detector-alignment-and-workload-exit-status.md +1 -1
  13. gpustack_runtime-0.2.5.post1/tests/gpustack_runtime/deployer/test_ascend_ranktable.py +224 -0
  14. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_amd.py +147 -0
  15. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_iluvatar.py +86 -2
  16. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_nvidia.py +138 -1
  17. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_thead.py +61 -1
  18. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/uv.lock +57 -43
  19. gpustack_runtime-0.2.4.post3/gpustack_runtime/_version_appendix.py +0 -1
  20. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.codespelldict +0 -0
  21. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.codespellrc +0 -0
  22. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.dockerignore +0 -0
  23. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.gitattributes +0 -0
  24. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.gitignore +0 -0
  25. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.pre-commit-config.yaml +0 -0
  26. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/.python-version +0 -0
  27. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/LICENSE +0 -0
  28. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/Makefile +0 -0
  29. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/README.md +0 -0
  30. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/deploy/manifests/docker-compose.yaml +0 -0
  31. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/deploy/manifests/kubernetes.yaml +0 -0
  32. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/docs/index.md +0 -0
  33. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/docs/modules/gpustack_runtime.deployer.md +0 -0
  34. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/docs/modules/gpustack_runtime.detector.md +0 -0
  35. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/docs/modules/gpustack_runtime.md +0 -0
  36. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/__init__.py +0 -0
  37. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/__main__.py +0 -0
  38. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/_version.pyi +0 -0
  39. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/cmds/__init__.py +0 -0
  40. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/cmds/__types__.py +0 -0
  41. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/cmds/deployer.py +0 -0
  42. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/cmds/detector.py +0 -0
  43. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/cmds/images.py +0 -0
  44. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/__init__.py +0 -0
  45. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/__patches__.py +0 -0
  46. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/__types__.py +0 -0
  47. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/__utils__.py +0 -0
  48. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
  49. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
  50. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/__utils__.py +0 -0
  51. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/amd.py +0 -0
  52. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
  53. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/iluvatar.py +0 -0
  54. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/metax.py +0 -0
  55. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/cdi/thead.py +0 -0
  56. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
  57. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/kuberentes.py +0 -0
  58. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/deployer/podman.py +0 -0
  59. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/__init__.py +0 -0
  60. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/__types__.py +0 -0
  61. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/__utils__.py +0 -0
  62. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/ascend.py +0 -0
  63. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/cambricon.py +0 -0
  64. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/hygon.py +0 -0
  65. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/metax.py +0 -0
  66. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/mthreads.py +0 -0
  67. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyamdgpu/__init__.py +0 -0
  68. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
  69. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pycndev/__init__.py +0 -0
  70. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pydcmi/__init__.py +0 -0
  71. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pydmi/__init__.py +0 -0
  72. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
  73. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
  74. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
  75. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
  76. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
  77. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
  78. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
  79. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
  80. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
  81. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/gpustack_runtime/logging.py +0 -0
  82. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/hatch.toml +0 -0
  83. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/mkdocs.yml +0 -0
  84. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/pack/Dockerfile +0 -0
  85. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/pack/Dockerfile.dummy +0 -0
  86. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/pytest.ini +0 -0
  87. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/ruff.toml +0 -0
  88. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
  89. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
  90. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
  91. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
  92. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_jsons.json +0 -0
  93. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_yamls.yaml +0 -0
  94. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_json.json +0 -0
  95. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_yaml.yaml +0 -0
  96. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
  97. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_docker_status.py +0 -0
  98. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_kubernetes_status.py +0 -0
  99. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_podman_status.py +0 -0
  100. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_privileged.py +0 -0
  101. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_resource_injection_policy.py +0 -0
  102. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_runtime_class.py +0 -0
  103. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_termination_grace_period.py +0 -0
  104. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
  105. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_visible_devices_ordering.py +0 -0
  106. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/deployer/test_workload_status.py +0 -0
  107. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
  108. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/README.md +0 -0
  109. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -0
  110. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -0
  111. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -0
  112. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -0
  113. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +0 -0
  114. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -0
  115. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -0
  116. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +0 -0
  117. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +0 -0
  118. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +0 -0
  119. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +0 -0
  120. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +0 -0
  121. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json +0 -0
  122. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +0 -0
  123. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +0 -0
  124. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -0
  125. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
  126. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
  127. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +0 -0
  128. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
  129. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
  130. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
  131. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
  132. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
  133. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
  134. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
  135. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
  136. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
  137. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json +0 -0
  138. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
  139. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
  140. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -0
  141. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_ascend.py +0 -0
  142. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_cambricon.py +0 -0
  143. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_detector_cli.py +0 -0
  144. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_detector_types.py +0 -0
  145. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
  146. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_hygon.py +0 -0
  147. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_metax.py +0 -0
  148. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_mig_devices.py +0 -0
  149. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_mthreads.py +0 -0
  150. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_pycndev.py +0 -0
  151. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_pydmi.py +0 -0
  152. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/tests/gpustack_runtime/detector/test_samples.py +0 -0
  153. {gpustack_runtime-0.2.4.post3 → gpustack_runtime-0.2.5.post1}/uv.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gpustack-runtime
3
- Version: 0.2.4.post3
3
+ Version: 0.2.5.post1
4
4
  Summary: GPUStack Runtime is library for detecting GPU resources and launching GPU workloads.
5
5
  Project-URL: Homepage, https://github.com/gpustack/runtime
6
6
  Project-URL: Bug Tracker, https://github.com/gpustack/gpustack/issues
@@ -16,7 +16,7 @@ Requires-Python: >=3.10
16
16
  Requires-Dist: argcomplete>=3.6.3
17
17
  Requires-Dist: cachetools>=5.5.2
18
18
  Requires-Dist: docker>=7.1.0
19
- Requires-Dist: gpustack-runner>=0.1.26.post2
19
+ Requires-Dist: gpustack-runner>=0.1.29
20
20
  Requires-Dist: kubernetes>=33.1.0
21
21
  Requires-Dist: mthreads-ml-py>=2.2.10
22
22
  Requires-Dist: nvidia-ml-py>=13.580.65
@@ -27,8 +27,8 @@ version_tuple: VERSION_TUPLE
27
27
  __commit_id__: COMMIT_ID
28
28
  commit_id: COMMIT_ID
29
29
 
30
- __version__ = version = '0.2.4.post3'
31
- __version_tuple__ = version_tuple = (0, 2, 4, 'post3')
30
+ __version__ = version = '0.2.5.post1'
31
+ __version_tuple__ = version_tuple = (0, 2, 5, 'post1')
32
32
  try:
33
33
  from ._version_appendix import git_commit
34
34
  __commit_id__ = commit_id = git_commit
@@ -0,0 +1 @@
1
+ git_commit = "55bbd00"
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations as __future_annotations__
2
2
 
3
+ import json
3
4
  import logging
5
+ from functools import cache
6
+ from pathlib import Path
4
7
 
5
8
  from ...detector import (
6
9
  Devices,
@@ -32,6 +35,28 @@ The CANN variant of the A5 generation, whose UB fabric replaces what the
32
35
  earlier generations reach through the shared memory device.
33
36
  """
34
37
 
38
+ _HCCL_RANKTABLE_PATH = "/etc/hccl_rootinfo.json"
39
+ """
40
+ The host ranktable ("rootinfo") file. Optional, user-maintained state: it
41
+ belongs to no driver package, and the vendor documents it as generated by
42
+ mindcluster-tools and mounted only when present, see
43
+ https://gitcode.com/Ascend/mind-cluster/blob/master/docs/en/scheduling/references/appendix.md.
44
+ """
45
+
46
+ _A5_RANKTABLE_VERSION = "2.0"
47
+ """
48
+ The ranktable version the A5 generation requires.
49
+
50
+ The format is bound to the chip generation -- 1.0 for A2, 1.2 for A3, 2.0 for
51
+ A5, see ascend-operator/pkg/ranktable/common/common.go:37-38 and
52
+ ranktable/v2dot0/ranktable.go:27. A5 loads libhccl_v2.so, which rejects an
53
+ older table outright rather than ignoring it.
54
+
55
+ Only the A5 row of that mapping has a measured failure behind it, so only A5 is
56
+ acted on. An A3 host carrying a 1.2 table is presumed correct rather than
57
+ warned about, because nothing here established otherwise.
58
+ """
59
+
35
60
  _A5_UB_MOUNT_PATTERNS = [
36
61
  "/usr/lib64/libummu*",
37
62
  "/usr/lib64/liburma*",
@@ -124,30 +149,51 @@ class AscendGenerator(Generator):
124
149
  if not common_device_nodes:
125
150
  return None
126
151
 
127
- common_mounts = []
128
- for p in [
129
- "/etc/hccl_rootinfo.json",
152
+ # Device.appendix defaults to None and callers pass their own devices
153
+ # in, so every read goes through `or {}`.
154
+ is_a5 = any(
155
+ get_ascend_cann_variant((dev.appendix or {}).get("arch_family"))
156
+ == _A5_CANN_VARIANT
157
+ for dev in devices
158
+ if dev
159
+ )
160
+
161
+ mount_paths = [
162
+ _HCCL_RANKTABLE_PATH,
130
163
  "/usr/local/Ascend/driver/topo",
131
164
  "/usr/local/Ascend/driver/lib64",
132
165
  "/usr/local/Ascend/driver/include",
133
166
  "/usr/local/dcmi",
134
167
  "/usr/local/bin/npu-smi",
135
168
  "/var/queue_schedule",
136
- ]:
169
+ ]
170
+
171
+ if is_a5:
172
+ # A5 loads libhccl_v2.so, which accepts a 2.0 ranktable only. A host
173
+ # file left over from an A2 fleet is 1.0, and HCCL refuses it with
174
+ # Config_Error_Ranktable(EI0014) rather than falling back, so every
175
+ # multi-card init dies. Nothing here writes the file, so the only
176
+ # lever is not mounting it -- verified by isolation: mounting only
177
+ # this file into an otherwise-working container reproduces EI0014,
178
+ # mounting only driver/topo does not.
179
+ #
180
+ # This is single-node evidence. A multi-node deployment does need a
181
+ # ranktable; when that lands it has to be a 2.0 one, not this.
182
+ #
183
+ # Only GPUStack's own mount list is covered here. Under the Env
184
+ # policy ascend-docker-runtime mounts the file itself, which
185
+ # `warn_incompatible_ranktable` reports and cannot prevent.
186
+ mount_paths.remove(_HCCL_RANKTABLE_PATH)
187
+
188
+ common_mounts = []
189
+ for p in mount_paths:
137
190
  cm = path_to_cdi_mount(
138
191
  path=p,
139
192
  )
140
193
  if cm:
141
194
  common_mounts.append(cm)
142
195
 
143
- # Device.appendix defaults to None and callers pass their own devices
144
- # in, so every read goes through `or {}`.
145
- if any(
146
- get_ascend_cann_variant((dev.appendix or {}).get("arch_family"))
147
- == _A5_CANN_VARIANT
148
- for dev in devices
149
- if dev
150
- ):
196
+ if is_a5:
151
197
  for pattern in _A5_UB_MOUNT_PATTERNS:
152
198
  ub_mounts = glob_to_cdi_mounts(pattern=pattern)
153
199
  if not ub_mounts:
@@ -225,3 +271,75 @@ class AscendGenerator(Generator):
225
271
  mounts=common_mounts,
226
272
  ),
227
273
  )
274
+
275
+
276
+ def read_ranktable_version(path: str = _HCCL_RANKTABLE_PATH) -> str | None:
277
+ """
278
+ Read the ``version`` of the host ranktable file.
279
+
280
+ The path is resolved as this process sees it, exactly as the mount list
281
+ above resolves the paths it emits. Both therefore agree on whether the file
282
+ exists.
283
+
284
+ Args:
285
+ path:
286
+ The ranktable path to read.
287
+
288
+ Returns:
289
+ The declared version, or None if the file is absent, unreadable or not
290
+ a JSON object carrying a string version.
291
+
292
+ """
293
+ try:
294
+ content = json.loads(Path(path).read_text(encoding="utf-8"))
295
+ except (OSError, ValueError):
296
+ return None
297
+
298
+ if not isinstance(content, dict):
299
+ return None
300
+
301
+ version = content.get("version")
302
+ return version if isinstance(version, str) else None
303
+
304
+
305
+ @cache
306
+ def warn_incompatible_ranktable() -> str | None:
307
+ """
308
+ Warn once if this A5 host carries a ranktable A5's HCCL will reject.
309
+
310
+ ascend-docker-runtime mounts the host ranktable whenever the file is there
311
+ -- its `addUBMount` is a bare `os.Stat` with no version check -- so under
312
+ the default Env policy GPUStack cannot prevent the mount. Reporting is the
313
+ only available action: the operator has to remove the file or replace it
314
+ with a 2.0 one.
315
+
316
+ Returns:
317
+ The warning message emitted, or None if there is nothing to warn about.
318
+
319
+ """
320
+ # Ordered so a host without the file pays nothing: the file is absent on
321
+ # almost every non-Ascend host, and detection is the expensive half.
322
+ version = read_ranktable_version()
323
+ if version is None or version == _A5_RANKTABLE_VERSION:
324
+ return None
325
+
326
+ devices = detect_devices(manufacturer=ManufacturerEnum.ASCEND)
327
+ if not devices or not any(
328
+ get_ascend_cann_variant((dev.appendix or {}).get("arch_family"))
329
+ == _A5_CANN_VARIANT
330
+ for dev in devices
331
+ if dev
332
+ ):
333
+ # An older generation's own ranktable is correct for it.
334
+ return None
335
+
336
+ msg = (
337
+ f"Host ranktable {_HCCL_RANKTABLE_PATH} declares version {version!r}, "
338
+ f"but this A5 (Ascend 950) host needs {_A5_RANKTABLE_VERSION!r}. "
339
+ f"Under the default Env policy ascend-docker-runtime mounts it on "
340
+ f"presence and HCCL refuses it with Config_Error_Ranktable(EI0014), "
341
+ f"failing every multi-card workload. Remove the file or replace it "
342
+ f"with a {_A5_RANKTABLE_VERSION} table."
343
+ )
344
+ logger.warning(msg)
345
+ return msg
@@ -58,6 +58,9 @@ from .__utils__ import (
58
58
  sensitive_env_var,
59
59
  )
60
60
  from .cdi import dump_config as cdi_dump_config
61
+ from .cdi.ascend import (
62
+ warn_incompatible_ranktable as warn_incompatible_ascend_ranktable,
63
+ )
61
64
 
62
65
  if TYPE_CHECKING:
63
66
  from collections.abc import Callable, Generator
@@ -1568,6 +1571,9 @@ class DockerDeployer(EndoscopicDeployer):
1568
1571
  raise TypeError(msg)
1569
1572
 
1570
1573
  self._prepare_mirrored_deployment()
1574
+ # ascend-docker-runtime mounts the host ranktable itself, so this can
1575
+ # only report, not prevent. Cached, so it costs one check per process.
1576
+ warn_incompatible_ascend_ranktable()
1571
1577
 
1572
1578
  if isinstance(workload, WorkloadPlan):
1573
1579
  workload = DockerWorkloadPlan(**workload.__dict__)
@@ -186,28 +186,14 @@ class AMDDetector(Detector):
186
186
  dev_asic_family_id = dev_gpudev_info.family_id
187
187
 
188
188
  dev_mem = 0
189
- dev_mem_status = DeviceMemoryStatusEnum.HEALTHY
190
189
  try:
191
190
  dev_gpu_vram_usage = pyamdsmi.amdsmi_get_gpu_vram_usage(dev)
192
191
  dev_mem = dev_gpu_vram_usage.get("vram_total")
193
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
194
- dev_ecc_count = pyamdsmi.amdsmi_get_gpu_ecc_count(
195
- dev,
196
- pyamdsmi.AmdSmiGpuBlock.UMC,
197
- )
198
- if dev_ecc_count.get("uncorrectable_count", 0) > 0:
199
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
200
192
  except pyamdsmi.AmdSmiException:
201
193
  dev_mem = byte_to_mebibyte( # byte to MiB
202
194
  pyrocmsmi.rsmi_dev_memory_total_get(dev_idx),
203
195
  )
204
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
205
- with contextlib.suppress(pyrocmsmi.ROCMSMIError):
206
- dev_ecc_count = pyrocmsmi.rsmi_dev_ecc_count_get(
207
- dev_idx,
208
- )
209
- if dev_ecc_count.uncorrectable_err > 0:
210
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
196
+ dev_mem_status = _get_memory_status(dev, dev_idx)
211
197
 
212
198
  # The power limit is inventory, so it stays here, while the used
213
199
  # power the same call carries belongs to the usage query.
@@ -331,20 +317,10 @@ class AMDDetector(Detector):
331
317
 
332
318
  dev_mem = 0
333
319
  dev_mem_used = 0
334
- # Health is reported by both queries, as the operator reports it
335
- # from DetectAccelerator and MonitorAccelerator alike.
336
- dev_mem_status = DeviceMemoryStatusEnum.HEALTHY
337
320
  try:
338
321
  dev_gpu_vram_usage = pyamdsmi.amdsmi_get_gpu_vram_usage(dev)
339
322
  dev_mem = dev_gpu_vram_usage.get("vram_total")
340
323
  dev_mem_used = dev_gpu_vram_usage.get("vram_used")
341
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
342
- dev_ecc_count = pyamdsmi.amdsmi_get_gpu_ecc_count(
343
- dev,
344
- pyamdsmi.AmdSmiGpuBlock.UMC,
345
- )
346
- if dev_ecc_count.get("uncorrectable_count", 0) > 0:
347
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
348
324
  except pyamdsmi.AmdSmiException:
349
325
  dev_mem = byte_to_mebibyte( # byte to MiB
350
326
  pyrocmsmi.rsmi_dev_memory_total_get(dev_idx),
@@ -352,13 +328,9 @@ class AMDDetector(Detector):
352
328
  dev_mem_used = byte_to_mebibyte( # byte to MiB
353
329
  pyrocmsmi.rsmi_dev_memory_usage_get(dev_idx),
354
330
  )
355
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
356
- with contextlib.suppress(pyrocmsmi.ROCMSMIError):
357
- dev_ecc_count = pyrocmsmi.rsmi_dev_ecc_count_get(
358
- dev_idx,
359
- )
360
- if dev_ecc_count.uncorrectable_err > 0:
361
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
331
+ # Health is reported by both queries, as the operator reports it
332
+ # from DetectAccelerator and MonitorAccelerator alike.
333
+ dev_mem_status = _get_memory_status(dev, dev_idx)
362
334
 
363
335
  # A sentinel reading means the same as a failed call -- AMD SMI
364
336
  # cannot tell the used power -- so both route to ROCm SMI.
@@ -523,6 +495,67 @@ class AMDDetector(Detector):
523
495
  return ret
524
496
 
525
497
 
498
+ def _get_memory_status(dev, dev_idx: int) -> DeviceMemoryStatusEnum:
499
+ """
500
+ Get a device's memory health from its uncorrected ECC error count.
501
+
502
+ Both queries produce it, mirroring the operator, which reports `Unhealthy`
503
+ from `DetectAccelerator` and `MonitorAccelerator` alike.
504
+
505
+ Args:
506
+ dev:
507
+ The AMD SMI device handle.
508
+ dev_idx:
509
+ The device index, for the ROCm SMI fallback.
510
+
511
+ Returns:
512
+ The memory status.
513
+
514
+ """
515
+ if envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
516
+ return DeviceMemoryStatusEnum.HEALTHY
517
+
518
+ try:
519
+ dev_ecc_count = pyamdsmi.amdsmi_get_gpu_ecc_count(
520
+ dev,
521
+ pyamdsmi.AmdSmiGpuBlock.UMC,
522
+ )
523
+ except pyamdsmi.AmdSmiException as e:
524
+ # Fail closed: a genuine driver error marks the device unhealthy, while
525
+ # a card without the counter cannot be judged. The stub binding raises
526
+ # a codeless error when amdsmi is absent, in which case ROCm SMI is
527
+ # the fallback, as it is for the memory read.
528
+ err_code = getattr(e, "err_code", None)
529
+ if err_code is not None:
530
+ if err_code != getattr(pyamdsmi, "AMDSMI_STATUS_NOT_SUPPORTED", None):
531
+ return DeviceMemoryStatusEnum.UNHEALTHY
532
+ return DeviceMemoryStatusEnum.HEALTHY
533
+ else:
534
+ if dev_ecc_count.get("uncorrectable_count", 0) > 0:
535
+ return DeviceMemoryStatusEnum.UNHEALTHY
536
+ return DeviceMemoryStatusEnum.HEALTHY
537
+
538
+ try:
539
+ dev_ecc_count = pyrocmsmi.rsmi_dev_ecc_count_get(dev_idx)
540
+ if dev_ecc_count.uncorrectable_err > 0:
541
+ return DeviceMemoryStatusEnum.UNHEALTHY
542
+ except pyrocmsmi.ROCMSMIError as e:
543
+ # rsmi_status_t comes from the ROCm-installed rsmiBindings, so it is
544
+ # looked up late: without ROCm the module has no such attribute.
545
+ rsmi_not_supported = getattr(
546
+ getattr(pyrocmsmi, "rsmi_status_t", None),
547
+ "RSMI_STATUS_NOT_SUPPORTED",
548
+ None,
549
+ )
550
+ if e.value not in (
551
+ pyrocmsmi.ROCMSMI_ERROR_FUNCTION_NOT_FOUND,
552
+ rsmi_not_supported,
553
+ ):
554
+ return DeviceMemoryStatusEnum.UNHEALTHY
555
+
556
+ return DeviceMemoryStatusEnum.HEALTHY
557
+
558
+
526
559
  def _get_reading(dev_info: dict, key: str, default: Any = None) -> Any:
527
560
  """
528
561
  Read one value out of an AMD SMI answer, treating its sentinel as absent.
@@ -141,7 +141,6 @@ class IluvatarDetector(Detector):
141
141
  dev_cores = pyixml.nvmlDeviceGetNumGpuCores(dev)
142
142
 
143
143
  dev_mem = 0
144
- dev_mem_status = DeviceMemoryStatusEnum.HEALTHY
145
144
  with contextlib.suppress(pyixml.NVMLError):
146
145
  # Prefer the v2 memory structure, falling back to v1 --
147
146
  # mirrors the operator's GetMemoryInfoV, which drops the
@@ -150,11 +149,7 @@ class IluvatarDetector(Detector):
150
149
  dev_mem = byte_to_mebibyte( # byte to MiB
151
150
  dev_mem_info.total,
152
151
  )
153
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
154
- with contextlib.suppress(pyixml.NVMLError):
155
- dev_health = pyixml.ixmlDeviceGetHealth(dev)
156
- if dev_health != pyixml.IXML_HEALTH_OK:
157
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
152
+ dev_mem_status = _get_memory_status(dev)
158
153
 
159
154
  dev_power = None
160
155
  with contextlib.suppress(pyixml.NVMLError):
@@ -251,7 +246,6 @@ class IluvatarDetector(Detector):
251
246
 
252
247
  dev_mem = 0
253
248
  dev_mem_used = 0
254
- dev_mem_status = DeviceMemoryStatusEnum.HEALTHY
255
249
  with contextlib.suppress(pyixml.NVMLError):
256
250
  # Same v2-then-v1 fallback as detect_info: the operator's
257
251
  # MonitorAccelerator re-reads the memory info rather than
@@ -263,11 +257,7 @@ class IluvatarDetector(Detector):
263
257
  dev_mem_used = byte_to_mebibyte( # byte to MiB
264
258
  dev_mem_info.used,
265
259
  )
266
- if not envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
267
- with contextlib.suppress(pyixml.NVMLError):
268
- dev_health = pyixml.ixmlDeviceGetHealth(dev)
269
- if dev_health != pyixml.IXML_HEALTH_OK:
270
- dev_mem_status = DeviceMemoryStatusEnum.UNHEALTHY
260
+ dev_mem_status = _get_memory_status(dev)
271
261
 
272
262
  dev_cores_util = None
273
263
  with contextlib.suppress(pyixml.NVMLError):
@@ -405,3 +395,34 @@ def _get_memory_info(dev):
405
395
  return pyixml.nvmlDeviceGetMemoryInfo(dev, version=pyixml.nvmlMemory_v2)
406
396
  except pyixml.NVMLError:
407
397
  return pyixml.nvmlDeviceGetMemoryInfo(dev)
398
+
399
+
400
+ def _get_memory_status(dev) -> DeviceMemoryStatusEnum:
401
+ """
402
+ Get a device's health from its dedicated health query.
403
+
404
+ Both queries produce it, mirroring the operator, which reports `Unhealthy`
405
+ from `DetectAccelerator` and `MonitorAccelerator` alike.
406
+
407
+ Args:
408
+ dev:
409
+ The device handle.
410
+
411
+ Returns:
412
+ The memory status.
413
+
414
+ """
415
+ if envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
416
+ return DeviceMemoryStatusEnum.HEALTHY
417
+
418
+ try:
419
+ dev_health = pyixml.ixmlDeviceGetHealth(dev)
420
+ if dev_health != pyixml.IXML_HEALTH_OK:
421
+ return DeviceMemoryStatusEnum.UNHEALTHY
422
+ except pyixml.NVMLError as e:
423
+ # Fail closed: a query the driver errors on marks the device
424
+ # unhealthy, while an unsupported query means it cannot be judged.
425
+ if e.value != pyixml.NVML_ERROR_NOT_SUPPORTED:
426
+ return DeviceMemoryStatusEnum.UNHEALTHY
427
+
428
+ return DeviceMemoryStatusEnum.HEALTHY
@@ -529,7 +529,12 @@ def _get_memory_status(
529
529
  memory_location: int,
530
530
  ) -> DeviceMemoryStatusEnum:
531
531
  """
532
- Get a device's memory health from its uncorrected ECC error counter.
532
+ Get a device's memory health.
533
+
534
+ The verdict is the uncorrected ECC error counter plus the driver's
535
+ recovery state: a GSP failure (Xid 119/154) leaves the ECC counters
536
+ readable at zero, so the recovery action and reset status fields are
537
+ probed as well.
533
538
 
534
539
  Both queries produce it, mirroring the operator, which reports `Unhealthy`
535
540
  from `DetectAccelerator` and `MonitorAccelerator` alike.
@@ -549,7 +554,7 @@ def _get_memory_status(
549
554
  if envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
550
555
  return DeviceMemoryStatusEnum.HEALTHY
551
556
 
552
- with contextlib.suppress(pynvml.NVMLError):
557
+ try:
553
558
  dev_mem_ecc_errors = pynvml.nvmlDeviceGetMemoryErrorCounter(
554
559
  dev,
555
560
  pynvml.NVML_MEMORY_ERROR_TYPE_UNCORRECTED,
@@ -558,6 +563,29 @@ def _get_memory_status(
558
563
  )
559
564
  if dev_mem_ecc_errors > 0:
560
565
  return DeviceMemoryStatusEnum.UNHEALTHY
566
+ except pynvml.NVMLError as e:
567
+ # Fail closed: a query the driver errors on (a wedged GSP answers
568
+ # NVML_ERROR_UNKNOWN after its RPC timeout) marks the card unhealthy,
569
+ # while an unsupported counter means the card cannot be judged.
570
+ if e.value != pynvml.NVML_ERROR_NOT_SUPPORTED:
571
+ return DeviceMemoryStatusEnum.UNHEALTHY
572
+
573
+ try:
574
+ dev_fields = pynvml.nvmlDeviceGetFieldValues(
575
+ dev,
576
+ fieldIds=[
577
+ pynvml.NVML_FI_DEV_GET_GPU_RECOVERY_ACTION,
578
+ pynvml.NVML_FI_DEV_RESET_STATUS,
579
+ ],
580
+ )
581
+ except pynvml.NVMLError as e:
582
+ if e.value != pynvml.NVML_ERROR_NOT_SUPPORTED:
583
+ return DeviceMemoryStatusEnum.UNHEALTHY
584
+ return DeviceMemoryStatusEnum.HEALTHY
585
+ for dev_field in dev_fields:
586
+ # A per-field error extracts to None: cannot judge, keep the verdict.
587
+ if _extract_field_value(dev_field):
588
+ return DeviceMemoryStatusEnum.UNHEALTHY
561
589
 
562
590
  return DeviceMemoryStatusEnum.HEALTHY
563
591
 
@@ -508,7 +508,7 @@ def _get_memory_status(
508
508
  if envs.GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK:
509
509
  return DeviceMemoryStatusEnum.HEALTHY
510
510
 
511
- with contextlib.suppress(pyhgml.HGMLError):
511
+ try:
512
512
  dev_mem_ecc_errors = pyhgml.hgmlDeviceGetMemoryErrorCounter(
513
513
  dev,
514
514
  pyhgml.HGML_MEMORY_ERROR_TYPE_UNCORRECTED,
@@ -517,6 +517,11 @@ def _get_memory_status(
517
517
  )
518
518
  if dev_mem_ecc_errors > 0:
519
519
  return DeviceMemoryStatusEnum.UNHEALTHY
520
+ except pyhgml.HGMLError as e:
521
+ # Fail closed: a query the driver errors on marks the device
522
+ # unhealthy, while an unsupported counter means it cannot be judged.
523
+ if e.value != pyhgml.HGML_ERROR_NOT_SUPPORTED:
524
+ return DeviceMemoryStatusEnum.UNHEALTHY
520
525
 
521
526
  return DeviceMemoryStatusEnum.HEALTHY
522
527
 
@@ -46,8 +46,15 @@ if TYPE_CHECKING:
46
46
  """
47
47
  GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK: bool = True
48
48
  """
49
- Set true to disable ECC errors check during detection,
49
+ Set true to disable the health check during detection,
50
50
  which is used to determine the health status of the device.
51
+
52
+ When enabled (set false), the check reads the uncorrected ECC error
53
+ counter and, on NVIDIA, the driver's recovery state (GPU reset required,
54
+ e.g. after a GSP failure); a query the driver errors on reports the device
55
+ unhealthy. Each query is a driver call per device, and against a wedged
56
+ device it can block until the driver's RPC timeout (up to 45s with GSP
57
+ firmware).
51
58
  """
52
59
  GPUSTACK_RUNTIME_DETECT_BACKEND_MAP_RESOURCE_KEY: dict[str, str] | None = None
53
60
  """
@@ -23,7 +23,7 @@ dependencies = [
23
23
  # NB(thxCode): Pin to this Podman version for a custom patch. See gpustack_runtime/deployer/__patches__.py.
24
24
  "podman==5.6.0",
25
25
  "pyyaml",
26
- "gpustack-runner>=0.1.26.post2",
26
+ "gpustack-runner>=0.1.29",
27
27
  "nvidia-ml-py>=13.580.65",
28
28
  "mthreads-ml-py>=2.2.10",
29
29
  "argcomplete>=3.6.3",
@@ -43,13 +43,13 @@ gpustack-runtime = "gpustack_runtime.__main__:main"
43
43
  dev=[
44
44
  "pyclean==3.4.0",
45
45
  "pre-commit==4.5.0",
46
- "pytest==8.4.2",
46
+ "pytest>=9.0.3,<10",
47
47
  "hatchling",
48
48
  "hatch-vcs",
49
49
  "mkdocs>=1.6.1",
50
50
  "mkdocstrings-python>=1.18.0",
51
51
  "mkdocs-material>=9.6.18",
52
- "pymdown-extensions>=10.16.1",
52
+ "pymdown-extensions>=11.0.1",
53
53
  "dockerpty>=0.4.1",
54
54
  "amdsmi>=7.0.2",
55
55
  ]
@@ -128,7 +128,7 @@ covered by F3). Known gaps, each to be closed or documented as a deliberate dive
128
128
  | Cambricon | Not a binding at all: a `cnmon info -e -m -u -j` shell-out parsing `cnmon_info.json`, with a `TODO(thxCode)` where the sample output should be. No driver version, no Neuware version, no cores, no power, no BDF, no NUMA, no health/ECC, no PCIe bus id. | New hand-written `pycndev` ctypes binding (following `pydcmi`/`pymxsml`) and a rewritten `cambricon.py` calling `GetDeviceCount`, `GetDeviceHandleByIndex`, `GetUUID`, `GetPCIeInfoV`, `GetCardName`, `GetMemoryInfoV`, `GetCardHealthStateV`, `GetVersionInfo`, `GetUtilizationInfo`, `GetTemperatureInfo`, `GetPowerInfo`, plus the Neuware version from `/usr/local/neuware/version.txt`. `cndev.h` in the operator's `binding/cndev` is the header of record. Four decisions taken while building `T3`, all recorded rather than assumed: **cores and the power limit stay unreported** — `pycndev` binds no core-count entry point and the operator reads neither for this vendor, so closing those two needs a binding addition of its own; **`driver_version` carries `major.minor.build`** where the operator formats only `major.minor`, since it is free precision from the same query and every other vendor here reports the driver's full string; **health comes from `cndevGetCardHealthStateV2().health` alone, with no ECC read**, because that is literally what the operator does (`memoryUnhealthy = healthInfo.Health == 0`); and **memory needs no unit conversion** — the header says MB but the operator, `cnmon` and this repo's Ascend detector all treat the value as MiB, so converting would under-report ~4.8 % against the operator on the same host, which is the very discrepancy Story 1 exists to remove. `C2` confirms the memory figure against `cnmon`. |
129
129
  | Cambricon | Per-device failure policy diverges from every sibling **on purpose**: `T3` follows the operator and `continue`s past a card whose required reads fail, where the other eight detectors let the error propagate. | A single faulty card therefore costs one device on Cambricon and **all** devices on the other eight. Graceful degradation is very likely the right behaviour everywhere and the other eight should move toward it, but that is a repo-wide behaviour change deserving its own task — not something to slip in per vendor. |
130
130
  | THead | Enumerates MIG-style GPU/compute instances, which the operator does not. | Keep — this is the appendix mechanism F2 preserves. |
131
- | **All vendors** | `GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK` **defaults to true**, so a default run never reads an ECC counter and every `memory_status` comes back `healthy`. The operator reads the uncorrected-ECC counter unconditionally. | Keep the default (the check costs a driver call per card per pass), but record it as a deliberate divergence: on the same host the runtime can report `healthy` where the operator reports `Unhealthy`, which is a code-path difference of exactly the kind `Story 1` exists to eliminate. `C2` must therefore compare health with the flag switched **off**, or it compares nothing. Found while building `T5`. |
131
+ | **All vendors** | `GPUSTACK_RUNTIME_DETECT_NO_HEALTH_CHECK` **defaults to true**, so a default run never reads an ECC counter and every `memory_status` comes back `healthy`. The operator reads the uncorrected-ECC counter unconditionally. | Keep the default (the check costs a driver call per card per pass), but record it as a deliberate divergence: on the same host the runtime can report `healthy` where the operator reports `Unhealthy`, which is a code-path difference of exactly the kind `Story 1` exists to eliminate. `C2` must therefore compare health with the flag switched **off**, or it compares nothing. Found while building `T5`. With the flag off, the NVIDIA check additionally probes the driver's recovery state (`NVML_FI_DEV_GET_GPU_RECOVERY_ACTION` / `NVML_FI_DEV_RESET_STATUS`, i.e. the Xid 154 GPU-reset-required state) and treats an errored query as `unhealthy` rather than swallowing it; against a wedged card such a query can block until the driver's GSP RPC timeout (up to 45s). |
132
132
 
133
133
  Acceptance criteria:
134
134
  - ~~A written parity table lives in the repo (module docstring or `docs/`) listing, per vendor, the