gpustack-runtime 0.2.2.post7__tar.gz → 0.2.3__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 (161) hide show
  1. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/PKG-INFO +2 -2
  2. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/deploy/manifests/kubernetes.yaml +24 -0
  3. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/_version.py +2 -2
  4. gpustack_runtime-0.2.3/gpustack_runtime/_version_appendix.py +1 -0
  5. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/cmds/detector.py +50 -9
  6. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/__types__.py +205 -1
  7. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/ascend.py +11 -3
  8. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/iluvatar.py +5 -1
  9. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/thead.py +18 -0
  10. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/docker.py +20 -0
  11. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/kuberentes.py +345 -1
  12. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/podman.py +20 -0
  13. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/__init__.py +8 -3
  14. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/__types__.py +168 -12
  15. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/__utils__.py +139 -14
  16. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/amd.py +208 -48
  17. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/ascend.py +298 -120
  18. gpustack_runtime-0.2.3/gpustack_runtime/detector/cambricon.py +348 -0
  19. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/hygon.py +107 -28
  20. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/iluvatar.py +146 -43
  21. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/metax.py +138 -49
  22. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/mthreads.py +137 -50
  23. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/nvidia.py +329 -136
  24. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyamdgpu/__init__.py +13 -0
  25. gpustack_runtime-0.2.3/gpustack_runtime/detector/pycndev/__init__.py +840 -0
  26. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pydcmi/__init__.py +52 -0
  27. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/thead.py +291 -95
  28. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/envs.py +22 -10
  29. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/ruff.toml +1 -0
  30. gpustack_runtime-0.2.3/specs/2026-08-14-detector-alignment-and-workload-exit-status.md +1088 -0
  31. gpustack_runtime-0.2.3/tests/gpustack_runtime/deployer/test_docker_status.py +181 -0
  32. gpustack_runtime-0.2.3/tests/gpustack_runtime/deployer/test_kubernetes_status.py +446 -0
  33. gpustack_runtime-0.2.3/tests/gpustack_runtime/deployer/test_podman_status.py +144 -0
  34. gpustack_runtime-0.2.3/tests/gpustack_runtime/deployer/test_runtime_class.py +183 -0
  35. gpustack_runtime-0.2.3/tests/gpustack_runtime/deployer/test_visible_devices_ordering.py +349 -0
  36. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/test_workload_status.py +76 -1
  37. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/README.md +46 -0
  38. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -2
  39. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -8
  40. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +54 -0
  41. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -2
  42. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +96 -96
  43. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -8
  44. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -8
  45. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +1 -1
  46. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +8 -8
  47. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +151 -128
  48. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +8 -8
  49. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +2 -2
  50. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json +54 -0
  51. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +1 -1
  52. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +9 -8
  53. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +402 -0
  54. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +9 -1
  55. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json +27 -0
  56. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +413 -0
  57. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_amd.py +572 -0
  58. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_ascend.py +735 -0
  59. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_cambricon.py +568 -0
  60. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_detector_cli.py +162 -0
  61. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_detector_types.py +422 -0
  62. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_hygon.py +521 -0
  63. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_iluvatar.py +333 -0
  64. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_metax.py +390 -0
  65. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/test_mig_devices.py +5 -5
  66. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_mthreads.py +445 -0
  67. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_nvidia.py +771 -0
  68. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_pycndev.py +926 -0
  69. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_samples.py +97 -0
  70. gpustack_runtime-0.2.3/tests/gpustack_runtime/detector/test_thead.py +830 -0
  71. gpustack_runtime-0.2.2.post7/gpustack_runtime/_version_appendix.py +0 -1
  72. gpustack_runtime-0.2.2.post7/gpustack_runtime/detector/cambricon.py +0 -133
  73. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/samples/README.md +0 -3
  74. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -29
  75. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -50
  76. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -35
  77. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_amd.py +0 -23
  78. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_ascend.py +0 -23
  79. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_cambricon.py +0 -23
  80. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_hygon.py +0 -23
  81. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_iluvatar.py +0 -23
  82. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_metax.py +0 -23
  83. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_mthreads.py +0 -23
  84. gpustack_runtime-0.2.2.post7/tests/gpustack_runtime/detector/test_nvidia.py +0 -23
  85. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.codespelldict +0 -0
  86. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.codespellrc +0 -0
  87. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.dockerignore +0 -0
  88. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.gitattributes +0 -0
  89. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.gitignore +0 -0
  90. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.pre-commit-config.yaml +0 -0
  91. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/.python-version +0 -0
  92. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/LICENSE +0 -0
  93. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/Makefile +0 -0
  94. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/README.md +0 -0
  95. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/deploy/manifests/docker-compose.yaml +0 -0
  96. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/docs/index.md +0 -0
  97. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/docs/modules/gpustack_runtime.deployer.md +0 -0
  98. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/docs/modules/gpustack_runtime.detector.md +0 -0
  99. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/docs/modules/gpustack_runtime.md +0 -0
  100. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/__init__.py +0 -0
  101. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/__main__.py +0 -0
  102. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/_version.pyi +0 -0
  103. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/cmds/__init__.py +0 -0
  104. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/cmds/__types__.py +0 -0
  105. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/cmds/deployer.py +0 -0
  106. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/cmds/images.py +0 -0
  107. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/__init__.py +0 -0
  108. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/__patches__.py +0 -0
  109. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/__utils__.py +0 -0
  110. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
  111. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
  112. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/__utils__.py +0 -0
  113. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/amd.py +0 -0
  114. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
  115. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/cdi/metax.py +0 -0
  116. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
  117. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
  118. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
  119. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
  120. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
  121. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
  122. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
  123. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
  124. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
  125. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
  126. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
  127. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/gpustack_runtime/logging.py +0 -0
  128. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/hatch.toml +0 -0
  129. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/mkdocs.yml +0 -0
  130. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/pack/Dockerfile +0 -0
  131. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/pack/Dockerfile.dummy +0 -0
  132. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/pyproject.toml +0 -0
  133. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/pytest.ini +0 -0
  134. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
  135. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
  136. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
  137. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
  138. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_jsons.json +0 -0
  139. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_yamls.yaml +0 -0
  140. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_json.json +0 -0
  141. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_yaml.yaml +0 -0
  142. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
  143. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/test_privileged.py +0 -0
  144. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
  145. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
  146. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
  147. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
  148. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
  149. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
  150. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
  151. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
  152. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
  153. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
  154. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
  155. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
  156. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
  157. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
  158. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
  159. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
  160. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/uv.lock +0 -0
  161. {gpustack_runtime-0.2.2.post7 → gpustack_runtime-0.2.3}/uv.toml +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: gpustack-runtime
