fullwave25 1.4.0.dev0__tar.gz → 1.4.1.dev1__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 (82) hide show
  1. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/.bumpversion.toml +1 -1
  2. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/PKG-INFO +1 -1
  3. fullwave25-1.4.1.dev1/docker/Dockerfile +59 -0
  4. fullwave25-1.4.1.dev1/docker/Dockerfile.dockerignore +10 -0
  5. fullwave25-1.4.1.dev1/docker/README.md +78 -0
  6. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/__init__.py +1 -1
  7. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/binary_manager.py +1 -1
  8. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/cuda_utils.py +36 -0
  9. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/launcher.py +46 -0
  10. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/pyproject.toml +1 -1
  11. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/uv.lock +1 -1
  12. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/.gitignore +0 -0
  13. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/.pre-commit-config.yaml +0 -0
  14. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/.python-version +0 -0
  15. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/CITATION.cff +0 -0
  16. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/CONTRIBUTING.md +0 -0
  17. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/LICENSE +0 -0
  18. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/Makefile +0 -0
  19. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/README.md +0 -0
  20. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/attenuation.png +0 -0
  21. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/beamformer.m +0 -0
  22. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  23. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu +0 -0
  24. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  25. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_2d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  26. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124 +0 -0
  27. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/debug_solver_bin/fullwave2_3d_exponential_attenuation_multi_gpu_cuda124_ +0 -0
  28. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/experiments/__init__.py +0 -0
  29. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/experiments/exp_scatterer_diameter.py +0 -0
  30. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/beamformer/__init__.py +0 -0
  31. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/beamformer/beamformer.py +0 -0
  32. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/constants/__init__.py +0 -0
  33. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/constants/material_properties.py +0 -0
  34. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/grid.py +0 -0
  35. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium.py +0 -0
  36. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/__init__.py +0 -0
  37. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/domain.py +0 -0
  38. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/medium_builder.py +0 -0
  39. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/__init__.py +0 -0
  40. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/data/.keep +0 -0
  41. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/data/abdominal_wall/i2365f_etfw1.mat +0 -0
  42. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/domain_abdominal_wall.py +0 -0
  43. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/domain_background.py +0 -0
  44. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/domain_scatterer.py +0 -0
  45. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/domain_simple.py +0 -0
  46. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/medium_builder/presets/domain_water_gel.py +0 -0
  47. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/sensor.py +0 -0
  48. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/__init__.py +0 -0
  49. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda118 +0 -0
  50. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda124 +0 -0
  51. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda129 +0 -0
  52. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/2d/num_relax=2/fullwave2_2d_2_relax_multi_gpu_cuda130 +0 -0
  53. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda118 +0 -0
  54. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda124 +0 -0
  55. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda129 +0 -0
  56. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/_gpu/3d/num_relax=2/fullwave2_3d_2_relax_multi_gpu_cuda130 +0 -0
  57. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513.mat +0 -0
  58. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/database/relaxation_params_database_num_relax=3_20260828_1513_invalid_cells.json +0 -0
  59. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456.mat +0 -0
  60. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/bins/database/relaxation_params_database_num_relax=4_20260828_1456_invalid_cells.json +0 -0
  61. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/input_file_writer.py +0 -0
  62. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/pml_builder.py +0 -0
  63. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/shipped_database.py +0 -0
  64. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/solver.py +0 -0
  65. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/source_type.py +0 -0
  66. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/solver/utils.py +0 -0
  67. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/source.py +0 -0
  68. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/transducer.py +0 -0
  69. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/transmit.py +0 -0
  70. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/__init__.py +0 -0
  71. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/check_functions.py +0 -0
  72. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/coordinates.py +0 -0
  73. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/memory_tempfile.py +0 -0
  74. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/numerical.py +0 -0
  75. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/plot_utils.py +0 -0
  76. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/pulse.py +0 -0
  77. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/relaxation_parameters.py +0 -0
  78. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/scatterer.py +0 -0
  79. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/signal_filter.py +0 -0
  80. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/fullwave/utils/signal_process.py +0 -0
  81. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/ruff.toml +0 -0
  82. {fullwave25-1.4.0.dev0 → fullwave25-1.4.1.dev1}/signal_filter_example.png +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.4.0-dev0"
