fix(gpu-service): address PR review on sliced scaling and theme logo

- Scale sliced CPU in millicores and RAM in MiB for precise, k8s-valid
  fractional allocations; show rounded decimals in the disabled inputs
- Disable the by-ratio option when the type has no sliced capacity
- Use the dark logo map under the dark theme in template cards
This commit is contained in:
jialin
2026-07-22 15:52:16 +08:00
parent 63ed6e777b
commit fe24f9b53d
3 changed files with 31 additions and 20 deletions
+20 -11
View File
@@ -282,30 +282,36 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
const fallbackCpu = resources.cpu; const fallbackCpu = resources.cpu;
// Sliced mode: scale a single card's unit resources by the chosen
// percentage (floored) — both CPU and RAM. Whole/CPU mode: multiply the
// unit by the count.
const percentage = _.toNumber( const percentage = _.toNumber(
resources.acceleratorSlicedMemoryPercentage resources.acceleratorSlicedMemoryPercentage
); );
const sliced = isGPUType && percentage > 0; const sliced = isGPUType && percentage > 0;
const wholeFactor = isGPUType ? accelerator : cpuCount; const wholeFactor = isGPUType ? accelerator : cpuCount;
const scale = (num: number, unit: string) => // Sliced mode: scale a single card's unit resources by the chosen
sliced // percentage. Scale CPU in millicores and RAM in MiB so fractional
? `${Math.max(1, _.floor((num * percentage) / 100))}${unit}` // slices stay precise and k8s-valid (integers) — e.g. 10% of a 4-core /
: `${wholeFactor * num}${unit}`; // 16Gi card → 400m / 1638Mi, not a rounded-up 1 core / 1Gi.
if (sliced && unitResourcesParsed) {
const cpuCores = unitResourcesParsed.cpu?.cores ?? 0;
const ramValue = unitResourcesParsed.ram?.value ?? 0;
return {
cpu: `${Math.max(1, _.floor((cpuCores * 1000 * percentage) / 100))}m`,
ram: `${Math.max(1, _.floor((ramValue * 1024 * percentage) / 100))}Mi`
};
}
// Whole / CPU mode: multiply the unit by the count.
return { return {
cpu: cpuNum cpu: cpuNum
? scale(cpuNum, unitResourcesParsed?.cpu?.unit || '') ? `${wholeFactor * cpuNum}${unitResourcesParsed?.cpu?.unit || ''}`
: // Don't stringify an unset value — `${undefined}` becomes the : // Don't stringify an unset value — `${undefined}` becomes the
// literal "undefined", which fails k8s quantity validation. // literal "undefined", which fails k8s quantity validation.
fallbackCpu fallbackCpu
? `${fallbackCpu}` ? `${fallbackCpu}`
: undefined, : undefined,
ram: ramNum ram: ramNum
? scale(ramNum, unitResourcesParsed?.ram?.unit || '') ? `${wholeFactor * ramNum}${unitResourcesParsed?.ram?.unit || ''}`
: resources.ram : resources.ram
}; };
}; };
@@ -329,13 +335,16 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
form.setFieldsValue({ form.setFieldsValue({
spec: { spec: {
resources: { resources: {
// Display the precise (rounded) fractional values — the inputs are
// disabled, so decimals are fine and match the submitted
// millicore / MiB allocation better than a floored integer.
cpu: cpu:
cpuCores != null && percentage > 0 cpuCores != null && percentage > 0
? Math.max(1, _.floor((cpuCores * percentage) / 100)) ? _.round((cpuCores * percentage) / 100, 2)
: null, : null,
ram: ram:
ramValue != null && percentage > 0 ramValue != null && percentage > 0
? Math.max(1, _.floor((ramValue * percentage) / 100)) ? _.round((ramValue * percentage) / 100, 2)
: null : null
} }
} }
@@ -134,6 +134,13 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
onSliceMemoryPercentageChange?.(value); onSliceMemoryPercentageChange?.(value);
}; };
// Max selectable ratio in sliced mode: status.onceMaxRequest.acceleratorSliced
// (a percentage). Ticks above it stay visible but disabled.
const slicedMaxPercentage =
_.toNumber(
selectedInstanceType?.status?.onceMaxRequest?.acceleratorSliced
) || 0;
const modeSegmented = showModeSwitch ? ( const modeSegmented = showModeSwitch ? (
<Segmented <Segmented
size="small" size="small"
@@ -149,7 +156,9 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
}, },
{ {
label: intl.formatMessage({ id: 'gpuservice.instance.mode.sliced' }), label: intl.formatMessage({ id: 'gpuservice.instance.mode.sliced' }),
value: 'sliced' value: 'sliced',
// No sliced capacity → keep the option visible but unselectable.
disabled: slicedMaxPercentage <= 0
} }
]} ]}
/> />
@@ -157,13 +166,6 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
const isSliced = showModeSwitch && sliceMode === 'sliced'; const isSliced = showModeSwitch && sliceMode === 'sliced';
// Max selectable ratio in sliced mode: status.onceMaxRequest.acceleratorSliced
// (a percentage). Ticks above it stay visible but disabled.
const slicedMaxPercentage =
_.toNumber(
selectedInstanceType?.status?.onceMaxRequest?.acceleratorSliced
) || 0;
// When the max ratio is below 10%, switch the ticks to a finer 1..10 scale // When the max ratio is below 10%, switch the ticks to a finer 1..10 scale
// so small slices are still selectable; otherwise use the 10..100 scale. // so small slices are still selectable; otherwise use the 10..100 scale.
const sliceTicks = const sliceTicks =
@@ -56,7 +56,7 @@ const matchImageLogo = (
isDark: boolean isDark: boolean
): { logo: string; type: string } | null => { ): { logo: string; type: string } | null => {
if (!image) return null; if (!image) return null;
const logoMap = imageLogoLightMap; const logoMap = isDark ? imageLogoDarkMap : imageLogoLightMap;
const lower = image.toLowerCase(); const lower = image.toLowerCase();
let matched: keyof typeof logoMap | null = null; let matched: keyof typeof logoMap | null = null;
let earliest = Infinity; let earliest = Infinity;