3
- Version: 0.2.2.post7
3
+ Version: 0.2.3
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
@@ -25,6 +25,25 @@ rules:
25
25
  - "pods/exec"
26
26
  verbs:
27
27
  - "*"
28
+ # Allow reading the Events of a workload Pod, so an image-pull failure
29
+ # reports the registry error behind a bare "ImagePullBackOff" reason.
30
+ # Kept as its own rule with only "get"/"list": the rule above grants "*",
31
+ # which would also allow creating and deleting Events.
32
+ - apiGroups:
33
+ - ""
34
+ resources:
35
+ - "events"
36
+ verbs:
37
+ - "get"
38
+ - "list"
39
+ # Allow resolving the vendor RuntimeClass (e.g. "ascend", "nvidia") for
40
+ # accelerated workload Pods, see GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS.
41
+ - apiGroups:
42
+ - "node.k8s.io"
43
+ resources:
44
+ - "runtimeclasses"
45
+ verbs:
46
+ - "get"
28
47
  ---
29
48
  apiVersion: rbac.authorization.k8s.io/v1
30
49
  kind: RoleBinding
@@ -76,6 +95,11 @@ spec:
76
95
  "app.kubernetes.io/name": "gpustack-runtime"
77
96
  spec:
78
97
  # Provide GPU resource request for gpustack-runtime if not using default runtime.
