gpustack-runtime 0.2.2.post6__tar.gz → 0.2.2.post8__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 (135) hide show
  1. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/PKG-INFO +1 -1
  2. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/deploy/manifests/kubernetes.yaml +13 -0
  3. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/_version.py +2 -2
  4. gpustack_runtime-0.2.2.post8/gpustack_runtime/_version_appendix.py +1 -0
  5. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/kuberentes.py +147 -1
  6. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/envs.py +22 -0
  7. gpustack_runtime-0.2.2.post8/tests/gpustack_runtime/deployer/test_privileged.py +137 -0
  8. gpustack_runtime-0.2.2.post8/tests/gpustack_runtime/deployer/test_runtime_class.py +183 -0
  9. gpustack_runtime-0.2.2.post6/gpustack_runtime/_version_appendix.py +0 -1
  10. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.codespelldict +0 -0
  11. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.codespellrc +0 -0
  12. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.dockerignore +0 -0
  13. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.gitattributes +0 -0
  14. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.gitignore +0 -0
  15. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.pre-commit-config.yaml +0 -0
  16. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/.python-version +0 -0
  17. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/LICENSE +0 -0
  18. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/Makefile +0 -0
  19. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/README.md +0 -0
  20. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/deploy/manifests/docker-compose.yaml +0 -0
  21. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/docs/index.md +0 -0
  22. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/docs/modules/gpustack_runtime.deployer.md +0 -0
  23. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/docs/modules/gpustack_runtime.detector.md +0 -0
  24. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/docs/modules/gpustack_runtime.md +0 -0
  25. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/__init__.py +0 -0
  26. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/__main__.py +0 -0
  27. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/_version.pyi +0 -0
  28. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/cmds/__init__.py +0 -0
  29. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/cmds/__types__.py +0 -0
  30. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/cmds/deployer.py +0 -0
  31. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/cmds/detector.py +0 -0
  32. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/cmds/images.py +0 -0
  33. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/__init__.py +0 -0
  34. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/__patches__.py +0 -0
  35. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/__types__.py +0 -0
  36. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/__utils__.py +0 -0
  37. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
  38. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
  39. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/__utils__.py +0 -0
  40. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/amd.py +0 -0
  41. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/ascend.py +0 -0
  42. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
  43. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/iluvatar.py +0 -0
  44. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/metax.py +0 -0
  45. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/cdi/thead.py +0 -0
  46. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/docker.py +0 -0
  47. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
  48. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/deployer/podman.py +0 -0
  49. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/__init__.py +0 -0
  50. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/__types__.py +0 -0
  51. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/__utils__.py +0 -0
  52. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/amd.py +0 -0
  53. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/ascend.py +0 -0
  54. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/cambricon.py +0 -0
  55. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/hygon.py +0 -0
  56. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/iluvatar.py +0 -0
  57. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/metax.py +0 -0
  58. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/mthreads.py +0 -0
  59. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/nvidia.py +0 -0
  60. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyamdgpu/__init__.py +0 -0
  61. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
  62. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pydcmi/__init__.py +0 -0
  63. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
  64. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
  65. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
  66. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
  67. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
  68. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
  69. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
  70. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
  71. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
  72. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/detector/thead.py +0 -0
  73. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/gpustack_runtime/logging.py +0 -0
  74. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/hatch.toml +0 -0
  75. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/mkdocs.yml +0 -0
  76. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/pack/Dockerfile +0 -0
  77. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/pack/Dockerfile.dummy +0 -0
  78. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/pyproject.toml +0 -0
  79. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/pytest.ini +0 -0
  80. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/ruff.toml +0 -0
  81. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
  82. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
  83. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
  84. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
  85. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_jsons.json +0 -0
  86. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_yamls.yaml +0 -0
  87. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_json.json +0 -0
  88. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_yaml.yaml +0 -0
  89. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
  90. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
  91. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/deployer/test_workload_status.py +0 -0
  92. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
  93. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/README.md +0 -0
  94. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -0
  95. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -0
  96. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -0
  97. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -0
  98. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +0 -0
  99. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -0
  100. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -0
  101. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +0 -0
  102. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +0 -0
  103. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +0 -0
  104. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +0 -0
  105. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +0 -0
  106. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +0 -0
  107. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +0 -0
  108. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -0
  109. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
  110. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
  111. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +0 -0
  112. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
  113. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
  114. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
  115. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
  116. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
  117. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
  118. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
  119. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
  120. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
  121. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
  122. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
  123. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -0
  124. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_amd.py +0 -0
  125. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_ascend.py +0 -0
  126. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_cambricon.py +0 -0
  127. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
  128. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_hygon.py +0 -0
  129. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_iluvatar.py +0 -0
  130. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_metax.py +0 -0
  131. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_mig_devices.py +0 -0
  132. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_mthreads.py +0 -0
  133. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/tests/gpustack_runtime/detector/test_nvidia.py +0 -0
  134. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/uv.lock +0 -0
  135. {gpustack_runtime-0.2.2.post6 → gpustack_runtime-0.2.2.post8}/uv.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gpustack-runtime
