@aws/ml-container-creator 0.13.3 → 0.13.5

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 (43) hide show
  1. package/README.md +23 -5
  2. package/infra/ci-harness/package-lock.json +1 -5
  3. package/package.json +5 -3
  4. package/pyproject.toml +21 -0
  5. package/requirements.txt +19 -0
  6. package/servers/instance-sizer/lib/model-resolver.js +127 -185
  7. package/servers/instance-sizer/lib/vram-estimator.js +86 -0
  8. package/servers/lib/catalogs/instances.json +0 -27
  9. package/src/app.js +2 -0
  10. package/src/lib/bootstrap-command-handler.js +35 -25
  11. package/src/lib/generated/cli-options.js +1 -1
  12. package/src/lib/generated/parameter-matrix.js +1 -1
  13. package/src/lib/generated/validation-rules.js +1 -1
  14. package/src/lib/prompt-runner.js +14 -31
  15. package/templates/IAM_PERMISSIONS.md +64 -13
  16. package/templates/do/.adapter_helper.py +451 -0
  17. package/templates/do/.benchmark_writer.py +13 -0
  18. package/templates/do/.stage_helper.py +419 -0
  19. package/templates/do/.tune_helper.py +218 -67
  20. package/templates/do/README.md +50 -604
  21. package/templates/do/__pycache__/.adapter_helper.cpython-312.pyc +0 -0
  22. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  23. package/templates/do/__pycache__/.tune_helper.cpython-312.pyc +0 -0
  24. package/templates/do/adapter +109 -4
  25. package/templates/do/benchmark +150 -12
  26. package/templates/do/build +2 -5
  27. package/templates/do/clean.d/async-inference.ejs +2 -5
  28. package/templates/do/clean.d/batch-transform.ejs +2 -5
  29. package/templates/do/clean.d/hyperpod-eks.ejs +2 -5
  30. package/templates/do/clean.d/managed-inference.ejs +2 -5
  31. package/templates/do/config +4 -0
  32. package/templates/do/deploy.d/async-inference.ejs +6 -9
  33. package/templates/do/deploy.d/batch-transform.ejs +4 -7
  34. package/templates/do/deploy.d/hyperpod-eks.ejs +1 -4
  35. package/templates/do/deploy.d/managed-inference.ejs +15 -6
  36. package/templates/do/lib/profile.sh +24 -15
  37. package/templates/do/push +2 -5
  38. package/templates/do/register +2 -5
  39. package/templates/do/stage +114 -292
  40. package/templates/do/submit +1 -4
  41. package/templates/do/tune +64 -10
  42. package/templates/MIGRATION.md +0 -488
  43. package/templates/TEMPLATE_SYSTEM.md +0 -243
@@ -228,33 +228,6 @@
228
228
  "gpuMemoryGb": 24,
229
229
  "gpuType": "NVIDIA A10G",
230
230
  "costTier": "medium"
231
- },
232
- "ml.p6-b200.48xlarge": {
233
- "category": "gpu",
234
- "gpus": 8,
235
- "vcpus": 192,
236
- "memGb": 1536,
237
- "accelerator": "8x B200 1440GB",
238
- "cudaVersions": [
239
- "12.4",
240
- "12.6"
241
- ],
242
- "tags": [
243
- "gpu",
244
- "multi-gpu",
245
- "b200",
246
- "cuda-12",
247
- "high-performance"
248
- ],
249
- "family": "p6",
250
- "acceleratorType": "cuda",
251
- "hardware": "NVIDIA B200",
252
- "gpuArchitecture": "Blackwell",
253
- "defaultCudaVersion": "12.6",
254
- "notes": "8x NVIDIA B200 GPUs (1440GB total). Next-gen Blackwell architecture",
255
- "gpuMemoryGb": 180,
256
- "gpuType": "NVIDIA B200",
257
- "costTier": "high"
258
231
  }
259
232
  },
