@aws/ml-container-creator 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +5 -2
- package/config/bootstrap-stack.json +40 -9
- package/infra/ci-harness/buildspec.yml +60 -0
- package/infra/ci-harness/package-lock.json +5 -1
- package/package.json +1 -1
- package/servers/README.md +41 -1
- package/servers/instance-sizer/index.js +10 -4
- package/servers/instance-sizer/lib/model-resolver.js +1 -1
- package/servers/lib/catalogs/model-sizes.json +135 -90
- package/servers/lib/catalogs/models.json +483 -411
- package/src/app.js +33 -2
- package/src/lib/bootstrap-command-handler.js +6 -0
- package/src/lib/cli-handler.js +1 -1
- package/src/lib/config-manager.js +41 -2
- package/src/lib/deployment-entry-schema.js +16 -0
- package/src/lib/mcp-client.js +3 -3
- package/src/lib/prompt-runner.js +179 -8
- package/src/lib/prompts.js +253 -7
- package/src/lib/registry-command-handler.js +12 -0
- package/templates/Dockerfile +12 -0
- package/templates/code/serving.properties +14 -0
- package/templates/do/adapter +1230 -0
- package/templates/do/adapters/.gitkeep +2 -0
- package/templates/do/add-ic +130 -0
- package/templates/do/benchmark +81 -9
- package/templates/do/clean +507 -17
- package/templates/do/config +28 -5
- package/templates/do/deploy +513 -367
- package/templates/do/ic/default.conf +32 -0
- package/templates/do/lib/endpoint-config.sh +216 -0
- package/templates/do/lib/inference-component.sh +167 -0
- package/templates/do/lib/secrets.sh +44 -0
- package/templates/do/lib/wait.sh +131 -0
- package/templates/do/logs +107 -27
- package/templates/do/optimize +528 -0
- package/templates/do/register +111 -1
- package/templates/do/status +337 -0
- package/templates/do/test +80 -28
package/src/app.js
CHANGED
|
@@ -302,6 +302,22 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
|
|
|
302
302
|
ignorePatterns.push('**/hyperpod/**');
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
// HyperPod is kubectl-based — no shared bash helpers or IC configs
|
|
306
|
+
if (answers.deploymentTarget === 'hyperpod-eks') {
|
|
307
|
+
ignorePatterns.push('**/do/lib/**');
|
|
308
|
+
ignorePatterns.push('**/do/ic/**');
|
|
309
|
+
ignorePatterns.push('**/do/add-ic');
|
|
310
|
+
ignorePatterns.push('**/do/status');
|
|
311
|
+
ignorePatterns.push('**/do/optimize');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Async and batch don't use inference components (IC is real-time only)
|
|
315
|
+
if (answers.deploymentTarget === 'async-inference' || answers.deploymentTarget === 'batch-transform') {
|
|
316
|
+
ignorePatterns.push('**/do/ic/**');
|
|
317
|
+
ignorePatterns.push('**/do/add-ic');
|
|
318
|
+
ignorePatterns.push('**/do/status');
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
// Resolve architecture
|
|
306
322
|
const resolver = new DeploymentConfigResolver();
|
|
307
323
|
let architecture = answers.architecture;
|
|
@@ -325,6 +341,13 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
|
|
|
325
341
|
// Exclude do/benchmark when benchmarking is not selected
|
|
326
342
|
if (!answers.includeBenchmark) {
|
|
327
343
|
ignorePatterns.push('**/do/benchmark');
|
|
344
|
+
ignorePatterns.push('**/do/optimize');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Exclude do/adapter and do/adapters/ when LoRA is not enabled
|
|
348
|
+
if (!answers.enableLora) {
|
|
349
|
+
ignorePatterns.push('**/do/adapter');
|
|
350
|
+
ignorePatterns.push('**/do/adapters/**');
|
|
328
351
|
}
|
|
329
352
|
|
|
330
353
|
// Exclude do/test when hosted-model-endpoint is not selected
|
|
@@ -567,7 +590,11 @@ async function _ensureTemplateVariables(answers, registryConfigManager = null) {
|
|
|
567
590
|
baseImage: null,
|
|
568
591
|
modelSource: 'huggingface',
|
|
569
592
|
artifactUri: '',
|
|
570
|
-
modelLoadStrategy: 'runtime'
|
|
593
|
+
modelLoadStrategy: 'runtime',
|
|
594
|
+
existingEndpointName: null,
|
|
595
|
+
enableLora: false,
|
|
596
|
+
maxLoras: 30,
|
|
597
|
+
maxLoraRank: 64
|
|
571
598
|
};
|
|
572
599
|
|
|
573
600
|
Object.entries(defaults).forEach(([key, value]) => {
|
|
@@ -1052,7 +1079,11 @@ function _setExecutablePermissions(destDir) {
|
|
|
1052
1079
|
'do/register',
|
|
1053
1080
|
'do/ci',
|
|
1054
1081
|
'do/manifest',
|
|
1055
|
-
'do/benchmark'
|
|
1082
|
+
'do/benchmark',
|
|
1083
|
+
'do/optimize',
|
|
1084
|
+
'do/status',
|
|
1085
|
+
'do/add-ic',
|
|
1086
|
+
'do/adapter'
|
|
1056
1087
|
];
|
|
1057
1088
|
|
|
1058
1089
|
shellScripts.forEach(script => {
|
|
@@ -199,6 +199,9 @@ export default class BootstrapCommandHandler {
|
|
|
199
199
|
if (stackOutputs.BatchS3BucketName) {
|
|
200
200
|
profileData.batchS3Bucket = stackOutputs.BatchS3BucketName;
|
|
201
201
|
}
|
|
202
|
+
if (stackOutputs.AdapterS3BucketName) {
|
|
203
|
+
profileData.adapterS3Bucket = stackOutputs.AdapterS3BucketName;
|
|
204
|
+
}
|
|
202
205
|
if (stackOutputs.BenchmarkS3BucketName) {
|
|
203
206
|
profileData.benchmarkS3Bucket = stackOutputs.BenchmarkS3BucketName;
|
|
204
207
|
}
|
|
@@ -390,6 +393,9 @@ export default class BootstrapCommandHandler {
|
|
|
390
393
|
if (outputs.BatchS3BucketName) {
|
|
391
394
|
console.log(` ✅ S3 bucket (batch): ${outputs.BatchS3BucketName}`);
|
|
392
395
|
}
|
|
396
|
+
if (outputs.AdapterS3BucketName) {
|
|
397
|
+
console.log(` ✅ S3 bucket (adapters): ${outputs.AdapterS3BucketName}`);
|
|
398
|
+
}
|
|
393
399
|
if (outputs.BenchmarkS3BucketName) {
|
|
394
400
|
console.log(` ✅ S3 bucket (benchmark): ${outputs.BenchmarkS3BucketName}`);
|
|
395
401
|
}
|
package/src/lib/cli-handler.js
CHANGED
|
@@ -204,7 +204,7 @@ VALIDATION OPTIONS:
|
|
|
204
204
|
|
|
205
205
|
MCP OPTIONS:
|
|
206
206
|
--smart Enable Bedrock-powered smart mode on all MCP servers
|
|
207
|
-
--discover
|
|
207
|
+
--no-discover Disable live registry lookups (HuggingFace API, quota checks) — catalog-only mode
|
|
208
208
|
|
|
209
209
|
REGISTRY SYSTEM:
|
|
210
210
|
The generator includes built-in registries for frameworks, models, and instance types:
|
|
@@ -1056,6 +1056,39 @@ export default class ConfigManager {
|
|
|
1056
1056
|
required: false,
|
|
1057
1057
|
default: null,
|
|
1058
1058
|
valueSpace: 'bounded'
|
|
1059
|
+
},
|
|
1060
|
+
enableLora: {
|
|
1061
|
+
cliOption: 'enable-lora',
|
|
1062
|
+
envVar: null,
|
|
1063
|
+
configFile: true,
|
|
1064
|
+
packageJson: false,
|
|
1065
|
+
mcp: false,
|
|
1066
|
+
promptable: true,
|
|
1067
|
+
required: false,
|
|
1068
|
+
default: false,
|
|
1069
|
+
valueSpace: 'bounded'
|
|
1070
|
+
},
|
|
1071
|
+
maxLoras: {
|
|
1072
|
+
cliOption: 'max-loras',
|
|
1073
|
+
envVar: null,
|
|
1074
|
+
configFile: true,
|
|
1075
|
+
packageJson: false,
|
|
1076
|
+
mcp: false,
|
|
1077
|
+
promptable: true,
|
|
1078
|
+
required: false,
|
|
1079
|
+
default: 30,
|
|
1080
|
+
valueSpace: 'bounded'
|
|
1081
|
+
},
|
|
1082
|
+
maxLoraRank: {
|
|
1083
|
+
cliOption: 'max-lora-rank',
|
|
1084
|
+
envVar: null,
|
|
1085
|
+
configFile: true,
|
|
1086
|
+
packageJson: false,
|
|
1087
|
+
mcp: false,
|
|
1088
|
+
promptable: true,
|
|
1089
|
+
required: false,
|
|
1090
|
+
default: 64,
|
|
1091
|
+
valueSpace: 'bounded'
|
|
1059
1092
|
}
|
|
1060
1093
|
};
|
|
1061
1094
|
}
|
|
@@ -1088,7 +1121,7 @@ export default class ConfigManager {
|
|
|
1088
1121
|
*/
|
|
1089
1122
|
_parseValue(parameter, value) {
|
|
1090
1123
|
// Handle boolean parameters
|
|
1091
|
-
if (parameter === 'includeSampleModel' || parameter === 'includeTesting' || parameter === 'skipPrompts' || parameter === 'includeBenchmark' || parameter === 'benchmarkStreaming') {
|
|
1124
|
+
if (parameter === 'includeSampleModel' || parameter === 'includeTesting' || parameter === 'skipPrompts' || parameter === 'includeBenchmark' || parameter === 'benchmarkStreaming' || parameter === 'enableLora') {
|
|
1092
1125
|
return value === true || value === 'true';
|
|
1093
1126
|
}
|
|
1094
1127
|
|
|
@@ -1598,7 +1631,7 @@ export default class ConfigManager {
|
|
|
1598
1631
|
if (!mcpServerConfigs || !mcpServerConfigs[serverName]) return null;
|
|
1599
1632
|
|
|
1600
1633
|
const smart = this.options.smart === true;
|
|
1601
|
-
const discover = this.options.discover
|
|
1634
|
+
const discover = this.options.discover !== false;
|
|
1602
1635
|
const serverConfig = mcpServerConfigs[serverName];
|
|
1603
1636
|
|
|
1604
1637
|
// Build a custom McpClient that passes context through
|
|
@@ -1924,6 +1957,12 @@ export default class ConfigManager {
|
|
|
1924
1957
|
if (param === 'instanceType' && finalConfig.deploymentTarget === 'hyperpod-eks' && !finalConfig.instanceType) {
|
|
1925
1958
|
return; // Skip validation only if truly missing for backward compat
|
|
1926
1959
|
}
|
|
1960
|
+
|
|
1961
|
+
// Special case: instanceType is not required when attaching to an existing endpoint
|
|
1962
|
+
// The instance type is inherited from the existing endpoint configuration
|
|
1963
|
+
if (param === 'instanceType' && finalConfig.existingEndpointName) {
|
|
1964
|
+
return; // Skip validation — instance is inherited from existing endpoint
|
|
1965
|
+
}
|
|
1927
1966
|
|
|
1928
1967
|
if (isEmpty) {
|
|
1929
1968
|
if (config.promptable) {
|
|
@@ -57,6 +57,22 @@ export default {
|
|
|
57
57
|
},
|
|
58
58
|
buildTarget: {
|
|
59
59
|
type: ['string', 'null']
|
|
60
|
+
},
|
|
61
|
+
icList: {
|
|
62
|
+
type: 'array',
|
|
63
|
+
items: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
required: ['name'],
|
|
66
|
+
properties: {
|
|
67
|
+
name: { type: 'string', minLength: 1 },
|
|
68
|
+
image: { type: 'string' },
|
|
69
|
+
gpuCount: { type: 'integer', minimum: 0 },
|
|
70
|
+
copyCount: { type: 'integer', minimum: 1 },
|
|
71
|
+
isAdapter: { type: 'boolean' },
|
|
72
|
+
baseIcName: { type: 'string' },
|
|
73
|
+
artifactUrl: { type: 'string' }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
}
|
|
62
78
|
},
|
package/src/lib/mcp-client.js
CHANGED
|
@@ -32,7 +32,7 @@ class McpClient {
|
|
|
32
32
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
33
33
|
this.parameterMatrix = options.parameterMatrix || {};
|
|
34
34
|
this.smart = options.smart || false;
|
|
35
|
-
this.discover = options.discover
|
|
35
|
+
this.discover = options.discover !== undefined ? options.discover : true;
|
|
36
36
|
this._transport = null;
|
|
37
37
|
this._client = null;
|
|
38
38
|
this._diagnosticMessage = null;
|
|
@@ -98,10 +98,10 @@ class McpClient {
|
|
|
98
98
|
|
|
99
99
|
// Build environment: merge process.env with server-specific env
|
|
100
100
|
// When --smart flag is active, inject BEDROCK_SMART=true for this run
|
|
101
|
-
//
|
|
101
|
+
// Discover mode is now default; inject DISCOVER_MODE=false only when explicitly disabled
|
|
102
102
|
// Always pass process.env so child processes inherit AWS credentials, profiles, etc.
|
|
103
103
|
const smartEnv = this.smart ? { BEDROCK_SMART: 'true' } : {};
|
|
104
|
-
const discoverEnv = this.discover ? {
|
|
104
|
+
const discoverEnv = this.discover === false ? { DISCOVER_MODE: 'false' } : {};
|
|
105
105
|
const serverEnv = env && Object.keys(env).length > 0 ? env : {};
|
|
106
106
|
const spawnEnv = { ...process.env, ...smartEnv, ...discoverEnv, ...serverEnv };
|
|
107
107
|
|
package/src/lib/prompt-runner.js
CHANGED
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
modelLoadStrategyPrompts,
|
|
19
19
|
modelProfilePrompts,
|
|
20
20
|
modulePrompts,
|
|
21
|
+
loraPrompts,
|
|
21
22
|
benchmarkPrompts,
|
|
22
23
|
infraRegionAndTargetPrompts,
|
|
24
|
+
infraExistingEndpointPrompts,
|
|
23
25
|
infraInstancePrompts,
|
|
24
26
|
infraAsyncPrompts,
|
|
25
27
|
infraBatchTransformPrompts,
|
|
@@ -29,7 +31,9 @@ import {
|
|
|
29
31
|
destinationPrompts,
|
|
30
32
|
baseImageSearchPrompts,
|
|
31
33
|
baseImagePrompts,
|
|
32
|
-
formatImageChoices
|
|
34
|
+
formatImageChoices,
|
|
35
|
+
filterByCudaGeneration,
|
|
36
|
+
instanceCatalogRaw
|
|
33
37
|
} from './prompts.js';
|
|
34
38
|
|
|
35
39
|
import fs from 'fs';
|
|
@@ -187,12 +191,40 @@ export default class PromptRunner {
|
|
|
187
191
|
// 3a. Region query
|
|
188
192
|
await this._queryMcpForRegion(frameworkAnswers, explicitConfig);
|
|
189
193
|
|
|
194
|
+
// 3a2. Existing endpoint prompt (only for realtime-inference)
|
|
195
|
+
// Requirements: 3.3, 4.3, 4.4 — endpoint-picker MCP query
|
|
196
|
+
let existingEndpointAnswers = {};
|
|
197
|
+
if (regionAndTargetAnswers.deploymentTarget === 'realtime-inference') {
|
|
198
|
+
// Query endpoint-picker MCP server for available endpoints
|
|
199
|
+
const resolvedRegion = regionAndTargetAnswers.customAwsRegion || regionAndTargetAnswers.awsRegion;
|
|
200
|
+
await this._queryMcpForEndpoints({ ...regionAndTargetAnswers, awsRegion: resolvedRegion }, explicitConfig);
|
|
201
|
+
|
|
202
|
+
const endpointPreviousAnswers = {
|
|
203
|
+
...regionAndTargetAnswers,
|
|
204
|
+
...(this._mcpEndpointChoices ? { _mcpEndpointChoices: this._mcpEndpointChoices } : {})
|
|
205
|
+
};
|
|
206
|
+
existingEndpointAnswers = await this._runPhase(
|
|
207
|
+
infraExistingEndpointPrompts,
|
|
208
|
+
endpointPreviousAnswers,
|
|
209
|
+
explicitConfig,
|
|
210
|
+
existingConfig
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Resolve custom endpoint name
|
|
214
|
+
if (existingEndpointAnswers.customExistingEndpointName) {
|
|
215
|
+
existingEndpointAnswers.existingEndpointName = existingEndpointAnswers.customExistingEndpointName;
|
|
216
|
+
delete existingEndpointAnswers.customExistingEndpointName;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
190
220
|
// 3b. Instance type — query instance-sizer with full context (model + profile + CUDA)
|
|
191
221
|
let instanceAnswers = {};
|
|
192
|
-
|
|
222
|
+
// Skip instance prompts when attaching to an existing endpoint (instance is inherited)
|
|
223
|
+
const useExistingEndpoint = !!(existingEndpointAnswers.existingEndpointName);
|
|
224
|
+
const needsInstance = !useExistingEndpoint && (regionAndTargetAnswers.deploymentTarget === 'realtime-inference' ||
|
|
193
225
|
regionAndTargetAnswers.deploymentTarget === 'async-inference' ||
|
|
194
226
|
regionAndTargetAnswers.deploymentTarget === 'batch-transform' ||
|
|
195
|
-
regionAndTargetAnswers.deploymentTarget === 'hyperpod-eks';
|
|
227
|
+
regionAndTargetAnswers.deploymentTarget === 'hyperpod-eks');
|
|
196
228
|
|
|
197
229
|
if (needsInstance) {
|
|
198
230
|
// Determine architecture type for heuristic fallback
|
|
@@ -230,6 +262,74 @@ export default class PromptRunner {
|
|
|
230
262
|
if (!instanceAnswers.instanceType && !explicitConfig.instanceType && this._architectureHeuristicDefault) {
|
|
231
263
|
instanceAnswers.instanceType = this._architectureHeuristicDefault;
|
|
232
264
|
}
|
|
265
|
+
|
|
266
|
+
// Process multi-select instance type results (Requirements: 6.4)
|
|
267
|
+
// When user selects multiple instances via checkbox, derive instanceType and instancePools
|
|
268
|
+
if (instanceAnswers.instanceTypeSelections && instanceAnswers.instanceTypeSelections.length > 0) {
|
|
269
|
+
let selections = instanceAnswers.instanceTypeSelections.slice(0, 5); // Cap at 5 (API limit)
|
|
270
|
+
|
|
271
|
+
// Resolve custom input: replace __custom_input__ sentinel with parsed instances
|
|
272
|
+
if (selections.includes('__custom_input__') && instanceAnswers.customInstanceTypeSelections) {
|
|
273
|
+
const customInstances = instanceAnswers.customInstanceTypeSelections
|
|
274
|
+
.split(',').map(s => s.trim()).filter(s => s.length > 0);
|
|
275
|
+
// Remove the sentinel and any other MCP selections, replace with custom entries
|
|
276
|
+
selections = selections.filter(s => s !== '__custom_input__');
|
|
277
|
+
selections = [...selections, ...customInstances];
|
|
278
|
+
delete instanceAnswers.customInstanceTypeSelections;
|
|
279
|
+
} else if (selections.includes('__custom_input__')) {
|
|
280
|
+
// Sentinel selected but no custom input provided — remove it
|
|
281
|
+
selections = selections.filter(s => s !== '__custom_input__');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Cap at 5 after custom expansion
|
|
285
|
+
if (selections.length > 5) {
|
|
286
|
+
console.log(' ⚠️ Maximum 5 instance types allowed. Using first 5 selections.');
|
|
287
|
+
selections = selections.slice(0, 5);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Filter to same CUDA generation and warn about incompatible removals
|
|
291
|
+
const { filtered, generation, removed } = filterByCudaGeneration(selections);
|
|
292
|
+
if (removed.length > 0) {
|
|
293
|
+
console.log(` ⚠️ Removed incompatible instances (different CUDA generation): ${removed.join(', ')}`);
|
|
294
|
+
console.log(` Keeping ${generation} generation: ${filtered.join(', ')}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const finalSelections = filtered.length > 0 ? filtered : selections;
|
|
298
|
+
|
|
299
|
+
if (finalSelections.length === 1) {
|
|
300
|
+
// Single selection → standard single instance type (no pools)
|
|
301
|
+
instanceAnswers.instanceType = finalSelections[0];
|
|
302
|
+
console.log(` ✓ Single instance selected: ${finalSelections[0]}`);
|
|
303
|
+
} else {
|
|
304
|
+
// Multiple selections → instance pools with priority = selection order
|
|
305
|
+
instanceAnswers.instanceType = finalSelections[0]; // backward compat: first is primary
|
|
306
|
+
instanceAnswers.instancePools = finalSelections.map((it, idx) => ({
|
|
307
|
+
InstanceType: it,
|
|
308
|
+
Priority: idx + 1
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
// Auto-generate multi-spec IC config from catalog
|
|
312
|
+
instanceAnswers.instancePoolSpecs = finalSelections.map(it => {
|
|
313
|
+
const entry = instanceCatalogRaw[it];
|
|
314
|
+
return {
|
|
315
|
+
instanceType: it,
|
|
316
|
+
gpuCount: entry?.gpus || 1,
|
|
317
|
+
minMemoryMb: entry?.gpuMemoryGb ? entry.gpuMemoryGb * 1024 : 1024
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(` ✓ Instance pools configured (${finalSelections.length} types):`);
|
|
322
|
+
finalSelections.forEach((it, idx) => {
|
|
323
|
+
const entry = instanceCatalogRaw[it];
|
|
324
|
+
const gpus = entry?.gpus || '?';
|
|
325
|
+
const mem = entry?.gpuMemoryGb || '?';
|
|
326
|
+
console.log(` Priority ${idx + 1}: ${it} (${gpus} GPUs, ${mem}GB GPU memory)`);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Clean up the raw selections from answers (not needed downstream)
|
|
331
|
+
delete instanceAnswers.instanceTypeSelections;
|
|
332
|
+
}
|
|
233
333
|
}
|
|
234
334
|
|
|
235
335
|
// In auto-prompt mode, use instance-sizer's top recommendation as the instance type
|
|
@@ -318,6 +418,7 @@ export default class PromptRunner {
|
|
|
318
418
|
// Combine all infrastructure answers
|
|
319
419
|
const infraAnswers = {
|
|
320
420
|
...regionAndTargetAnswers,
|
|
421
|
+
...existingEndpointAnswers,
|
|
321
422
|
...instanceAnswers,
|
|
322
423
|
...asyncAnswers,
|
|
323
424
|
...batchTransformAnswers,
|
|
@@ -414,6 +515,14 @@ export default class PromptRunner {
|
|
|
414
515
|
}
|
|
415
516
|
}
|
|
416
517
|
|
|
518
|
+
// LoRA adapter prompts — only for transformers with vllm/sglang/djl-lmi
|
|
519
|
+
// Requirements: 1.1, 1.2, 1.4
|
|
520
|
+
let loraAnswers = {};
|
|
521
|
+
const loraSubAnswers = await this._runPhase(loraPrompts, { ...frameworkAnswers, ...engineAnswers }, explicitConfig, existingConfig);
|
|
522
|
+
if (loraSubAnswers.enableLora !== undefined) {
|
|
523
|
+
loraAnswers = loraSubAnswers;
|
|
524
|
+
}
|
|
525
|
+
|
|
417
526
|
// Validate instance type against framework requirements (now that framework version is known)
|
|
418
527
|
const finalInstanceType = infraAnswers.customInstanceType || infraAnswers.instanceType;
|
|
419
528
|
if (finalInstanceType && frameworkVersionAnswers.frameworkVersion) {
|
|
@@ -456,6 +565,7 @@ export default class PromptRunner {
|
|
|
456
565
|
...ngcApiKeyAnswers,
|
|
457
566
|
...moduleAnswers,
|
|
458
567
|
...benchmarkAnswers,
|
|
568
|
+
...loraAnswers,
|
|
459
569
|
...projectAnswers,
|
|
460
570
|
...destinationAnswers,
|
|
461
571
|
buildTimestamp
|
|
@@ -988,9 +1098,9 @@ export default class PromptRunner {
|
|
|
988
1098
|
if (!modelName || modelName === 'Custom (enter manually)') return;
|
|
989
1099
|
|
|
990
1100
|
const smart = this.options.smart === true;
|
|
991
|
-
const discover = this.options.discover
|
|
1101
|
+
const discover = this.options.discover !== false;
|
|
992
1102
|
|
|
993
|
-
const modeLabel = [smart && '[smart]', discover && '[discover]'].filter(Boolean).join(' ');
|
|
1103
|
+
const modeLabel = [smart && '[smart]', !discover && '[no-discover]'].filter(Boolean).join(' ');
|
|
994
1104
|
console.log(` 🔍 Querying instance-sizer${modeLabel ? ` ${modeLabel}` : ''}...`);
|
|
995
1105
|
|
|
996
1106
|
try {
|
|
@@ -1005,8 +1115,8 @@ export default class PromptRunner {
|
|
|
1005
1115
|
const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
|
|
1006
1116
|
|
|
1007
1117
|
const serverArgs = [...(serverConfig.args || [])];
|
|
1008
|
-
if (discover && !serverArgs.includes('--discover')) {
|
|
1009
|
-
serverArgs.push('--discover');
|
|
1118
|
+
if (!discover && !serverArgs.includes('--no-discover')) {
|
|
1119
|
+
serverArgs.push('--no-discover');
|
|
1010
1120
|
}
|
|
1011
1121
|
|
|
1012
1122
|
const transport = new StdioClientTransport({
|
|
@@ -1083,6 +1193,11 @@ export default class PromptRunner {
|
|
|
1083
1193
|
|
|
1084
1194
|
console.log(` ✓ ${choices.length} compatible instance(s) found${vramInfo}`);
|
|
1085
1195
|
|
|
1196
|
+
// Warn if all instances had zero quota but were restored for visibility
|
|
1197
|
+
if (parsed.metadata?.allFilteredByQuota) {
|
|
1198
|
+
console.log(' ⚠️ All instances have zero quota — request a quota increase for your preferred type');
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1086
1201
|
// Check if availability data is present (recommendations have capacityType)
|
|
1087
1202
|
const hasAvailabilityData = recommendations.some(r => r.capacityType);
|
|
1088
1203
|
|
|
@@ -1187,6 +1302,62 @@ export default class PromptRunner {
|
|
|
1187
1302
|
}
|
|
1188
1303
|
}
|
|
1189
1304
|
|
|
1305
|
+
/**
|
|
1306
|
+
* Query the endpoint-picker MCP server for available InService real-time endpoints.
|
|
1307
|
+
* Populates this._mcpEndpointChoices for the existing endpoint selection prompt.
|
|
1308
|
+
* Graceful fallback: if MCP server fails (no credentials, timeout), skip and create new endpoint.
|
|
1309
|
+
* Requirements: 3.3, 4.3, 4.4
|
|
1310
|
+
* @private
|
|
1311
|
+
*/
|
|
1312
|
+
async _queryMcpForEndpoints(infraAnswers, explicitConfig) {
|
|
1313
|
+
const cm = this.configManager;
|
|
1314
|
+
if (!cm) return;
|
|
1315
|
+
|
|
1316
|
+
const mcpServers = cm.getMcpServerNames();
|
|
1317
|
+
if (!mcpServers.includes('endpoint-picker')) return;
|
|
1318
|
+
|
|
1319
|
+
// Skip if existing endpoint already provided via CLI/config
|
|
1320
|
+
if (explicitConfig.existingEndpointName) return;
|
|
1321
|
+
|
|
1322
|
+
console.log(' 🔍 Querying endpoint-picker...');
|
|
1323
|
+
|
|
1324
|
+
try {
|
|
1325
|
+
const result = await cm.queryMcpServer('endpoint-picker', {
|
|
1326
|
+
awsRegion: infraAnswers.awsRegion,
|
|
1327
|
+
deploymentTarget: 'realtime-inference'
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
if (result && result.choices?.endpointName?.length > 0) {
|
|
1331
|
+
const endpointNames = result.choices.endpointName;
|
|
1332
|
+
const metadata = result.metadata || {};
|
|
1333
|
+
|
|
1334
|
+
// Build choices with metadata annotations
|
|
1335
|
+
this._mcpEndpointChoices = endpointNames.map(name => {
|
|
1336
|
+
const meta = metadata[name];
|
|
1337
|
+
if (meta) {
|
|
1338
|
+
const gpuInfo = meta.availableGpus === '?' ? 'GPUs: ?' : `${meta.availableGpus} GPUs free`;
|
|
1339
|
+
return {
|
|
1340
|
+
name: `${name} (${meta.instanceType}, ${gpuInfo}, ${meta.icCount} IC${meta.icCount !== 1 ? 's' : ''})`,
|
|
1341
|
+
value: name
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
return { name, value: name };
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
console.log(` ✓ ${endpointNames.length} endpoint(s) with available capacity`);
|
|
1348
|
+
} else {
|
|
1349
|
+
if (result?.message) {
|
|
1350
|
+
console.log(` ↳ ${result.message}`);
|
|
1351
|
+
} else {
|
|
1352
|
+
console.log(' ↳ No endpoints with available capacity found');
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
// Graceful fallback: if MCP server fails, skip and create new endpoint
|
|
1357
|
+
console.log(` ⚠️ endpoint-picker: ${err.message || 'query failed'} — will create new endpoint`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1190
1361
|
/**
|
|
1191
1362
|
* Query MCP base-image-picker server after deployment config is selected.
|
|
1192
1363
|
* Populates _mcpBaseImageChoices for the base image selection prompt.
|
|
@@ -1204,7 +1375,7 @@ export default class PromptRunner {
|
|
|
1204
1375
|
if (!mcpServers.includes('base-image-picker')) return;
|
|
1205
1376
|
|
|
1206
1377
|
const smart = this.options.smart === true;
|
|
1207
|
-
const discover = this.options.discover
|
|
1378
|
+
const discover = this.options.discover !== false;
|
|
1208
1379
|
const framework = frameworkAnswers.framework;
|
|
1209
1380
|
const modelServer = frameworkAnswers.modelServer;
|
|
1210
1381
|
const architecture = frameworkAnswers.architecture || frameworkAnswers.deploymentConfig?.split('-')[0];
|