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.
- gpustack_runtime-0.2.7/.agents/skills/runtime-release/SKILL.md +100 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.gitignore +4 -1
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/AGENTS.md +2 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/PKG-INFO +2 -2
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/_version.py +2 -2
- gpustack_runtime-0.2.7/gpustack_runtime/_version_appendix.py +1 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__types__.py +294 -1
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/amd.py +13 -19
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/ascend.py +157 -34
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/cambricon.py +19 -3
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/iluvatar.py +7 -5
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/nvidia.py +14 -9
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pydcmi/__init__.py +11 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/thead.py +8 -5
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/envs.py +30 -6
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pyproject.toml +7 -1
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/specs/2026-08-14-detector-alignment-and-workload-exit-status.md +2 -2
- gpustack_runtime-0.2.7/specs/2026-10-30-detector-health-alignment-and-identity-cache.md +479 -0
- gpustack_runtime-0.2.7/tests/gpustack_runtime/detector/conftest.py +19 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_amd.py +53 -14
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_ascend.py +312 -11
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_cambricon.py +112 -4
- gpustack_runtime-0.2.7/tests/gpustack_runtime/detector/test_detector_cache.py +544 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_iluvatar.py +20 -8
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_nvidia.py +119 -18
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_pydmi.py +73 -1
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_thead.py +59 -24
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/uv.lock +87 -42
- gpustack_runtime-0.2.5.post4/gpustack_runtime/_version_appendix.py +0 -1
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.codespelldict +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.codespellrc +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.dockerignore +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.gitattributes +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.pre-commit-config.yaml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/.python-version +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/ADOPTERS.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/DCO +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/LICENSE +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/Makefile +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/README.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/deploy/manifests/docker-compose.yaml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/deploy/manifests/kubernetes.yaml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/index.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.deployer.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.detector.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/docs/modules/gpustack_runtime.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/__main__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/_version.pyi +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/__types__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/deployer.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/detector.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/cmds/images.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__patches__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__types__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/__utils__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/__utils__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/amd.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/ascend.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/iluvatar.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/metax.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/cdi/thead.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/docker.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/kubernetes.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/deployer/podman.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__utils__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/hygon.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/metax.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/mthreads.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyamdgpu/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pycndev/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pydmi/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/logging.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/hatch.toml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/mkdocs.yml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pack/Dockerfile +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pack/Dockerfile.dummy +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/pytest.ini +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/ruff.toml +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
- {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
- {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
- {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
- {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
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_ascend_ranktable.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_docker_status.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_kubernetes_status.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_logs_options.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_podman_status.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_privileged.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_resource_injection_policy.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_runtime_class.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_termination_grace_period.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_visible_devices_ordering.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_workload_resource_injection_policy.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/deployer/test_workload_status.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/README.md +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_cli.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_types.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_hygon.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_metax.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_mig_devices.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_mthreads.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_pycndev.py +0 -0
- {gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/tests/gpustack_runtime/detector/test_samples.py +0 -0
- {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.
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
31
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
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"
|
{gpustack_runtime-0.2.5.post4 → gpustack_runtime-0.2.7}/gpustack_runtime/detector/__types__.py
RENAMED
|
@@ -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 =
|
|
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
|
|
525
|
-
#
|
|
526
|
-
#
|
|
527
|
-
#
|
|
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
|
-
|
|
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
|
|
543
|
-
#
|
|
544
|
-
#
|
|
545
|
-
|
|
546
|
-
|
|
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
|
|