3
- Version: 0.2.2.post6
3
+ Version: 0.2.2.post8
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,14 @@ rules:
25
25
  - "pods/exec"
26
26
  verbs:
27
27
  - "*"
28
+ # Allow resolving the vendor RuntimeClass (e.g. "ascend", "nvidia") for
29
+ # accelerated workload Pods, see GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS.
30
+ - apiGroups:
31
+ - "node.k8s.io"
32
+ resources:
33
+ - "runtimeclasses"
34
+ verbs:
35
+ - "get"
28
36
  ---
29
37
  apiVersion: rbac.authorization.k8s.io/v1
30
38
  kind: RoleBinding
@@ -76,6 +84,11 @@ spec:
76
84
  "app.kubernetes.io/name": "gpustack-runtime"
77
85
  spec:
78
86
  # Provide GPU resource request for gpustack-runtime if not using default runtime.
87
+ # The runtimeClassName set here is mirrored to every deployed workload Pod
88
+ # that does not resolve one itself. Usually this is unnecessary: workload
89
+ # Pods requesting device plugin resources automatically adopt the mapped
90
+ # vendor RuntimeClass when it exists in the cluster,
91
+ # see GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS.
79
92
  #
80
93
  # runtimeClassName: nvidia
81
94
 
@@ -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.post6'
31
- __version_tuple__ = version_tuple = (0, 2, 2, 'post6')
30
+ __version__ = version = '0.2.2.post8'
31
+ __version_tuple__ = version_tuple = (0, 2, 2, 'post8')
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 = "cad3f1b"
@@ -325,6 +325,151 @@ def _pin_pod_for_kueue(
325
325
  }
326
326
 
327
327
 
