Skip to content

Commit 5c6aee2

Browse files
author
Han Wang
committed
Merge upstream/master into overlap-aoti-gpu-cuda-ci
Master's deepmodeling#5896 split the CUDA workflow into separate test_python and test_cc jobs on separate runners, superseding this PR's lane-1 cc-prep tail. Resolution keeps deepmodeling#5896's two-job structure and re-applies this PR's remaining pieces: the aoti_compile/gpu two-lane pytest overlap inside test_python (hides the CPU-bound .pt2 compile tests behind the GPU lane), the per-lane JUnit reports, and the persistent AOTInductor compile cache -- now restored in BOTH jobs, since the test_cc fixture builds (gen_*.py) drive the same inductor backend and reuse the cached kernels.
2 parents 86128ef + 5f7b747 commit 5c6aee2

12 files changed

Lines changed: 328 additions & 89 deletions

.github/workflows/test_cc.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ name: Test C++
1414
jobs:
1515
testcc:
1616
name: Test C++
17-
runs-on: ubuntu-22.04
17+
runs-on: ${{ matrix.check_memleak == false && github.repository_owner == 'deepmodeling' && 'cpu' || 'ubuntu-22.04' }}
1818
strategy:
1919
matrix:
20+
# Only regular jobs run the LAMMPS tests. Use self-hosted CPU runners
21+
# for those jobs in deepmodeling, with a hosted fallback for forks.
2022
include:
2123
- check_memleak: true
2224
enable_tensorflow: true
@@ -36,6 +38,11 @@ jobs:
3638
enable_paddle: true
3739
steps:
3840
- uses: actions/checkout@v7
41+
- name: Install GCC
42+
run: |
43+
sudo apt-get update
44+
sudo apt-get install --yes build-essential
45+
if: ${{ matrix.check_memleak == false && github.repository_owner == 'deepmodeling' }}
3946
- uses: actions/setup-python@v6
4047
with:
4148
python-version: "3.11"

