|
7 | 7 | addNumberWithUnits, |
8 | 8 | compareNumberWithUnits, |
9 | 9 | convertToBinaryUnit, |
| 10 | + InputSizeUnit, |
10 | 11 | } from '../../helper'; |
11 | 12 | import { useSuspendedBackendaiClient } from '../../hooks'; |
12 | 13 | import { useResourceSlots } from '../../hooks/backendai'; |
@@ -62,6 +63,58 @@ export const isMinOversMaxValue = (min: number, max: number) => { |
62 | 63 | return min >= max; |
63 | 64 | }; |
64 | 65 |
|
| 66 | +/** |
| 67 | + * Returns true when the given accelerator slot name represents a unified |
| 68 | + * memory architecture, where the accelerator memory and the host memory |
| 69 | + * share a single physical pool. Identified by a `.unified` suffix on the |
| 70 | + * slot name (e.g. `cuda.unified`). |
| 71 | + */ |
| 72 | +export const isUnifiedAcceleratorSlot = (slotName?: string | null): boolean => { |
| 73 | + return !!slotName && _.endsWith(slotName, '.unified'); |
| 74 | +}; |
| 75 | + |
| 76 | +/** |
| 77 | + * Maps a slot's `display_unit` (e.g. `GiB`, `MiB`, `TiB`) to the |
| 78 | + * corresponding `InputSizeUnit` accepted by `convertToBinaryUnit`. Falls |
| 79 | + * back to `'g'` (GiB) when the display unit is missing or not a recognized |
| 80 | + * binary size unit. |
| 81 | + */ |
| 82 | +export const displayUnitToInputSizeUnit = ( |
| 83 | + displayUnit?: string | null, |
| 84 | +): InputSizeUnit => { |
| 85 | + const first = (_.first(displayUnit ?? '') ?? '').toLowerCase(); |
| 86 | + const validUnits: ReadonlyArray<InputSizeUnit> = [ |
| 87 | + 'k', |
| 88 | + 'm', |
| 89 | + 'g', |
| 90 | + 't', |
| 91 | + 'p', |
| 92 | + 'e', |
| 93 | + ]; |
| 94 | + const match = validUnits.find((u) => u === first); |
| 95 | + return match ?? 'g'; |
| 96 | +}; |
| 97 | + |
| 98 | +/** |
| 99 | + * Returns the accelerator-field number derived from the host memory value |
| 100 | + * for a unified-memory slot. Reads the slot's `display_unit` from |
| 101 | + * `mergedResourceSlots` and converts `mem` to that unit. Falls back to |
| 102 | + * `'g'` (GiB) when the display unit is unavailable. |
| 103 | + */ |
| 104 | +export const getUnifiedAcceleratorValueFromMem = ( |
| 105 | + mem: string | number | undefined, |
| 106 | + slotName: string | undefined, |
| 107 | + mergedResourceSlots: |
| 108 | + | Record<string, { display_unit?: string } | undefined> |
| 109 | + | undefined, |
| 110 | +): number => { |
| 111 | + const displayUnit = slotName |
| 112 | + ? mergedResourceSlots?.[slotName]?.display_unit |
| 113 | + : undefined; |
| 114 | + const targetUnit = displayUnitToInputSizeUnit(displayUnit); |
| 115 | + return convertToBinaryUnit(mem || '0g', targetUnit)?.number ?? 0; |
| 116 | +}; |
| 117 | + |
65 | 118 | export interface ResourceAllocationFormValue { |
66 | 119 | resource: { |
67 | 120 | cpu: number; |
@@ -864,6 +917,32 @@ const ResourceAllocationFormItems: React.FC< |
864 | 917 | ) { |
865 | 918 | runShmemAutomationRule(M_plus_S || '0g'); |
866 | 919 | } |
| 920 | + // When the active accelerator slot is a |
| 921 | + // unified-memory slot, the accelerator field |
| 922 | + // shares a single pool with host memory. |
| 923 | + // Derive the accelerator value from `mem` |
| 924 | + // at the source of change rather than via a |
| 925 | + // watcher effect, so submit-time form state |
| 926 | + // is always consistent. |
| 927 | + const activeAcceleratorType = |
| 928 | + form.getFieldValue([ |
| 929 | + 'resource', |
| 930 | + 'acceleratorType', |
| 931 | + ]); |
| 932 | + if ( |
| 933 | + isUnifiedAcceleratorSlot( |
| 934 | + activeAcceleratorType, |
| 935 | + ) |
| 936 | + ) { |
| 937 | + form.setFieldValue( |
| 938 | + ['resource', 'accelerator'], |
| 939 | + getUnifiedAcceleratorValueFromMem( |
| 940 | + M_plus_S, |
| 941 | + activeAcceleratorType, |
| 942 | + mergedResourceSlots, |
| 943 | + ), |
| 944 | + ); |
| 945 | + } |
867 | 946 | }} |
868 | 947 | /> |
869 | 948 | </Form.Item> |
@@ -921,6 +1000,10 @@ const ResourceAllocationFormItems: React.FC< |
921 | 1000 | currentAcceleratorType as keyof typeof resourceSlots |
922 | 1001 | ] === 'unique'; |
923 | 1002 |
|
| 1003 | + const isUnifiedType = isUnifiedAcceleratorSlot( |
| 1004 | + currentAcceleratorType, |
| 1005 | + ); |
| 1006 | + |
924 | 1007 | const isSingleCluster = |
925 | 1008 | form.getFieldValue('cluster_size') < 2; |
926 | 1009 | const hasQuantumSize = _.isNumber( |
@@ -965,9 +1048,15 @@ const ResourceAllocationFormItems: React.FC< |
965 | 1048 | /> |
966 | 1049 | ), |
967 | 1050 | }} |
| 1051 | + extra={ |
| 1052 | + isUnifiedType |
| 1053 | + ? t('session.launcher.UnifiedAcceleratorMemoryNote') |
| 1054 | + : undefined |
| 1055 | + } |
968 | 1056 | dependencies={[ |
969 | 1057 | ['resource', 'acceleratorType'], |
970 | 1058 | 'cluster_size', |
| 1059 | + ['resource', 'mem'], |
971 | 1060 | ]} |
972 | 1061 | rules={[ |
973 | 1062 | { |
@@ -1139,7 +1228,8 @@ const ResourceAllocationFormItems: React.FC< |
1139 | 1228 | }, |
1140 | 1229 | }} |
1141 | 1230 | disabled={ |
1142 | | - supportedAcceleratorTypesInRGByImage?.length === 0 |
| 1231 | + supportedAcceleratorTypesInRGByImage?.length === |
| 1232 | + 0 || isUnifiedType |
1143 | 1233 | } |
1144 | 1234 | min={0} |
1145 | 1235 | max={ |
@@ -1193,6 +1283,40 @@ const ResourceAllocationFormItems: React.FC< |
1193 | 1283 | }; |
1194 | 1284 | }, |
1195 | 1285 | )} |
| 1286 | + onChange={(nextType: string) => { |
| 1287 | + // Keep the accelerator field consistent |
| 1288 | + // at the moment the slot type changes, |
| 1289 | + // rather than relying on a watcher |
| 1290 | + // effect that runs after render. |
| 1291 | + if (isUnifiedAcceleratorSlot(nextType)) { |
| 1292 | + // Switching INTO a unified slot: |
| 1293 | + // mirror the current `mem` value |
| 1294 | + // converted to the slot's display |
| 1295 | + // unit. |
| 1296 | + form.setFieldValue( |
| 1297 | + ['resource', 'accelerator'], |
| 1298 | + getUnifiedAcceleratorValueFromMem( |
| 1299 | + form.getFieldValue([ |
| 1300 | + 'resource', |
| 1301 | + 'mem', |
| 1302 | + ]), |
| 1303 | + nextType, |
| 1304 | + mergedResourceSlots, |
| 1305 | + ), |
| 1306 | + ); |
| 1307 | + } else { |
| 1308 | + // Switching OUT of a unified slot |
| 1309 | + // into a discrete one: reset to the |
| 1310 | + // discrete slot's min so the stale |
| 1311 | + // mirrored value does not bleed |
| 1312 | + // through. |
| 1313 | + form.setFieldValue( |
| 1314 | + ['resource', 'accelerator'], |
| 1315 | + resourceLimits.accelerators[nextType] |
| 1316 | + ?.min ?? 0, |
| 1317 | + ); |
| 1318 | + } |
| 1319 | + }} |
1196 | 1320 | /> |
1197 | 1321 | </Form.Item> |
1198 | 1322 | ) : undefined, |
|
0 commit comments