Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/unittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: GPU Unit Tests

on:
push:
branches: [main]
pull_request:
branches: [main]
Comment on lines +6 to +7
Comment on lines +6 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Gate untrusted PRs before using self-hosted runners

For a fork PR, the checkout contains contributor-controlled code that is executed by pytest on the persistent self-hosted H20 runner. The container still receives the runner's proxy values and writable shared uv cache, so malicious tests can exfiltrate embedded proxy credentials or poison state consumed by later trusted jobs; contents: read does not protect these host resources. Require trusted approval or an allowlist before scheduling fork code, or use an ephemeral isolated runner.

Useful? React with 👍 / 👎.

workflow_dispatch:

permissions:
contents: read

concurrency:
group: gpu-unittest-${{ github.ref }}
cancel-in-progress: true

jobs:
unittest:
name: Unit Tests (H20, 4 GPUs)
runs-on: [self-hosted, linux, x64, h20, gpu]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 优先级:P1

补充核验:这个 fork PR 的首次运行已经在 H20 runner 上完成 Checkout 并进入宿主机的 Create test container 步骤;缺少 CI_GPU_DEVICES 只会让当前脚本退出,并不是执行权限边界。PR 若修改这些 run 步骤,就能以 runner 账户直接使用 Docker,绕过第 49–64 行设置的 GPU、网络与挂载限制,影响同机另一 runner 及后续任务。

建议在启用公开 PR 自动执行前,把整个 runner 放进每次销毁的隔离环境;若保留持久化主机,则需要在 PR 无法修改的调度/runner 访问策略中限制为受信工作流和经审核的提交,单独增加本 YAML 内的条件不足以建立边界。GitHub 的自托管 runner 安全说明也指出了这种持久化风险。当前账户无法读取仓库级 fork 审批策略,因此这里不声称所有外部 PR 都会免审批运行;如已有外部约束,请提供其配置依据以便复核。

timeout-minutes: 30
env:
CI_IMAGE: ghcr.io/redai-studio/relaxrl:latest
Comment thread
SigureMo marked this conversation as resolved.
CI_CONTAINER_NAME: relax-unittest-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Create test container
env:
CUDA_VISIBLE_DEVICES: 0,1,2,3
HOST_UV_CACHE_DIR: /home/relax-ci/.cache/uv
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
ARTIFACT_DIR: ${{ runner.temp }}/unittest-${{ github.run_id }}-${{ github.run_attempt }}
run: |
set -euo pipefail
: "${CI_GPU_DEVICES:?Set CI_GPU_DEVICES to the four GPUs assigned to this runner}"
IFS=',' read -r -a gpu_devices <<< "$CI_GPU_DEVICES"
if [[ ${#gpu_devices[@]} -ne 4 ]]; then
echo "CI_GPU_DEVICES must list exactly four GPU indices or UUIDs" >&2
exit 1
fi
mkdir -p "$HOST_UV_CACHE_DIR" "$ARTIFACT_DIR"

# Keep network, IPC and /tmp private for parallel Ray/NCCL tests.
# Create before starting so cancellation during a pull cannot start tests.
docker create --pull always --rm --init \
--name "$CI_CONTAINER_NAME" \
--gpus "\"device=$CI_GPU_DEVICES\"" \
--shm-size=32g \
-v "$GITHUB_WORKSPACE:/source:ro" \
-v "$ARTIFACT_DIR:/artifacts" \
-v "$HOST_UV_CACHE_DIR:$UV_CACHE_DIR" \
--workdir /workspace/Relax \
--env CUDA_VISIBLE_DEVICES \
--env UV_CACHE_DIR \
--env UV_LINK_MODE \
--env http_proxy \
--env https_proxy \
--env no_proxy \
--entrypoint /bin/bash "$CI_IMAGE" -c 'sleep infinity'
docker inspect --format 'Test image: {{.Config.Image}} ({{.Image}})' "$CI_CONTAINER_NAME"
docker start "$CI_CONTAINER_NAME"

- name: Prepare workspace and show GPUs
run: |
docker exec "$CI_CONTAINER_NAME" bash -euc '
cp -a --no-preserve=ownership /source/. /workspace/Relax/
nvidia-smi
'

- name: Install dependencies
run: |
docker exec "$CI_CONTAINER_NAME" uv pip install \
--system --break-system-packages -r requirements.txt \
--extra-index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

- name: Run unittest
run: |
docker exec "$CI_CONTAINER_NAME" bash -euo pipefail -c '
python -m pytest tests/ -v -ra --tb=short --junitxml=/artifacts/junit.xml \
2>&1 | tee /artifacts/pytest.log
'

- name: Remove test container
if: always()
run: docker rm --force "$CI_CONTAINER_NAME" >/dev/null 2>&1 || true

- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: unittest-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/unittest-${{ github.run_id }}-${{ github.run_attempt }}/
if-no-files-found: warn
retention-days: 14
13 changes: 13 additions & 0 deletions tests/backends/megatron/test_chunked_mtp_loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from __future__ import annotations

import types
from collections.abc import Iterator

import pytest
import torch
Expand All @@ -35,6 +36,18 @@
S, B, H, V, LAYERS = 16, 2, 8, 32, 2


@pytest.fixture(scope="module", autouse=True)
def _single_threaded_cpu() -> Iterator[None]:
"""Run bitwise CPU comparisons with one thread, then restore the previous
setting."""
previous_num_threads = torch.get_num_threads()
torch.set_num_threads(1)
try:
yield
finally:
torch.set_num_threads(previous_num_threads)


class _FakeHead(nn.Module):
"""Stand-in for ColumnParallelLinear output_layer.

Expand Down