328
+ def _is_device_plugin_resource(resource_key: str) -> bool:
329
+ """
330
+ Report whether a resource key belongs to a device plugin resource family,
331
+ which is either a CDI kind ("nvidia.com/gpu") or one of its suffixed
332
+ variants ("nvidia.com/gpu.shared", "nvidia.com/gpu.sliced.units",
333
+ "nvidia.com/gpu.partitioned.mig-1g.20gb").
334
+ """
335
+ return any(
336
+ resource_key == cdi or resource_key.startswith(f"{cdi}.")
337
+ for cdi in envs.GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_CDI.values()
338
+ )
339
+
340
+
341
+ def _match_runtime_class(resource_key: str) -> str:
342
+ """
343
+ Return the RuntimeClass name mapped to the device plugin resource family
344
+ of the given resource key, which matches either a mapped base key
345
+ ("huawei.com/npu") or one of its suffixed variants
346
+ ("huawei.com/npu.sliced", "huawei.com/npu.sliced.units").
347
+ Return an empty string if the key belongs to no mapped family.
348
+ """
349
+ for base_key, runtime_class_name in (
350
+ envs.GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS or {}
351
+ ).items():
352
+ if resource_key == base_key or resource_key.startswith(f"{base_key}."):
353
+ return runtime_class_name
354
+ return ""
355
+
356
+
357
+ def _resolve_runtime_class_name(
358
+ pod: kubernetes.client.V1Pod,
359
+ client: kubernetes.client.ApiClient,
360
+ ):
361
+ """
362
+ Resolve the RuntimeClass for the Pod from the device plugin resources
363
+ requested by its containers, and set `pod.spec.runtime_class_name`
364
+ when the mapped RuntimeClass object exists in the cluster.
365
+
366
+ A Pod that already names a runtime class is left untouched, and the
367
+ mirrored deployment fill stays the fallback for Pods without any
368
+ mapped device request, so the precedence is:
369
+ explicit > discovered > mirrored.
370
+ """
371
+ if pod.spec.runtime_class_name:
372
+ return
373
+
374
+ # Find the RuntimeClass mapped to the requested device resources,
375
+ # keeping the first match on multi-vendor conflicts.
376
+ runtime_class_name = ""
377
+ for container in (pod.spec.containers or []) + (pod.spec.init_containers or []):
378
+ resources = container.resources
379
+ if not resources or not resources.requests:
380
+ continue
381
+ for r_k in resources.requests:
382
+ matched = _match_runtime_class(r_k)
383
+ if not matched:
384
+ continue
385
+ if runtime_class_name and runtime_class_name != matched:
386
+ logger.warning(
387
+ "Container '%s' requests resource '%s' mapped to RuntimeClass '%s', "
388
+ "but keeping the first matched RuntimeClass '%s'",
389
+ container.name,
390
+ r_k,
391
+ matched,
392
+ runtime_class_name,
393
+ )
394
+ continue
395
+ runtime_class_name = matched
396
+ if not runtime_class_name:
397
+ return
398
+
399
+ # Set the RuntimeClass only if the object exists in the cluster,
400
+ # as naming a missing class makes the kubelet reject the Pod.
401
+ node_api = kubernetes.client.NodeV1Api(client)
402
+ try:
403
+ node_api.read_runtime_class(name=runtime_class_name)
404
+ except kubernetes.client.exceptions.ApiException as e:
405
+ if e.status == 403:
406
+ logger.warning(
407
+ "Workload requests device resources mapped to RuntimeClass '%s', "
408
+ "but the deployer has no permission to read RuntimeClasses, "
409
+ "leaving the Pod runtimeClassName empty",
410
+ runtime_class_name,
411
+ )
412
+ return
413
+ if e.status == 404:
414
+ logger.warning(
415
+ "Workload requests device resources mapped to RuntimeClass '%s', "
416
+ "but that RuntimeClass is not available in the cluster, "
417
+ "leaving the Pod runtimeClassName empty",
418
+ runtime_class_name,
419
+ )
420
+ return
421
+ raise
422
+
423
+ logger.info(
424
+ "Setting Pod runtimeClassName to '%s', "
425
+ "resolved from the requested device resources",
426
+ runtime_class_name,
427
+ )
428
+ pod.spec.runtime_class_name = runtime_class_name
429
+
430
+
431
+ def _resolve_privileged(container: Container) -> bool:
432
+ """
433
+ Resolve whether a container runs privileged.
434
+
435
+ Privilege is dropped when the container's devices are handed out by a
436
+ device plugin, which is the case for every device plugin resource family
437
+ and, under the KDP injection policy, for every mapped device request.
438
+
439
+ A privileged container receives all device nodes of the host, so it
440
+ enumerates -- and can use -- every accelerator on the node, no matter
441
+ which one the device plugin allocated to it. That silently undoes
442
+ slicing: a workload holding a single MIG device or a single memory slice
443
+ still sees the untouched cards next to it, and a soft-slicing limit
444
+ lands on whichever device comes first instead of the allocated one.
445
+ """
446
+ if not container.execution or not container.execution.privileged:
447
+ return False
448
+ if not container.resources:
449
+ return True
450
+
451
+ kdp = get_resource_injection_policy() == "kdp"
452
+ for r_k in container.resources:
453
+ if r_k in ("cpu", "memory"):
454
+ continue
455
+ if _is_device_plugin_resource(r_k) or (
456
+ kdp
457
+ and (
458
+ r_k
459
+ in envs.GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_VISIBLE_DEVICES
460
+ or r_k == envs.GPUSTACK_RUNTIME_DEPLOY_AUTOMAP_RESOURCE_KEY
461
+ )
462
+ ):
463
+ clogger.info(
464
+ "Dropping privilege of container '%s', "
465
+ "as its device request '%s' is allocated by a device plugin",
466
+ container.name,
467
+ r_k,
468
+ )
469
+ return False
470
+ return True
471
+
472
+
328
473
  class KubernetesDeployer(EndoscopicDeployer):
