Skip to content

Added support for AMD GPUs in "docker run --gpus". #49952

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

Merged
merged 1 commit into from
Jun 11, 2025

Conversation

sgopinath1
Copy link
Contributor

@sgopinath1 sgopinath1 commented May 10, 2025

This change adds support for AMD GPUs in docker run --gpus command.

- What I did

Added backend code to support the exact same interface used today for Nvidia GPUs, allowing customers to use the same docker commands for both Nvidia and AMD GPUs.

- How I did it

  • Followed the same approach as Nvidia by registering a new driver with gpu capability.
  • Similar to the Nvidia GPU driver, the AMD driver maps the --gpus input in the docker command to an Environment Variable, AMD_VISIBLE_DEVICES, that is handled by the AMD container runtime.
  • The AMD driver is registered only if the Nvidia container runtime is not installed on the system and the AMD container runtime is installed.

- How to verify it

AMD container runtime must be installed on the system to verify this functionality. AMD container runtime is expected to be published as an open-source project soon.

The following commands can be used to specify which GPUs are required inside the container and rocm-smi output can be used to verify the correct GPUs are made available inside the container.

  • To use all available GPUs

    docker run --runtime=amd --gpus all rocm/rocm-terminal rocm-smi

    OR

    docker run --runtime=amd --gpus device=all rocm/rocm-terminal rocm-smi

  • To use any 2 GPUs

    docker run --runtime=amd --gpus 2 rocm/rocm-terminal rocm-smi

  • To use a set of specific GPUs

    docker run --runtime=amd --gpus 1,2,3 rocm/rocm-terminal rocm-smi

    OR

    docker run --runtime=amd --gpus '"device=1,2,3"' rocm/rocm-terminal rocm-smi

- Human readable description for the release notes

Add support for AMD GPUs in `docker run --gpus`.

@elezar
Copy link
Contributor

elezar commented May 13, 2025

@sgopinath1 as a maintainer of the NVIDIA Container Toolkit and its components I would strongly recommend against using the environment variable to control this behaviour -- even as an interim solution. Adding this behaviour now means that we have to keep in mind when implemening a --gpus flag to CDI mapping as discussed in #49824.

@sgopinath1
Copy link
Contributor Author

sgopinath1 commented May 15, 2025

@elezar a couple of points:

  1. This PR is neither introducing new user-visible behavior nor changing the existing behavior. The backend code is also identical to the Nvidia driver. So, IMO, this should not add any new variables / considerations when we move to the long-term solution of mapping --gpus flag to CDI.
  2. The AMD container toolkit will support CDI. However, this PR is for customers who are insisting on parity with Nvidia w.r.t the --gpus flag. As I understand, there is no timeline for the long-term solution yet. We need to provide customers a way to use the --gpus flag with AMD GPUs asap.

@deke997
Copy link

deke997 commented May 17, 2025

We would love to be able to use --gpus for AMD!

BTW, the AMD Container Toolkit is now published: https://instinct.docs.amd.com/projects/container-toolkit/en/latest/container-runtime/overview.html

