Skip to content

2.0.0-beta.65 - SageMaker HyperPod

beta.65 brings SageMaker HyperPod to the AWS provider: persistent ML training fleets, Slurm or EKS orchestrated, with task governance and typed workloads. It also fixes two Worker bundling bugs that broke deployed apps, and makes destroy and nuke converge in one pass.

HyperPod is AWS’s persistent fleet for ML training and inference: accelerated instances with automatic health checks and faulty-node replacement, staying up between jobs. Where ECS and EKS run application containers, HyperPod runs distributed training (#924).

AWS.SageMaker.Cluster provisions the fleet. The default orchestrator is Slurm — an S3 lifecycle script, an execution role, and instance groups:

const cluster = yield* AWS.SageMaker.Cluster("TrainingCluster", {
instanceGroups: {
controller: {
InstanceType: "ml.t3.medium",
InstanceCount: 1,
ExecutionRole: role.roleArn,
LifeCycleConfig: {
SourceS3Uri: script.sourceS3Uri,
OnCreate: script.onCreate,
},
},
},
});

Instance groups reconcile in place: change a count or add a group and the cluster updates; remove one and it is deleted from the cluster. Jobs are submitted with sbatch over SSM — every node is an SSM target.

Pass orchestrator: { Eks } and the fleet’s nodes join an EKS cluster you provide. From there, workloads are ordinary Kubernetes objects — and AWS.EKS.Job and Deployment opt onto HyperPod nodes with the hyperpod prop. The instance-group keys carry through the cluster’s attributes as types, so a typo’d group name is a compile error:

const train = yield* AWS.EKS.Job(
"Train",
{
cluster: eks,
main: import.meta.url,
hyperpod: { instanceGroup: hyperpod.instanceGroups.workers },
},
Effect.gen(function* () {
return {
run: Effect.gen(function* () {
// training logic; bindings land IAM on pod identity
}),
};
}),
);

Task governance arbitrates the fleet between teams: ClusterSchedulerConfig sets priority classes and ComputeQuota reserves per-team capacity (Kueue under the hood). A workload submits through governance by passing the quota resource:

hyperpod: {
instanceGroup: hyperpod.instanceGroups.workers,
quota, // → hyperpod-ns-research + queue label
priorityClass: "training",
},

Docs: HyperPod. The full working stack (both orchestrators, governance, every workload tier) is examples/aws-hyperpod.

Two bundling bugs could break a deployed Worker even though the deploy succeeded. Both are fixed.

Hono/Express-style route registrations were stripped from the bundle (#952). purePlugin annotated top-level calls like app.get("/", handler) as pure, and under minification that annotation is a deletion instruction — so every request 404’d. Statement-position calls are now only annotated in packages that explicitly declare sideEffects: false, and never in entry modules.

Cross-chunk evaluation order is now correct (#940). Rolldown could split top-level initializers (e.g. Drizzle pgTable schemas) into a chunk that ran before its dependencies, failing Cloudflare startup validation with Cannot access 'a' before initialization. strictExecutionOrder is now on by default, preserving ESM semantics across chunk boundaries.

The same PR opens up per-Worker control of chunking: build forwards rolldown output options and preserveEntrySignatures:

Cloudflare.Worker("Api", {
main,
build: {
output: {
codeSplitting: {
groups: [{ name: "auth", test: "packages/(schema|auth)/" }],
},
},
},
});

Destroying a stack with a VPC-attached Lambda used to strand subnets and security groups on DependencyViolation, forcing repeated destroy runs (#937). The cause: Lambda’s Hyperplane ENIs linger in the VPC for many minutes after the function is deleted, and AWS’s own reaper can take 20+ minutes to clear them. Subnet and security-group deletes now identify detached Lambda ENIs and delete them directly, so one destroy removes everything.

alchemy unsafe nuke gets the same treatment (#950):

  • --independent retries each delete on its own capped backoff instead of a shared barrier, so one hanging delete only ever stalls itself.
  • Providers declare which resource types their cloud-side teardown consumes (a HyperPod cluster deletes its ENIs by assuming its IAM role inside its VPC), and nuke deletes in topologically-ordered waves so a role is never deleted out from under an in-flight cluster teardown.
  • _redirects and _headers now reach Cloudflare (#928) — the files were read and hashed but never sent, so redirect and header rules were silently dropped. They now apply in deploys, no-op redeploys, and alchemy dev.
  • Workflows under alchemy dev: pausing a workflow no longer reports errored (#934), and workflow classes export correctly in vite dev (#933).
  • Docker.Container supports labels and stopTimeout (#931) — enabling Traefik-managed routing in a remote Docker context. Thanks Gerbuuun!
  • alchemy/Test/Vitest is restored (#947) — the internal move to alchemy-test accidentally removed the public vitest harness that shipped through beta.63.
  • Branded primitives stay opaque in Outputs (#941) — a string & Brand<"UserId"> Action output is now Output<UserId> instead of exploding into an ObjectExpr over every string method.
  • SQL fragment helpers compose on wrapped clients (#951) — sql`INSERT INTO users ${sql.insert(row)}` and nested sql.and/sql.or now replay against the resolved client, with new live test suites covering the full SqlClient surface on D1 and Postgres.
  • The alchemy-test plain reporter prints a [done/total] progress counter per test and a failed-test list at the end of the run.