@aws/ml-container-creator 0.13.5 → 0.15.1

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.
Files changed (37) hide show
  1. package/config/parameter-schema-v2.json +33 -5
  2. package/infra/ci-harness/lib/ci-harness-stack.ts +13 -5
  3. package/infra/ci-harness/package-lock.json +121 -111
  4. package/infra/ci-harness/package.json +1 -1
  5. package/package.json +2 -2
  6. package/servers/endpoint-picker/index.js +23 -14
  7. package/servers/instance-sizer/index.js +72 -4
  8. package/servers/instance-sizer/lib/model-resolver.js +28 -2
  9. package/src/app.js +15 -0
  10. package/src/lib/config-loader.js +18 -0
  11. package/src/lib/config-manager.js +6 -1
  12. package/src/lib/dataset-slug.js +152 -0
  13. package/src/lib/generated/cli-options.js +9 -3
  14. package/src/lib/generated/parameter-matrix.js +15 -4
  15. package/src/lib/generated/validation-rules.js +1 -1
  16. package/src/lib/mcp-client.js +15 -1
  17. package/src/lib/mcp-query-runner.js +11 -1
  18. package/src/lib/prompt-runner.js +40 -20
  19. package/src/lib/prompts/feature-prompts.js +1 -1
  20. package/src/lib/template-manager.js +0 -7
  21. package/src/lib/template-variable-resolver.js +51 -1
  22. package/src/lib/tune-config-state.js +14 -1
  23. package/templates/do/.benchmark_writer.py +43 -0
  24. package/templates/do/.register_helper.py +1185 -0
  25. package/templates/do/.tune_helper.py +168 -2
  26. package/templates/do/__pycache__/.adapter_helper.cpython-312.pyc +0 -0
  27. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  28. package/templates/do/__pycache__/.register_helper.cpython-312.pyc +0 -0
  29. package/templates/do/__pycache__/.tune_helper.cpython-312.pyc +0 -0
  30. package/templates/do/adapter +319 -27
  31. package/templates/do/add-ic +85 -3
  32. package/templates/do/benchmark +28 -8
  33. package/templates/do/config +20 -0
  34. package/templates/do/lib/inference-component.sh +56 -3
  35. package/templates/do/register +557 -6
  36. package/templates/do/test +12 -2
  37. package/templates/do/tune +219 -6
package/templates/do/tune CHANGED
@@ -46,10 +46,13 @@ ARG_DRY_RUN=false
46
46
  ARG_LIST_MODELS=false
47
47
  ARG_NO_STALE_WARNING=false
48
48
  ARG_DISCOVER=false
49
+ ARG_LIST_DATASETS=false
49
50
  ARG_DISCOVER_FILTER=""
50
51
  ARG_COLUMN_MAP=""
51
52
  ARG_TAKE=""
52
53
  ARG_ACCEPT_EULA=false
54
+ ARG_DATASET_NAME=""
55
+ ARG_EVALUATOR_NAME=""
53
56
 
54
57
 
55
58
  # ── _parse_args() ─────────────────────────────────────────────────────────────
@@ -148,6 +151,7 @@ _parse_args() {
148
151
  --help|-h) ARG_HELP=true; shift ;;
149
152
  --dry-run) ARG_DRY_RUN=true; shift ;;
150
153
  --list-models) ARG_LIST_MODELS=true; shift ;;
154
+ --list-datasets) ARG_LIST_DATASETS=true; shift ;;
151
155
  --no-stale-warning) ARG_NO_STALE_WARNING=true; shift ;;
152
156
  --column-map)
153
157
  if [ -z "${2:-}" ]; then
@@ -169,6 +173,18 @@ _parse_args() {
169
173
  exit 1
170
174
  fi
171
175
  ARG_TAKE="$2"; shift 2 ;;
176
+ --dataset-name)
177
+ if [ -z "${2:-}" ]; then
178
+ echo "❌ --dataset-name requires a registered dataset name"
179
+ exit 1
180
+ fi
181
+ ARG_DATASET_NAME="$2"; shift 2 ;;
182
+ --evaluator-name)
183
+ if [ -z "${2:-}" ]; then
184
+ echo "❌ --evaluator-name requires a registered evaluator name"
185
+ exit 1
186
+ fi
187
+ ARG_EVALUATOR_NAME="$2"; shift 2 ;;
172
188
  *)
