From 4da7d0ed843cc3ad0ab37330a0c404ee6517fa3e Mon Sep 17 00:00:00 2001 From: yuchengen Date: Mon, 27 Apr 2026 16:15:23 +0800 Subject: [PATCH 1/3] feat: add transformers 5.5.0 container image for OC9 --- frameworks/tramsformers/5.5.0/Dockerfile | 22 ++++ frameworks/tramsformers/5.5.0/README.md | 20 ++++ frameworks/tramsformers/5.5.0/test.sh | 112 ++++++++++++++++++ frameworks/tramsformers/5.5.0/test_result.png | Bin 0 -> 53475 bytes 4 files changed, 154 insertions(+) create mode 100644 frameworks/tramsformers/5.5.0/Dockerfile create mode 100644 frameworks/tramsformers/5.5.0/README.md create mode 100644 frameworks/tramsformers/5.5.0/test.sh create mode 100644 frameworks/tramsformers/5.5.0/test_result.png diff --git a/frameworks/tramsformers/5.5.0/Dockerfile b/frameworks/tramsformers/5.5.0/Dockerfile new file mode 100644 index 0000000..130cf13 --- /dev/null +++ b/frameworks/tramsformers/5.5.0/Dockerfile @@ -0,0 +1,22 @@ + +FROM opencloudos/opencloudos9-cuda-devel:12.8 + +LABEL maintainer="stronking 363133710@qq.com" +LABEL org.opencontainers.image.source="https://gitee.com/OpenCloudOS/ai-agent-container" +LABEL org.opencontainers.image.description="Torch 2.10.0 (GPU) on OpenCloudOS 9" +ENV NVIDIA_VISIBLE_DEVICES=all + +ENV NVIDIA_VISIBLE_DEVICES=all \ + PYTHONUNBUFFERED=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PIP_DEFAULT_TIMEOUT=120 + + +WORKDIR /home + +RUN --mount=type=cache,id=pip-cache-opencloudos9-cu128,target=/root/.cache/pip \ + pip install "transformers[torch] @ git+https://github.com/huggingface/transformers.git@v5.5.0" + +COPY test.sh . + +RUN ["python3"] \ No newline at end of file diff --git a/frameworks/tramsformers/5.5.0/README.md b/frameworks/tramsformers/5.5.0/README.md new file mode 100644 index 0000000..86f19f6 --- /dev/null +++ b/frameworks/tramsformers/5.5.0/README.md @@ -0,0 +1,20 @@ +# Transformers 5.5.0 on OpenCloudOS 9 + +## 基本信息 +- **框架版本**:v5.5.0 +- **基础镜像**:opencloudos9-cuda-devel:12.8 +- **Python 版本**:3.11 +- **CUDA 版本**: 12.x 或 更高 + +## 构建 + +docker build -t oc9-transformers:5.5.0 . + +## 镜像启动命令 + +docker run -d --gpus all --name oc9-transformers oc9-transformers:5.5.0 + +## 镜像测试命令 + +docker run --rm --gpus all oc9-transformers:5.5.0 bash test.sh + diff --git a/frameworks/tramsformers/5.5.0/test.sh b/frameworks/tramsformers/5.5.0/test.sh new file mode 100644 index 0000000..7142b30 --- /dev/null +++ b/frameworks/tramsformers/5.5.0/test.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -e +export CUDA_HOME=/usr/local/cuda +export PATH=/usr/local/cuda/bin:$PATH +export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH +echo "=== transformers 基础功能测试 ===" + +# 1. 检查 Python +echo -n "检查 python3... " +python3 --version >/dev/null 2>&1 && echo "✓ 通过" || { echo "✗ 失败"; exit 1; } + +# 2. 检查 transformers 是否可正常导入 +echo -n "检查 transformers import... " +TRANSFORMERS_VERSION=$(python3 -c "import transformers; print(transformers.__version__)") \ + && echo "✓ 通过,版本:${TRANSFORMERS_VERSION}" \ + || { echo "✗ 失败"; exit 1; } + +# 3. 检查 PyTorch 后端是否可用 +echo -n "检查 torch import... " +TORCH_VERSION=$(python3 -c "import torch; print(torch.__version__)") \ + && echo "✓ 通过,版本:${TORCH_VERSION}" \ + || { echo "✗ 失败:未检测到 torch,无法验证 transformers 推理功能"; exit 1; } + +# 4. 可选 CUDA 环境检查 +# 如果是 GPU 镜像,可以运行:REQUIRE_CUDA=1 ./test.sh +if [ "${REQUIRE_CUDA:-1}" = "1" ]; then + echo "=== CUDA 环境检查 ===" + + echo -n "检查 nvidia-smi... " + command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1 \ + && echo "✓ 通过" \ + || { echo "✗ 失败:nvidia-smi 不可用"; exit 1; } + + echo -n "检查 torch.cuda... " + python3 - <<'PY' +import torch + +assert torch.cuda.is_available(), "torch.cuda.is_available() 为 False" +print(f"CUDA available: {torch.cuda.is_available()}") +print(f"CUDA version: {torch.version.cuda}") +print(f"GPU device: {torch.cuda.get_device_name(0)}") +PY + echo "✓ 通过" + + if command -v nvcc >/dev/null 2>&1; then + echo "nvcc 版本:" + nvcc --version + else + echo "提示:未检测到 nvcc,运行时镜像不一定需要 nvcc" + fi +fi + +# 5. 验证 transformers 核心功能:本地 tokenizer + 小模型 forward +echo -n "检查 transformers tokenizer / model forward... " + +TMP_DIR=$(mktemp -d) +trap 'rm -rf "$TMP_DIR"' EXIT + +cat > "${TMP_DIR}/vocab.txt" <<'EOF' +[PAD] +[UNK] +[CLS] +[SEP] +[MASK] +hello +world +! +transformers +test +EOF + +python3 - "${TMP_DIR}" <<'PY' +import sys +import torch +from transformers import BertTokenizerFast, BertConfig, AutoModel + +tmp_dir = sys.argv[1] + +# 本地构造一个极小 vocab,避免联网下载模型 +tokenizer = BertTokenizerFast(vocab_file=f"{tmp_dir}/vocab.txt") + +encoded = tokenizer( + "hello world !", + return_tensors="pt", + padding=True, + truncation=True, +) + +config = BertConfig( + vocab_size=tokenizer.vocab_size, + hidden_size=32, + num_hidden_layers=1, + num_attention_heads=4, + intermediate_size=64, +) + +model = AutoModel.from_config(config) +model.eval() + +with torch.no_grad(): + outputs = model(**encoded) + +assert outputs.last_hidden_state.shape[0] == 1 +assert outputs.last_hidden_state.shape[-1] == 32 + +print("tokenizer vocab_size:", tokenizer.vocab_size) +print("last_hidden_state shape:", tuple(outputs.last_hidden_state.shape)) +PY + +echo "✓ 通过" + +echo "=== 所有测试通过 ===" \ No newline at end of file diff --git a/frameworks/tramsformers/5.5.0/test_result.png b/frameworks/tramsformers/5.5.0/test_result.png new file mode 100644 index 0000000000000000000000000000000000000000..b34c3c9b6d669deaef4c9c9fb2180fa18c280991 GIT binary patch literal 53475 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 literal 0 HcmV?d00001 -- Gitee From 8ba3c445de1fef71d95a5b61bebbe0b1eaad76d1 Mon Sep 17 00:00:00 2001 From: yuchengen Date: Wed, 29 Apr 2026 11:17:26 +0800 Subject: [PATCH 2/3] feat: add build.conf and rename direction name --- .../{tramsformers => transformers}/5.5.0/Dockerfile | 0 .../{tramsformers => transformers}/5.5.0/README.md | 0 frameworks/transformers/5.5.0/build.conf | 4 ++++ .../{tramsformers => transformers}/5.5.0/test.sh | 0 .../5.5.0/test_result.png | Bin 5 files changed, 4 insertions(+) rename frameworks/{tramsformers => transformers}/5.5.0/Dockerfile (100%) rename frameworks/{tramsformers => transformers}/5.5.0/README.md (100%) create mode 100644 frameworks/transformers/5.5.0/build.conf rename frameworks/{tramsformers => transformers}/5.5.0/test.sh (100%) rename frameworks/{tramsformers => transformers}/5.5.0/test_result.png (100%) diff --git a/frameworks/tramsformers/5.5.0/Dockerfile b/frameworks/transformers/5.5.0/Dockerfile similarity index 100% rename from frameworks/tramsformers/5.5.0/Dockerfile rename to frameworks/transformers/5.5.0/Dockerfile diff --git a/frameworks/tramsformers/5.5.0/README.md b/frameworks/transformers/5.5.0/README.md similarity index 100% rename from frameworks/tramsformers/5.5.0/README.md rename to frameworks/transformers/5.5.0/README.md diff --git a/frameworks/transformers/5.5.0/build.conf b/frameworks/transformers/5.5.0/build.conf new file mode 100644 index 0000000..347fa33 --- /dev/null +++ b/frameworks/transformers/5.5.0/build.conf @@ -0,0 +1,4 @@ +# transformers 5.5.0 on OpenCloudOS 9 (GPU) +IMAGE_NAME=oc9-transformers +IMAGE_TAG=5.5.0 +GPU_TEST=true \ No newline at end of file diff --git a/frameworks/tramsformers/5.5.0/test.sh b/frameworks/transformers/5.5.0/test.sh similarity index 100% rename from frameworks/tramsformers/5.5.0/test.sh rename to frameworks/transformers/5.5.0/test.sh diff --git a/frameworks/tramsformers/5.5.0/test_result.png b/frameworks/transformers/5.5.0/test_result.png similarity index 100% rename from frameworks/tramsformers/5.5.0/test_result.png rename to frameworks/transformers/5.5.0/test_result.png -- Gitee From e3a4a0c99029185f6e781fab70a3b2a5d515db1c Mon Sep 17 00:00:00 2001 From: yuchengen Date: Wed, 29 Apr 2026 16:53:47 +0800 Subject: [PATCH 3/3] feat: modify test.sh and dont copy test.sh --- frameworks/transformers/5.5.0/Dockerfile | 2 - frameworks/transformers/5.5.0/test.sh | 317 ++++++++++++++++------- 2 files changed, 219 insertions(+), 100 deletions(-) diff --git a/frameworks/transformers/5.5.0/Dockerfile b/frameworks/transformers/5.5.0/Dockerfile index 130cf13..44701f4 100644 --- a/frameworks/transformers/5.5.0/Dockerfile +++ b/frameworks/transformers/5.5.0/Dockerfile @@ -17,6 +17,4 @@ WORKDIR /home RUN --mount=type=cache,id=pip-cache-opencloudos9-cu128,target=/root/.cache/pip \ pip install "transformers[torch] @ git+https://github.com/huggingface/transformers.git@v5.5.0" -COPY test.sh . - RUN ["python3"] \ No newline at end of file diff --git a/frameworks/transformers/5.5.0/test.sh b/frameworks/transformers/5.5.0/test.sh index 7142b30..14a9b3c 100644 --- a/frameworks/transformers/5.5.0/test.sh +++ b/frameworks/transformers/5.5.0/test.sh @@ -1,112 +1,233 @@ -#!/bin/bash -set -e -export CUDA_HOME=/usr/local/cuda -export PATH=/usr/local/cuda/bin:$PATH -export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH -echo "=== transformers 基础功能测试 ===" - -# 1. 检查 Python -echo -n "检查 python3... " -python3 --version >/dev/null 2>&1 && echo "✓ 通过" || { echo "✗ 失败"; exit 1; } - -# 2. 检查 transformers 是否可正常导入 -echo -n "检查 transformers import... " -TRANSFORMERS_VERSION=$(python3 -c "import transformers; print(transformers.__version__)") \ - && echo "✓ 通过,版本:${TRANSFORMERS_VERSION}" \ - || { echo "✗ 失败"; exit 1; } - -# 3. 检查 PyTorch 后端是否可用 -echo -n "检查 torch import... " -TORCH_VERSION=$(python3 -c "import torch; print(torch.__version__)") \ - && echo "✓ 通过,版本:${TORCH_VERSION}" \ - || { echo "✗ 失败:未检测到 torch,无法验证 transformers 推理功能"; exit 1; } - -# 4. 可选 CUDA 环境检查 -# 如果是 GPU 镜像,可以运行:REQUIRE_CUDA=1 ./test.sh -if [ "${REQUIRE_CUDA:-1}" = "1" ]; then - echo "=== CUDA 环境检查 ===" - - echo -n "检查 nvidia-smi... " - command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi >/dev/null 2>&1 \ - && echo "✓ 通过" \ - || { echo "✗ 失败:nvidia-smi 不可用"; exit 1; } - - echo -n "检查 torch.cuda... " - python3 - <<'PY' -import torch - -assert torch.cuda.is_available(), "torch.cuda.is_available() 为 False" -print(f"CUDA available: {torch.cuda.is_available()}") -print(f"CUDA version: {torch.version.cuda}") -print(f"GPU device: {torch.cuda.get_device_name(0)}") -PY - echo "✓ 通过" - - if command -v nvcc >/dev/null 2>&1; then - echo "nvcc 版本:" - nvcc --version - else - echo "提示:未检测到 nvcc,运行时镜像不一定需要 nvcc" - fi +#!/usr/bin bash +# 在容器外执行:验证指定 Docker 镜像中的 transformers / torch / CUDA 基础功能。 +# 用法:bash test_transformers_docker.sh [额外 docker run 参数...] +# 示例:bash test_transformers_docker.sh my-transformers:latest +# 示例:REQUIRE_CUDA=0 bash test_transformers_docker.sh my-transformers:latest +# 示例:DOCKER_NETWORK=bridge bash test_transformers_docker.sh my-transformers:latest + +set -Eeuo pipefail + +IMAGE="${1:-}" +if [[ -z "${IMAGE}" || "${IMAGE}" == "-h" || "${IMAGE}" == "--help" ]]; then + cat <<'USAGE' +用法: + bash test_transformers_docker.sh [额外 docker run 参数...] + +环境变量: + REQUIRE_CUDA=1|0 是否强制要求 CUDA/GPU 可用,默认 1 + GPUS=all 传给 docker run --gpus 的值,默认 all + PYTHON_BIN=python3 容器内 Python 命令,默认 python3 + DOCKER_NETWORK=none docker 网络模式,默认 none,避免测试时联网下载模型 + TIMEOUT_SECONDS=180 整体测试超时时间,默认 180 秒 + +示例: + bash test_transformers_docker.sh registry.example.com/ai/transformers:cu121 + REQUIRE_CUDA=0 bash test_transformers_docker.sh transformers-cpu:test + DOCKER_NETWORK=bridge bash test_transformers_docker.sh my-image:tag --ipc=host +USAGE + exit 1 fi +shift || true -# 5. 验证 transformers 核心功能:本地 tokenizer + 小模型 forward -echo -n "检查 transformers tokenizer / model forward... " - -TMP_DIR=$(mktemp -d) -trap 'rm -rf "$TMP_DIR"' EXIT - -cat > "${TMP_DIR}/vocab.txt" <<'EOF' -[PAD] -[UNK] -[CLS] -[SEP] -[MASK] -hello -world -! -transformers -test -EOF - -python3 - "${TMP_DIR}" <<'PY' -import sys -import torch -from transformers import BertTokenizerFast, BertConfig, AutoModel +REQUIRE_CUDA="${REQUIRE_CUDA:-1}" +GPUS="${GPUS:-all}" +PYTHON_BIN="${PYTHON_BIN:-python3}" +DOCKER_NETWORK="${DOCKER_NETWORK:-none}" +TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-180}" +EXTRA_DOCKER_ARGS=("$@") -tmp_dir = sys.argv[1] +log() { printf '\033[1;34m%s\033[0m\n' "$*"; } +ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33m! %s\033[0m\n' "$*"; } +fail() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; } -# 本地构造一个极小 vocab,避免联网下载模型 -tokenizer = BertTokenizerFast(vocab_file=f"{tmp_dir}/vocab.txt") +[[ "${REQUIRE_CUDA}" =~ ^[01]$ ]] || fail "REQUIRE_CUDA 只能是 1 或 0,当前值: ${REQUIRE_CUDA}" -encoded = tokenizer( - "hello world !", - return_tensors="pt", - padding=True, - truncation=True, -) +command -v docker >/dev/null 2>&1 || fail "未找到 docker 命令" +docker info >/dev/null 2>&1 || fail "docker daemon 不可用,请确认 Docker 服务已启动且当前用户有权限访问" + +if ! docker image inspect "${IMAGE}" >/dev/null 2>&1; then + warn "本地未找到镜像 ${IMAGE};docker run 可能会尝试拉取镜像" +fi -config = BertConfig( - vocab_size=tokenizer.vocab_size, - hidden_size=32, - num_hidden_layers=1, - num_attention_heads=4, - intermediate_size=64, +DOCKER_ARGS=(run --rm -i) +if [[ "${REQUIRE_CUDA}" == "1" ]]; then + DOCKER_ARGS+=(--gpus "${GPUS}") +fi +DOCKER_ARGS+=( + --network "${DOCKER_NETWORK}" + -e "REQUIRE_CUDA=${REQUIRE_CUDA}" + -e "PYTHON_BIN=${PYTHON_BIN}" + -e "TRANSFORMERS_OFFLINE=1" + -e "HF_HUB_OFFLINE=1" + -e "HF_HOME=/tmp/hf-home" ) +DOCKER_ARGS+=("${EXTRA_DOCKER_ARGS[@]}") +DOCKER_ARGS+=("${IMAGE}" /bin/bash -s) + +log "=== Transformers Docker 镜像功能测试 ===" +printf '镜像: %s\n' "${IMAGE}" +printf 'CUDA 强制检查: %s\n' "${REQUIRE_CUDA}" +printf 'Docker 网络: %s\n' "${DOCKER_NETWORK}" +printf 'Python 命令: %s\n' "${PYTHON_BIN}" +if ((${#EXTRA_DOCKER_ARGS[@]} > 0)); then + printf '额外 docker 参数: %s\n' "${EXTRA_DOCKER_ARGS[*]}" +fi + +RUN_CMD=(docker "${DOCKER_ARGS[@]}") +if command -v timeout >/dev/null 2>&1; then + RUN_CMD=(timeout --preserve-status "${TIMEOUT_SECONDS}s" "${RUN_CMD[@]}") +fi + +"${RUN_CMD[@]}" <<'IN_CONTAINER' +set -Eeuo pipefail + +ok() { printf '\033[1;32m✓ %s\033[0m\n' "$*"; } +warn() { printf '\033[1;33m! %s\033[0m\n' "$*"; } +fail() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; } +section() { printf '\n\033[1;34m=== %s ===\033[0m\n' "$*"; } + +PY="${PYTHON_BIN:-python3}" + +if [[ -d /usr/local/cuda ]]; then + export CUDA_HOME="${CUDA_HOME:-/usr/local/cuda}" + export PATH="${CUDA_HOME}/bin:${PATH}" + export LD_LIBRARY_PATH="${CUDA_HOME}/lib64:${LD_LIBRARY_PATH:-}" +fi + +export TRANSFORMERS_OFFLINE="${TRANSFORMERS_OFFLINE:-1}" +export HF_HUB_OFFLINE="${HF_HUB_OFFLINE:-1}" +export HF_HOME="${HF_HOME:-/tmp/hf-home}" +mkdir -p "${HF_HOME}" -model = AutoModel.from_config(config) -model.eval() +section "1. Python 基础检查" +command -v "${PY}" >/dev/null 2>&1 || fail "容器内未找到 Python 命令: ${PY}" +"${PY}" --version +ok "Python 可用" -with torch.no_grad(): - outputs = model(**encoded) +section "2. Python 包导入检查" +"${PY}" - <<'PY' +import importlib +import platform +import sys -assert outputs.last_hidden_state.shape[0] == 1 -assert outputs.last_hidden_state.shape[-1] == 32 +packages = ["torch", "transformers", "tokenizers"] +print("python_executable:", sys.executable) +print("platform:", platform.platform()) + +for name in packages: + mod = importlib.import_module(name) + version = getattr(mod, "__version__", "unknown") + print(f"{name}: {version}") +PY +ok "torch / transformers / tokenizers 导入正常" -print("tokenizer vocab_size:", tokenizer.vocab_size) -print("last_hidden_state shape:", tuple(outputs.last_hidden_state.shape)) +section "3. CUDA / GPU 检查" +if [[ "${REQUIRE_CUDA:-1}" == "1" ]]; then + command -v nvidia-smi >/dev/null 2>&1 || fail "nvidia-smi 不可用;请检查宿主机 NVIDIA 驱动、nvidia-container-toolkit、docker run --gpus 参数" + nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader || fail "nvidia-smi 执行失败" + + "${PY}" - <<'PY' +import torch + +assert torch.cuda.is_available(), "torch.cuda.is_available() 为 False" +print("torch_cuda_version:", torch.version.cuda) +print("gpu_count:", torch.cuda.device_count()) +print("gpu_0_name:", torch.cuda.get_device_name(0)) + +x = torch.randn(512, 512, device="cuda") +y = x @ x +torch.cuda.synchronize() +print("gpu_matmul_shape:", tuple(y.shape)) PY + ok "CUDA / GPU 可用,且 torch GPU 计算正常" +else + warn "REQUIRE_CUDA=0,跳过强制 CUDA 检查" +fi + +section "4. Transformers 离线功能检查:tokenizer + model forward + save/load + pipeline" +"${PY}" - <<'PY' +import os +import tempfile +import torch +from transformers import ( + AutoModel, + BertConfig, + BertForMaskedLM, + BertTokenizerFast, + pipeline, +) + +require_cuda = os.environ.get("REQUIRE_CUDA", "1") == "1" +device = torch.device("cuda:0" if require_cuda else "cpu") + +with tempfile.TemporaryDirectory() as tmp_dir: + vocab_path = os.path.join(tmp_dir, "vocab.txt") + with open(vocab_path, "w", encoding="utf-8") as f: + f.write("\n".join([ + "[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]", + "hello", "world", "!", "transformers", "test", "good", + ])) + + tokenizer = BertTokenizerFast(vocab_file=vocab_path, do_lower_case=True) + encoded = tokenizer("hello world !", return_tensors="pt", padding=True, truncation=True) + + config = BertConfig( + vocab_size=tokenizer.vocab_size, + hidden_size=32, + num_hidden_layers=1, + num_attention_heads=4, + intermediate_size=64, + max_position_embeddings=64, + ) + + model = AutoModel.from_config(config).to(device).eval() + encoded_on_device = {k: v.to(device) for k, v in encoded.items()} + with torch.no_grad(): + outputs = model(**encoded_on_device) + + assert outputs.last_hidden_state.shape[0] == 1 + assert outputs.last_hidden_state.shape[-1] == 32 + print("forward_device:", str(device)) + print("tokenizer_vocab_size:", tokenizer.vocab_size) + print("last_hidden_state_shape:", tuple(outputs.last_hidden_state.shape)) + + save_dir = os.path.join(tmp_dir, "tiny-bert") + model.save_pretrained(save_dir) + tokenizer.save_pretrained(save_dir) + + loaded = AutoModel.from_pretrained(save_dir, local_files_only=True).to(device).eval() + with torch.no_grad(): + loaded_outputs = loaded(**encoded_on_device) + + assert loaded_outputs.last_hidden_state.shape == outputs.last_hidden_state.shape + print("local_save_load: ok") + + mlm = BertForMaskedLM(config).to(device).eval() + fill_mask = pipeline( + "fill-mask", + model=mlm, + tokenizer=tokenizer, + device=0 if device.type == "cuda" else -1, + top_k=1, + ) + + result = fill_mask("hello [MASK] !") + assert isinstance(result, list) and len(result) == 1 + assert "token_str" in result[0] + print("pipeline_fill_mask_token:", result[0]["token_str"]) +PY +ok "Transformers 离线核心功能正常" + +section "5. 可选组件提示" +if command -v nvcc >/dev/null 2>&1; then + nvcc --version | sed -n '1,4p' +else + warn "未检测到 nvcc:运行时镜像通常不需要 nvcc,只有编译 CUDA 扩展时才需要" +fi -echo "✓ 通过" +section "测试结果" +ok "所有检查通过" +IN_CONTAINER -echo "=== 所有测试通过 ===" \ No newline at end of file +ok "宿主机侧 docker run 验证完成" \ No newline at end of file -- Gitee