mirror of https://github.com/portainer/portainer
fix(gpu) EE-3191 fix gpu bugs (#7451)
parent
36888b5ad4
commit
04eb718f88
|
@ -1,6 +1,10 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { components } from 'react-select';
|
import { components, MultiValue } from 'react-select';
|
||||||
import { OnChangeValue } from 'react-select/dist/declarations/src/types';
|
import { MultiValueRemoveProps } from 'react-select/dist/declarations/src/components/MultiValue';
|
||||||
|
import {
|
||||||
|
ActionMeta,
|
||||||
|
OnChangeValue,
|
||||||
|
} from 'react-select/dist/declarations/src/types';
|
||||||
import { OptionProps } from 'react-select/dist/declarations/src/components/Option';
|
import { OptionProps } from 'react-select/dist/declarations/src/components/Option';
|
||||||
|
|
||||||
import { Select } from '@@/form-components/ReactSelect';
|
import { Select } from '@@/form-components/ReactSelect';
|
||||||
|
@ -9,6 +13,7 @@ import { Tooltip } from '@@/Tip/Tooltip';
|
||||||
|
|
||||||
interface Values {
|
interface Values {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
useSpecific: boolean;
|
||||||
selectedGPUs: string[];
|
selectedGPUs: string[];
|
||||||
capabilities: string[];
|
capabilities: string[];
|
||||||
}
|
}
|
||||||
|
@ -81,6 +86,17 @@ function Option(props: OptionProps<GpuOption, true>) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MultiValueRemove(props: MultiValueRemoveProps<GpuOption, true>) {
|
||||||
|
const {
|
||||||
|
selectProps: { value },
|
||||||
|
} = props;
|
||||||
|
if (value && (value as MultiValue<GpuOption>).length === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return <components.MultiValueRemove {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function Gpu({
|
export function Gpu({
|
||||||
values,
|
values,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -97,6 +113,11 @@ export function Gpu({
|
||||||
: gpu.name,
|
: gpu.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options.unshift({
|
||||||
|
value: 'all',
|
||||||
|
label: 'Use All GPUs',
|
||||||
|
});
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [gpus, usedGpus, usedAllGpus]);
|
}, [gpus, usedGpus, usedAllGpus]);
|
||||||
|
|
||||||
|
@ -112,11 +133,22 @@ export function Gpu({
|
||||||
onChangeValues('enabled', !values.enabled);
|
onChangeValues('enabled', !values.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeSelectedGpus(newValue: OnChangeValue<GpuOption, true>) {
|
function onChangeSelectedGpus(
|
||||||
onChangeValues(
|
newValue: OnChangeValue<GpuOption, true>,
|
||||||
'selectedGPUs',
|
actionMeta: ActionMeta<GpuOption>
|
||||||
newValue.map((option) => option.value)
|
) {
|
||||||
);
|
let { useSpecific } = values;
|
||||||
|
let selectedGPUs = newValue.map((option) => option.value);
|
||||||
|
|
||||||
|
if (actionMeta.action === 'select-option') {
|
||||||
|
useSpecific = actionMeta.option?.value !== 'all';
|
||||||
|
selectedGPUs = selectedGPUs.filter((value) =>
|
||||||
|
useSpecific ? value !== 'all' : value === 'all'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValues = { ...values, selectedGPUs, useSpecific };
|
||||||
|
onChange(newValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeSelectedCaps(newValue: OnChangeValue<GpuOption, true>) {
|
function onChangeSelectedCaps(newValue: OnChangeValue<GpuOption, true>) {
|
||||||
|
@ -128,8 +160,9 @@ export function Gpu({
|
||||||
|
|
||||||
const gpuCmd = useMemo(() => {
|
const gpuCmd = useMemo(() => {
|
||||||
const devices = values.selectedGPUs.join(',');
|
const devices = values.selectedGPUs.join(',');
|
||||||
|
const deviceStr = devices === 'all' ? 'all,' : `device=${devices},`;
|
||||||
const caps = values.capabilities.join(',');
|
const caps = values.capabilities.join(',');
|
||||||
return `--gpus 'device=${devices},"capabilities=${caps}"`;
|
return `--gpus '${deviceStr}"capabilities=${caps}"'`;
|
||||||
}, [values.selectedGPUs, values.capabilities]);
|
}, [values.selectedGPUs, values.capabilities]);
|
||||||
|
|
||||||
const gpuValue = useMemo(
|
const gpuValue = useMemo(
|
||||||
|
@ -164,9 +197,12 @@ export function Gpu({
|
||||||
isMulti
|
isMulti
|
||||||
closeMenuOnSelect
|
closeMenuOnSelect
|
||||||
value={gpuValue}
|
value={gpuValue}
|
||||||
|
isClearable={false}
|
||||||
|
backspaceRemovesValue={false}
|
||||||
isDisabled={!values.enabled}
|
isDisabled={!values.enabled}
|
||||||
onChange={onChangeSelectedGpus}
|
onChange={onChangeSelectedGpus}
|
||||||
options={options}
|
options={options}
|
||||||
|
components={{ MultiValueRemove }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,11 +212,7 @@ export function Gpu({
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<div className="col-sm-3 col-lg-2 control-label text-left">
|
<div className="col-sm-3 col-lg-2 control-label text-left">
|
||||||
Capabilities
|
Capabilities
|
||||||
<Tooltip
|
<Tooltip message="‘compute’ and ‘utility’ capabilities are preselected by Portainer because they are used by default when you don’t explicitly specify capabilities with docker CLI ‘--gpus’ option." />
|
||||||
message={
|
|
||||||
"This is the generated equivalent of the '--gpus' docker CLI parameter based on your settings."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-sm-9 col-lg-10 text-left">
|
<div className="col-sm-9 col-lg-10 text-left">
|
||||||
<Select<GpuOption, true>
|
<Select<GpuOption, true>
|
||||||
|
|
|
@ -72,7 +72,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
GPU: {
|
GPU: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
useSpecific: false,
|
useSpecific: false,
|
||||||
selectedGPUs: [],
|
selectedGPUs: ['all'],
|
||||||
capabilities: ['compute', 'utility'],
|
capabilities: ['compute', 'utility'],
|
||||||
},
|
},
|
||||||
Console: 'none',
|
Console: 'none',
|
||||||
|
@ -469,28 +469,24 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
function prepareGPUOptions(config) {
|
function prepareGPUOptions(config) {
|
||||||
const driver = 'nvidia';
|
const driver = 'nvidia';
|
||||||
const gpuOptions = $scope.formValues.GPU;
|
const gpuOptions = $scope.formValues.GPU;
|
||||||
|
const existingDeviceRequest = _.find($scope.config.HostConfig.DeviceRequests, { Driver: driver });
|
||||||
const existingDeviceRequest = _.find($scope.config.HostConfig.DeviceRequests, function (o) {
|
|
||||||
return o.Driver === driver || o.Capabilities[0][0] === 'gpu';
|
|
||||||
});
|
|
||||||
if (existingDeviceRequest) {
|
if (existingDeviceRequest) {
|
||||||
_.pullAllBy(config.HostConfig.DeviceRequests, [existingDeviceRequest], 'Driver');
|
_.pullAllBy(config.HostConfig.DeviceRequests, [existingDeviceRequest], 'Driver');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gpuOptions.enabled) {
|
if (!gpuOptions.enabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const deviceRequest = {
|
||||||
const deviceRequest = existingDeviceRequest || {
|
|
||||||
Driver: driver,
|
Driver: driver,
|
||||||
Count: -1,
|
Count: -1,
|
||||||
DeviceIDs: [], // must be empty if Count != 0 https://github.com/moby/moby/blob/master/daemon/nvidia_linux.go#L50
|
DeviceIDs: [], // must be empty if Count != 0 https://github.com/moby/moby/blob/master/daemon/nvidia_linux.go#L50
|
||||||
Capabilities: [], // array of ORed arrays of ANDed capabilites = [ [c1 AND c2] OR [c1 AND c3] ] : https://github.com/moby/moby/blob/master/api/types/container/host_config.go#L272
|
Capabilities: [], // array of ORed arrays of ANDed capabilites = [ [c1 AND c2] OR [c1 AND c3] ] : https://github.com/moby/moby/blob/master/api/types/container/host_config.go#L272
|
||||||
// Options: { property1: "string", property2: "string" }, // seems to never be evaluated/used in docker API ?
|
// Options: { property1: "string", property2: "string" }, // seems to never be evaluated/used in docker API ?
|
||||||
};
|
};
|
||||||
|
if (gpuOptions.useSpecific) {
|
||||||
deviceRequest.DeviceIDs = gpuOptions.selectedGPUs;
|
deviceRequest.DeviceIDs = gpuOptions.selectedGPUs;
|
||||||
deviceRequest.Count = 0;
|
deviceRequest.Count = 0;
|
||||||
|
}
|
||||||
deviceRequest.Capabilities = [gpuOptions.capabilities];
|
deviceRequest.Capabilities = [gpuOptions.capabilities];
|
||||||
|
|
||||||
config.HostConfig.DeviceRequests.push(deviceRequest);
|
config.HostConfig.DeviceRequests.push(deviceRequest);
|
||||||
|
@ -661,6 +657,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||||
$scope.formValues.GPU.selectedGPUs = deviceRequest.DeviceIDs || [];
|
$scope.formValues.GPU.selectedGPUs = deviceRequest.DeviceIDs || [];
|
||||||
if ($scope.formValues.GPU.useSpecific) {
|
if ($scope.formValues.GPU.useSpecific) {
|
||||||
$scope.formValues.GPU.selectedGPUs = deviceRequest.DeviceIDs;
|
$scope.formValues.GPU.selectedGPUs = deviceRequest.DeviceIDs;
|
||||||
|
} else {
|
||||||
|
$scope.formValues.GPU.selectedGPUs = ['all'];
|
||||||
}
|
}
|
||||||
// we only support a single set of capabilities for now
|
// we only support a single set of capabilities for now
|
||||||
// UI needs to be reworked in order to support OR combinations of AND capabilities
|
// UI needs to be reworked in order to support OR combinations of AND capabilities
|
||||||
|
|
Loading…
Reference in New Issue