2
+ current_version = "1.4.1-dev1"
3
3
  parse = """(?x)
4
4
  (?P<major>0|[1-9]\\d*)\\.
5
5
  (?P<minor>0|[1-9]\\d*)\\.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fullwave25
3
- Version: 1.4.0-dev0
3
+ Version: 1.4.1-dev1
4
4
  Summary: Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities
5
5
  Author: Masashi Sode, Gianmarco Pinton
6
6
  Maintainer: Masashi Sode
@@ -0,0 +1,59 @@
1
+ # fullwave25 with the released solver binaries, for Linux systems whose glibc is older than
2
+ # the binaries need (2.34, for example Ubuntu 20.04 or RHEL 8).
3
+ #
4
+ # Build from the repository root:
5
+ # docker build -f docker/Dockerfile -t fullwave25 .
6
+ #
7
+ # See docker/README.md for how to run it with Docker or Apptainer.
8
+
9
+ FROM ubuntu:22.04
10
+
11
+ # The release tag of the solver binaries, and the CUDA versions to include. The wrapper
12
+ # picks the binary that matches the CUDA version of the host driver.
13
+ ARG FULLWAVE_BIN_TAG=fullwave_bin_v1.5.2
14
+ ARG FULLWAVE_BIN_CUDA_VERSIONS="118 124 129 130"
15
+ ARG PYTHON_VERSION=3.12
16
+
17
+ ENV DEBIAN_FRONTEND=noninteractive
18
+ RUN apt-get update \
19
+ && apt-get install -y --no-install-recommends ca-certificates curl ffmpeg \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ COPY --from=ghcr.io/astral-sh/uv:0.9.9 /uv /usr/local/bin/uv
23
+
24
+ ENV UV_PYTHON_INSTALL_DIR=/opt/python \
25
+ UV_LINK_MODE=copy \
26
+ UV_COMPILE_BYTECODE=1 \
27
+ VIRTUAL_ENV=/opt/venv \
28
+ PATH=/opt/venv/bin:${PATH}
29
+ RUN uv venv --python ${PYTHON_VERSION} /opt/venv
30
+
31
+ COPY . /opt/fullwave25-src
32
+ # The [ctk] extra installs the CUDA libraries and headers that CuPy needs at run time.
33
+ RUN uv pip install --no-cache /opt/fullwave25-src \
34
+ && uv pip install --no-cache "cupy-cuda12x[ctk]==$(python -c "import importlib.metadata as m; print(m.version('cupy-cuda12x'))")"
35
+
36
+ # Put the solver binaries where the installed package looks first, so a run needs no
37
+ # download and works when the home directory is mounted from the host (Apptainer).
38
+ RUN set -eu; \
39
+ bins="$(python -c "import importlib.util; print(importlib.util.find_spec('fullwave').submodule_search_locations[0])")/solver/bins"; \
40
+ url="https://github.com/pinton-lab/fullwave25/releases/download/${FULLWAVE_BIN_TAG}"; \
41
+ for cuda in ${FULLWAVE_BIN_CUDA_VERSIONS}; do \
42
+ for dim in 2d 3d; do \
43
+ mkdir -p "${bins}/gpu/${dim}" "${bins}/exponential_attenuation/gpu/${dim}"; \
44
+ curl -fsSL -o "${bins}/gpu/${dim}/fullwave2_${dim}_n_relax_multi_gpu_cuda${cuda}" \
45
+ "${url}/fullwave2_${dim}_n_relax_multi_gpu_cuda${cuda}"; \
46
+ curl -fsSL -o "${bins}/exponential_attenuation/gpu/${dim}/fullwave2_${dim}_exponential_attenuation_multi_gpu_cuda${cuda}" \
47
+ "${url}/fullwave2_${dim}_exponential_attenuation_multi_gpu_cuda${cuda}"; \
48
+ done; \
49
+ done; \
50
+ chmod 755 "${bins}"/gpu/*/* "${bins}"/exponential_attenuation/gpu/*/*; \
51
+ echo "${FULLWAVE_BIN_TAG}" > "${bins}/RELEASE_TAG"
52
+
53
+ # A container started with --user has no home of its own, so the caches of CuPy,
54
+ # matplotlib and numba go to /tmp. Apptainer sets HOME to the host home instead.
55
+ ENV HOME=/tmp \
56
+ NUMBA_CACHE_DIR=/tmp/numba
57
+
58
+ WORKDIR /work
59
+ CMD ["python"]
@@ -0,0 +1,10 @@
1
+ # Only the files needed to install the package go into the build context.
2
+ *
3
+ !fullwave
4
+ !pyproject.toml
5
+ !README.md
6
+ !LICENSE
7
+ fullwave/solver/bins/gpu
8
+ fullwave/solver/bins/exponential_attenuation
9
+ fullwave/solver/bins/_*
10
+ **/__pycache__
@@ -0,0 +1,78 @@
1
+ # fullwave25 in a container
2
+
3
+ The released solver binaries need glibc 2.34 or newer (for example Ubuntu 22.04, Debian 12 or RHEL 9). On an older system, such as Ubuntu 20.04 or RHEL 8, they stop at start with an error like:
4
+
5
+ ```text
6
+ /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found
7
+ ```
8
+
9
+ This image runs fullwave25 and the released solver binaries on such a system. The host needs only an NVIDIA driver for CUDA 12.0 or newer, and Docker with the NVIDIA Container Toolkit, or Apptainer.
10
+
11
+ The image contains:
12
+
13
+ - Ubuntu 22.04
14
+ - Python 3.12 and fullwave25, installed from this repository checkout, with the CUDA 12 libraries that CuPy needs
15
+ - the solver binaries of the release `fullwave_bin_v1.5.2`, for CUDA 11.8, 12.4, 12.9 and 13.0
16
+ - `ffmpeg`, for the video export of the plotting functions
17
+
18
+ ## Build
19
+
20
+ Run from the repository root:
21
+
22
+ ```sh
23
+ docker build -f docker/Dockerfile -t fullwave25 .
24
+ ```
25
+
26
+ To use another release of the solver binaries:
27
+
28
+ ```sh
29
+ docker build -f docker/Dockerfile -t fullwave25 --build-arg FULLWAVE_BIN_TAG=<release tag> .
30
+ ```
31
+
32
+ ## Run with Docker
33
+
34
+ Mount your working directory at `/work` and run your script there. The outputs are written to your working directory, owned by your user.
35
+
36
+ ```sh
37
+ docker run --rm --gpus all --user "$(id -u):$(id -g)" \
38
+ -v "$PWD":/work fullwave25 python my_simulation.py
39
+ ```
40
+
41
+ If `--gpus all` is not available, use `--device nvidia.com/gpu=all` (CDI).
42
+
43
+ To use selected GPUs, pass them to fullwave25 as usual with `cuda_device_id` in `fullwave.Solver`.
44
+
45
+ ## Run with Apptainer
46
+
47
+ Apptainer runs the same image without Docker. Build the image on a machine with Docker, and save it to a file:
48
+
49
+ ```sh
50
+ docker save fullwave25 -o fullwave25.tar
51
+ ```
52
+
53
+ Copy `fullwave25.tar` to the target machine, and convert it there:
54
+
55
+ ```sh
56
+ apptainer build fullwave25.sif docker-archive://fullwave25.tar
57
+ ```
58
+
59
+ The saved image is about 4.3 GB and the `.sif` file about 2.3 GB, and the conversion needs temporary space of several GB in addition. If `/tmp` is small, set `APPTAINER_TMPDIR` and `APPTAINER_CACHEDIR` to a folder on a larger disk.
60
+
61
+ Then run your script. `--nv` gives the container the GPUs and the driver of the host. Apptainer mounts your home and the current directory by default.
62
+
63
+ ```sh
64
+ apptainer exec --nv fullwave25.sif python my_simulation.py
65
+ ```
66
+
67
+ If Apptainer prints `Error changing the container working directory`, your working directory is reached through a link to another disk. Run from its real path:
68
+
69
+ ```sh
70
+ cd "$(pwd -P)"
71
+ ```
72
+
73
+ ## Check the installation
74
+
75
+ ```sh
76
+ apptainer exec --nv fullwave25.sif python -c "import fullwave; print(fullwave.__version__)"
77
+ docker run --rm --gpus all fullwave25 nvidia-smi -L
78
+ ```
@@ -66,7 +66,7 @@ try:
66
66
  __version__ = version("fullwave")
67
67
  except PackageNotFoundError:
68
68
  # Update via bump-my-version, not manually
69
- __version__ = "1.4.0-dev0"
69
+ __version__ = "1.4.1-dev1"
70
70
 
71
71
  VERSION = __version__ # for convenience
72
72
  logger.info("Fullwave version: %s", __version__)
@@ -28,7 +28,7 @@ CACHE_DIR = Path.home() / ".cache" / "fullwave25" / "bins"
28
28
 
29
29
  # Pinned release tag for the solver binaries.
30
30
  # Update this only when new binaries are uploaded to a GitHub release.
31
- BINARY_RELEASE_TAG = "fullwave_bin_v1.5"
31
+ BINARY_RELEASE_TAG = "fullwave_bin_v1.5.2"
32
32
 
33
33
 
34
34
  def _download_url(filename: str, tag: str) -> str:
@@ -213,6 +213,42 @@ def cuCtxDetach(ctx): # noqa: ANN001, ANN201, D103, N802
213
213
  return cuda.cuCtxDetach(ctx)
214
214
 
215
215
 
216
+ @cuda_api_call
217
+ def cuDeviceCanAccessPeer(can_access, dev, peer_dev): # noqa: ANN001, ANN201, D103, N802
218
+ return cuda.cuDeviceCanAccessPeer(can_access, dev, peer_dev)
219
+
220
+
221
+ def find_consecutive_gpus_without_peer_access(ordinals: list[int]) -> list[tuple[int, int]]:
222
+ """Return the consecutive GPU pairs that cannot access each other's memory directly.
223
+
224
+ Parameters
225
+ ----------
226
+ ordinals : list[int]
227
+ CUDA device ordinals as this process sees them, in the requested order.
228
+
229
+ Returns
230
+ -------
231
+ list[tuple[int, int]]
232
+ Each consecutive pair (a, b) of ``ordinals`` where a cannot access b or b
233
+ cannot access a.
234
+
235
+ """
236
+ cuInit(0)
237
+ pairs = []
238
+ for first, second in zip(ordinals, ordinals[1:], strict=False):
239
+ device_first = ctypes.c_int()
240
+ device_second = ctypes.c_int()
241
+ cuDeviceGet(ctypes.byref(device_first), first)
242
+ cuDeviceGet(ctypes.byref(device_second), second)
243
+ forward = ctypes.c_int()
244
+ backward = ctypes.c_int()
245
+ cuDeviceCanAccessPeer(ctypes.byref(forward), device_first, device_second)
246
+ cuDeviceCanAccessPeer(ctypes.byref(backward), device_second, device_first)
247
+ if not forward.value or not backward.value:
248
+ pairs.append((first, second))
249
+ return pairs
250
+
251
+
216
252
  # Main function to get CUDA device specs
217
253
  @lru_cache(maxsize=1)
218
254
  def get_cuda_device_specs() -> list[dict[str, Any]]:
@@ -1,5 +1,6 @@
1
1
  """Module for launching Fullwave simulation."""
2
2
 
3
+ import ctypes
3
4
  import logging
4
5
  import os
5
6
  import re
@@ -79,6 +80,8 @@ class Launcher:
79
80
  cuda_device_id,
80
81
  verify_gpu=verify_gpu,
81
82
  )
83
+ if use_gpu and verify_gpu:
84
+ self._warn_if_gpus_lack_peer_access(self.cuda_device_id)
82
85
  self.save_gpu_memory = save_gpu_memory
83
86
  logger.debug("Launcher instance created.")
84
87
 
@@ -160,6 +163,49 @@ class Launcher:
160
163
  message = f"CUDA device ID {device_id} does not exist."
161
164
  raise ValueError(message)
162
165
 
166
+ @staticmethod
167
+ def _warn_if_gpus_lack_peer_access(device_id_str: str) -> None:
168
+ """Warn when consecutive GPUs of a multi-GPU run have no peer-to-peer access.
169
+
170
+ Without peer access (no NVLink, and a PCIe chipset or GPU that refuses it,
171
+ as on GeForce cards), a multi-GPU run is much slower than with peer access.
172
+
173
+ The binary runs with ``CUDA_VISIBLE_DEVICES`` set to ``device_id_str``. The
174
+ CUDA ordinals of this process name the same GPUs only when this process has
175
+ no ``CUDA_VISIBLE_DEVICES`` of its own, so the check is skipped otherwise.
176
+ It never raises: if the CUDA driver cannot answer, it logs at debug level.
177
+
178
+ Parameters
179
+ ----------
180
+ device_id_str : str
181
+ The CUDA device ID(s) in string format, for example "0,1,2,3".
182
+
183
+ """
184
+ device_ids = [int(device_id) for device_id in device_id_str.split(",")]
185
+ if len(device_ids) < 2:
186
+ return
187
+ if "CUDA_VISIBLE_DEVICES" in os.environ:
188
+ logger.debug(
189
+ "Peer access check skipped: CUDA_VISIBLE_DEVICES is set in this process.",
190
+ )
191
+ return
192
+ try:
193
+ from .cuda_utils import find_consecutive_gpus_without_peer_access # noqa: PLC0415
194
+
195
+ pairs = find_consecutive_gpus_without_peer_access(device_ids)
196
+ except (AttributeError, NameError, OSError, RuntimeError, ctypes.ArgumentError) as error:
197
+ logger.debug("Peer access check skipped: %s", error)
198
+ return
199
+ if not pairs:
200
+ return
201
+ pair_text = ", ".join(f"GPU {first} <-> GPU {second}" for first, second in pairs)
202
+ logger.warning(
203
+ "No peer-to-peer access between %s. A multi-GPU run on these GPUs is much "
204
+ "slower than on GPUs with NVLink or PCIe peer access. Check with a short "
205
+ "run whether fewer GPUs give a faster simulation.",
206
+ pair_text,
207
+ )
208
+
163
209
  @staticmethod
164
210
  def _configure_cuda_device_id(
165
211
  cuda_device_id: str | int | list | None,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fullwave25"
3
- version = "1.4.0-dev0" # Update via bump-my-version, not manually
3
+ version = "1.4.1-dev1" # Update via bump-my-version, not manually
4
4
  description = "Fullwave 2.5: Ultrasound wave propagation simulation with heterogeneous power law attenuation modelling capabilities"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -761,7 +761,7 @@ wheels = [
761
761
 
762
762
  [[package]]
763
763
  name = "fullwave25"
764
- version = "1.4.0.dev0"
764
+ version = "1.4.1.dev1"
765
765
  source = { editable = "." }
766
766
  dependencies = [
767
767
  { name = "cupy-cuda12x" },
File without changes