329
474
  """
330
475
  Deployer implementation for Kubernetes.
@@ -1048,7 +1193,7 @@ class KubernetesDeployer(EndoscopicDeployer):
1048
1193
  run_as_user=c.execution.run_as_user,
1049
1194
  run_as_group=c.execution.run_as_group,
1050
1195
  read_only_root_filesystem=c.execution.readonly_rootfs,
1051
- privileged=c.execution.privileged,
1196
+ privileged=_resolve_privileged(c),
1052
1197
  capabilities=(
1053
1198
  kubernetes.client.V1Capabilities(
1054
1199
  add=c.execution.capabilities.add,
@@ -1242,6 +1387,7 @@ class KubernetesDeployer(EndoscopicDeployer):
1242
1387
  _apply_instance_type_admission(pod, workload.instance_type)
1243
1388
  _pin_pod_for_kueue(pod, self._node_name)
1244
1389
 
1390
+ _resolve_runtime_class_name(pod, self._client)
1245
1391
  pod = self._mutate_create_pod(pod)
1246
1392
  if envs.GPUSTACK_RUNTIME_DEPLOY_PRINT_CONVERSION:
1247
1393
  clogger.info(
@@ -185,6 +185,17 @@ if TYPE_CHECKING:
185
185
  e.g., `{"nvidia.com/devices": ["CUDA_VISIBLE_DEVICES"], "amd.com/devices": ["ROCR_VISIBLE_DEVICES"]}`.
186
186
  The key is the resource key, and the value is a list of environment variable names.
187
187
  """
188
+ GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS: dict[str, str] | None = None
189
+ """
190
+ Manual mapping of Kubernetes RuntimeClass names,
191
+ which is used to tell the Kubernetes Deployer which RuntimeClass to run the workload Pod with,
192
+ e.g., `{"nvidia.com/gpu": "nvidia", "huawei.com/npu": "ascend"}`.
193
+ The key is the device plugin base resource key, and the value is the RuntimeClass name.
194
+ During deployment, if any container requests a resource of a mapped family
195
+ (the base key or one of its suffixed variants, e.g., "huawei.com/npu.sliced"),
196
+ and a RuntimeClass object with the mapped name exists in the cluster,
197
+ the workload Pod carries that RuntimeClass name.
198
+ """
188
199
  GPUSTACK_RUNTIME_DEPLOY_RUNTIME_VISIBLE_DEVICES_VALUE_UUID: set[str] | None = None
