diff --git a/CMakeLists.txt b/CMakeLists.txt index d9ddd211ec8..b99ff10b794 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -212,7 +212,7 @@ if (NOT BUILD_PYTHON AND PYTHON_VERSIONS) message(WARNING "The BUILD_PYTHON option is set to off and the PYTHON_VERSIONS variable is provided. The latter will be ignored.") elseif (BUILD_PYTHON AND "${PYTHON_VERSIONS}" STREQUAL "") - set(PYTHON_VERSIONS "3.9;3.10;3.11;3.12;3.13") + set(PYTHON_VERSIONS "3.9;3.10;3.11;3.12;3.13;3.13t") endif () if (BUILD_PYTHON) diff --git a/conda/build_conda_packages.sh b/conda/build_conda_packages.sh index 31cd5de0b78..13d5a9ce6fc 100644 --- a/conda/build_conda_packages.sh +++ b/conda/build_conda_packages.sh @@ -78,7 +78,7 @@ export GIT_CLONE_PROTECTION_ACTIVE=true conda mambabuild ${CONDA_BUILD_OPTIONS} dali_native_libs/recipe # Building DALI python bindings package -conda mambabuild ${CONDA_BUILD_OPTIONS} --variants="{python: [3.10, 3.11, 3.12]}" dali_python_bindings/recipe +conda mambabuild ${CONDA_BUILD_OPTIONS} --variants="{python: [3.10, 3.11, 3.12, 3.13, 3.13t]}" dali_python_bindings/recipe # Copying the artifacts from conda prefix mkdir -p artifacts diff --git a/dali/python/nvidia/dali/backend.py b/dali/python/nvidia/dali/backend.py index eb24cce2b95..ce2af17d6e2 100644 --- a/dali/python/nvidia/dali/backend.py +++ b/dali/python/nvidia/dali/backend.py @@ -85,7 +85,7 @@ def deprecation_warning(what): "Please update your environment to use Python 3.9, " "or newer." ) - # py3.13 warning + # py3.13 warning. Handles both 3.13 and 3.13t. if sys.version_info[0] == 3 and sys.version_info[1] == 13: deprecation_warning("Python 3.13 support is experimental and not officially tested.") diff --git a/dali/test/python/free-threading/test_multithreading.py b/dali/test/python/free-threading/test_multithreading.py new file mode 100644 index 00000000000..83e62ce7e09 --- /dev/null +++ b/dali/test/python/free-threading/test_multithreading.py @@ -0,0 +1,69 @@ +import os +import numpy as np +import threading +from concurrent.futures import ThreadPoolExecutor +from nvidia.dali.pipeline import pipeline_def +import nvidia.dali.types as types +import nvidia.dali.fn as fn + +test_data_root = os.environ["DALI_EXTRA_PATH"] +image_file = os.path.join(test_data_root, "db", "single", "jpeg", "100", "swan-3584559_640.jpg") + +batch_size = 12 +prefetch_queue_depth = 3 +num_dali_threads = 8 +num_workers = 100 +test_input = [np.fromfile(image_file, dtype=np.uint8)] * batch_size + + +@pipeline_def( + batch_size=batch_size, + num_threads=num_dali_threads, + device_id=0, + prefetch_queue_depth=prefetch_queue_depth, +) +def dali_pipeline(): + enc = fn.external_source(name="INPUT") + img = fn.decoders.image(enc, device="mixed") + img = fn.resize(img, size=(224, 224)) + img = fn.crop_mirror_normalize( + img, + crop=(224, 224), + dtype=types.FLOAT, + mean=[0.485 * 255, 0.456 * 255, 0.406 * 255], + std=[0.229 * 255, 0.224 * 255, 0.225 * 255], + ) + return img + + +def run_thread(dali_pipeline, barrier, thread_id, results): + results[thread_id] = [] + barrier.wait() + dali_pipeline.build() + for _ in range(prefetch_queue_depth * 2): # Feed twice as many as the queue depth + dali_pipeline.feed_input("INPUT", test_input) + barrier.wait() + for _ in range(prefetch_queue_depth): + outputs = dali_pipeline.run() + results[thread_id].append(outputs[0].as_cpu().as_array()) + + +def test_parallel_pipelines(num_workers=num_workers): + """Test running two separate DALI pipelines in different threads.""" + results = {} + barrier = threading.Barrier(num_workers) + + with ThreadPoolExecutor(max_workers=num_workers) as executor: + futures = [ + executor.submit(run_thread, dali_pipeline(), barrier, i, results) + for i in range(num_workers) + ] + for future in futures: + future.result() + + assert len(results) == num_workers + ref = results[0][0] + for worker_id in range(num_workers): + assert len(results[worker_id]) == prefetch_queue_depth + for i in range(prefetch_queue_depth): + assert np.allclose(results[worker_id][i], ref) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8ed9a5d3f7f..57dc7904e8e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -13,9 +13,9 @@ ENV PYVER=${PYVER} \ PYTHONPATH=/opt/python/v \ PYBIN=${PYTHONPATH}/bin \ PYLIB=${PYTHONPATH}/lib \ - PATH=/opt/python/cp${PYV}-cp${PYV}/bin:/opt/python/cp39-cp39/bin:${PYBIN}:/opt/python/cp310-cp310/bin:/opt/python/cp311-cp311/bin:${PYBIN}:/opt/python/cp312-cp312/bin:/opt/python/cp313-cp313/bin:${PYBIN}:${PATH} \ - LD_LIBRARY_PATH=/opt/python/cp${PYV}-cp${PYV}/lib:/usr/local/lib:/opt/dali/${DALI_BUILD_DIR}:/opt/python/cp39-cp39/lib:/opt/python/cp310-cp310/lib:/opt/python/cp311-cp311/lib:/opt/python/cp312-cp312/lib:/opt/python/cp313-cp313/lib:${PYLIB}:${LD_LIBRARY_PATH} \ - LIBRARY_PATH=/opt/python/cp${PYV}-cp${PYV}/lib:/usr/local/lib:/opt/dali/${DALI_BUILD_DIR}:/opt/python/cp39-cp39/lib:/opt/python/cp310-cp310/lib:/opt/python/cp311-cp311/lib:/opt/python/cp312-cp312/lib:/opt/python/cp313-cp313/lib:${PYLIB}:${LIBRARY_PATH} + PATH=/opt/python/cp${PYV}-cp${PYV}/bin:/opt/python/cp39-cp39/bin:${PYBIN}:/opt/python/cp310-cp310/bin:/opt/python/cp311-cp311/bin:${PYBIN}:/opt/python/cp312-cp312/bin:/opt/python/cp313-cp313/bin:${PYBIN}:/opt/python/cp313-cp313t/bin:${PYBIN}:${PATH} \ + LD_LIBRARY_PATH=/opt/python/cp${PYV}-cp${PYV}/lib:/usr/local/lib:/opt/dali/${DALI_BUILD_DIR}:/opt/python/cp39-cp39/lib:/opt/python/cp310-cp310/lib:/opt/python/cp311-cp311/lib:/opt/python/cp312-cp312/lib:/opt/python/cp313-cp313/lib:/opt/python/cp313-cp313t/lib:${PYLIB}:${LD_LIBRARY_PATH} \ + LIBRARY_PATH=/opt/python/cp${PYV}-cp${PYV}/lib:/usr/local/lib:/opt/dali/${DALI_BUILD_DIR}:/opt/python/cp39-cp39/lib:/opt/python/cp310-cp310/lib:/opt/python/cp311-cp311/lib:/opt/python/cp312-cp312/lib:/opt/python/cp313-cp313/lib:${PYLIB}:/opt/python/cp313-cp313t/lib:${PYLIB}:${LIBRARY_PATH} RUN ln -s /opt/python/cp${PYV}* /opt/python/v diff --git a/docker/Dockerfile.build.aarch64-linux b/docker/Dockerfile.build.aarch64-linux index 1ecf787a1e8..1c8c322ce11 100644 --- a/docker/Dockerfile.build.aarch64-linux +++ b/docker/Dockerfile.build.aarch64-linux @@ -29,7 +29,7 @@ RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/ python3.10 python3.10-dev \ python3.11 python3.11-dev \ python3.12 python3.12-dev \ - python3.13 python3.13-dev && \ + python3.13 python3.13-dev python3.13-nogil && \ apt-key adv --fetch-key http://repo.download.nvidia.com/jetson/jetson-ota-public.asc && \ add-apt-repository 'deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/cross-linux-aarch64/ /' && \ apt-get update && \ @@ -95,6 +95,7 @@ RUN /bin/bash -c 'DALI_DEPS_VERSION_SHA=${DALI_DEPS_VERSION_SHA:-$(cat /tmp/DALI /tmp/dali_deps/build_scripts/build_deps.sh && rm -rf /tmp/dali_deps && rm -rf /tmp/DALI_DEPS_VERSION' # hack - install cross headers in the default python paths, so host python3-config would point to them +# free-threaded sources are included in 3.13.0 RUN export PYVERS="3.9.0 3.10.0 3.11.0 3.12.0 3.13.0" && \ for PYVER in ${PYVERS}; do \ cd /tmp && (curl -L https://www.python.org/ftp/python/${PYVER}/Python-${PYVER}.tgz | tar -xzf - || exit 1) && \ diff --git a/docker/build_helper.sh b/docker/build_helper.sh index b50a2dd35ec..5695eab3195 100755 --- a/docker/build_helper.sh +++ b/docker/build_helper.sh @@ -77,6 +77,16 @@ export TEST_BUNDLED_LIBS=${TEST_BUNDLED_LIBS:-YES} export PYTHON_VERSIONS=${PYTHON_VERSIONS} # use all available pythons +# PYTHON_GIL can be set to 0 only if Python is compiled with --disable-gil. +# Check if Python is compiled with --disable-gil. +set +e +python -c 'import sysconfig ; exit(sysconfig.get_config_var("Py_GIL_DISABLED"))' +# Set PYTHON_GIL accordingly. +if [ $? -ne 0 ]; then + export PYTHON_GIL=0 +fi +set -e + cmake ../ -DCMAKE_INSTALL_PREFIX=. \ -DARCH=${ARCH} \ -DCUDA_TARGET_ARCHS=${CUDA_TARGET_ARCHS} \ diff --git a/plugins/setup.py.in b/plugins/setup.py.in index da55ba01d1f..0fbb032bca6 100644 --- a/plugins/setup.py.in +++ b/plugins/setup.py.in @@ -70,13 +70,14 @@ if __name__ == "__main__": author='NVIDIA Corporation', license='Apache License 2.0', license_files = ('LICENSE', 'COPYRIGHT', 'Acknowledgements.txt'), - python_requires='>=3.8, <3.13', + python_requires='>=3.8, <3.14', classifiers=[ 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ], include_package_data=True, extras_require={}, diff --git a/qa/TL0_free-threading/test.sh b/qa/TL0_free-threading/test.sh new file mode 100644 index 00000000000..c04ab78d576 --- /dev/null +++ b/qa/TL0_free-threading/test.sh @@ -0,0 +1,2 @@ +#!/bin/bash -e +bash -e ./test_nofw.sh diff --git a/qa/TL0_free-threading/test_body.sh b/qa/TL0_free-threading/test_body.sh new file mode 100644 index 00000000000..99513b0503d --- /dev/null +++ b/qa/TL0_free-threading/test_body.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +# PYTHON_GIL can be set to 0 only if Python is compiled with --disable-gil. +# Check if Python is compiled with --disable-gil. +set +e +python3 -c 'import sysconfig ; exit(sysconfig.get_config_var("Py_GIL_DISABLED"))' +# Set PYTHON_GIL accordingly. +if [ $? -ne 0 ]; then + export PYTHON_GIL=0 +fi +set -e + +test_py_with_framework() { + ${python_new_invoke_test} -A '!slow' -s free-threading +} + +test_no_fw() { + test_py_with_framework +} + +run_all() { + test_no_fw +} diff --git a/qa/TL0_free-threading/test_nofw.sh b/qa/TL0_free-threading/test_nofw.sh new file mode 100755 index 00000000000..c3b02df9e98 --- /dev/null +++ b/qa/TL0_free-threading/test_nofw.sh @@ -0,0 +1,16 @@ +#!/bin/bash -e +# used pip packages +pip_packages='${python_test_runner_package} numpy' + +target_dir=./dali/test/python + +# test_body definition is in separate file so it can be used without setup +source test_body.sh + +test_body() { + test_no_fw +} + +pushd ../.. +source ./qa/test_template.sh +popd