98
+ # The runtimeClassName set here is mirrored to every deployed workload Pod
99
+ # that does not resolve one itself. Usually this is unnecessary: workload
100
+ # Pods requesting device plugin resources automatically adopt the mapped
101
+ # vendor RuntimeClass when it exists in the cluster,
102
+ # see GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS.
79
103
  #
80
104
  # runtimeClassName: nvidia
81
105
 
@@ -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.2.post7'
31
- __version_tuple__ = version_tuple = (0, 2, 2, 'post7')
30
+ __version__ = version = '0.2.3'
31
+ __version_tuple__ = version_tuple = (0, 2, 3)
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 = "518db70"
@@ -26,6 +26,7 @@ class DetectDevicesSubCommand(SubCommand):
26
26
 
27
27
  format: str = "table"
28
28
  watch: int = 0
29
+ no_usage: bool = False
29
30
 
30
31
  @staticmethod
31
32
  def register(parser: _SubParsersAction):
@@ -49,19 +50,26 @@ class DetectDevicesSubCommand(SubCommand):
49
50
  help="Continuously watch for GPU in intervals of N seconds",
50
51
  )
51
52
 
53
+ detect_parser.add_argument(
54
+ "--no-usage",
55
+ action="store_true",
56
+ help="Report inventory only, without querying utilization, temperature or power",
57
+ )
58
+
52
59
  detect_parser.set_defaults(func=DetectDevicesSubCommand)
53
60
 
54
61
  def __init__(self, args: Namespace):
55
62
  self.format = args.format
56
63
  self.watch = args.watch
64
+ self.no_usage = args.no_usage
57
65
 
58
66
  def run(self):
59
67
  while True:
60
- devs: Devices = detect_devices(fast=False)
68
+ devs: Devices = detect_devices(fast=False, usage=not self.no_usage)
61
69
  print("\033[2J\033[H", end="")
62
70
  match self.format.lower():
63
71
  case "json":
64
- print(format_devices_json(devs))
72
+ print(format_devices_json(devs, usage=not self.no_usage))
65
73
  case _:
66
74
  # Group devices by manufacturer.
67
75
  group_devs = group_devices_by_manufacturer(devs)
@@ -70,7 +78,9 @@ class DetectDevicesSubCommand(SubCommand):
70
78
  else:
71
79
  # Print each group separately.
72
80
  for devs in group_devs.values():
73
- print(format_devices_table(devs))
81
+ print(
82
+ format_devices_table(devs, usage=not self.no_usage),
83
+ )
74
84
  if not self.watch:
75
85
  break
76
86
  time.sleep(self.watch)
@@ -134,11 +144,37 @@ class GetDevicesTopologySubCommand(SubCommand):
134
144
  print(os.linesep.join(legend_lines))
135
145
 
136
146
 
137
- def format_devices_json(devs: Devices) -> str:
138
- return json.dumps([dev.to_dict() for dev in devs], indent=2)
147
+ _USAGE_ONLY_KEYS = (
148
+ "cores_utilization",
149
+ "memory_used",
150
+ "memory_utilization",
151
+ "temperature",
152
+ "power_used",
153
+ )
154
+ """
155
+ The keys only the usage query fills. `memory_status` is deliberately absent:
156
+ the information query reports it too, which is why the table keeps Status.
157
+ """
158
+
159
+
160
+ def format_devices_json(devs: Devices, usage: bool = True) -> str:
161
+ devs_dict = [dev.to_dict() for dev in devs]
162
+
163
+ if not usage:
164
+ # Without the usage query these fields hold their defaults, and a
165
+ # serialized 0 reads as a real idle measurement. The table drops them
166
+ # for that reason, and a machine-readable consumer is the more likely of
167
+ # the two to act on it -- so absent, meaning unmeasured.
168
+ for dev_dict in devs_dict:
169
+ mig_devs_dict = (dev_dict.get("appendix") or {}).get("mig_devices") or []
170
+ for d in [dev_dict, *mig_devs_dict]:
171
+ for key in _USAGE_ONLY_KEYS:
172
+ d.pop(key, None)
173
+
174
+ return json.dumps(devs_dict, indent=2)
139
175
 