.github/workflows/test_cuda.yml

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,14 @@ concurrency:
1414
cancel-in-progress: true
1515
name: Test CUDA
1616
jobs:
17-
test_cuda:
18-
name: Test Python and C++ on CUDA
17+
test_python:
18+
name: Test Python on CUDA
1919
runs-on: gpu
20-
# The full CUDA suite (serial pytest ~3h43m + C++ + LAMMPS) exceeds the
21-
# default 360-min job limit; the self-hosted GPU runner has no hard cap.
20+
# The serial Python test suite can take several hours on the GPU runner.
2221
timeout-minutes: 480
23-
# Share ONE AOTInductor on-disk compile cache across every step: the two
24-
# pytest lanes AND the C++ fixture builds (gen_*.py in test_cc_local.sh)
25-
# all drive the same inductor backend on the same architectures, so the C++
26-
# fixture compiles reuse the kernels the Python lane just built. actions/cache
27-
# (below) persists it across runs so unchanged models never recompile.
22+
# Share ONE AOTInductor on-disk compile cache across the two pytest lanes;
23+
# actions/cache (below) persists it across runs so unchanged models never
24+
# recompile.
2825
env:
2926
TORCHINDUCTOR_CACHE_DIR: ${{ github.workspace }}/.inductor-cache
3027
# https://github.com/deepmodeling/deepmd-kit/pull/2884#issuecomment-1744216845
@@ -81,47 +78,32 @@ jobs:
8178
DP_ENABLE_NATIVE_OPTIMIZATION: 1
8279
DP_ENABLE_PYTORCH: 1
8380
- run: dp --version
84-
- name: Run Python tests + C++ build (overlap CPU compile work with the GPU lane)
85-
# Two lanes on one GPU box, sized so only TWO heavy workstreams ever run
86-
# at once -- the shape that was green before the C++ prep was (wrongly)
87-
# added as a THIRD concurrent stream (that 3-way overlap SIGFAULTed both
88-
# Python lanes via TORCHINDUCTOR_CACHE_DIR contention; see #5882):
89-
# Lane 1 (CPU compile stream): the .pt2-freezing tests (-m aoti_compile)
90-
# -- CPU-bound, GPU ~98% idle while inductor/g++/ptxas run -- THEN the
91-
# C++ build + gen_*.py .pt2 fixtures (also CPU-bound, GPU idle). They
92-
# run SEQUENTIALLY in one lane, so the compile work never races the
93-
# shared inductor cache and cc-prep reuses the kernels the aoti tests
94-
# just built.
95-
# Lane 2 (GPU stream): the GPU-bound tests (-m "not aoti_compile") -- the
96-
# ~19k-test bulk and the wall-clock bottleneck. It does not freeze
97-
# .pt2, so it only reads the inductor cache, never competing to write.
98-
# The whole CPU stream is hidden under lane 2; ctest + LAMMPS run after
99-
# BOTH lanes join.
81+
- name: Run Python tests (overlap CPU-bound aoti compiles with the GPU lane)
82+
# Two pytest lanes on one GPU box:
83+
# Lane 1 (CPU stream): the .pt2-freezing tests (-m aoti_compile) --
84+
# CPU-bound, GPU ~98% idle while inductor/g++/ptxas run.
85+
# Lane 2 (GPU stream): the GPU-bound remainder (-m "not aoti_compile")
86+
# -- the bulk of the suite and the wall-clock bottleneck. It does not
87+
# freeze .pt2, so it only reads the inductor cache, never competing
88+
# to write.
89+
# Wall-clock becomes max(lane 1, lane 2) instead of their sum; the whole
90+
# compile stream is hidden behind the GPU lane. (The C++ build + fixture
91+
# generation runs in the separate test_cc job.)
10092
run: |
10193
# Cap the compile lane's inductor threads to ~half the cores so lane 2
10294
# keeps CPU for its host-side GPU dispatch; nice the compile work too.
10395
nc=$(( $(nproc) / 2 )); [ "$nc" -lt 1 ] && nc=1
10496
# rc=0 + `|| rc=$?` so a lane's failure does not let the step's default
10597
# `set -e` skip the wall-clock echo (the timing we most want on failure).
106-
# Lane 1: aoti_compile pytest, THEN the C++ build + fixture generation.
10798
( ta=$(date +%s); rc=0
10899
TORCHINDUCTOR_COMPILE_THREADS=$nc nice -n 15 \
109100
python -m pytest source/tests -m "aoti_compile" \
110101
-p no:cacheprovider --junit-xml=junit-aoti.xml || rc=$?
111102
# pytest exit 5 == "no tests collected"; harmless for the compile lane.
112103
[ "$rc" = 5 ] && rc=0
113104
echo "[lane aoti_compile] wall=$(( $(date +%s) - ta ))s exit=$rc"
114-
tc=$(date +%s); rc_cc=0
115-
( export LD_LIBRARY_PATH=$CUDA_PATH/lib64:/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH
116-
export OMP_NUM_THREADS=1 TF_INTRA_OP_PARALLELISM_THREADS=1 \
117-
TF_INTER_OP_PARALLELISM_THREADS=1 \
118-
CMAKE_GENERATOR=Ninja DP_VARIANT=cuda DP_USE_MPICH2=1
119-
source/tests/infer/convert-models.sh
120-
DP_CC_SKIP_CTEST=1 nice -n 19 source/install/test_cc_local.sh ) || rc_cc=$?
121-
echo "[lane cc-prep] wall=$(( $(date +%s) - tc ))s exit=$rc_cc"
122-
exit $(( rc != 0 || rc_cc != 0 )) ) &
105+
exit $rc ) &
123106
pid_a=$!
124-
# Lane 2: the GPU-bound unit tests (the bulk).
125107
( tb=$(date +%s); rc=0
126108
python -m pytest source/tests -m "not aoti_compile" \
127109
-p no:cacheprovider --junit-xml=junit-gpu.xml || rc=$?
@@ -130,7 +112,7 @@ jobs:
130112
pid_b=$!
131113
rc_a=0; wait $pid_a || rc_a=$?
132114
rc_b=0; wait $pid_b || rc_b=$?
133-
echo "lane exit codes: aoti_compile+cc-prep=$rc_a gpu=$rc_b"
115+
echo "lane exit codes: aoti_compile=$rc_a gpu=$rc_b"
134116
exit $(( rc_a != 0 || rc_b != 0 ))
135117
env:
136118
NUM_WORKERS: 0
@@ -150,10 +132,71 @@ jobs:
150132
junit-aoti.xml
151133
junit-gpu.xml
152134
if-no-files-found: warn
153-
- name: C++ tests (ctest; build + fixtures done in lane 1 above)
154-
run: |
135+
136+
test_cc:
137+
name: Test C++ on CUDA
138+
runs-on: gpu
139+
# The gen_*.py fixture builds drive the same AOTInductor backend on the same
140+
# architectures as the Python lanes, so they reuse the kernels cached by
141+
# test_python / previous runs.
142+
env:
143+
TORCHINDUCTOR_CACHE_DIR: ${{ github.workspace }}/.inductor-cache
144+
if: github.repository_owner == 'deepmodeling' && (github.event_name == 'pull_request' && github.event.label && github.event.label.name == 'Test CUDA' || github.event_name == 'workflow_dispatch' || github.event_name == 'merge_group')
145+
steps:
146+
# Jobs run on separate runners, so the C++ job needs its own complete
147+
# CUDA toolchain and Python dependency installation.
148+
- uses: actions/checkout@v7
149+
- name: Cache AOTInductor compile artifacts
150+
# Restored AFTER checkout so checkout's git-clean can't wipe it. Both
151+
# CUDA jobs share the same key; if both try to save it, the second save
152+
# is skipped with a warning (caches are immutable), which is harmless.
153+
uses: actions/cache@v6
154+
with:
155+
path: ${{ github.workspace }}/.inductor-cache
156+
key: inductor-cuda-${{ github.sha }}
157+
restore-keys: |
158+
inductor-cuda-
159+
- uses: actions/setup-python@v6
160+
with:
161+
python-version: "3.11"
162+
# cache: 'pip'
163+
- name: Install wget and unzip
164+
run: sudo apt-get update && sudo apt-get install -y wget unzip
165+
- uses: lukka/get-cmake@latest
166+
with:
167+
useLocalCache: true
168+
useCloudCache: false
169+
- run: |
170+
UBUNTU_VERSION=$(lsb_release -rs | tr -d '.')
171+
OS_NAME="ubuntu${UBUNTU_VERSION}"
172+
echo "Current OS: ${OS_NAME}"
173+
174+
wget https://developer.download.nvidia.com/compute/cuda/repos/${OS_NAME}/x86_64/cuda-keyring_1.1-1_all.deb \
175+
&& sudo dpkg -i cuda-keyring_1.1-1_all.deb \
176+
&& sudo apt-get update \
177+
&& sudo apt-get -y install cuda-toolkit-12-9 cudnn9-cuda-12
178+
echo "CUDA_PATH=/usr/local/cuda-12.9" >> $GITHUB_ENV
179+
echo "/usr/local/cuda-12.9/bin" >> $GITHUB_PATH
180+
# if: false # skip as we use nvidia image
181+
- run: python -m pip install -U uv
182+
- run: source/install/uv_with_retry.sh pip install --system --group pin_tensorflow_gpu --group pin_pytorch_gpu --group pin_jax_gpu
183+
- run: |
184+
export PYTORCH_ROOT=$(python -c 'import torch;print(torch.__path__[0])')
185+
export TENSORFLOW_ROOT=$(python -c 'import importlib.util,pathlib;print(pathlib.Path(importlib.util.find_spec("tensorflow").origin).parent)')
186+
pip install --find-links "https://www.paddlepaddle.org.cn/packages/nightly/cu126/paddlepaddle-gpu/" --index-url https://pypi.org/simple --trusted-host www.paddlepaddle.org.cn --trusted-host paddlepaddle.org.cn "paddlepaddle-gpu==3.4.0.dev20260310"
187+
source/install/uv_with_retry.sh pip install --system -v -e .[gpu,test,lmp,cu12,torch,jax] mpi4py --reinstall-package deepmd-kit
188+
# See https://github.com/jax-ml/jax/issues/29042
189+
source/install/uv_with_retry.sh pip install --system -U 'nvidia-cublas-cu12>=12.9.0.13'
190+
env:
191+
DP_VARIANT: cuda
192+
DP_ENABLE_NATIVE_OPTIMIZATION: 1
193+
DP_ENABLE_PYTORCH: 1
194+
- run: dp --version
195+
- name: Convert models
196+
run: source/tests/infer/convert-models.sh
197+
- run: |
155198
export LD_LIBRARY_PATH=$CUDA_PATH/lib64:/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH
156-
DP_CC_SKIP_BUILD=1 source/install/test_cc_local.sh
199+
source/install/test_cc_local.sh
157200
env:
158201
OMP_NUM_THREADS: 1
159202
TF_INTRA_OP_PARALLELISM_THREADS: 1
@@ -177,12 +220,12 @@ jobs:
177220
CUDA_VISIBLE_DEVICES: 0
178221
pass:
179222
name: Pass testing on CUDA
180-
needs: [test_cuda]
223+
needs: [test_python, test_cc]
181224
runs-on: ubuntu-slim
182225
if: always()
183226
steps:
184227
- name: Decide whether the needed jobs succeeded or failed
185228
uses: re-actors/alls-green@release/v1
186229
with:
187230
jobs: ${{ toJSON(needs) }}
188-
allowed-skips: test_cuda
231+
allowed-skips: test_python, test_cc

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ repos:
2323
- id: check-toml
2424
# Python
2525
- repo: https://github.com/PyCQA/isort
26-
rev: 9.0.0a3
26+
rev: 9.0.0b1
2727
hooks:
2828
- id: isort
2929
files: \.py$
3030
exclude: ^source/3rdparty
3131
- repo: https://github.com/astral-sh/ruff-pre-commit
3232
# Ruff version.
33-
rev: v0.15.18
33+
rev: v0.15.22
3434
hooks:
3535
- id: ruff
3636
args: ["--fix"]
@@ -62,7 +62,7 @@ repos:
6262
- mdformat-gfm-alerts==2.0.0
6363
# C++
6464
- repo: https://github.com/pre-commit/mirrors-clang-format
65-
rev: v22.1.5
65+
rev: v22.1.8
6666
hooks:
6767
- id: clang-format
6868
exclude: ^(source/3rdparty|source/lib/src/gpu/cudart/.+\.inc|.+\.ipynb$|source/tests/infer/.+\.json$)

source/lmp/pair_base.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ PairDeepBaseModel::PairDeepBaseModel(
437437
out_rel = 0;
438438
out_rel_v = 0;
439439
stdf_comm_buff_size = 0;
440+
counts = nullptr;
441+
displacements = nullptr;
442+
tagsend = nullptr;
443+
tagrecv = nullptr;
444+
stdfsend = nullptr;
445+
stdfrecv = nullptr;
440446
eps = 0.;
441447
eps_v = 0.;
442448
scale = NULL;
@@ -485,13 +491,40 @@ void PairDeepBaseModel::print_summary(const string pre) const {
485491
}
486492

487493
PairDeepBaseModel::~PairDeepBaseModel() {
494+
destroy_model_deviation_buffers();
488495
if (allocated) {
489496
memory->destroy(setflag);
490497
memory->destroy(cutsq);
491498
memory->destroy(scale);
492499
}
493500
}
494501

502+
void PairDeepBaseModel::ensure_model_deviation_buffers() {
503+
if (counts == nullptr) {
504+
memory->create(counts, comm->nprocs, "deepmd:counts");
505+
memory->create(displacements, comm->nprocs, "deepmd:displacements");
506+
}
507+
508+
const int ntotal = atom->natoms;
509+
if (ntotal > stdf_comm_buff_size) {
510+
memory->grow(stdfsend, ntotal, "deepmd:stdfsendall");
511+
memory->grow(stdfrecv, ntotal, "deepmd:stdfrecvall");
512+
memory->grow(tagsend, ntotal, "deepmd:tagsendall");
513+
memory->grow(tagrecv, ntotal, "deepmd:tagrecvall");
514+
stdf_comm_buff_size = ntotal;
515+
}
516+
}
517+
518+
void PairDeepBaseModel::destroy_model_deviation_buffers() {
519+
memory->destroy(counts);
520+
memory->destroy(displacements);
521+
memory->destroy(stdfsend);
522+
memory->destroy(stdfrecv);
523+
memory->destroy(tagsend);
524+
memory->destroy(tagrecv);
525+
stdf_comm_buff_size = 0;
526+
}
527+
495528
void PairDeepBaseModel::allocate() {
496529
allocated = 1;
497530
int n = atom->ntypes;
@@ -537,17 +570,7 @@ void PairDeepBaseModel::init_style() {
537570
// neighbor->requests[irequest]->newton = 2;
538571
#endif
539572
if (out_each == 1) {
540-
int ntotal = atom->natoms;
541-
int nprocs = comm->nprocs;
542-
if (ntotal > stdf_comm_buff_size) {
543-
stdf_comm_buff_size = ntotal;
544-
}
545-
memory->create(counts, nprocs, "deepmd:counts");
546-
memory->create(displacements, nprocs, "deepmd:displacements");
547-
memory->create(stdfsend, ntotal, "deepmd:stdfsendall");
548-
memory->create(stdfrecv, ntotal, "deepmd:stdfrecvall");
549-
memory->create(tagsend, ntotal, "deepmd:tagsendall");
550-
memory->create(tagrecv, ntotal, "deepmd:tagrecvall");
573+
ensure_model_deviation_buffers();
551574
}
552575
}
553576

source/lmp/pair_base.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ class PairDeepBaseModel : public Pair {
6161
deepmd_compat::DeepBaseModel& deep_base;
6262
deepmd_compat::DeepBaseModelDevi& deep_base_model_devi;
6363
virtual void allocate();
64+
// The model-deviation gather arrays are owned by this pair style. Keep
65+
// their allocation in one place so repeated LAMMPS initialization cannot
66+
// overwrite a live pointer and leak the previous allocation.
67+
void ensure_model_deviation_buffers();
68+
void destroy_model_deviation_buffers();
6469
double** scale;
6570
unsigned numb_models;
6671
double cutoff;

source/lmp/pair_deepmd.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -557,18 +557,7 @@ void PairDeepMD::compute(int eflag, int vflag) {
557557
// Gather std_f and tags
558558
tagint* tag = atom->tag;
559559
int nprocs = comm->nprocs;
560-
// Grow arrays if necessary
561-
if (atom->natoms > stdf_comm_buff_size) {
562-
stdf_comm_buff_size = atom->natoms;
563-
memory->destroy(stdfsend);
564-
memory->destroy(stdfrecv);
565-
memory->destroy(tagsend);
566-
memory->destroy(tagrecv);
567-
memory->create(stdfsend, stdf_comm_buff_size, "deepmd:stdfsendall");
568-
memory->create(stdfrecv, stdf_comm_buff_size, "deepmd:stdfrecvall");
569-
memory->create(tagsend, stdf_comm_buff_size, "deepmd:tagsendall");
570-
memory->create(tagrecv, stdf_comm_buff_size, "deepmd:tagrecvall");
571-
}
560+
ensure_model_deviation_buffers();
572561
for (int ii = 0; ii < nlocal; ii++) {
573562
tagsend[ii] = tag[ii];
574563
stdfsend[ii] = std_f[ii];

source/lmp/pair_deepspin.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -472,18 +472,7 @@ void PairDeepSpin::compute(int eflag, int vflag) {
472472
// Gather std_f and tags
473473
tagint* tag = atom->tag;
474474
int nprocs = comm->nprocs;
475-
// Grow arrays if necessary
476-
if (atom->natoms > stdf_comm_buff_size) {
477-
stdf_comm_buff_size = atom->natoms;
478-
memory->destroy(stdfsend);
479-
memory->destroy(stdfrecv);
480-
memory->destroy(tagsend);
481-
memory->destroy(tagrecv);
482-
memory->create(stdfsend, stdf_comm_buff_size, "deepmd:stdfsendall");
483-
memory->create(stdfrecv, stdf_comm_buff_size, "deepmd:stdfrecvall");
484-
memory->create(tagsend, stdf_comm_buff_size, "deepmd:tagsendall");
485-
memory->create(tagrecv, stdf_comm_buff_size, "deepmd:tagrecvall");
486-
}
475+
ensure_model_deviation_buffers();
487476
for (int ii = 0; ii < nlocal; ii++) {
488477
tagsend[ii] = tag[ii];
489478
stdfsend[ii] = std_f[ii];

source/lmp/tests/test_lammps.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,21 @@ def test_pair_deepmd_model_devi(lammps) -> None:
347347
assert md[3] == pytest.approx(np.sqrt(np.mean(np.square(expected_md_v))))
348348

349349

350+
def test_pair_deepmd_model_devi_atomic_reinit_clear_smoke(lammps) -> None:
351+
"""Smoke-test repeated pair initialization followed by pair destruction."""
352+
lammps.pair_style(
353+
f"deepmd {pb_file.resolve()} {pb_file2.resolve()} "
354+
f"out_file {md_file.resolve()} out_freq 1 atomic"
355+
)
356+
lammps.pair_coeff("* *")
357+
358+
# Re-enter init_style() on one pair, then destroy it. This guards against
359+
# future double-free or use-after-free mistakes in the buffer lifecycle.
360+
lammps.run(0)
361+
lammps.run(0)
362+
lammps.clear()
363+
364+
350365
def test_pair_deepmd_model_devi_virial(lammps) -> None:
351366
lammps.pair_style(
352367
f"deepmd {pb_file.resolve()} {pb_file2.resolve()} out_file {md_file.resolve()} out_freq 1 atomic"

source/op/tf/prod_env_mat_multi_device.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,8 +1964,11 @@ class ProdEnvMatAMixOp : public OpKernel {
19641964
deepmd::prod_env_mat_a_cpu(
19651965
em, em_deriv, rij, nlist, coord, type, inlist, max_nbor_size, avg,
19661966
std, nloc, frame_nall, rcut_r, rcut_r_smth, sec_a, f_type);
1967-
// do nlist mapping if coords were copied
1968-
_map_nei_info_cpu(nlist, ntype, nmask, type, &idx_mapping[0], nloc,
1967+
// The mapping is populated only when PBC coordinate copies are made.
1968+
// Other neighbor-list modes must not form a pointer into the empty
1969+
// vector.
1970+
_map_nei_info_cpu(nlist, ntype, nmask, type,
1971+
b_nlist_map ? idx_mapping.data() : nullptr, nloc,
19691972
nnei, ntypes, b_nlist_map);
19701973
}
19711974
}

0 commit comments

Comments
 (0)