260
233
  "recommendations": {
package/src/app.js CHANGED
@@ -400,6 +400,8 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
400
400
  ignorePatterns.push('**/do/adapters/**');
401
401
  ignorePatterns.push('**/do/tune');
402
402
  ignorePatterns.push('**/do/.tune_helper.py');
403
+ ignorePatterns.push('**/do/.stage_helper.py');
404
+ ignorePatterns.push('**/do/.adapter_helper.py');
403
405
  ignorePatterns.push('**/do/train');
404
406
  ignorePatterns.push('**/do/.train_build_request.py');
405
407
  ignorePatterns.push('**/do/.train_status_parser.py');
@@ -170,7 +170,7 @@ export default class BootstrapCommandHandler {
170
170
  console.log('\nšŸš€ Bootstrap — Shared AWS Infrastructure Setup\n');
171
171
 
172
172
  // Verify AWS CLI v2 is installed
173
- if (!this.provisioners._verifyCliV2()) {
173
+ if (!this._verifyCliV2()) {
174
174
  return;
175
175
  }
176
176
 
@@ -261,7 +261,7 @@ export default class BootstrapCommandHandler {
261
261
  }
262
262
 
263
263
  profileData.roleArn = stackOutputs.RoleArn;
264
- profileData.ecrRepositoryName = stackOutputs.EcrRepositoryName;
264
+ profileData.ecrRepositoryName = stackOutputs.EcrRepositoryName || 'ml-container-creator';
265
265
  profileData.stackName = stackName;
266
266
  profileData.sharedInfraFrom = otherStack; // Track that this profile reuses another's stack
267
267
  if (stackOutputs.AsyncS3BucketName) profileData.asyncS3Bucket = stackOutputs.AsyncS3BucketName;
@@ -459,39 +459,49 @@ export default class BootstrapCommandHandler {
459
459
 
460
460
  // --no-rollback prevents rollback on AlreadyExists errors for IAM roles
461
461
  // that may pre-exist from a prior deployment or another region.
462
- // Check if benchmark bucket already exists (from a prior torn-down stack with RETAIN policy)
463
- let importBucketCtx = '';
462
+ // Check if benchmark results bucket already exists.
463
+ // If it does, skip CDK deploy for benchmark infra — just update the profile.
464
+ let benchmarkBucketExists = false;
464
465
  if (options.benchmarkInfra) {
466
+ const resultsBucketName = `mlcc-benchmark-results-${profileData.accountId}-${profileData.awsRegion}`;
465
467
  try {
466
468
  execSync(
467
- `aws s3api head-bucket --bucket mlcc-benchmark-results-${profileData.accountId}-${profileData.awsRegion}${profileData.awsProfile ? ` --profile ${profileData.awsProfile}` : ''} --region ${profileData.awsRegion}`,
469
+ `aws s3api head-bucket --bucket ${resultsBucketName}${profileData.awsProfile ? ` --profile ${profileData.awsProfile}` : ''} --region ${profileData.awsRegion}`,
468
470
  { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
469
471
  );
470
- importBucketCtx = ' -c importExistingBenchmarkBucket=true';
471
- console.log(' ā„¹ļø Benchmark results bucket already exists — importing into stack');
472
+ benchmarkBucketExists = true;
473
+ console.log(` āœ… Benchmark results bucket already exists: ${resultsBucketName}`);
474
+ console.log(' Skipping CDK deploy for benchmark infra — updating profile only.');
475
+ profileData.benchmarkInfraProvisioned = true;
476
+ profileData.ciGlueDatabase = profileData.ciGlueDatabase || 'mlcc_ci';
477
+ profileData.ciBenchmarkResultsBucket = resultsBucketName;
472
478
  } catch {
473
479
  // Bucket doesn't exist — will be created fresh
474
480
  }
475
481
  }
476
- const cdkDeployCmd = options.benchmarkInfra
477
- ? `npx cdk deploy MlccCiHarnessStack --require-approval never --no-rollback --parameters MlccCiHarnessStack:CreateBenchmarkInfra=true${importBucketCtx}`
478
- : 'npx cdk deploy MlccCiHarnessStack --require-approval never --no-rollback';
479
- execSync(
480
- cdkDeployCmd,
481
- {
482
- cwd: ciHarnessDir,
483
- encoding: 'utf8',
484
- stdio: 'inherit',
485
- env: {
486
- ...process.env,
487
- AWS_REGION: profileData.awsRegion,
488
- CDK_DEFAULT_REGION: profileData.awsRegion,
489
- CDK_DEFAULT_ACCOUNT: profileData.accountId,
490
- AWS_PROFILE: profileData.awsProfile
482
+
483
+ // Only run CDK deploy if we actually need to create infrastructure
484
+ if (!benchmarkBucketExists || !options.benchmarkInfra) {
485
+ const cdkDeployCmd = options.benchmarkInfra
486
+ ? 'npx cdk deploy MlccCiHarnessStack --require-approval never --no-rollback --parameters MlccCiHarnessStack:CreateBenchmarkInfra=true'
487
+ : 'npx cdk deploy MlccCiHarnessStack --require-approval never --no-rollback';
488
+ execSync(
489
+ cdkDeployCmd,
490
+ {
491
+ cwd: ciHarnessDir,
492
+ encoding: 'utf8',
493
+ stdio: 'inherit',
494
+ env: {
495
+ ...process.env,
496
+ AWS_REGION: profileData.awsRegion,
497
+ CDK_DEFAULT_REGION: profileData.awsRegion,
498
+ CDK_DEFAULT_ACCOUNT: profileData.accountId,
499
+ AWS_PROFILE: profileData.awsProfile
500
+ }
491
501
  }
492
- }
493
- );
494
- console.log(' āœ… CI harness stack deployed');
502
+ );
503
+ console.log(' āœ… CI harness stack deployed');
504
+ }
495
505
 
496
506
  profileData.ciInfraProvisioned = true;
497
507
  profileData.ciTableName = 'mlcc-ci-table';
@@ -1,6 +1,6 @@
1
1
  // AUTO-GENERATED by scripts/codegen-cli.js — DO NOT EDIT
2
2
  // Source: config/parameter-schema-v2.json
3
- // Generated: 2026-06-12T22:03:00.429Z
3
+ // Generated: 2026-06-15T20:16:03.840Z
4
4
 
5
5
  /**
6
6
  * CLI option definitions derived from parameter-schema-v2.json.
@@ -1,6 +1,6 @@
1
1
  // AUTO-GENERATED by scripts/codegen-parameter-matrix.js — DO NOT EDIT
2
2
  // Source: config/parameter-schema-v2.json
3
- // Generated: 2026-06-12T22:03:00.552Z
3
+ // Generated: 2026-06-15T20:16:03.952Z
4
4
 
5
5
  /**
6
6
  * Parameter matrix defining how each parameter is loaded from various sources.
@@ -1,6 +1,6 @@
1
1
  // AUTO-GENERATED by scripts/codegen-validator.js — DO NOT EDIT
2
2
  // Source: config/parameter-schema-v2.json
3
- // Generated: 2026-06-12T22:03:00.468Z
3
+ // Generated: 2026-06-15T20:16:03.877Z
4
4
 
5
5
  /**
6
6
  * Validation rules derived from parameter-schema-v2.json.
@@ -18,8 +18,6 @@ import {
18
18
  modelLoadStrategyPrompts,
19
19
  modelProfilePrompts,
20
20
  modulePrompts,
21
- loraPrompts,
22
- benchmarkPrompts,
23
21
  infraRegionAndTargetPrompts,
24
22
  infraExistingEndpointPrompts,
25
23
  infraInstancePrompts,
@@ -521,38 +519,23 @@ export default class PromptRunner {
521
519
  const ngcApiKeyAnswers = { ngcApiKey: secretAnswers.ngcApiKey, ngcTokenArn: secretAnswers.ngcTokenArn };
522
520
 
523
521
  // Module selection
524
- const moduleAnswers = await this._runPhase(modulePrompts, { ...frameworkAnswers, ...engineAnswers }, explicitConfig, existingConfig);
525
-
526
- // Ensure transformers, diffusors, and ineligible Triton backends don't get sample model
527
- if (frameworkAnswers.architecture === 'transformers' ||
528
- frameworkAnswers.architecture === 'diffusors' ||
529
- (frameworkAnswers.architecture === 'triton' &&
530
- !this._tritonBackends[frameworkAnswers.backend]?.supportsSampleModel)) {
522
+ // Only ask about sample model for non-transformers/diffusors (Triton etc.)
523
+ const moduleAnswers = {};
524
+ if (frameworkAnswers.architecture !== 'transformers' &&
525
+ frameworkAnswers.architecture !== 'diffusors') {
526
+ const sampleModelAnswers = await this._runPhase(
527
+ modulePrompts.filter(p => p.name === 'includeSampleModel'),
528
+ { ...frameworkAnswers, ...engineAnswers }, explicitConfig, existingConfig
529
+ );
530
+ Object.assign(moduleAnswers, sampleModelAnswers);
531
+ } else {
531
532
  moduleAnswers.includeSampleModel = false;
532
533
  }
533
534
 
534
- // Benchmark prompts — derive includeBenchmark from testTypes selection or CLI flag
535
- // Requirements: 1.1, 1.2
536
- let benchmarkAnswers = {};
537
- if (frameworkAnswers.architecture === 'transformers' || frameworkAnswers.architecture === 'diffusors') {
538
- const testTypes = moduleAnswers.testTypes || [];
539
- const includeBenchmark = testTypes.includes('sagemaker-ai-automated-benchmarking') ||
540
- explicitConfig.includeBenchmark === true ||
541
- explicitConfig.includeBenchmark === 'true';
542
- benchmarkAnswers.includeBenchmark = includeBenchmark;
543
- if (includeBenchmark) {
544
- const subAnswers = await this._runPhase(benchmarkPrompts, { ...frameworkAnswers, ...moduleAnswers, includeBenchmark }, explicitConfig, existingConfig);
545
- benchmarkAnswers = { ...benchmarkAnswers, ...subAnswers };
546
- }
547
- }
548
-
549
- // LoRA adapter prompts — only for transformers with vllm/sglang/djl-lmi
550
- // Requirements: 1.1, 1.2, 1.4
551
- let loraAnswers = {};
552
- const loraSubAnswers = await this._runPhase(loraPrompts, { ...frameworkAnswers, ...engineAnswers }, explicitConfig, existingConfig);
553
- if (loraSubAnswers.enableLora !== undefined) {
554
- loraAnswers = loraSubAnswers;
555
- }
535
+ // Test types, benchmark, and LoRA are always-on (BL-122)
536
+ moduleAnswers.testTypes = ['hosted-model-endpoint', 'sagemaker-ai-automated-benchmarking'];
537
+ const benchmarkAnswers = { includeBenchmark: true };
538
+ const loraAnswers = { enableLora: true };
556
539
 
557
540
  // Validate instance type against framework requirements (now that framework version is known)
558
541
  const finalInstanceType = infraAnswers.customInstanceType || infraAnswers.instanceType;
@@ -10,14 +10,47 @@ This project uses three sets of IAM permissions:
10
10
 
11
11
  ## SageMaker Execution Role
12
12
 
13
- The bootstrap command creates an IAM role (`mlcc-sagemaker-execution-role`) with permissions for:
13
+ The bootstrap command creates an IAM role (`mlcc-sagemaker-execution-role`) with these permission groups:
14
14
 
15
- - **SageMaker**: Create, update, delete, and invoke endpoints, endpoint configs, models, and inference components
16
- - **ECR**: Pull images from the `ml-container-creator` repository
17
- - **CloudWatch Logs**: Write container logs
18
- - **S3**: Read model artifacts from `ml-container-creator-*` buckets
15
+ ### Endpoint Management
16
+ Create, update, delete, describe, and invoke endpoints, endpoint configs, models, and inference components.
19
17
 
20
- The role is defined in the CloudFormation stack template (`config/bootstrap-stack.json`) and updated automatically when you re-run bootstrap after upgrading.
18
+ ### AI Benchmarking
19
+ Create, describe, list, stop, and delete AI benchmark jobs, AI recommendation jobs, and AI workload configs.
20
+
21
+ ### Training & Model Customization
22
+ Create/describe/stop training jobs, model packages, model package groups. Access SageMaker Hub contents. Manage training plans.
23
+
24
+ ### MLflow Integration
25
+ List/describe MLflow tracking servers and apps. Create presigned URLs. Call MLflow app APIs.
26
+
27
+ ### ECR
28
+ Pull container images (GetAuthorizationToken, BatchGetImage, GetDownloadUrlForLayer, BatchCheckLayerAvailability).
29
+
30
+ ### S3
31
+ Read and write model artifacts, adapters, benchmark results:
32
+ - `s3:GetObject`, `s3:PutObject`, `s3:AbortMultipartUpload`, `s3:ListBucket`
33
+ - Scoped to `mlcc-*` and `ml-container-creator-*` buckets
34
+
35
+ ### CloudWatch Logs
36
+ Create log groups/streams and put log events.
37
+
38
+ ### Secrets Manager
39
+ Read and write secrets prefixed with `mlcc/` or `ml-container-creator/` (used for HF tokens, API keys).
40
+
41
+ ### SNS
42
+ Publish notifications to `mlcc-*` and `ml-container-creator-*` topics (benchmark completion alerts).
43
+
44
+ ### Service Quotas & Capacity
45
+ Query service quotas and training plan availability for instance selection.
46
+
47
+ ### Lambda
48
+ Invoke functions (reward model evaluation during training/tuning).
49
+
50
+ ### PassRole
51
+ Self-pass to SageMaker service, scoped to `mlcc-sagemaker-execution-role`.
52
+
53
+ The role is defined in `config/bootstrap-stack.json` and updated automatically when you re-run bootstrap after upgrading.
21
54
 
22
55
  If you use a custom role (`--role-arn`), ensure it has at minimum:
23
56
 
@@ -25,12 +58,15 @@ If you use a custom role (`--role-arn`), ensure it has at minimum:
25
58
  |-----------|---------|
26
59
  | `sagemaker:CreateEndpoint`, `CreateEndpointConfig`, `CreateModel`, `CreateInferenceComponent` | Deploy |
27
60
  | `sagemaker:DeleteEndpoint`, `DeleteEndpointConfig`, `DeleteModel`, `DeleteInferenceComponent` | Clean up |
28
- | `sagemaker:DescribeEndpoint`, `DescribeEndpointConfig`, `DescribeModel`, `DescribeInferenceComponent` | Status checks |
61
+ | `sagemaker:DescribeEndpoint`, `DescribeEndpointConfig`, `DescribeModel`, `DescribeInferenceComponent`, `ListInferenceComponents` | Status |
29
62
  | `sagemaker:InvokeEndpoint`, `InvokeEndpointAsync` | Inference |
30
63
  | `sagemaker:UpdateEndpoint`, `UpdateEndpointWeightsAndCapacities`, `UpdateInferenceComponent` | Updates |
31
- | `ecr:GetAuthorizationToken`, `BatchGetImage`, `GetDownloadUrlForLayer`, `BatchCheckLayerAvailability` | Pull container image |
32
- | `logs:CreateLogGroup`, `CreateLogStream`, `PutLogEvents` | Container logging |
33
- | `s3:GetObject`, `s3:ListBucket` on `ml-container-creator-*` | Model artifact access |
64
+ | `sagemaker:CreateAIBenchmarkJob`, `DescribeAIBenchmarkJob`, `ListAIBenchmarkJobs` | Benchmark |
65
+ | `sagemaker:CreateTrainingJob`, `DescribeTrainingJob`, `StopTrainingJob` | Training/tuning |
66
+ | `ecr:GetAuthorizationToken`, `BatchGetImage`, `GetDownloadUrlForLayer`, `BatchCheckLayerAvailability` | Pull image |
67
+ | `logs:CreateLogGroup`, `CreateLogStream`, `PutLogEvents` | Logging |
68
+ | `s3:GetObject`, `s3:PutObject`, `s3:ListBucket` on project buckets | Artifacts |
69
+ | `iam:PassRole` (to sagemaker.amazonaws.com) | Role delegation |
34
70
 
35
71
  Trust policy must allow `sagemaker.amazonaws.com` to assume the role.
36
72
 
@@ -48,12 +84,27 @@ Your AWS user or CI system needs these permissions to run the do-scripts:
48
84
 
49
85
  | Script | Permissions Needed |
50
86
  |--------|-------------------|
87
+ | `./do/build` | Local only — no AWS permissions |
88
+ | `./do/run` | Local only — no AWS permissions |
51
89
  | `./do/push` | `ecr:GetAuthorizationToken`, `ecr:PutImage`, `ecr:InitiateLayerUpload`, `ecr:UploadLayerPart`, `ecr:CompleteLayerUpload`, `ecr:BatchCheckLayerAvailability` |
52
90
  | `./do/submit` | `codebuild:CreateProject`, `codebuild:StartBuild`, `codebuild:BatchGetBuilds`, `iam:CreateRole`, `iam:PutRolePolicy`, `iam:PassRole`, `s3:PutObject`, `s3:CreateBucket` |
53
- | `./do/deploy` | `sagemaker:CreateEndpointConfig`, `sagemaker:CreateEndpoint`, `sagemaker:CreateInferenceComponent`, `sagemaker:DescribeEndpoint`, `iam:PassRole` |
54
- | `./do/clean` | `sagemaker:DeleteEndpoint`, `sagemaker:DeleteEndpointConfig`, `sagemaker:DeleteInferenceComponent`, `codebuild:DeleteProject`, `iam:DeleteRole`, `iam:DeleteRolePolicy` |
91
+ | `./do/stage` | `s3:PutObject`, `s3:GetObject`, `s3:ListBucket` on mlcc-* buckets |
92
+ | `./do/deploy` | `sagemaker:CreateEndpointConfig`, `sagemaker:CreateEndpoint`, `sagemaker:CreateModel`, `sagemaker:CreateInferenceComponent`, `sagemaker:DescribeEndpoint`, `iam:PassRole` |
93
+ | `./do/add-ic` | `sagemaker:CreateInferenceComponent`, `sagemaker:DescribeEndpoint`, `sagemaker:ListInferenceComponents`, `iam:PassRole` |
55
94
  | `./do/test` | `sagemaker-runtime:InvokeEndpoint` |
56
- | `bootstrap` | `cloudformation:*`, `iam:CreateRole`, `iam:PutRolePolicy`, `iam:TagRole`, `ecr:CreateRepository`, `s3:CreateBucket` (and `sts:GetCallerIdentity`) |
95
+ | `./do/benchmark` | `sagemaker:CreateAIBenchmarkJob`, `sagemaker:DescribeAIBenchmarkJob`, `sagemaker:ListAIBenchmarkJobs`, `sagemaker:CreateAIWorkloadConfig`, `iam:PassRole`, `s3:GetObject` |
96
+ | `./do/train` | `sagemaker:CreateTrainingJob`, `sagemaker:DescribeTrainingJob`, `iam:PassRole`, `s3:GetObject`, `s3:PutObject` |
97
+ | `./do/tune` | `sagemaker:CreateTrainingJob`, `sagemaker:DescribeTrainingJob`, `iam:PassRole`, `s3:GetObject`, `s3:PutObject` |
98
+ | `./do/adapter` | `sagemaker:CreateInferenceComponent`, `sagemaker:UpdateInferenceComponent`, `sagemaker:DescribeInferenceComponent`, `s3:GetObject` |
99
+ | `./do/optimize` | `sagemaker:CreateModel`, `sagemaker:DescribeModel`, `s3:GetObject`, `s3:PutObject` |
100
+ | `./do/register` | `sagemaker:CreateModelPackage`, `sagemaker:CreateModelPackageGroup`, `sagemaker:DescribeModelPackage` |
101
+ | `./do/logs` | `logs:GetLogEvents`, `logs:FilterLogEvents`, `logs:DescribeLogStreams` |
102
+ | `./do/status` | `sagemaker:DescribeEndpoint`, `sagemaker:DescribeInferenceComponent`, `sagemaker:ListInferenceComponents` |
103
+ | `./do/clean` | `sagemaker:DeleteEndpoint`, `sagemaker:DeleteEndpointConfig`, `sagemaker:DeleteModel`, `sagemaker:DeleteInferenceComponent`, `codebuild:DeleteProject`, `iam:DeleteRole`, `iam:DeleteRolePolicy` |
104
+ | `./do/export` | Local only — reads config files |
105
+ | `./do/validate` | Local only — validates project structure |
106
+ | `./do/manifest` | Local only — generates deployment manifest |
107
+ | `bootstrap` | `cloudformation:*`, `iam:CreateRole`, `iam:PutRolePolicy`, `iam:TagRole`, `ecr:CreateRepository`, `s3:CreateBucket`, `sts:GetCallerIdentity` |
57
108
 
58
109
  <% if (framework === 'transformers' && hfToken) { %>
59
110
  ## HuggingFace Token Security