140
176
 
141
- def format_devices_table(devs: Devices) -> str:
177
+ def format_devices_table(devs: Devices, usage: bool = True) -> str:
142
178
  if not devs:
143
179
  return "No GPUs detected."
144
180
 
@@ -150,9 +186,14 @@ def format_devices_table(devs: Devices) -> str:
150
186
  row = [
151
187
  str(dev.index),
152
188
  dev.name if dev.name else "N/A",
153
- f"{dev.memory_used}MiB / {dev.memory}MiB",
154
- f"{dev.cores_utilization}%",
155
- f"{dev.temperature}C" if dev.temperature is not None else "N/A",
189
+ # Without the usage query these fields hold their defaults, and a
190
+ # rendered 0 would read as a real idle measurement. Total memory
191
+ # and the status stay, being reported by the information query.
192
+ f"{dev.memory_used}MiB / {dev.memory}MiB"
193
+ if usage
194
+ else f"N/A / {dev.memory}MiB",
195
+ f"{dev.cores_utilization}%" if usage else "N/A",
196
+ f"{dev.temperature}C" if usage and dev.temperature is not None else "N/A",
156
197
  dev.compute_capability if dev.compute_capability else "N/A",
157
198
  "OK" if dev.memory_status == DeviceMemoryStatusEnum.HEALTHY else "ERR",
158
199
  ]
@@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
8
8
  from concurrent.futures import ThreadPoolExecutor
9
9
  from dataclasses import dataclass, field
10
10
  from enum import Enum
11
- from typing import TYPE_CHECKING
11
+ from typing import TYPE_CHECKING, Any
12
12
 
13
13
  from dataclasses_json import dataclass_json
14
14
 
@@ -1130,6 +1130,165 @@ class WorkloadStatusOperation:
1130
1130
  return self._name_rfc1123_guard
1131
1131
 
1132
1132
 
