gpustack-runtime 0.2.5.post4__tar.gz → 0.2.7__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 (162) hide show
  1. gpustack_runtime-0.2.7/.agents/skills/runtime-release/SKILL.md +100 -0
  2. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.gitignore +4 -1
  3. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/AGENTS.md +2 -0
  4. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/PKG-INFO +2 -2
  5. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/_version.py +2 -2
  6. gpustack_runtime-0.2.7/gpustack_runtime/_version_appendix.py +1 -0
  7. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__types__.py +294 -1
  8. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/amd.py +13 -19
  9. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/ascend.py +157 -34
  10. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/cambricon.py +19 -3
  11. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/iluvatar.py +7 -5
  12. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/nvidia.py +14 -9
  13. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pydcmi/__init__.py +11 -0
  14. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/thead.py +8 -5
  15. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/envs.py +30 -6
  16. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pyproject.toml +7 -1
  17. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/specs/2026-08-14-detector-alignment-and-workload-exit-status.md +2 -2
  18. gpustack_runtime-0.2.7/specs/2026-10-30-detector-health-alignment-and-identity-cache.md +479 -0
  19. gpustack_runtime-0.2.7/tests/gpustack_runtime/detector/conftest.py +19 -0
  20. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_amd.py +53 -14
  21. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_ascend.py +312 -11
  22. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_cambricon.py +112 -4
  23. gpustack_runtime-0.2.7/tests/gpustack_runtime/detector/test_detector_cache.py +544 -0
  24. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_iluvatar.py +20 -8
  25. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_nvidia.py +119 -18
  26. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_pydmi.py +73 -1
  27. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_thead.py +59 -24
  28. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/uv.lock +87 -42
  29. gpustack_runtime-0.2.5.post4/gpustack_runtime/_version_appendix.py +0 -1
  30. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.codespelldict +0 -0
  31. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.codespellrc +0 -0
  32. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.dockerignore +0 -0
  33. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.gitattributes +0 -0
  34. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.pre-commit-config.yaml +0 -0
  35. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.python-version +0 -0
  36. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/ADOPTERS.md +0 -0
  37. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/DCO +0 -0
  38. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/LICENSE +0 -0
  39. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/Makefile +0 -0
  40. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/README.md +0 -0
  41. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/deploy/manifests/docker-compose.yaml +0 -0
  42. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/deploy/manifests/kubernetes.yaml +0 -0
  43. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/index.md +0 -0
  44. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.deployer.md +0 -0
  45. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.detector.md +0 -0
  46. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.md +0 -0
  47. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/__init__.py +0 -0
  48. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/__main__.py +0 -0
  49. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/_version.pyi +0 -0
  50. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/__init__.py +0 -0
  51. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/__types__.py +0 -0
  52. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/deployer.py +0 -0
  53. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/detector.py +0 -0
  54. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/images.py +0 -0
  55. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__init__.py +0 -0
  56. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__patches__.py +0 -0
  57. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__types__.py +0 -0
  58. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__utils__.py +0 -0
  59. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
  60. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
  61. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__utils__.py +0 -0
  62. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/amd.py +0 -0
  63. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/ascend.py +0 -0
  64. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
  65. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/iluvatar.py +0 -0
  66. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/metax.py +0 -0
  67. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/thead.py +0 -0
  68. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/docker.py +0 -0
  69. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
  70. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/kubernetes.py +0 -0
  71. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/podman.py +0 -0
  72. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__init__.py +0 -0
  73. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__utils__.py +0 -0
  74. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/hygon.py +0 -0
  75. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/metax.py +0 -0
  76. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/mthreads.py +0 -0
  77. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyamdgpu/__init__.py +0 -0
  78. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
  79. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pycndev/__init__.py +0 -0
  80. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pydmi/__init__.py +0 -0
  81. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
  82. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
  83. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
  84. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
  85. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
  86. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
  87. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
  88. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
  89. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
  90. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/logging.py +0 -0
  91. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/hatch.toml +0 -0
  92. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/mkdocs.yml +0 -0
  93. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pack/Dockerfile +0 -0
  94. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pack/Dockerfile.dummy +0 -0
  95. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pytest.ini +0 -0
  96. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/ruff.toml +0 -0
  97. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
  98. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
  99. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
  100. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
  101. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_jsons.json +0 -0
  102. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_yamls.yaml +0 -0
  103. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_json.json +0 -0
  104. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_yaml.yaml +0 -0
  105. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
  106. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_ascend_ranktable.py +0 -0
  107. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_docker_status.py +0 -0
  108. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_kubernetes_status.py +0 -0
  109. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_logs_options.py +0 -0
  110. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_podman_status.py +0 -0
  111. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_privileged.py +0 -0
  112. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_resource_injection_policy.py +0 -0
  113. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_runtime_class.py +0 -0
  114. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_termination_grace_period.py +0 -0
  115. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
  116. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_visible_devices_ordering.py +0 -0
  117. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_workload_resource_injection_policy.py +0 -0
  118. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_workload_status.py +0 -0
  119. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
  120. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/README.md +0 -0
  121. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -0
  122. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -0
  123. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -0
  124. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -0
  125. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +0 -0
  126. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -0
  127. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -0
  128. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +0 -0
  129. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +0 -0
  130. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +0 -0
  131. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +0 -0
  132. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +0 -0
  133. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json +0 -0
  134. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +0 -0
  135. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +0 -0
  136. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -0
  137. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
  138. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
  139. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +0 -0
  140. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
  141. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
  142. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
  143. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
  144. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
  145. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
  146. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
  147. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
  148. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
  149. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json +0 -0
  150. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
  151. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
  152. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -0
  153. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_cli.py +0 -0
  154. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_types.py +0 -0
  155. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
  156. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_hygon.py +0 -0
  157. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_metax.py +0 -0
  158. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_mig_devices.py +0 -0
  159. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_mthreads.py +0 -0
  160. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_pycndev.py +0 -0
  161. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_samples.py +0 -0
  162. {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/uv.toml +0 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: runtime-release
3
+ description: Cut a gpustack-runtime package release end to end (select and push the tag, monitor the release workflow, curate the GitHub Release notes).
4
+ ---
5
+
6
+ # Runtime release
7
+
8
+ One maintainer duty: cut a package release. Push a version tag; `release.yml` builds and publishes
9
+ it. Then write the GitHub Release notes.
10
+
11
+ ## Cut a package release
12
+
13
+ The git tag is the single source of truth: hatch-vcs derives the package version from it at build
14
+ time, and there is no version file to bump. Pushing a `v*.*.*` tag runs
15
+ `.github/workflows/release.yml`: build the distributions, create the GitHub Release with the
16
+ wheel/sdist assets, publish to PyPI, then verify an isolated install. Branch pushes to `main` or
17
+ `v*-dev` run the same job against TestPyPI and create no release.
18
+
19
+ Two workflow facts shape the procedure:
20
+
21
+ - The GitHub Release is a pre-release only when the tag contains `rc`. A final tag goes public and
22
+ becomes `latest` immediately. There is no promotion gate.
23
+ - The workflow generates no baseline notes. The release body starts empty, so curating the notes is
24
+ not polish; it is the entire text the reader gets.
25
+
26
+ PyPI is the irreversible step: a version once published there cannot be re-uploaded or edited.
27
+
28
+ ### Hard rules
29
+
30
+ - Tag shapes: `vX.Y.Z` final, `vX.Y.ZrcN` pre-release, `vX.Y.Z.postN` post-release. PEP 440
31
+ canonical spellings only; history contains the non-dotted `v0.2.5postN` — do not repeat it.
32
+ - A tag whose PyPI publish succeeded is burned. NEVER delete or move it; fix forward with a new
33
+ version. Delete-and-recreate is limited to a tag that never reached PyPI and is interactive-only.
34
+ - An `rc` tag stays a pre-release; never mark it latest.
35
+ - Release from `origin/main` HEAD by default. A maintenance line (`v*-dev` branch) targets that
36
+ branch's HEAD; flag the choice in the summary.
37
+ - Interactive mode confirms every mutating step (tag push, note replacement). Auto mode runs
38
+ unattended through note curation and reports the release URL.
39
+
40
+ ### 1. Preflight (read-only)
41
+
42
+ ```sh
43
+ gh auth status
44
+ git fetch origin --tags --prune
45
+ git status --porcelain # must be empty
46
+ git tag -l 'v*' --sort=-v:refname | head -5
47
+ ```
48
+
49
+ ### 2. Version and commit (confirm)
50
+
51
+ - Pick the target ref: `origin/main` by default, or the `v*-dev` branch for a maintenance line. The
52
+ steps below call it `$TARGET`; `$SHA` is its HEAD (`git rev-parse "$TARGET"`).
53
+ - Read the range since the previous tag: `git log --no-merges --oneline <last>..$TARGET`.
54
+ - Propose the bump from the Conventional Commits in range: any `feat` → minor; fixes, docs, and
55
+ chores → patch. Use `.postN` only to re-issue the same version line (packaging metadata or a
56
+ docs-only rebuild of unchanged content).
57
+ - Confirm the (version, commit) pair before tagging. A final tag publishes immediately, so this
58
+ confirmation is the only gate.
59
+
60
+ ### 3. Draft notes
61
+
62
+ Group the range by type: Features / Fixes / Docs / Other; omit empty sections. Keep highlights and
63
+ fold noisy `chore`/`ci`/`test` entries. Link PRs from `(#NNN)` subjects. Do not open the body with
64
+ a heading that repeats the tag. End with the Full Changelog compare URL
65
+ (`https://github.com/gpustack/runtime/compare/<last>...<VER>`). Write the draft to `notes.md`;
66
+ step 5 publishes that file.
67
+
68
+ ### 4. Tag and monitor (confirm, then read-only)
69
+
70
+ ```sh
71
+ git tag -a "$VER" "$SHA" -m "Release $VER"
72
+ git push origin "$VER"
73
+ gh run list --branch "$VER" --limit 5 # the release.yml run for the tag
74
+ gh run watch <run-id> --exit-status # background it; wait for the completion notification
75
+ ```
76
+
77
+ The job's own verify step installs the package from PyPI in an isolated env, but it is unpinned and
78
+ only prints the installed version. A green run proves the upload succeeded, not that PyPI already
79
+ serves the new version; read the verify log's `version =` line and confirm it equals `$VER`. On
80
+ failure read `gh run view <run-id> --log-failed`; retry the same run on transient errors. If the
81
+ failure is real and PyPI already holds the version, cut the next version instead — never re-tag.
82
+
83
+ ### 5. Curate notes (confirm)
84
+
85
+ ```sh
86
+ gh release view "$VER" --json body,isPrerelease # the body starts empty
87
+ gh release edit "$VER" --notes-file notes.md
88
+ ```
89
+
90
+ A maintenance-line final keeps `--latest=false` when a newer stable tag exists. Verify the badge
91
+ with `gh api repos/gpustack/runtime/releases/latest --jq .tag_name`. An `rc` release stays a
92
+ pre-release and needs no further action.
93
+
94
+ ## Scope
95
+
96
+ Package releases and their notes run here. No other repository document changes with a release:
97
+ the version comes from the tag, and no README or docs table tracks it. Container image builds are
98
+ `make package` (`pack/`) and deployment manifests live in `deploy/`; both are outside this skill.
99
+ Docs-only changes never trigger a release: the release workflow ignores docs paths on branch
100
+ pushes, so docs updates need no tag.
@@ -3,7 +3,10 @@
3
3
  .vscode/
4
4
  .DS_Store
5
5
  *.swp
6
- .claude/
6
+ **/.claude/*
7
+ !/.claude/skills
8
+ /.claude/skills/*
9
+ !/.claude/skills/runtime-release
7
10
 
8
11
  # Python-generated files
9
12
  _version.py
@@ -59,6 +59,8 @@ Setup is `make install` (uv sync plus pre-commit hooks). Day-to-day targets:
59
59
  `gpustack_runtime/_version.py` is generated by hatch-vcs and `_version_appendix.py` by
60
60
  `make prepare`; never hand-edit either.
61
61
 
62
+ Cut package releases with the [runtime-release](.agents/skills/runtime-release/SKILL.md) skill.
63
+
62
64
  ### Python conventions
63
65
 
64
66
  - Prefer clarity over cleverness to simplify long-term maintenance.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: gpustack-runtime
3
- Version: 0.2.5.post4
3
+ Version: 0.2.7
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.29
19
+ Requires-Dist: gpustack-runner>=0.1.32.post2
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.5.post4'
31
- __version_tuple__ = version_tuple = (0, 2, 5, 'post4')
30
+ __version__ = version = '0.2.7'
31
+ __version_tuple__ = version_tuple = (0, 2, 7)
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 = "489860c"
@@ -1,6 +1,9 @@
1
1
  from __future__ import annotations as __future_annotations__
2
2
 
3
+ import copy
3
4
  import logging
5
+ import threading
6
+ import time
4
7
  from abc import ABC, abstractmethod
5
8
  from dataclasses import dataclass
6
9
  from enum import Enum
@@ -9,10 +12,17 @@ from typing import Any
9
12
 
10
13
  from dataclasses_json import dataclass_json
11
14
 
15
+ from .. import envs
12
16
  from ..logging import debug_log_exception, debug_log_warning
13
17
 
14
18
  logger = logging.getLogger(__name__)
15
19
 
20
+ _monotonic = time.monotonic
21
+ """
22
+ The clock the identity cache's time to live is measured on. A module level
23
+ indirection, so tests can inject a clock of their own.
24
+ """
25
+
16
26
 
17
27
  class ManufacturerEnum(str, Enum):
18
28
  """
@@ -606,6 +616,285 @@ def _usage_fields_to_merge(usage: Device) -> tuple[str, ...]:
606
616
  return tuple(field for field in _DEVICE_USAGE_FIELDS if field != "memory_status")
607
617
 
608
618
 
619
+ def _clear_health_verdict(devices: Devices) -> None:
620
+ """
621
+ Drop the health verdict of the given devices, in place.
622
+
623
+ A verdict read when the devices were detected says nothing about them
624
+ now, and the refresh that could have confirmed it failed. So the devices
625
+ are served as inventory, with no verdict, rather than with one that went
626
+ stale.
627
+
628
+ Args:
629
+ devices:
630
+ The devices to drop the verdict of.
631
+
632
+ """
633
+ for dev in devices:
634
+ dev.memory_status = DeviceMemoryStatusEnum.UNKNOWN
635
+ # A MIG device is an entry of the card's appendix, and carries its own
636
+ # verdict, which the CLI reports as well.
637
+ for mig_dev in (dev.appendix or {}).get("mig_devices") or []:
638
+ mig_dev["memory_status"] = DeviceMemoryStatusEnum.UNKNOWN
639
+
640
+
641
+ @dataclass
642
+ class _IdentityCacheEntry:
643
+ """
644
+ A cached device identity and the moment it expires.
645
+ """
646
+
647
+ devices: Devices
648
+ """
649
+ The detected identity, handed out as a copy.
650
+ """
651
+
652
+ expires_at: float
653
+ """
654
+ The clock reading after which the identity is stale.
655
+ """
656
+
657
+
658
+ class _RefreshFlight:
659
+ """
660
+ One identity refresh of a manufacturer.
661
+
662
+ A flight carries its own completion, so a caller that joined it takes
663
+ that flight's outcome, whatever a later flight does.
664
+ """
665
+
666
+ def __init__(self, stale: Devices | None = None):
667
+ self.done = threading.Event()
668
+ self.devices: Devices | None = None
669
+ self.error: BaseException | None = None
670
+ self.stale = stale
671
+ """
672
+ The last good identity when the flight started: the fallback a failed
673
+ flight owes its callers.
674
+ """
675
+
676
+ def complete(self, devices: Devices | None, error: BaseException | None) -> None:
677
+ """
678
+ Complete the flight with the devices it detected or the error it
679
+ raised.
680
+
681
+ Args:
682
+ devices:
683
+ The devices the flight detected, empty when it detected none.
684
+ error:
685
+ The exception the flight raised, None when it succeeded.
686
+
687
+ """
688
+ self.devices = devices
689
+ self.error = error
690
+ self.done.set()
691
+
692
+ def outcome(self) -> Devices | None:
693
+ """
694
+ Return the devices the flight detected, or the last good identity
695
+ when the flight failed.
696
+
697
+ The last good identity is served without its health verdict: the
698
+ verdict was read when the identity was detected, and the refresh that
699
+ could have confirmed it failed.
700
+
701
+ Returns:
702
+ The devices to serve, as a copy.
703
+
704
+ Raises:
705
+ The exception the flight raised, when no identity is cached to
706
+ fall back on.
707
+
708
+ """
709
+ if self.error is not None:
710
+ if self.stale is None:
711
+ raise self.error
712
+ # The last good identity is better than nothing, and the next call
713
+ # retries the detection.
714
+ devices = copy.deepcopy(self.stale)
715
+ _clear_health_verdict(devices)
716
+ return devices
717
+ return copy.deepcopy(self.devices)
718
+
719
+
720
+ class _VendorIdentityState:
721
+ """
722
+ The identity cache state of one manufacturer.
723
+
724
+ The condition guards the entry and the live flight. A refresh runs
725
+ outside the lock, so a wedged vendor delays only the callers waiting for
726
+ that vendor.
727
+ """
728
+
729
+ def __init__(self):
730
+ self.condition = threading.Condition()
731
+ self.entry: _IdentityCacheEntry | None = None
732
+ self.flight: _RefreshFlight | None = None
733
+ """
734
+ The refresh running now, if any. A caller that finds one joins it
735
+ rather than starting a second detection of the same vendor.
736
+ """
737
+
738
+
739
+ class _IdentityCache:
740
+ """
741
+ The process wide cache of detected device identities.
742
+
743
+ One state per manufacturer, so a slow refresh of one vendor delays only
744
+ the callers waiting for that vendor.
745
+ """
746
+
747
+ def __init__(self):
748
+ self._lock = threading.Lock()
749
+ self._states: dict[ManufacturerEnum, _VendorIdentityState] = {}
750
+
751
+ def state(self, manufacturer: ManufacturerEnum) -> _VendorIdentityState:
752
+ """
753
+ Return the cache state of the given manufacturer.
754
+
755
+ Args:
756
+ manufacturer:
757
+ The manufacturer to take the state of.
758
+
759
+ Returns:
760
+ The state of the manufacturer.
761
+
762
+ """
763
+ with self._lock:
764
+ state = self._states.get(manufacturer)
765
+ if state is None:
766
+ state = _VendorIdentityState()
767
+ self._states[manufacturer] = state
768
+ return state
769
+
770
+ def reset(self) -> None:
771
+ """
772
+ Drop every cached identity.
773
+ """
774
+ with self._lock:
775
+ self._states.clear()
776
+
777
+ def identity(self, detector: Detector, ttl: float) -> Devices | None:
778
+ """
779
+ Return the identity of the given detector, from the cache when it is
780
+ still fresh.
781
+
782
+ The identity is handed out as a copy: the usage merge writes into the
783
+ devices it is given, so a shared object would let one caller
784
+ contaminate the cache.
785
+
786
+ A fresh identity is served with the health verdict it was detected
787
+ with. A stale one is served without any verdict: the refresh that
788
+ could have confirmed it failed, so the last good identity stands for
789
+ the inventory only.
790
+
791
+ Args:
792
+ detector:
793
+ The detector to take the identity of.
794
+ ttl:
795
+ The time to live of a cached identity, in seconds.
796
+ Zero disables the cache.
797
+
798
+ Returns:
799
+ The devices carrying the identity, or None if there is none.
800
+
801
+ Raises:
802
+ The exception the detection raised, when the cache holds no
803
+ identity to fall back on. An interruption propagates to the caller
804
+ that asked for the detection, and the waiters of the same refresh
805
+ get its outcome.
806
+
807
+ """
808
+ if ttl <= 0:
809
+ # The cache is disabled: the identity is detected on every call.
810
+ return detector.detect_info()
811
+
812
+ state = self.state(detector.manufacturer)
813
+ with state.condition:
814
+ entry = state.entry
815
+ if entry is not None and _monotonic() < entry.expires_at:
816
+ return copy.deepcopy(entry.devices)
817
+ flight = state.flight
818
+ if flight is not None:
819
+ # A refresh is running: join it, so the callers of one vendor
820
+ # share one detection. The flight carries its own completion,
821
+ # so a later flight cannot postpone this caller's outcome.
822
+ state.condition.wait_for(flight.done.is_set)
823
+ return flight.outcome()
824
+ # No refresh is running: start one. The last good identity is the
825
+ # fallback the flight owes its callers when it fails.
826
+ flight = _RefreshFlight(
827
+ stale=entry.devices if entry is not None else None,
828
+ )
829
+ state.flight = flight
830
+
831
+ # The detection runs outside the lock: a wedged vendor must not hold
832
+ # the cache for the other vendors.
833
+ devices: Devices | None = None
834
+ error: BaseException | None = None
835
+ try:
836
+ devices = detector.detect_info()
837
+ except Exception as e:
838
+ debug_log_exception(
839
+ logger,
840
+ "Failed to detect %s devices identity, keeping the last known one",
841
+ detector.manufacturer,
842
+ )
843
+ error = e
844
+ except BaseException as e:
845
+ # An interruption settles the flight as well: a flight left
846
+ # pending blocks every later caller of the vendor forever. The
847
+ # interruption keeps propagating to the thread that asked for the
848
+ # detection, while the waiters get the flight's outcome.
849
+ debug_log_exception(
850
+ logger,
851
+ "Interrupted while detecting %s devices identity",
852
+ detector.manufacturer,
853
+ )
854
+ error = e
855
+ raise
856
+ finally:
857
+ with state.condition:
858
+ if error is None and devices:
859
+ # Only a non empty identity is cached: one enumeration glitch
860
+ # must not pin an empty inventory for the time to live.
861
+ state.entry = _IdentityCacheEntry(
862
+ devices=devices,
863
+ expires_at=_monotonic() + ttl,
864
+ )
865
+ flight.complete(devices, error)
866
+ state.flight = None
867
+ state.condition.notify_all()
868
+ return flight.outcome()
869
+
870
+
871
+ _IDENTITY_CACHE = _IdentityCache()
872
+ """
873
+ The process wide cache of detected device identities.
874
+ """
875
+
876
+
877
+ def _identity_cache_ttl() -> float:
878
+ """
879
+ Return the time to live of a cached device identity, in seconds.
880
+
881
+ Zero disables the cache. The environment variable is read once per
882
+ process, so it must be set before the first detection.
883
+
884
+ Returns:
885
+ The time to live, in seconds.
886
+
887
+ """
888
+ return float(envs.GPUSTACK_RUNTIME_DETECT_INFO_CACHE_TTL)
889
+
890
+
891
+ def _reset_identity_cache() -> None:
892
+ """
893
+ Drop every cached device identity.
894
+ """
895
+ _IDENTITY_CACHE.reset()
896
+
897
+
609
898
  class Detector(ABC):
610
899
  """
611
900
  Base class for all detectors.
@@ -677,6 +966,10 @@ class Detector(ABC):
677
966
  """
678
967
  Detect devices and return a list of Device objects.
679
968
 
969
+ The identity of the devices is cached process wide, per manufacturer,
970
+ so a repeated collection pays only the usage query. The usage is read
971
+ on every call, and so is the health it carries.
972
+
680
973
  Args:
681
974
  usage:
682
975
  Whether to fetch the devices' usage as well.
@@ -685,7 +978,7 @@ class Detector(ABC):
685
978
  A list of detected Device objects, or None if detection fails.
686
979
 
687
980
  """
688
- devices = self.detect_info()
981
+ devices = _IDENTITY_CACHE.identity(self, _identity_cache_ttl())
689
982
  if usage and devices:
690
983
  try:
691
984
  self.detect_usage(devices)
@@ -502,6 +502,9 @@ def _get_memory_status(dev, dev_idx: int) -> DeviceMemoryStatusEnum:
502
502
  Both queries produce it, mirroring the operator, which reports `Unhealthy`
503
503
  from `DetectAccelerator` and `MonitorAccelerator` alike.
504
504
 
505
+ A failed read yields no verdict, as the operator's counter read does: only
506
+ a readable value may condemn a card.
507
+
505
508
  Args:
506
509
  dev:
507
510
  The AMD SMI device handle.
@@ -521,14 +524,13 @@ def _get_memory_status(dev, dev_idx: int) -> DeviceMemoryStatusEnum:
521
524
  pyamdsmi.AmdSmiGpuBlock.UMC,
522
525
  )
523
526
  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.
527
+ # Fail open: a genuine driver error yields no verdict, as the
528
+ # operator's counter read does. The stub binding raises a codeless
529
+ # error when amdsmi is absent, in which case ROCm SMI is the
530
+ # fallback, as it is for the memory read.
528
531
  err_code = getattr(e, "err_code", None)
529
532
  if err_code is not None:
530
- if err_code != getattr(pyamdsmi, "AMDSMI_STATUS_NOT_SUPPORTED", None):
531
- return DeviceMemoryStatusEnum.UNHEALTHY
533
+ debug_log_warning(logger, "Failed to read the UMC ECC error count")
532
534
  return DeviceMemoryStatusEnum.HEALTHY
533
535
  else:
534
536
  if dev_ecc_count.get("uncorrectable_count", 0) > 0:
@@ -539,19 +541,11 @@ def _get_memory_status(dev, dev_idx: int) -> DeviceMemoryStatusEnum:
539
541
  dev_ecc_count = pyrocmsmi.rsmi_dev_ecc_count_get(dev_idx)
540
542
  if dev_ecc_count.uncorrectable_err > 0:
541
543
  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
544
+ except pyrocmsmi.ROCMSMIError:
545
+ # Fail open: a terminal error inside the fallback yields no verdict
546
+ # either, so the card stays unjudged.
547
+ debug_log_warning(logger, "Failed to read the ROCm SMI ECC error count")
548
+ return DeviceMemoryStatusEnum.HEALTHY
555
549
 
556
550
  return DeviceMemoryStatusEnum.HEALTHY
557
551