Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

opencv: misc cleanups; fix CUDA build #339619

Merged
merged 2 commits into from
Sep 25, 2024
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
81 changes: 81 additions & 0 deletions pkgs/development/cuda-modules/tests/opencv-and-torch/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
cudaPackages,
lib,
writeGpuTestPython,
# Configuration flags
openCVFirst,
useOpenCVDefaultCuda,
useTorchDefaultCuda,
}:
let
inherit (lib.strings) optionalString;

openCVBlock = ''

import cv2
print("OpenCV version:", cv2.__version__)

# Ensure OpenCV can access the GPU.
assert cv2.cuda.getCudaEnabledDeviceCount() > 0, "No CUDA devices found for OpenCV"
print("OpenCV CUDA device:", cv2.cuda.printCudaDeviceInfo(cv2.cuda.getDevice()))

# Ensure OpenCV can access the GPU.
print(cv2.getBuildInformation())

a = cv2.cuda.GpuMat(size=(256, 256), type=cv2.CV_32S, s=1)
b = cv2.cuda.GpuMat(size=(256, 256), type=cv2.CV_32S, s=1)
c = int(cv2.cuda.sum(cv2.cuda.add(a, b))[0]) # OpenCV returns a Scalar float object.

assert c == 2 * 256 * 256, f"Expected {2 * 256 * 256} OpenCV, got {c}"

'';

torchBlock = ''

import torch
print("Torch version:", torch.__version__)

# Set up the GPU.
torch.cuda.init()
# Ensure the GPU is available.
assert torch.cuda.is_available(), "CUDA is not available to Torch"
print("Torch CUDA device:", torch.cuda.get_device_properties(torch.cuda.current_device()))

a = torch.ones(256, 256, dtype=torch.int32).cuda()
b = torch.ones(256, 256, dtype=torch.int32).cuda()
c = (a + b).sum().item()
assert c == 2 * 256 * 256, f"Expected {2 * 256 * 256} for Torch, got {c}"

'';

content = if openCVFirst then openCVBlock + torchBlock else torchBlock + openCVBlock;

torchName = "torch" + optionalString useTorchDefaultCuda "-with-default-cuda";
openCVName = "opencv4" + optionalString useOpenCVDefaultCuda "-with-default-cuda";
in
# TODO: Ensure the expected CUDA libraries are loaded.
# TODO: Ensure GPU access works as expected.
writeGpuTestPython {
name = if openCVFirst then "${openCVName}-then-${torchName}" else "${torchName}-then-${openCVName}";
libraries =
# NOTE: These are purposefully in this order.
pythonPackages:
let
effectiveOpenCV = pythonPackages.opencv4.override (prevAttrs: {
cudaPackages = if useOpenCVDefaultCuda then prevAttrs.cudaPackages else cudaPackages;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we referring to prevAttrs here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect we actually want s/prevAttrs.cudaPackages/finalAttrs/ here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also why do we need this branch, we cudaPackages passed from the caller aren't enough? What exactly are we testing here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why are we referring to prevAttrs here?

The override attribute also accepts a function of the form prevAttrs: <attr set>, where prevAttrs is the previous set of attributes passed to (or resolved by) callPackage.

I suspect we actually want s/prevAttrs.cudaPackages/finalAttrs/ here?

The cudaPackages provided to opencv4 should be the one given by top-level (or the one automatically provided by callPackage) when useOpenCVDefaultCuda is true, thus essentially passing through the previous value for cudaPackages. When useOpenCVDefaultCuda is false, cudaPackages should be the version of cudaPackages from the enclosing scope (e.g., cudaPackages_11_8 if the derivation is from cudaPackages_11_8.tests.<whatever>).

Also why do we need this branch, we cudaPackages passed from the caller aren't enough? What exactly are we testing here

In short, it's a matrix of tests of packages against versions of the cudaPackages package set.

Because the tests exist in each cudaPackages* package set, it makes sense to have a copy of the test which builds not with the cudaPackages argument supplied at the top-level or by callPackage, but with the cudaPackages from the enclosing scope. In this way, we're testing a single package (well, in the case of this PR, OpenCV and PyTorch) against multiple different versions of CUDA.

If we want to hold the version of cudaPackages used by both packages fixed (meaning they are either set to some specific version at the top-level due to compatibility requirements or use the default version by way of cudaPackages being populated by callPackage), it doesn't make sense for the test to live in cudaPackages.tests because the tests are irrespective of the version of cudaPackages.

Does that make sense?

});
effectiveTorch = pythonPackages.torchWithCuda.override (prevAttrs: {
cudaPackages = if useTorchDefaultCuda then prevAttrs.cudaPackages else cudaPackages;
});
in
if openCVFirst then
[
effectiveOpenCV
effectiveTorch
]
else
[
effectiveTorch
effectiveOpenCV
];
} content
Loading