1133
+ @dataclass_json
1134
+ @dataclass
1135
+ class WorkloadStatusExit:
1136
+ """
1137
+ An exit information of a workload's container.
1138
+
1139
+ Attributes:
1140
+ name (str):
1141
+ Name representing the exited target, e.g., human-readable container name.
1142
+ token (WorkloadOperationToken):
1143
+ Token of the exited target, e.g., container ID.
1144
+ exit_code (int | None):
1145
+ Exit code of the container.
1146
+ reason (str):
1147
+ Short reason of the termination or the blocking.
1148
+ message (str):
1149
+ Detailed message of the termination or the blocking.
1150
+ started_at (str):
1151
+ Start time of the container's last execution.
1152
+ finished_at (str):
1153
+ Finish time of the container's last execution.
1154
+ restart_count (int):
1155
+ Restart times of the container.
1156
+
1157
+ """
1158
+
1159
+ name: str
1160
+ """
1161
+ Name representing the exited target, e.g., human-readable container name.
1162
+ """
1163
+ token: WorkloadOperationToken
1164
+ """
1165
+ Token of the exited target, e.g. container ID,
1166
+ which addresses the same container as the logs/exec operations.
1167
+ """
1168
+ exit_code: int | None = None
1169
+ """
1170
+ Exit code of the container,
1171
+ None if the container has not terminated, e.g. blocked from starting.
1172
+ """
1173
+ reason: str = ""
1174
+ """
1175
+ Short reason of the termination or the blocking,
1176
+ e.g. "OOMKilled", "Error", "ImagePullBackOff".
1177
+ """
1178
+ message: str = ""
1179
+ """
1180
+ Detailed message of the termination or the blocking.
1181
+ """
1182
+ started_at: str = ""
1183
+ """
1184
+ Start time of the container's last execution,
1185
+ empty if the container has never started.
1186
+ """
1187
+ finished_at: str = ""
1188
+ """
1189
+ Finish time of the container's last execution,
1190
+ empty if the container has not finished.
1191
+ """
1192
+ restart_count: int = 0
1193
+ """
1194
+ Restart times of the container.
1195
+ """
1196
+
1197
+
1198
+ _CONTAINER_EXITED_STATUSES = ("exited", "dead", "restarting")
1199
+ """
1200
+ Container statuses carrying an exit code. "restarting" is one of them: a
1201
+ crash-looping container sits there between attempts with the last attempt's
1202
+ ExitCode already filled in, and that code is the diagnosis being asked for.
1203
+ """
1204
+
1205
+ _CONTAINER_UNSET_TIMESTAMP_PREFIX = "0001-01-01"
1206
+ """
1207
+ What Docker fills an unset timestamp with -- Go's zero time -- rather than
1208
+ omitting the key, which is why a plain `.get(..., "")` never defaults.
1209
+ """
1210
+
1211
+
1212
+ def parse_container_exit(
1213
+ c: Any,
1214
+ component_name_label: str,
1215
+ ) -> WorkloadStatusExit | None:
1216
+ """
1217
+ Build the exit entry of a container that has terminated.
1218
+
1219
+ Docker and Podman report the same State object -- podman-py is Docker-API
1220
+ compatible and both call sites inspect a fully reloaded container -- so both
1221
+ deployers share this. Keeping it in one place is the point: the reason
1222
+ derivation and the timestamp normalization below are exactly the kind of
1223
+ detail that silently drifts when it lives in two files.
1224
+
1225
+ Args:
1226
+ c:
1227
+ A Docker-API-compatible container to inspect.
1228
+ component_name_label:
1229
+ The label the deployer names its components with.
1230
+
1231
+ Returns:
1232
+ A WorkloadStatusExit if the container has terminated, None if it is
1233
+ still running or pending and so contributes nothing.
1234
+
1235
+ """
1236
+ if c.status not in _CONTAINER_EXITED_STATUSES:
1237
+ return None
1238
+
1239
+ # Every read is .get()-able: a container whose State is missing keys (or
1240
+ # missing entirely) must degrade gracefully, never raise.
1241
+ state = c.attrs.get("State", {}) or {}
1242
+
1243
+ exit_code = state.get("ExitCode")
1244
+ if state.get("OOMKilled"):
1245
+ reason = "OOMKilled"
1246
+ elif state.get("Error"):
1247
+ reason = "Error"
1248
+ elif exit_code not in (0, None):
1249
+ # Docker fills State.Error for a container that failed to *start*, not
1250
+ # for one that ran and exited non-zero -- the commonest crash of all.
1251
+ # Without this the caller has an exit code and no reason, so it sets no
1252
+ # state_message, while the Kubernetes deployer reports "Error" for the
1253
+ # same event. Same field, same answer, either backend.
1254
+ reason = "Error"
1255
+ else:
1256
+ reason = ""
1257
+
1258
+ return WorkloadStatusExit(
1259
+ name=c.labels.get(component_name_label, "") or c.name,
1260
+ token=c.attrs.get("Id", "") or c.name,
1261
+ exit_code=exit_code,
1262
+ reason=reason,
1263
+ message=state.get("Error", ""),
1264
+ started_at=_parse_container_timestamp(state.get("StartedAt")),
1265
+ finished_at=_parse_container_timestamp(state.get("FinishedAt")),
1266
+ restart_count=c.attrs.get("RestartCount", 0),
1267
+ )
1268
+
1269
+
1270
+ def _parse_container_timestamp(value: str | None) -> str:
1271
+ """
1272
+ Normalize a container's State timestamp to what WorkloadStatusExit promises.
1273
+
1274
+ Args:
1275
+ value:
1276
+ The raw timestamp as the container's State reports it.
1277
+
1278
+ Returns:
1279
+ The timestamp, empty if it never happened.
1280
+
1281
+ """
1282
+ if not value or value.startswith(_CONTAINER_UNSET_TIMESTAMP_PREFIX):
1283
+ # WorkloadStatusExit documents these as empty when they never happened,
1284
+ # and Go's zero time rendered by a UI reads as "January 1, year 1".
1285
+ return ""
1286
+ # Docker reports 9-digit nanoseconds where the Kubernetes deployer formats 6
1287
+ # (_TIMESTAMP_FORMAT), and strptime's %f accepts at most 6, so a consumer
1288
+ # parsing this field would succeed on one backend and raise on the other.
1289
+ return re.sub(r"(\.\d{6})\d+", r"\1", value)
1290
+
1291
+
1133
1292
  @dataclass_json
