@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.
- package/README.md +23 -5
- package/infra/ci-harness/package-lock.json +1 -5
- package/package.json +5 -3
- package/pyproject.toml +21 -0
- package/requirements.txt +19 -0
- package/servers/instance-sizer/lib/model-resolver.js +127 -185
- package/servers/instance-sizer/lib/vram-estimator.js +86 -0
- package/servers/lib/catalogs/instances.json +0 -27
- package/src/app.js +2 -0
- package/src/lib/bootstrap-command-handler.js +35 -25
- package/src/lib/generated/cli-options.js +1 -1
- package/src/lib/generated/parameter-matrix.js +1 -1
- package/src/lib/generated/validation-rules.js +1 -1
- package/src/lib/prompt-runner.js +14 -31
- package/templates/IAM_PERMISSIONS.md +64 -13
- package/templates/do/.adapter_helper.py +451 -0
- package/templates/do/.benchmark_writer.py +13 -0
- package/templates/do/.stage_helper.py +419 -0
- package/templates/do/.tune_helper.py +218 -67
- package/templates/do/README.md +50 -604
- package/templates/do/__pycache__/.adapter_helper.cpython-312.pyc +0 -0
- package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
- package/templates/do/__pycache__/.tune_helper.cpython-312.pyc +0 -0
- package/templates/do/adapter +109 -4
- package/templates/do/benchmark +150 -12
- package/templates/do/build +2 -5
- package/templates/do/clean.d/async-inference.ejs +2 -5
- package/templates/do/clean.d/batch-transform.ejs +2 -5
- package/templates/do/clean.d/hyperpod-eks.ejs +2 -5
- package/templates/do/clean.d/managed-inference.ejs +2 -5
- package/templates/do/config +4 -0
- package/templates/do/deploy.d/async-inference.ejs +6 -9
- package/templates/do/deploy.d/batch-transform.ejs +4 -7
- package/templates/do/deploy.d/hyperpod-eks.ejs +1 -4
- package/templates/do/deploy.d/managed-inference.ejs +15 -6
- package/templates/do/lib/profile.sh +24 -15
- package/templates/do/push +2 -5
- package/templates/do/register +2 -5
- package/templates/do/stage +114 -292
- package/templates/do/submit +1 -4
- package/templates/do/tune +64 -10
- package/templates/MIGRATION.md +0 -488
- 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.
|
|
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
|
|
463
|
-
|
|
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
|
|
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
|
-
|
|
471
|
-
console.log(
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
-
|
|
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-parameter-matrix.js ā DO NOT EDIT
|
|
2
2
|
// Source: config/parameter-schema-v2.json
|
|
3
|
-
// Generated: 2026-06-
|
|
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.
|
package/src/lib/prompt-runner.js
CHANGED
|
@@ -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
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
|
13
|
+
The bootstrap command creates an IAM role (`mlcc-sagemaker-execution-role`) with these permission groups:
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
61
|
+
| `sagemaker:DescribeEndpoint`, `DescribeEndpointConfig`, `DescribeModel`, `DescribeInferenceComponent`, `ListInferenceComponents` | Status |
|
|
29
62
|
| `sagemaker:InvokeEndpoint`, `InvokeEndpointAsync` | Inference |
|
|
30
63
|
| `sagemaker:UpdateEndpoint`, `UpdateEndpointWeightsAndCapacities`, `UpdateInferenceComponent` | Updates |
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
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/
|
|
54
|
-
| `./do/
|
|
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
|
-
| `
|
|
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
|