173
189
  echo "❌ Unknown option: $1"
174
190
  echo " Run ./do/tune --help for usage."
@@ -775,10 +791,69 @@ else:
775
791
  _validate_dataset() {
776
792
  local dataset="${ARG_DATASET}"
777
793
 
794
+ # If --dataset-name is set, resolve from registry (AC-2b.4)
795
+ # --dataset-name takes precedence over --dataset for named registry lookup
796
+ if [ -n "${ARG_DATASET_NAME}" ]; then
797
+ echo "🔍 Resolving dataset '${ARG_DATASET_NAME}' from registry..."
798
+ local resolve_result
799
+ resolve_result=$(python3 "${SCRIPT_DIR}/.register_helper.py" resolve-dataset \
800
+ --name "${ARG_DATASET_NAME}" 2>/dev/null) || resolve_result=""
801
+
802
+ if [ -n "${resolve_result}" ]; then
803
+ local resolved_uri
804
+ resolved_uri=$(echo "${resolve_result}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('s3_uri',''))" 2>/dev/null) || resolved_uri=""
805
+ if [ -n "${resolved_uri}" ]; then
806
+ echo " Resolved to: ${resolved_uri}"
807
+ dataset="${resolved_uri}"
808
+ ARG_DATASET="${resolved_uri}"
809
+ else
810
+ echo "❌ Dataset '${ARG_DATASET_NAME}' not found in registry"
811
+ echo " Register it first: ./do/register --dataset --dataset-name ${ARG_DATASET_NAME} --dataset-s3-uri s3://..."
812
+ exit 1
813
+ fi
814
+ else
815
+ echo "❌ Failed to resolve dataset '${ARG_DATASET_NAME}' from registry"
816
+ echo " Register it first: ./do/register --dataset --dataset-name ${ARG_DATASET_NAME} --dataset-s3-uri s3://..."
817
+ exit 1
818
+ fi
819
+ fi
820
+
821
+ # If --dataset value is not an S3 URI or HF reference, treat as a registry name
822
+ if [ -z "${ARG_DATASET_NAME}" ] && [ -n "${dataset}" ] && \
823
+ [[ "${dataset}" != s3://* ]] && [[ "${dataset}" != hf://* ]]; then
824
+ # Looks like a name — resolve from registry
825
+ ARG_DATASET_NAME="${dataset}"
826
+ dataset=""
827
+ fi
828
+
778
829
  if [ -z "${dataset}" ]; then
779
- echo " --dataset is required"
780
- echo " Provide an S3 URI (s3://bucket/path.jsonl) or HF reference (hf://org/name)"
781
- exit 1
830
+ if [ -n "${ARG_DATASET_NAME}" ]; then
831
+ # Name-based resolution happens below via resolve-dataset
832
+ echo "🔍 Resolving dataset '${ARG_DATASET_NAME}' from registry..."
833
+ local resolve_result
834
+ resolve_result=$(python3 "${SCRIPT_DIR}/.register_helper.py" resolve-dataset \
835
+ --name "${ARG_DATASET_NAME}" 2>/dev/null) || resolve_result=""
836
+
837
+ if [ -n "${resolve_result}" ]; then
838
+ local resolved_uri
839
+ resolved_uri=$(echo "${resolve_result}" | grep -E '^\{' | tail -1 | python3 -c "import sys,json; print(json.load(sys.stdin).get('s3_uri',''))" 2>/dev/null) || resolved_uri=""
840
+ if [ -n "${resolved_uri}" ]; then
841
+ echo " Resolved to: ${resolved_uri}"
842
+ dataset="${resolved_uri}"
843
+ RESOLVED_DATASET_S3_URI="${resolved_uri}"
844
+ return 0
845
+ fi
846
+ fi
847
+ echo "❌ Dataset '${ARG_DATASET_NAME}' not found in registry"
848
+ echo " Run ./do/tune --list-datasets to see available datasets."
849
+ echo " Register: ./do/register dataset <name> --s3-uri <uri> --technique <sft|dpo>"
850
+ exit 1
851
+ else
852
+ echo "❌ --dataset is required"
853
+ echo " Provide an S3 URI (s3://bucket/path.jsonl), HF reference (hf://org/name), or registered name"
854
+ echo " Run ./do/tune --list-datasets to see available registered datasets."
855
+ exit 1
856
+ fi
782
857
  fi
783
858
 
784
859
  # Determine dataset type
@@ -908,11 +983,10 @@ _validate_dataset() {
908
983
  fi
909
984
 
910
985
  RESOLVED_DATASET_S3_URI=$(echo "${stage_result}" | python3 -c "import sys,json; print(json.load(sys.stdin)['s3_uri'])" 2>/dev/null)
911
- local num_records
912
- num_records=$(echo "${stage_result}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('num_records',0))" 2>/dev/null) || num_records="0"
986
+ RESOLVED_DATASET_ROW_COUNT=$(echo "${stage_result}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('num_records',0))" 2>/dev/null) || RESOLVED_DATASET_ROW_COUNT="0"
913
987
 
914
988
  echo " ✅ Staged to: ${RESOLVED_DATASET_S3_URI}"
915
- echo " Records: ${num_records}"
989
+ echo " Records: ${RESOLVED_DATASET_ROW_COUNT}"
916
990
  echo ""
917
991
 
918
992
  else
@@ -1126,6 +1200,37 @@ print(entry.get('provider', ''))
1126
1200
  if [ -n "${ARG_REWARD_PROMPT}" ]; then
1127
1201
  submit_args+=(--reward-prompt "${ARG_REWARD_PROMPT}")
1128
1202
  fi
1203
+
1204
+ # Resolve evaluator from registry if --evaluator-name is set (AC-2c.3, AC-2c.4)
1205
+ if [ -n "${ARG_EVALUATOR_NAME}" ] && [ -z "${ARG_REWARD_FUNCTION}" ] && [ -z "${ARG_REWARD_PROMPT}" ]; then
1206
+ echo "🔍 Resolving evaluator '${ARG_EVALUATOR_NAME}' from registry..."
1207
+ local ev_resolve_result
1208
+ ev_resolve_result=$(python3 "${SCRIPT_DIR}/.register_helper.py" resolve-evaluator \
1209
+ --name "${ARG_EVALUATOR_NAME}" 2>/dev/null) || ev_resolve_result=""
1210
+
1211
+ if [ -n "${ev_resolve_result}" ]; then
1212
+ local ev_type ev_arn_or_uri
1213
+ ev_type=$(echo "${ev_resolve_result}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('type',''))" 2>/dev/null) || ev_type=""
1214
+ ev_arn_or_uri=$(echo "${ev_resolve_result}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('arn_or_uri',''))" 2>/dev/null) || ev_arn_or_uri=""
1215
+
1216
+ if [ -n "${ev_arn_or_uri}" ]; then
1217
+ echo " Resolved evaluator: ${ev_type} → ${ev_arn_or_uri}"
1218
+ if [ "${ev_type}" = "lambda" ]; then
1219
+ submit_args+=(--reward-function "${ev_arn_or_uri}")
1220
+ else
1221
+ submit_args+=(--reward-prompt "${ev_arn_or_uri}")
1222
+ fi
1223
+ else
1224
+ echo "⚠️ Evaluator '${ARG_EVALUATOR_NAME}' not found in registry"
1225
+ echo " Register it first: ./do/register --evaluator --evaluator-name ${ARG_EVALUATOR_NAME} ..."
1226
+ exit 1
1227
+ fi
1228
+ else
1229
+ echo "⚠️ Failed to resolve evaluator '${ARG_EVALUATOR_NAME}' from registry"
1230
+ echo " Register it first: ./do/register --evaluator --evaluator-name ${ARG_EVALUATOR_NAME} ..."
1231
+ exit 1
1232
+ fi
1233
+ fi
1129
1234
  if [ "${ARG_ACCEPT_EULA}" = true ]; then
1130
1235
  submit_args+=(--accept-eula)
1131
1236
  fi
@@ -1217,6 +1322,14 @@ print(entry.get('provider', ''))
1217
1322
  _update_config_var "TUNE_TECHNIQUE" "${ARG_TECHNIQUE}"
1218
1323
  _update_config_var "TUNE_TRAINING_TYPE" "${ARG_TRAINING_TYPE}"
1219
1324
  _update_config_var "TUNE_DATASET_PATH" "${ARG_DATASET}"
1325
+ _update_config_var "TUNE_DATASET_S3_URI" "${RESOLVED_DATASET_S3_URI:-}"
1326
+ _update_config_var "TUNE_DATASET_ROW_COUNT" "${RESOLVED_DATASET_ROW_COUNT:-0}"
1327
+ _update_config_var "TUNE_DATASET_SOURCE" "${ARG_DATASET}"
1328
+ _update_config_var "TUNE_LAST_JOB_NAME" "${JOB_NAME}"
1329
+ # Technique-specific dataset vars (allows --from-tune <technique> resolution)
1330
+ _update_config_var "TUNE_DATASET_S3_URI_${technique_upper}" "${RESOLVED_DATASET_S3_URI:-}"
1331
+ _update_config_var "TUNE_DATASET_ROW_COUNT_${technique_upper}" "${RESOLVED_DATASET_ROW_COUNT:-0}"
1332
+ _update_config_var "TUNE_DATASET_SOURCE_${technique_upper}" "${ARG_DATASET}"
1220
1333
  }
1221
1334
 
1222
1335
 
@@ -1292,6 +1405,56 @@ _handle_interrupt() {
1292
1405
  exit 130
1293
1406
  }
1294
1407
 
1408
+ # ── _derive_dataset_slug() ────────────────────────────────────────────────────
1409
+ # Derive a short slug from the dataset argument for config variable naming.
1410
+ # Rules: lowercase, strip non-alphanumeric (keep hyphens), truncate to 20 chars,
1411
+ # collapse consecutive hyphens, strip leading/trailing hyphens.
1412
+ #
1413
+ # Examples:
1414
+ # hf://tatsu-lab/alpaca → alpaca
1415
+ # hf://Open-Orca/OpenOrca → openorca
1416
+ # s3://bucket/path/train.jsonl → train
1417
+ # s3://bucket/path/file.parquet → file
1418
+ _derive_dataset_slug() {
1419
+ local dataset="${1:-}"
1420
+ local slug=""
1421
+
1422
+ if [ -z "${dataset}" ]; then
1423
+ echo ""
1424
+ return
1425
+ fi
1426
+
1427
+ if [[ "${dataset}" == hf://* ]]; then
1428
+ # HuggingFace format: hf://org/name or hf://org/name?file=...
1429
+ # Strip query params
1430
+ local hf_path="${dataset#hf://}"
1431
+ hf_path="${hf_path%%\?*}"
1432
+ # Take the last component (dataset name, not org)
1433
+ slug="${hf_path##*/}"
1434
+ elif [[ "${dataset}" == s3://* ]]; then
1435
+ # S3 format: s3://bucket/path/file.ext → slug from filename without extension
1436
+ local filename="${dataset##*/}"
1437
+ slug="${filename%.*}"
1438
+ else
1439
+ # Fallback: use the last path component without extension
1440
+ local filename="${dataset##*/}"
1441
+ slug="${filename%.*}"
1442
+ fi
1443
+
1444
+ # Slugification: lowercase, strip non-alphanumeric (keep hyphens)
1445
+ slug=$(echo "${slug}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g')
1446
+ # Collapse consecutive hyphens
1447
+ slug=$(echo "${slug}" | sed 's/-\{2,\}/-/g')
1448
+ # Strip leading/trailing hyphens
1449
+ slug=$(echo "${slug}" | sed 's/^-//;s/-$//')
1450
+ # Truncate to 20 chars
1451
+ slug="${slug:0:20}"
1452
+ # Strip trailing hyphen after truncation
1453
+ slug=$(echo "${slug}" | sed 's/-$//')
1454
+
1455
+ echo "${slug}"
1456
+ }
1457
+
1295
1458
  # ── _handle_completion() ──────────────────────────────────────────────────────
1296
1459
  # Store output paths, detect output type, print next-step commands.
1297
1460
  _handle_completion() {
@@ -1351,6 +1514,14 @@ _handle_completion() {
1351
1514
  # Store output paths in config
1352
1515
  if [ "${output_type}" = "adapter" ]; then
1353
1516
  _update_config_var "TUNE_ADAPTER_PATH_${technique_upper}" "${artifact_path}"
1517
+ # Write dataset-specific adapter path if dataset slug is available
1518
+ local dataset_slug
1519
+ dataset_slug=$(_derive_dataset_slug "${ARG_DATASET:-}")
1520
+ if [ -n "${dataset_slug}" ]; then
1521
+ local slug_upper
1522
+ slug_upper=$(echo "${dataset_slug}" | tr '[:lower:]' '[:upper:]' | sed 's/-/_/g')
1523
+ _update_config_var "TUNE_ADAPTER_PATH_${technique_upper}_${slug_upper}" "${artifact_path}"
1524
+ fi
1354
1525
  else
1355
1526
  _update_config_var "TUNE_MODEL_PATH_${technique_upper}" "${artifact_path}"
1356
1527
  fi
@@ -1361,9 +1532,14 @@ _handle_completion() {
1361
1532
  echo "📋 Next steps:"
1362
1533
  echo ""
1363
1534
  if [ "${output_type}" = "adapter" ]; then
1535
+ local dataset_slug
1536
+ dataset_slug=$(_derive_dataset_slug "${ARG_DATASET:-}")
1364
1537
  echo " Deploy as LoRA adapter:"
1365
1538
  echo " ./do/adapter add tuned-${ARG_TECHNIQUE} --from-tune"
1366
1539
  echo " ./do/adapter add tuned-${ARG_TECHNIQUE} --from-tune ${ARG_TECHNIQUE}"
1540
+ if [ -n "${dataset_slug}" ]; then
1541
+ echo " ./do/adapter add tuned-${ARG_TECHNIQUE}-${dataset_slug} --from-tune ${ARG_TECHNIQUE}-${dataset_slug}"
1542
+ fi
1367
1543
  echo " ./do/adapter add tuned-${ARG_TECHNIQUE} --weights ${artifact_path}"
1368
1544
  else
1369
1545
  echo " Deploy as new inference component:"
@@ -1460,6 +1636,43 @@ if [ "${ARG_DISCOVER}" = true ]; then
1460
1636
  exit 0
1461
1637
  fi
1462
1638
 
1639
+ # Handle --list-datasets (before requiring --technique and --dataset)
1640
+ if [ "${ARG_LIST_DATASETS}" = true ]; then
1641
+ echo ""
1642
+ echo "📦 Registered datasets:"
1643
+ echo ""
1644
+
1645
+ _ds_output=$(python3 "${SCRIPT_DIR}/.register_helper.py" list-datasets 2>/dev/null) || _ds_output=""
1646
+ _ds_json=$(echo "${_ds_output}" | grep -E '^\{' | tail -1)
1647
+
1648
+ if [ -n "${_ds_json}" ]; then
1649
+ _ds_count=$(echo "${_ds_json}" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('datasets',[])))" 2>/dev/null) || _ds_count=0
1650
+ if [ "${_ds_count}" -gt 0 ]; then
1651
+ printf " %-25s %-10s %-8s %s\n" "NAME" "TECHNIQUE" "ROWS" "S3 URI"
1652
+ printf " %-25s %-10s %-8s %s\n" "----" "---------" "----" "------"
1653
+ echo "${_ds_json}" | python3 -c "
1654
+ import sys, json
1655
+ data = json.load(sys.stdin)
1656
+ for ds in data.get('datasets', []):
1657
+ name = ds.get('name','')[:25]
1658
+ tech = ds.get('technique','')[:10]
1659
+ rows = str(ds.get('row_count',''))[:8]
1660
+ uri = ds.get('s3_uri','')
1661
+ print(f' {name:<25} {tech:<10} {rows:<8} {uri}')
1662
+ " 2>/dev/null
1663
+ else
1664
+ echo " (none registered)"
1665
+ fi
1666
+ else
1667
+ echo " (none registered)"
1668
+ fi
1669
+ echo ""
1670
+ echo " Register: ./do/register dataset <name> --s3-uri <uri> --technique <sft|dpo>"
1671
+ echo " Use: ./do/tune --technique sft --dataset <name>"
1672
+ echo ""
1673
+ exit 0
1674
+ fi
1675
+
1463
1676
  # Validate required arguments for job submission
1464
1677
  if [ -z "${ARG_TECHNIQUE}" ]; then
1465
1678
  echo "❌ --technique is required"