1134
1293
  @dataclass
1135
1294
  class WorkloadStatus:
@@ -1153,6 +1312,9 @@ class WorkloadStatus:
1153
1312
  The operation for the executable containers of the workload.
1154
1313
  loggable (list[WorkloadStatusOperation]):
1155
1314
  The operation for the loggable containers of the workload.
1315
+ exits (list[WorkloadStatusExit]):
1316
+ The exit information for the terminated or start-blocked containers
1317
+ of the workload.
1156
1318
  state (WorkloadStatusStateEnum):
1157
1319
  Current state of the workload.
1158
1320
 
@@ -1192,6 +1354,12 @@ class WorkloadStatus:
1192
1354
  """
1193
1355
  The operation for the loggable containers of the workload.
1194
1356
  """
1357
+ exits: list[WorkloadStatusExit] | None = field(default_factory=list)
1358
+ """
1359
+ The exit information for the terminated or start-blocked containers
1360
+ of the workload, one entry per container.
1361
+ Containers in neither state are not listed.
1362
+ """
1195
1363
  state: WorkloadStatusStateEnum = WorkloadStatusStateEnum.UNKNOWN
1196
1364
  """
1197
1365
  The current state of the workload.
@@ -1619,6 +1787,42 @@ class Deployer(ABC):
1619
1787
  )
1620
1788
  return ret
1621
1789
 
1790
+ def map_visible_devices_ordering(
1791
+ self,
1792
+ runtime_envs: list[str],
1793
+ ) -> dict[str, str]:
1794
+ """
1795
+ Return the device ordering environment variables
1796
+ for the given runtime visible devices env names.
1797
+
1798
+ Only meaningful for a container seeing every device of the host:
1799
+ it must number the devices as the detector, the driver and the vendor
1800
+ tooling do, otherwise an index computed from detection addresses
1801
+ another device inside the container.
1802
+ For example, CUDA's default ordering, CUDA_DEVICE_ORDER=FASTEST_FIRST,
1803
+ sorts the visible devices by a performance heuristic, which reshuffles
1804
+ the ordinals on a heterogeneous host,
1805
+ while PCI_BUS_ID sorts by PCI bus id, as NVML and the detector enumerate,
1806
+ see https://github.com/gpustack/gpustack/issues/6041.
1807
+ No other manufacturer documents such an ordering switch,
1808
+ so none of them contributes a variable.
1809
+
1810
+ Args:
1811
+ runtime_envs:
1812
+ The runtime visible devices environment variable names.
1813
+
1814
+ Returns:
1815
+ A dictionary mapping device ordering environment variable names
1816
+ to corresponding values.
1817
+
1818
+ """
1819
+ if any(
1820
+ self.get_manufacturer(runtime_env) == ManufacturerEnum.NVIDIA
1821
+ for runtime_env in runtime_envs
1822
+ ):
1823
+ return {"CUDA_DEVICE_ORDER": "PCI_BUS_ID"}
1824
+ return {}
1825
+
1622
1826
  def map_visible_devices_affinities(
1623
1827
  self,
1624
1828
  runtime_envs: list[str],
@@ -112,9 +112,17 @@ class AscendGenerator(Generator):
112
112
 
113
113
  container_device_nodes = []
114
114
 
115
- cdn_path = f"/dev/davinci{dev.index}"
116
- if dev.appendix.get("vgpu", False):
117
- cdn_path = f"/dev/vdavinci{dev.index}"
115
+ # The device node is numbered by the driver's physical id, which
116
+ # Device.index no longer carries: it is the detector's enumeration
117
+ # index, i.e. the DCMI logic id here. The two are different
118
+ # numbers, so a device without a physical id is skipped instead of
119
+ # addressed by the index, which would resolve to another NPU's
120
+ # node. The detector already drops such a device; this guards the
121
+ # devices a caller passes in.
122
+ cdn_number = dev.appendix.get("physical_id")
123
+ if cdn_number is None:
124
+ continue
125
+ cdn_path = f"/dev/davinci{cdn_number}"
118
126
  cdn = device_to_cdi_device_node(
119
127
  path=cdn_path,
120
128
  )
@@ -81,8 +81,12 @@ class IluvatarGenerator(Generator):
81
81
 
82
82
  container_device_nodes = []
83
83
 
84
+ # The device node is numbered by the driver's minor number, which
85
+ # Device.index no longer carries: it is the detector's enumeration
86
+ # index. Fall back to it for a device whose minor number could not
87
+ # be read.
84
88
  cdn = device_to_cdi_device_node(
85
- path=f"/dev/iluvatar{dev.index}",
89
+ path=f"/dev/iluvatar{dev.appendix.get('minor_number', dev.index)}",
86
90
  )
87
91
  if not cdn:
88
92
  continue
@@ -82,11 +82,29 @@ class THeadGenerator(Generator):
82
82
 
83
83
  container_device_nodes = []
84
84
 
85
+ # Named after the card ordinal, i.e. the enumeration index, not
86
+ # after the driver's minor number that the detector records in
87
+ # appendix["minor_number"]. Unlike /dev/nvidia{N} and
88
+ # /dev/iluvatar{N}, which the minor number does name, the operator
89
+ # records T-Head's purely to PROVE this node addresses the card it
90
+ # describes, by comparing it against the node's character-device
91
+ # minor.
85
92
  cdn = device_to_cdi_device_node(
86
93
  path=f"/dev/alixpu_ppu{dev.index}",
87
94
  )
88
95
  if not cdn:
89
96
  continue
97
+
98
+ # So make that comparison rather than assume it: where the detector
99
+ # read a minor, the node this ordinal names must carry it. The two
100
+ # numbers are independent -- neither is computed from the other, at
101
+ # any offset or none -- so a mismatch means this ordinal addresses a
102
+ # neighbouring accelerator, which the operator's allocator refuses
103
+ # outright rather than hand over.
104
+ dev_minor_number = dev.appendix.get("minor_number")
105
+ if dev_minor_number is not None and cdn.minor != dev_minor_number:
106
+ continue
107
+
90
108
  all_device_nodes.append(cdn)
91
109
  container_device_nodes.append(cdn)
92
110
 
@@ -45,6 +45,7 @@ from .__types__ import (
45
45
  WorkloadStatus,
46
46
  WorkloadStatusOperation,
47
47
  WorkloadStatusStateEnum,
48
+ parse_container_exit,
48
49
  )
49
50
  from .__utils__ import (
50
51
  _MiB,
@@ -279,6 +280,12 @@ class DockerWorkloadStatus(WorkloadStatus):
279
280
  self.executable.append(op)
280
281
  self.loggable.append(op)
281
282
 
283
+ exit_ = parse_container_exit(c, _LABEL_COMPONENT_NAME)
284
+ if exit_ is not None:
285
+ self.exits.append(exit_)
286
+ if exit_.reason and not self.state_message:
287
+ self.state_message = exit_.message or exit_.reason
288
+
282
289
  self.state = self.parse_state(d_containers)
283
290
 
284
291
 
@@ -1111,6 +1118,19 @@ class DockerDeployer(EndoscopicDeployer):
1111
1118
  )
1112
1119
  create_options["environment"].update(b_vs)
1113
1120
 
1121
+ # If requesting all devices or privileged,
1122
+ # the container sees every device of the host,
1123
+ # so pin the device ordering to keep its numbering
1124
+ # aligned with the detection.
1125
+ if r_v == "all" or privileged:
1126
+ o_vs = self.map_visible_devices_ordering(runtime_envs)
1127
+ # Take the ordering as default,
1128
+ # never overwrite the one declared by the container.
1129
+ create_options["environment"] = {
1130
+ **o_vs,
1131
+ **create_options["environment"],
1132
+ }
1133
+
1114
1134
  # Configure affinity if applicable.
1115
1135
  create_options.update(
1116
1136
  self.map_visible_devices_affinities(