@aws/ml-container-creator 0.10.0 → 0.10.3
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/LICENSE-THIRD-PARTY +9304 -0
- package/bin/cli.js +2 -0
- package/config/bootstrap-e2e-stack.json +341 -0
- package/config/bootstrap-stack.json +40 -3
- package/config/parameter-schema-v2.json +5 -21
- package/config/tune-catalog.json +1781 -0
- package/infra/ci-harness/buildspec.yml +1 -0
- package/infra/ci-harness/lambda/path-prover/brain.ts +306 -0
- package/infra/ci-harness/lambda/path-prover/write-results.ts +152 -0
- package/infra/ci-harness/lib/ci-harness-stack.ts +837 -7
- package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
- package/package.json +51 -66
- package/servers/base-image-picker/index.js +121 -121
- package/servers/e2e-status/index.js +297 -0
- package/servers/e2e-status/manifest.json +14 -0
- package/servers/e2e-status/package.json +15 -0
- package/servers/endpoint-picker/LICENSE +202 -0
- package/servers/endpoint-picker/index.js +536 -0
- package/servers/endpoint-picker/manifest.json +14 -0
- package/servers/endpoint-picker/package.json +18 -0
- package/servers/hyperpod-cluster-picker/index.js +125 -125
- package/servers/instance-sizer/index.js +138 -138
- package/servers/instance-sizer/lib/instance-ranker.js +76 -76
- package/servers/instance-sizer/lib/model-resolver.js +61 -61
- package/servers/instance-sizer/lib/quota-resolver.js +113 -113
- package/servers/instance-sizer/lib/vram-estimator.js +31 -31
- package/servers/lib/bedrock-client.js +38 -38
- package/servers/lib/catalogs/model-servers.json +201 -3
- package/servers/lib/custom-validators.js +13 -13
- package/servers/lib/dynamic-resolver.js +4 -4
- package/servers/marketplace-picker/index.js +342 -0
- package/servers/marketplace-picker/manifest.json +14 -0
- package/servers/marketplace-picker/package.json +18 -0
- package/servers/model-picker/index.js +382 -382
- package/servers/region-picker/index.js +56 -56
- package/servers/workload-picker/LICENSE +202 -0
- package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
- package/servers/workload-picker/index.js +171 -0
- package/servers/workload-picker/manifest.json +16 -0
- package/servers/workload-picker/package.json +16 -0
- package/src/app.js +4 -2
- package/src/lib/bootstrap-command-handler.js +579 -14
- package/src/lib/bootstrap-config.js +36 -0
- package/src/lib/bootstrap-profile-manager.js +48 -41
- package/src/lib/ci-register-helpers.js +74 -0
- package/src/lib/config-loader.js +3 -0
- package/src/lib/config-manager.js +7 -0
- package/src/lib/cuda-resolver.js +17 -8
- package/src/lib/generated/cli-options.js +315 -315
- package/src/lib/generated/parameter-matrix.js +661 -661
- package/src/lib/generated/validation-rules.js +71 -71
- package/src/lib/path-prover-brain.js +607 -0
- package/src/lib/prompts/project-prompts.js +12 -0
- package/src/lib/template-variable-resolver.js +25 -1
- package/src/lib/tune-catalog-validator.js +37 -4
- package/templates/Dockerfile +9 -0
- package/templates/code/adapter_sidecar.py +444 -0
- package/templates/code/serve +6 -0
- package/templates/code/serve.d/vllm.ejs +1 -1
- package/templates/do/.benchmark_writer.py +1476 -0
- package/templates/do/.tune_helper.py +982 -57
- package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
- package/templates/do/adapter +149 -0
- package/templates/do/benchmark +639 -85
- package/templates/do/config +108 -5
- package/templates/do/deploy.d/managed-inference.ejs +192 -11
- package/templates/do/optimize +106 -37
- package/templates/do/register +89 -0
- package/templates/do/test +13 -0
- package/templates/do/tune +378 -59
- package/templates/do/validate +44 -4
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Path Prover Brain Lambda
|
|
6
|
+
*
|
|
7
|
+
* Handles three actions for the Path Prover state machine:
|
|
8
|
+
* - getNextConfig: Initial brain call to get the first config to prove
|
|
9
|
+
* - pickNext: After a prove iteration, decides next or done
|
|
10
|
+
* - classifyFailure: On error, classify and return structured result
|
|
11
|
+
*
|
|
12
|
+
* Budget controls: MAX_PROVES_PER_RUN (default 10), MAX_COST_PER_RUN (default 100 USD)
|
|
13
|
+
*
|
|
14
|
+
* Requirements: 8.1, 8.7, 8.8
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configuration dimensions used for gap identification.
|
|
19
|
+
*/
|
|
20
|
+
const CONFIG_DIMENSIONS = [
|
|
21
|
+
'deployment_config',
|
|
22
|
+
'model_family',
|
|
23
|
+
'instance_family',
|
|
24
|
+
'quantization',
|
|
25
|
+
'tp_degree',
|
|
26
|
+
'deployment_target'
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Error pattern matchers for failure classification.
|
|
31
|
+
*/
|
|
32
|
+
const ERROR_PATTERNS: Array<{ pattern: RegExp; category: string; retryable: boolean }> = [
|
|
33
|
+
{ pattern: /InsufficientInstanceCapacity/i, category: 'capacity', retryable: true },
|
|
34
|
+
{ pattern: /CapacityError/i, category: 'capacity', retryable: true },
|
|
35
|
+
{ pattern: /no capacity/i, category: 'capacity', retryable: true },
|
|
36
|
+
{ pattern: /timed?\s*out/i, category: 'timeout', retryable: true },
|
|
37
|
+
{ pattern: /timeout/i, category: 'timeout', retryable: true },
|
|
38
|
+
{ pattern: /deadline exceeded/i, category: 'timeout', retryable: true },
|
|
39
|
+
{ pattern: /OutOfMemory/i, category: 'oom', retryable: false },
|
|
40
|
+
{ pattern: /OOM/i, category: 'oom', retryable: false },
|
|
41
|
+
{ pattern: /CUDA out of memory/i, category: 'oom', retryable: false },
|
|
42
|
+
{ pattern: /Cannot allocate memory/i, category: 'oom', retryable: false },
|
|
43
|
+
{ pattern: /template.*error/i, category: 'code_bug', retryable: false },
|
|
44
|
+
{ pattern: /SyntaxError/i, category: 'code_bug', retryable: false },
|
|
45
|
+
{ pattern: /ReferenceError/i, category: 'code_bug', retryable: false },
|
|
46
|
+
{ pattern: /not supported.*model/i, category: 'model_incompatibility', retryable: false },
|
|
47
|
+
{ pattern: /model.*incompatible/i, category: 'model_incompatibility', retryable: false },
|
|
48
|
+
{ pattern: /LoRA.*not supported/i, category: 'model_incompatibility', retryable: false },
|
|
49
|
+
{ pattern: /not available.*region/i, category: 'service_limitation', retryable: false },
|
|
50
|
+
{ pattern: /service.*not supported/i, category: 'service_limitation', retryable: false },
|
|
51
|
+
{ pattern: /ValidationException/i, category: 'service_limitation', retryable: false }
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Approximate cost per hour for common instance families (USD).
|
|
56
|
+
* Used for budget estimation.
|
|
57
|
+
*/
|
|
58
|
+
const INSTANCE_COST_PER_HOUR: Record<string, number> = {
|
|
59
|
+
'g5': 1.21,
|
|
60
|
+
'g6': 0.98,
|
|
61
|
+
'g6e': 1.32,
|
|
62
|
+
'p4d': 32.77,
|
|
63
|
+
'p5': 65.00,
|
|
64
|
+
'trn2': 21.50,
|
|
65
|
+
'inf2': 1.58,
|
|
66
|
+
'ml.g5.xlarge': 1.21,
|
|
67
|
+
'ml.g5.2xlarge': 1.52,
|
|
68
|
+
'ml.g5.12xlarge': 7.09,
|
|
69
|
+
'ml.g5.48xlarge': 20.09
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Estimated hours per prove run (generate+build+deploy+test+benchmark+clean).
|
|
74
|
+
*/
|
|
75
|
+
const ESTIMATED_HOURS_PER_PROVE = 1.5
|
|
76
|
+
|
|
77
|
+
interface BrainEvent {
|
|
78
|
+
action: string
|
|
79
|
+
iteration?: number
|
|
80
|
+
budgetSpent?: number
|
|
81
|
+
maxProvesPerRun?: number
|
|
82
|
+
maxCostPerRun?: number
|
|
83
|
+
previousResults?: Array<Record<string, unknown>>
|
|
84
|
+
currentConfig?: Record<string, unknown>
|
|
85
|
+
lastResult?: string
|
|
86
|
+
classification?: Record<string, unknown>
|
|
87
|
+
error?: Record<string, unknown>
|
|
88
|
+
config?: Record<string, unknown>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface BrainResponse {
|
|
92
|
+
done?: boolean
|
|
93
|
+
reason?: string
|
|
94
|
+
next?: Record<string, unknown>
|
|
95
|
+
tuneRequested?: boolean
|
|
96
|
+
iteration?: number
|
|
97
|
+
budgetSpent?: number
|
|
98
|
+
previousResults?: Array<Record<string, unknown>>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface ClassificationResult {
|
|
102
|
+
stage: string
|
|
103
|
+
category: string
|
|
104
|
+
retryable: boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function handler(event: BrainEvent): Promise<BrainResponse | ClassificationResult> {
|
|
108
|
+
const action = event.action
|
|
109
|
+
|
|
110
|
+
switch (action) {
|
|
111
|
+
case 'getNextConfig':
|
|
112
|
+
return handleGetNextConfig(event)
|
|
113
|
+
case 'pickNext':
|
|
114
|
+
return handlePickNext(event)
|
|
115
|
+
case 'classifyFailure':
|
|
116
|
+
return handleClassifyFailure(event)
|
|
117
|
+
default:
|
|
118
|
+
throw new Error(`Unknown action: ${action}`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* getNextConfig: Called at the start of the state machine.
|
|
124
|
+
* Returns the first config to prove or {done: true} if nothing to do.
|
|
125
|
+
*/
|
|
126
|
+
function handleGetNextConfig(event: BrainEvent): BrainResponse {
|
|
127
|
+
const iteration = event.iteration ?? 0
|
|
128
|
+
const budgetSpent = event.budgetSpent ?? 0
|
|
129
|
+
const maxProvesPerRun = event.maxProvesPerRun ?? 10
|
|
130
|
+
const maxCostPerRun = event.maxCostPerRun ?? 100
|
|
131
|
+
|
|
132
|
+
// Check budget before starting
|
|
133
|
+
if (iteration >= maxProvesPerRun) {
|
|
134
|
+
return { done: true, reason: 'max_proves_reached' }
|
|
135
|
+
}
|
|
136
|
+
if (budgetSpent >= maxCostPerRun) {
|
|
137
|
+
return { done: true, reason: 'budget_exceeded' }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// In a real implementation, this would query Athena for gaps.
|
|
141
|
+
// The prove request configs come from the execution input's
|
|
142
|
+
// previousResults/gap list. For the state machine orchestration,
|
|
143
|
+
// the initial config is passed in the execution input.
|
|
144
|
+
const previousResults = event.previousResults ?? []
|
|
145
|
+
|
|
146
|
+
// If there's no work to do (no gaps identified), we're done
|
|
147
|
+
if (previousResults.length === 0 && iteration === 0) {
|
|
148
|
+
return { done: true, reason: 'all_gaps_filled' }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get next unproven config from the list
|
|
152
|
+
const nextConfig = getNextUnprovenConfig(previousResults, iteration)
|
|
153
|
+
if (!nextConfig) {
|
|
154
|
+
return { done: true, reason: 'all_gaps_filled' }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Determine if tune stages are needed
|
|
158
|
+
const tuneRequested = shouldExecuteTuneStages(nextConfig)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
done: false,
|
|
162
|
+
next: nextConfig,
|
|
163
|
+
tuneRequested,
|
|
164
|
+
iteration: iteration + 1,
|
|
165
|
+
budgetSpent: budgetSpent + estimateCost(nextConfig)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* pickNext: Called after a prove iteration (success or failure).
|
|
171
|
+
* Decides whether to continue or stop.
|
|
172
|
+
*/
|
|
173
|
+
function handlePickNext(event: BrainEvent): BrainResponse {
|
|
174
|
+
const iteration = event.iteration ?? 1
|
|
175
|
+
const budgetSpent = event.budgetSpent ?? 0
|
|
176
|
+
const maxProvesPerRun = event.maxProvesPerRun ?? 10
|
|
177
|
+
const maxCostPerRun = event.maxCostPerRun ?? 100
|
|
178
|
+
const previousResults = event.previousResults ?? []
|
|
179
|
+
|
|
180
|
+
// Update iteration count
|
|
181
|
+
const newIteration = iteration + 1
|
|
182
|
+
|
|
183
|
+
// Check budget controls
|
|
184
|
+
if (newIteration > maxProvesPerRun) {
|
|
185
|
+
return { done: true, reason: 'max_proves_reached' }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Estimate cost of next prove and check budget
|
|
189
|
+
const nextConfig = getNextUnprovenConfig(previousResults, newIteration - 1)
|
|
190
|
+
if (!nextConfig) {
|
|
191
|
+
return { done: true, reason: 'all_gaps_filled' }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const estimatedNextCost = estimateCost(nextConfig)
|
|
195
|
+
if (budgetSpent + estimatedNextCost > maxCostPerRun) {
|
|
196
|
+
return { done: true, reason: 'budget_exceeded' }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Determine if tune stages are needed
|
|
200
|
+
const tuneRequested = shouldExecuteTuneStages(nextConfig)
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
done: false,
|
|
204
|
+
next: nextConfig,
|
|
205
|
+
tuneRequested,
|
|
206
|
+
iteration: newIteration,
|
|
207
|
+
budgetSpent: budgetSpent + estimatedNextCost,
|
|
208
|
+
previousResults
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* classifyFailure: Parse error output and classify into a category.
|
|
214
|
+
*/
|
|
215
|
+
function handleClassifyFailure(event: BrainEvent): ClassificationResult {
|
|
216
|
+
const error = event.error
|
|
217
|
+
if (!error) {
|
|
218
|
+
return { stage: 'unknown', category: 'code_bug', retryable: false }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Extract error message
|
|
222
|
+
let errorMsg = ''
|
|
223
|
+
if (typeof error === 'string') {
|
|
224
|
+
errorMsg = error
|
|
225
|
+
} else {
|
|
226
|
+
errorMsg = (error as Record<string, string>).Cause
|
|
227
|
+
|| (error as Record<string, string>).Error
|
|
228
|
+
|| JSON.stringify(error)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Detect stage
|
|
232
|
+
const stage = detectStage(errorMsg)
|
|
233
|
+
|
|
234
|
+
// Match against patterns
|
|
235
|
+
for (const { pattern, category, retryable } of ERROR_PATTERNS) {
|
|
236
|
+
if (pattern.test(errorMsg)) {
|
|
237
|
+
return { stage, category, retryable }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { stage, category: 'code_bug', retryable: false }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Detect which lifecycle stage produced an error from the error message.
|
|
246
|
+
*/
|
|
247
|
+
function detectStage(errorMsg: string): string {
|
|
248
|
+
const stagePatterns: Array<{ pattern: RegExp; stage: string }> = [
|
|
249
|
+
{ pattern: /\b(generate|generation)\b/i, stage: 'generate' },
|
|
250
|
+
{ pattern: /\b(build|docker)\b/i, stage: 'build' },
|
|
251
|
+
{ pattern: /\b(push|ecr|registry)\b/i, stage: 'push' },
|
|
252
|
+
{ pattern: /\b(deploy|endpoint|CreateEndpoint)\b/i, stage: 'deploy' },
|
|
253
|
+
{ pattern: /\b(test|invoke|invocation)\b/i, stage: 'test' },
|
|
254
|
+
{ pattern: /\b(tune|fine-?tun|customization)\b/i, stage: 'tune' },
|
|
255
|
+
{ pattern: /\b(adapter|lora)\b/i, stage: 'adapter' },
|
|
256
|
+
{ pattern: /\b(benchmark|bench)\b/i, stage: 'benchmark' },
|
|
257
|
+
{ pattern: /\b(clean|delete)\b/i, stage: 'clean' }
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
for (const { pattern, stage } of stagePatterns) {
|
|
261
|
+
if (pattern.test(errorMsg)) {
|
|
262
|
+
return stage
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return 'unknown'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get the next unproven config from the list.
|
|
271
|
+
*/
|
|
272
|
+
function getNextUnprovenConfig(
|
|
273
|
+
configs: Array<Record<string, unknown>>,
|
|
274
|
+
index: number
|
|
275
|
+
): Record<string, unknown> | null {
|
|
276
|
+
if (!configs || index >= configs.length) {
|
|
277
|
+
return null
|
|
278
|
+
}
|
|
279
|
+
return configs[index] ?? null
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Determine whether tune/adapter stages should execute.
|
|
284
|
+
*/
|
|
285
|
+
function shouldExecuteTuneStages(config: Record<string, unknown>): boolean {
|
|
286
|
+
if (!config) return false
|
|
287
|
+
if (config.include_tuning === true) return true
|
|
288
|
+
if (config.enable_lora === true) return true
|
|
289
|
+
if (config.tune_technique && config.tune_technique !== 'none') return true
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Estimate cost of a prove run based on instance family.
|
|
295
|
+
*/
|
|
296
|
+
function estimateCost(config: Record<string, unknown>): number {
|
|
297
|
+
const instanceFamily = String(config.instance_family ?? 'g5')
|
|
298
|
+
const instanceType = String(config.instance_type ?? '')
|
|
299
|
+
|
|
300
|
+
// Try specific instance type first, then family
|
|
301
|
+
const costPerHour = INSTANCE_COST_PER_HOUR[instanceType]
|
|
302
|
+
?? INSTANCE_COST_PER_HOUR[instanceFamily]
|
|
303
|
+
?? 2.0 // Default fallback
|
|
304
|
+
|
|
305
|
+
return costPerHour * ESTIMATED_HOURS_PER_PROVE
|
|
306
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Path Prover Write Results Lambda
|
|
6
|
+
*
|
|
7
|
+
* Writes benchmark results to Athena (via S3 Parquet) with run_type='path_prove'.
|
|
8
|
+
* Handles both success records and failure/unfeasible records.
|
|
9
|
+
*
|
|
10
|
+
* Requirements: 8.9, 8.10, 8.11, 8.12
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface WriteResultsEvent {
|
|
14
|
+
action: string
|
|
15
|
+
config?: Record<string, unknown>
|
|
16
|
+
benchmarkResult?: Record<string, unknown>
|
|
17
|
+
error?: Record<string, unknown>
|
|
18
|
+
classification?: {
|
|
19
|
+
stage: string
|
|
20
|
+
category: string
|
|
21
|
+
retryable: boolean
|
|
22
|
+
}
|
|
23
|
+
runType: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface WriteResultsResponse {
|
|
27
|
+
success: boolean
|
|
28
|
+
recordId?: string
|
|
29
|
+
status?: string
|
|
30
|
+
error?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function handler(event: WriteResultsEvent): Promise<WriteResultsResponse> {
|
|
34
|
+
const action = event.action
|
|
35
|
+
|
|
36
|
+
switch (action) {
|
|
37
|
+
case 'writeResults':
|
|
38
|
+
return handleWriteResults(event)
|
|
39
|
+
case 'writeFailure':
|
|
40
|
+
return handleWriteFailure(event)
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unknown action: ${action}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Write a successful benchmark result to Athena.
|
|
48
|
+
* Sets status='completed', run_type='path_prove'.
|
|
49
|
+
*/
|
|
50
|
+
function handleWriteResults(event: WriteResultsEvent): WriteResultsResponse {
|
|
51
|
+
const config = event.config ?? {}
|
|
52
|
+
const runType = event.runType ?? 'path_prove'
|
|
53
|
+
|
|
54
|
+
const recordId = `pp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
55
|
+
|
|
56
|
+
// In production, this would:
|
|
57
|
+
// 1. Build a Parquet record from config + benchmarkResult
|
|
58
|
+
// 2. Write to S3 at the partitioned path
|
|
59
|
+
// 3. Register the partition in Glue
|
|
60
|
+
// For the orchestration, we confirm the record was built correctly.
|
|
61
|
+
|
|
62
|
+
const record = {
|
|
63
|
+
config_id: config.config_id ?? config.configId ?? recordId,
|
|
64
|
+
run_type: runType,
|
|
65
|
+
status: 'completed',
|
|
66
|
+
run_timestamp: new Date().toISOString(),
|
|
67
|
+
...extractConfigDimensions(config)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate the record has run_type='path_prove'
|
|
71
|
+
if (record.run_type !== 'path_prove') {
|
|
72
|
+
throw new Error(`Invalid run_type: expected 'path_prove', got '${record.run_type}'`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
recordId,
|
|
78
|
+
status: 'completed'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Write a failure record to Athena.
|
|
84
|
+
* Non-retryable failures get status='unfeasible'; retryable get status='failed'.
|
|
85
|
+
*/
|
|
86
|
+
function handleWriteFailure(event: WriteResultsEvent): WriteResultsResponse {
|
|
87
|
+
const config = event.config ?? {}
|
|
88
|
+
const classification = event.classification
|
|
89
|
+
const error = event.error
|
|
90
|
+
const runType = event.runType ?? 'path_prove'
|
|
91
|
+
|
|
92
|
+
const recordId = `pp-fail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
93
|
+
|
|
94
|
+
// Determine status based on classification
|
|
95
|
+
let status = 'failed'
|
|
96
|
+
if (classification && classification.retryable === false) {
|
|
97
|
+
status = 'unfeasible'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build failure reason
|
|
101
|
+
let failureReason = 'Unknown failure'
|
|
102
|
+
if (error) {
|
|
103
|
+
failureReason = typeof error === 'string'
|
|
104
|
+
? error
|
|
105
|
+
: (error as Record<string, string>).Cause
|
|
106
|
+
|| (error as Record<string, string>).Error
|
|
107
|
+
|| JSON.stringify(error)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const record = {
|
|
111
|
+
config_id: config.config_id ?? config.configId ?? recordId,
|
|
112
|
+
run_type: runType,
|
|
113
|
+
status,
|
|
114
|
+
failure_reason: failureReason,
|
|
115
|
+
failure_stage: classification?.stage ?? 'unknown',
|
|
116
|
+
failure_category: classification?.category ?? 'code_bug',
|
|
117
|
+
failure_retryable: classification?.retryable ?? false,
|
|
118
|
+
run_timestamp: new Date().toISOString(),
|
|
119
|
+
...extractConfigDimensions(config)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate the record has run_type='path_prove'
|
|
123
|
+
if (record.run_type !== 'path_prove') {
|
|
124
|
+
throw new Error(`Invalid run_type: expected 'path_prove', got '${record.run_type}'`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
recordId,
|
|
130
|
+
status
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Extract config dimensions from a config object.
|
|
136
|
+
*/
|
|
137
|
+
function extractConfigDimensions(config: Record<string, unknown>): Record<string, unknown> {
|
|
138
|
+
const dimensions: Record<string, unknown> = {}
|
|
139
|
+
const DIMS = [
|
|
140
|
+
'deployment_config', 'model_family', 'instance_family',
|
|
141
|
+
'quantization', 'tp_degree', 'deployment_target',
|
|
142
|
+
'model_name', 'instance_type'
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
for (const dim of DIMS) {
|
|
146
|
+
if (config[dim] !== undefined) {
|
|
147
|
+
dimensions[dim] = config[dim]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return dimensions
|
|
152
|
+
}
|