Comment on lines 56 to 63
// countToDevicesAMD returns the list 0, 1, ... count-1 of deviceIDs.
func countToDevicesAMD(count int) string {
devices := make([]string, count)
for i := range devices {
devices[i] = strconv.Itoa(i)
}
return strings.Join(devices, ",")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the same implementation as countToDevices in nvidia_linux.go. Does it make sense to just use that function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, makes sense. Changed accordingly.


const amdContainerRuntime = "amd-container-runtime"

func init() {
Copy link
Contributor

Choose a reason for hiding this comment

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

@sgopinath1 instead of having a separate init function for nvidia and amd GPUs, does it make sense to refactor this and the code in nvidia_linux.go to have a single init function that checks for the existence of the various executables and registers the drivers accordingly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I have modified the init function in nvidia_linux.go to register the AMD driver also.

@sgopinath1
Copy link
Contributor Author

@elezar thanks for reviewing. I have updated the code as per your suggestions. Please review.

const nvidiaHook = "nvidia-container-runtime-hook"
const (
nvidiaHook = "nvidia-container-runtime-hook"
amdHook = "amd-container-runtime"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
amdHook = "amd-container-runtime"
amdContainerRuntimeExecutableName = "amd-container-runtime"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done as suggested.

@sgopinath1
Copy link
Contributor Author

sgopinath1 commented May 26, 2025

@elezar Let me know if there are any further comments on the changes. Thanks.

@elezar
Copy link
Contributor

elezar commented May 27, 2025

LGTM

@thaJeztah
Copy link
Member

Could you do a quick rebase and squash the commits?

@sgopinath1
Copy link
Contributor Author

Could you do a quick rebase and squash the commits?

Done.

Comment on lines 55 to 56
} else {
// no "gpu" capability
}
Copy link
Member

Choose a reason for hiding this comment

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

Hm.. looks like the linter doesn't like this (even with a comment inside the branch 🤔)

daemon/nvidia_linux.go:55:9: SA9003: empty branch (staticcheck)
	} else {
	       ^

Comment on lines 38 to 56
if _, err := exec.LookPath(nvidiaHook); err == nil {
capset := capabilities.Set{"gpu": struct{}{}, "nvidia": struct{}{}}
nvidiaDriver := &deviceDriver{
capset: capset,
updateSpec: setNvidiaGPUs,
}
for c := range allNvidiaCaps {
nvidiaDriver.capset[string(c)] = struct{}{}
}
registerDeviceDriver("nvidia", nvidiaDriver)
} else if _, err := exec.LookPath(amdContainerRuntimeExecutableName); err == nil {
capset := capabilities.Set{"gpu": struct{}{}, "amd": struct{}{}}
amdDriver := &deviceDriver{
capset: capset,
updateSpec: setAMDGPUs,
}
registerDeviceDriver("amd", amdDriver)
} else {
// no "gpu" capability
}
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps an early return would work;

Suggested change
if _, err := exec.LookPath(nvidiaHook); err == nil {
capset := capabilities.Set{"gpu": struct{}{}, "nvidia": struct{}{}}
nvidiaDriver := &deviceDriver{
capset: capset,
updateSpec: setNvidiaGPUs,
}
for c := range allNvidiaCaps {
nvidiaDriver.capset[string(c)] = struct{}{}
}
registerDeviceDriver("nvidia", nvidiaDriver)
} else if _, err := exec.LookPath(amdContainerRuntimeExecutableName); err == nil {
capset := capabilities.Set{"gpu": struct{}{}, "amd": struct{}{}}
amdDriver := &deviceDriver{
capset: capset,
updateSpec: setAMDGPUs,
}
registerDeviceDriver("amd", amdDriver)
} else {
// no "gpu" capability
}
if _, err := exec.LookPath(nvidiaHook); err == nil {
capset := capabilities.Set{"gpu": struct{}{}, "nvidia": struct{}{}}
for c := range allNvidiaCaps {
capset[string(c)] = struct{}{}
}
registerDeviceDriver("nvidia", &deviceDriver{
capset: capset,
updateSpec: setNvidiaGPUs,
})
return
}
if _, err := exec.LookPath(amdContainerRuntimeExecutableName); err == nil {
registerDeviceDriver("amd", &deviceDriver{
capset: capabilities.Set{"gpu": struct{}{}, "amd": struct{}{}},
updateSpec: setAMDGPUs,
})
return
}
// no "gpu" capability

Copy link
Member

Choose a reason for hiding this comment

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

Curious though; should amd and nvidia be considered mutually exclusive? Would splitting this into two init funcs (once in the nvidia file, one in the amd file) and both register a driver (if present) work?

Copy link
Contributor

Choose a reason for hiding this comment

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

They are mutually exclusive, but I suggested that the same init function be used because @sgopinath1 was also checking for the NVIDIA runtime in the AMD init function. The intent was to ensure that the AMD runtime is not used if the NVIDIA hook is present and that the nvidia logic takes precedence.

It may be cleaner to repeat some code here to keep these logically separate.

Copy link
Member

Choose a reason for hiding this comment

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

Gotcha; yup, makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps an early return would work;

@thaJeztah Changed as suggested.

@sgopinath1
Copy link
Contributor Author

@thaJeztah Let me know if there are any further comments or the changes look good. Thanks.

@thaJeztah
Copy link
Member

I have renamed amd_linux.go to gpu_amd_linux.go in this PR. Could you please rename nvidia_linux.go in your PR?

It's probably good to rename it as part of this PR to have them both follow the same naming pattern; git should be smart enough to handle the rename if the other PR is rebased.

Can you do the above, and squash the commits ?

@thaJeztah
Copy link
Member

thaJeztah commented Jun 4, 2025

Hm... actually, I just realised that effectively the related selection code is in devices.go - perhaps my suggestion to use a common gpu_ prefix would make more sense to use devices_ as prefix; WDYT?

  • daemon/devices.go
  • daemon/devices_amd_linux.go
  • daemon/devices_nvidia_linux.go

Added backend code to support the exact same interface
used today for Nvidia GPUs, allowing customers to use
the same docker commands for both Nvidia and AMD GPUs.

Signed-off-by: Sudheendra Gopinath <[email protected]>

Reused common functions from nvidia_linux.go.

Removed duplicate code in amd_linux.go by reusing
the init() and countToDevices() functions in
nvidia_linux.go. AMD driver is registered in init().

Signed-off-by: Sudheendra Gopinath <[email protected]>

Renamed amd-container-runtime constant

Signed-off-by: Sudheendra Gopinath <[email protected]>

Removed empty branch to keep linter happy.

Also renamed amd_linux.go to gpu_amd_linux.go.

Signed-off-by: Sudheendra Gopinath <[email protected]>

Renamed nvidia_linux.go and gpu_amd_linux.go.

Signed-off-by: Sudheendra Gopinath <[email protected]>
@sgopinath1
Copy link
Contributor Author

Hm... actually, I just realised that effectively the related selection code is in devices.go - perhaps my suggestion to use a common gpu_ prefix would make more sense to use devices_ as prefix; WDYT?

  • daemon/devices.go
  • daemon/devices_amd_linux.go
  • daemon/devices_nvidia_linux.go

@thaJeztah I have renamed the files as above and squashed the commits.
cc: @elezar

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

thanks!

LGTM

@vvoland vvoland self-assigned this Jun 6, 2025
@vvoland vvoland requested review from vvoland and elezar June 6, 2025 17:31
Comment on lines +49 to +55
// Register AMD driver if AMD helper binary is present.
if _, err := exec.LookPath(amdContainerRuntimeExecutableName); err == nil {
registerDeviceDriver("amd", &deviceDriver{
capset: capabilities.Set{"gpu": struct{}{}, "amd": struct{}{}},
updateSpec: setAMDGPUs,
})
return
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's okay-ish for now, but since this already needs the AMD container toolkit to be installed... what I'd really love is:

  • Shell out to amd-ctk cdi generate to generate the actual CDI configs (on init, and then perhaps on every gpu requests?)
  • And then just rewrite the gpus value to a proper CDI device ID and pass it to the CDI driver

This way, the user won't need to pass --runtime=amd to override the container runtime to the AMD runc wrapper.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vvoland We should consider this as part of the long-term plan of migrating ---gpus to CDI format. @elezar mentioned that he would be working on a plan for this here.

@vvoland vvoland added this to the 28.3.0 milestone Jun 9, 2025
Copy link
Member

@laurazard laurazard left a comment

Choose a reason for hiding this comment

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

LGTM

@thaJeztah
Copy link
Member

I "hijacked" the original ticket to re-purpose it for the discussion on reimplementing --gpus through CDI; the ticket already had a lot of information captured around implementing through CDI, so I thought it was better to repurpose the ticket than to create a new one (and loosing that information), but we can still do so;

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

still LGTM

let's bring this one in

@thaJeztah thaJeztah merged commit 3b1d2f7 into moby:master Jun 11, 2025
231 of 232 checks passed
@vvoland vvoland added the kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny label Jun 13, 2025
@dmcgowan dmcgowan moved this from Open to Complete in Maintainer spotlight Jun 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/daemon docs/revisit impact/changelog kind/feature Functionality or other elements that the project doesn't currently have. Features are new and shiny status/2-code-review
Projects
Status: Complete
Development

Successfully merging this pull request may close these issues.

6 participants