189
200
  """
190
201
  Apply UUIDs for the given runtime visible devices environment variables.
@@ -538,6 +549,17 @@ variables: dict[str, Callable[[], Any]] = {
538
549
  "alibabacloud.com/devices=alibabacloud.com/ppu;",
539
550
  ),
540
551
  ),
552
+ "GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS": lambda: to_dict(
553
+ getenv(
554
+ "GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS",
555
+ "amd.com/gpu=amd;"
556
+ "huawei.com/npu=ascend;"
557
+ "cambricon.com/mlu=cambricon;"
558
+ "iluvatar.com/gpu=iluvatar;"
559
+ "mthreads.com/gpu=mthreads;"
560
+ "nvidia.com/gpu=nvidia;",
561
+ ),
562
+ ),
541
563
  "GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_VISIBLE_DEVICES": lambda: to_dict(
542
564
  getenv(
543
565
  "GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_VISIBLE_DEVICES",
@@ -0,0 +1,137 @@
1
+ import pytest
2
+
3
+ from gpustack_runtime.deployer.__types__ import (
4
+ Container,
5
+ ContainerExecution,
6
+ ContainerResources,
7
+ )
8
+ from gpustack_runtime.deployer.kuberentes import _resolve_privileged
9
+
10
+
11
+ def _container(privileged: bool | None, resources: dict | None = None) -> Container:
12
+ container_resources = None
13
+ if resources is not None:
14
+ container_resources = ContainerResources()
15
+ container_resources.update(resources)
16
+ return Container(
17
+ name="default",
18
+ image="gpustack/runner:latest",
19
+ execution=(
20
+ ContainerExecution(privileged=privileged)
21
+ if privileged is not None
22
+ else None
23
+ ),
24
+ resources=container_resources,
25
+ )
26
+
27
+
28
+ @pytest.mark.parametrize(
29
+ "name, privileged, resources, policy, expected",
30
+ [
31
+ (
32
+ "no execution",
33
+ None,
34
+ None,
35
+ "env",
36
+ False,
37
+ ),
38
+ (
39
+ "not requested",
40
+ False,
41
+ {"nvidia.com/devices": "0"},
42
+ "env",
43
+ False,
44
+ ),
45
+ (
46
+ "no resources",
47
+ True,
48
+ None,
49
+ "env",
50
+ True,
51
+ ),
52
+ (
53
+ "non-device resources only",
54
+ True,
55
+ {"cpu": "2", "memory": "4Gi"},
56
+ "env",
57
+ True,
58
+ ),
59
+ (
60
+ "specific whole cards, env injection",
61
+ True,
62
+ {"cpu": "2", "nvidia.com/devices": "0,1"},
63
+ "env",
64
+ True,
65
+ ),
66
+ (
67
+ "all devices, env injection",
68
+ True,
69
+ {"nvidia.com/devices": "all"},
70
+ "env",
71
+ True,
72
+ ),
73
+ (
74
+ "specific whole cards, kdp injection",
75
+ True,
76
+ {"nvidia.com/devices": "0,1"},
77
+ "kdp",
78
+ False,
79
+ ),
80
+ (
81
+ "auto-mapped devices, kdp injection",
82
+ True,
83
+ {"gpustack.ai/devices": "0"},
84
+ "kdp",
85
+ False,
86
+ ),
87
+ (
88
+ "exclusive whole card",
89
+ True,
90
+ {"nvidia.com/gpu": "1"},
91
+ "env",
92
+ False,
93
+ ),
94
+ (
95
+ "soft slice",
96
+ True,
97
+ {
98
+ "nvidia.com/gpu.sliced": "1",
99
+ "nvidia.com/gpu.sliced.memory-percentage": "50",
100
+ "nvidia.com/gpu.sliced.cores-percentage": "50",
101
+ },
102
+ "env",
103
+ False,
104
+ ),
105
+ (
106
+ "hard partition",
107
+ True,
108
+ {
109
+ "nvidia.com/gpu.partitioned": "1",
110
+ "nvidia.com/gpu.partitioned.mig-1g.20gb": "1",
111
+ },
112
+ "env",
113
+ False,
114
+ ),
115
+ (
116
+ "non-NVIDIA soft slice",
117
+ True,
118
+ {"amd.com/gpu.sliced": "1"},
119
+ "env",
120
+ False,
121
+ ),
122
+ (
123
+ "resource key merely prefixed by a CDI kind",
124
+ True,
125
+ {"nvidia.com/gpu-alike": "1"},
126
+ "env",
127
+ True,
128
+ ),
129
+ ],
130
+ )
131
+ def test_resolve_privileged(name, privileged, resources, policy, expected, monkeypatch):
132
+ monkeypatch.setattr(
133
+ "gpustack_runtime.deployer.kuberentes.get_resource_injection_policy",
134
+ lambda: policy,
135
+ )
136
+ actual = _resolve_privileged(_container(privileged, resources))
137
+ assert actual == expected, f"case {name} expected {expected}, but got {actual}"
@@ -0,0 +1,183 @@
1
+ import logging
2
+
3
+ import kubernetes
4
+ import pytest
5
+
6
+ from gpustack_runtime import envs
7
+ from gpustack_runtime.deployer.kuberentes import (
8
+ _match_runtime_class,
9
+ _resolve_runtime_class_name,
10
+ )
11
+
12
+
13
+ @pytest.mark.parametrize(
14
+ "name, resource_key, expected",
15
+ [
16
+ ("base key", "huawei.com/npu", "ascend"),
17
+ ("sliced variant", "huawei.com/npu.sliced", "ascend"),
18
+ ("sliced units variant", "huawei.com/npu.sliced.units", "ascend"),
19
+ ("shared variant", "nvidia.com/gpu.shared", "nvidia"),
20
+ (
21
+ "partitioned variant",
22
+ "nvidia.com/gpu.partitioned.mig-1g.20gb",
23
+ "nvidia",
24
+ ),
25
+ ("unmapped base key", "hygon.com/dcu", ""),
26
+ ("unrelated resource", "cpu", ""),
27
+ ("gpustack automap key", "gpustack.ai/devices", ""),
28
+ ("key merely prefixed by a base key", "nvidia.com/gpu-alike", ""),
29
+ ],
30
+ )
31
+ def test_match_runtime_class(name, resource_key, expected):
32
+ actual = _match_runtime_class(resource_key)
33
+ assert actual == expected, f"case {name} expected {expected}, but got {actual}"
34
+
35
+
36
+ def _pod(
37
+ containers_requests: list[dict | None],
38
+ runtime_class_name: str | None = None,
39
+ ) -> kubernetes.client.V1Pod:
40
+ containers = []
41
+ for i, requests in enumerate(containers_requests):
42
+ container = kubernetes.client.V1Container(name=f"container-{i}")
43
+ if requests is not None:
44
+ container.resources = kubernetes.client.V1ResourceRequirements(
45
+ requests=requests,
46
+ limits=requests,
47
+ )
48
+ containers.append(container)
49
+ return kubernetes.client.V1Pod(
50
+ spec=kubernetes.client.V1PodSpec(
51
+ containers=containers,
52
+ runtime_class_name=runtime_class_name,
53
+ ),
54
+ )
55
+
56
+
57
+ def _fake_node_api(monkeypatch, existing: set[str], forbidden: bool = False):
58
+ """
59
+ Patch NodeV1Api so that reading a RuntimeClass succeeds for names in
60
+ `existing`, raises 403 when `forbidden`, and 404 otherwise.
61
+ """
62
+ calls = []
63
+
64
+ class FakeNodeV1Api:
65
+ def __init__(self, client):
66
+ pass
67
+
68
+ def read_runtime_class(self, name):
69
+ calls.append(name)
70
+ if forbidden:
71
+ raise kubernetes.client.exceptions.ApiException(
72
+ status=403,
73
+ reason="Forbidden",
74
+ )
75
+ if name not in existing:
76
+ raise kubernetes.client.exceptions.ApiException(
77
+ status=404,
78
+ reason="Not Found",
79
+ )
80
+ return kubernetes.client.V1RuntimeClass(
81
+ metadata=kubernetes.client.V1ObjectMeta(name=name),
82
+ handler=name,
83
+ )
84
+
85
+ monkeypatch.setattr(kubernetes.client, "NodeV1Api", FakeNodeV1Api)
86
+ return calls
87
+
88
+
89
+ def test_resolve_runtime_class_name_base_key(monkeypatch):
90
+ _fake_node_api(monkeypatch, existing={"nvidia"})
91
+ pod = _pod([{"nvidia.com/gpu": "1"}])
92
+
93
+ _resolve_runtime_class_name(pod, None)
94
+
95
+ assert pod.spec.runtime_class_name == "nvidia"
96
+
97
+
98
+ def test_resolve_runtime_class_name_suffixed_family(monkeypatch):
99
+ _fake_node_api(monkeypatch, existing={"ascend"})
100
+ pod = _pod(
101
+ [
102
+ {
103
+ "huawei.com/npu.sliced": "1",
104
+ "huawei.com/npu.sliced.cores-percentage": "30",
105
+ "huawei.com/npu.sliced.memory-percentage": "20",
106
+ },
107
+ ],
108
+ )
109
+
110
+ _resolve_runtime_class_name(pod, None)
111
+
112
+ assert pod.spec.runtime_class_name == "ascend"
113
+
114
+
115
+ def test_resolve_runtime_class_name_missing_class(monkeypatch, caplog):
116
+ _fake_node_api(monkeypatch, existing=set())
117
+ pod = _pod([{"huawei.com/npu.sliced": "1"}])
118
+
119
+ with caplog.at_level(logging.WARNING):
120
+ _resolve_runtime_class_name(pod, None)
121
+
122
+ assert pod.spec.runtime_class_name is None
123
+ assert "ascend" in caplog.text
124
+
125
+
126
+ def test_resolve_runtime_class_name_forbidden(monkeypatch, caplog):
127
+ _fake_node_api(monkeypatch, existing={"ascend"}, forbidden=True)
128
+ pod = _pod([{"huawei.com/npu.sliced": "1"}])
129
+
130
+ with caplog.at_level(logging.WARNING):
131
+ _resolve_runtime_class_name(pod, None)
132
+
133
+ assert pod.spec.runtime_class_name is None
134
+ assert "ascend" in caplog.text
135
+ assert "permission" in caplog.text
136
+
137
+
138
+ def test_resolve_runtime_class_name_never_overwritten(monkeypatch):
139
+ calls = _fake_node_api(monkeypatch, existing={"ascend"})
140
+ pod = _pod([{"huawei.com/npu.sliced": "1"}], runtime_class_name="custom")
141
+
142
+ _resolve_runtime_class_name(pod, None)
143
+
144
+ assert pod.spec.runtime_class_name == "custom"
145
+ assert not calls
146
+
147
+
148
+ def test_resolve_runtime_class_name_conflict_keeps_first(monkeypatch, caplog):
149
+ _fake_node_api(monkeypatch, existing={"nvidia", "ascend"})
150
+ pod = _pod(
151
+ [
152
+ {"nvidia.com/gpu": "1"},
153
+ {"huawei.com/npu.sliced": "1"},
154
+ ],
155
+ )
156
+
157
+ with caplog.at_level(logging.WARNING):
158
+ _resolve_runtime_class_name(pod, None)
159
+
160
+ assert pod.spec.runtime_class_name == "nvidia"
161
+ assert "ascend" in caplog.text
162
+
163
+
164
+ def test_resolve_runtime_class_name_no_mapped_resources(monkeypatch):
165
+ calls = _fake_node_api(monkeypatch, existing={"nvidia"})
166
+ pod = _pod([{"cpu": "2", "memory": "4Gi"}, None])
167
+
168
+ _resolve_runtime_class_name(pod, None)
169
+
170
+ assert pod.spec.runtime_class_name is None
171
+ assert not calls
172
+
173
+
174
+ def test_default_runtime_class_map():
175
+ mapping = envs.GPUSTACK_RUNTIME_DEPLOY_RESOURCE_KEY_MAP_RUNTIME_CLASS
176
+ assert mapping == {
177
+ "amd.com/gpu": "amd",
178
+ "huawei.com/npu": "ascend",
179
+ "cambricon.com/mlu": "cambricon",
180
+ "iluvatar.com/gpu": "iluvatar",
181
+ "mthreads.com/gpu": "mthreads",
182
+ "nvidia.com/gpu": "nvidia",
183
+ }
@@ -1 +0,0 @@
1
- git_commit = "79ee112"