Skip to content

beam-thunder integration - #1814

Open
rohanphadnis-thunder wants to merge 56 commits into
beam-cloud:mainfrom
Thunder-Compute:main
Open

beam-thunder integration#1814
rohanphadnis-thunder wants to merge 56 commits into
beam-cloud:mainfrom
Thunder-Compute:main

Conversation

@rohanphadnis-thunder

@rohanphadnis-thunder rohanphadnis-thunder commented Jul 29, 2026

Copy link
Copy Markdown

Beam-Thunder Integration

This PR is for integrating beta9 with Thunder Compute. Key changes:

Phase 1: gpu_virtualized

gpu_virtualized, a boolean value is propagated from the Python beam client SDK to the beam gateway to each individual worker. Note that this is still a temporary change, preserved for its usefulness in prototyping. Eventually, GPU virtualization is controlled at the level of the pool. If a pool is virtualized, all its containers would be virtualized as well.

Phase 2: Beam Worker

The beam worker implements beta9/pkg/worker/thunder.go. This contains the ContainerThunderManager, a struct which implements the GPUManager interface. It mounts nvidia-smi, libcuda.so, and libnvidia-ml.so into the container from the host, but blocks the mounting of any physical GPU. For assigning and unassigning GPUs, it will use the gateway's thunder service and mint/revoke enrollment tokens as needed. Finally, when the container starts, the worker will run the curl installer in the container.

Phase 3: Beam Agent

The beam agent change is very easy.

  1. Tailscale is initialized on the host node. This was already the case. Thunder will simply need an IP address which is discoverable to all nodes and container clients in the pool. It will read the IP address of the tailscale0 interface.
  2. Next, the gateway will use newly added RPC call CreateNodeEnrollment to get a node enrollment token and run the node curl installer.
  3. Once the installer is run, the agent can continue its setup.

Phase 4: Beam Gateway

The beam gateway provides RPC calls to the beam agent and the beam worker in order to mint these enrollment tokens. It acts as a central client for the Thunder Compute API. This design ensures that the global API token isn't propagated to each beam agent/worker.

To make the Thunder information stateful, 3 new redis maps are added:

  1. for mapping client id to enrollment token id. This is so that when the client is unenrolled (ie during sandbox teardown), the thunder client's enrollment can be revoked.
  2. for mapping machine id to enrollment token id. This is so that if nodes ever need to be unregistered, the enrollment tokens for the nodes can be revoked.
  3. for mapping beam pools to thunder zone IDs. Each pool gets its own Thunder Zone ID.

Summary by cubic

Integrates Thunder virtual GPUs end-to-end with pool-level control, gateway-managed enrollments, and startup hooks so containers aren’t marked started until Thunder is installed. Adds an agent systemd stop hook to uninstall Thunder, improves scheduling/logs, and removes the TTL parameter from the Thunder Redis repository.

  • New Features

    • Pool/API: Virtualization is pool-level; expose gpuVirtualized to agent/worker and OpenAPI; add GetAgentPoolVirtualization RPC; add GPU scheduling request logs.
    • Worker: Choose Thunder vs physical GPUs per request; startup hooks gate “started” until installer completes; inject Thunder LD_PRELOAD; mount nvidia-smi, libcuda.so.1, libnvidia-ml.so.1, and Thunder lib; send metrics through the selected GPU manager; revoke enrollments and unassign on teardown.
    • Agent: Auto node enrollment via CreateNodeEnrollment/DeleteNodeEnrollment using cache-locality private IP; pass pool gpuVirtualized; soft-fail with logs if unavailable; add systemd ExecStopPost to uninstall Thunder on shutdown.
    • Gateway: New ThunderService with Redis-backed repository (client/node enrollments, per-pool locks, zone mappings) using github.com/Thunder-Compute/thunder-sdk; register gRPC and enable agent-token RPCs in the auth interceptor.
    • Tooling/Proto: Generate pkg/gateway/services/thunder/thunder.proto; update OpenAPI with gpuVirtualized.
  • Migration

    • Set THUNDER_API_URL and THUNDER_API_TOKEN in the gateway environment.
    • Ensure agents can resolve a private IP for thunderd; node enrollment runs automatically.
    • Enable virtualization per pool via WorkerPoolConfig.gpuVirtualized (propagated via WORKER_GPU_VIRTUALIZED).

Written for commit f4ce2b9. Summary will update on new commits.

Review in cubic

@luke-lombardi luke-lombardi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High level this looks good, mostly just some nit-picks around code organization

Comment thread pkg/abstractions/common/instance.go Outdated
request := &types.ContainerRequest{
Cpu: i.StubConfig.Runtime.Cpu,
GpuCount: uint32(gpuCount),
GpuVirtualized: i.StubConfig.Runtime.GpuVirtualized,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still required? I know we talked about removing from the client, but I feel like it should also be removed from the container request as well.

Comment thread pkg/worker/lifecycle.go Outdated
}

func (s *Worker) deleteContainer(containerId string) {
s.thunderSetupTracker.Delete(containerId)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like the thundersetuptracker calls could be pushed down into the runtime layer potentially? feels like it could get confusing having those calls up here next to the core container lifecycle pieces

Comment thread pkg/worker/lifecycle.go Outdated
}()

exitCode, _ = s.runContainer(ctx, request, outputLogger, outputWriter, startedChan, checkpointPIDChan, opts.StartupStartedAt, opts.StartupPortBindings, opts.CheckpointFilesystemRestore)
exitCode, _ = s.runContainer(ctx, request, outputLogger, outputWriter, startedChan, checkpointPIDChan, thunderInstallResult, opts.StartupStartedAt, opts.StartupPortBindings, opts.CheckpointFilesystemRestore)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above, if theres a way to get this to be jammed into a runtime (under pkg/runtime) I think that would be cleaner

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pkg/gateway/services/thunder/db.go">

<violation number="1" location="pkg/gateway/services/thunder/db.go:85">
P1: Custom agent: **Prevent Redundant Code Duplication**

The three CRUD flows (client enrollment, node enrollment, zone) repeat nearly identical `Get`, `Save`, `Delete`, and `List` code. For example, each `SaveX` validates required fields, marshals JSON, and runs `TxPipelined(Set + SAdd)`; each `DeleteX` runs `TxPipelined(Del + SRem)`; each `ListX` does `SMembers`, `sort.Strings`, key building, and `listJSON`. Since the file already uses Go generics via `listJSON[T]`, you can extract these into shared generic helpers that take key builders and validation callbacks. This would reduce ~12 duplicated method bodies to ~4 generic helpers and prevent inconsistencies when the persistence pattern evolves.</violation>
</file>

<file name="pkg/gateway/services/thunder/service_test.go">

<violation number="1" location="pkg/gateway/services/thunder/service_test.go:21">
P2: Custom agent: **Prevent Redundant Code Duplication**

This new 854-line test file repeats two substantial blocks that could be extracted into shared test helpers. The client-enrollment setup boilerplate (newThunderRedisClient + container/worker test repositories + AddWorker for worker-1/pool-1/machine-1 + SetContainerState for container-1/workspace-1/worker-1) is duplicated nearly verbatim across at least five tests (TestServiceCreateAndDeleteClientEnrollment, TestServiceCreateClientEnrollmentReusesExistingZone, TestServiceCreateClientEnrollmentReplacesExistingToken, TestServiceCreateClientEnrollmentSucceedsWhenPreviousTokenRevokeFails, TestServicePrivateWorkerTokenIsWorkspaceScoped), differing only in Gpu/GpuCount. Likewise, the httptest mock Thunder API handler (Authorization check, POST zones, POST enrollment-token, DELETE enrollment-token revocation) is repeated with high similarity across the client and node enrollment tests, differing only in role/gpuType/gpuCount assertions. Consolidating these into helpers (e.g., a setupEnrollmentTest(t, gpu, gpuCount) helper and a reusable Thunder API stub factory) would reduce maintenance burden and reduce the risk that the tests drift out of sync with each other when the enrollment flow next changes.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread pkg/worker/thunder.go Outdated
Comment thread pkg/gateway/services/thunder/db.go Outdated
@@ -0,0 +1,262 @@
package thunder

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Prevent Redundant Code Duplication

The three CRUD flows (client enrollment, node enrollment, zone) repeat nearly identical Get, Save, Delete, and List code. For example, each SaveX validates required fields, marshals JSON, and runs TxPipelined(Set + SAdd); each DeleteX runs TxPipelined(Del + SRem); each ListX does SMembers, sort.Strings, key building, and listJSON. Since the file already uses Go generics via listJSON[T], you can extract these into shared generic helpers that take key builders and validation callbacks. This would reduce ~12 duplicated method bodies to ~4 generic helpers and prevent inconsistencies when the persistence pattern evolves.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/gateway/services/thunder/db.go, line 85:

<comment>The three CRUD flows (client enrollment, node enrollment, zone) repeat nearly identical `Get`, `Save`, `Delete`, and `List` code. For example, each `SaveX` validates required fields, marshals JSON, and runs `TxPipelined(Set + SAdd)`; each `DeleteX` runs `TxPipelined(Del + SRem)`; each `ListX` does `SMembers`, `sort.Strings`, key building, and `listJSON`. Since the file already uses Go generics via `listJSON[T]`, you can extract these into shared generic helpers that take key builders and validation callbacks. This would reduce ~12 duplicated method bodies to ~4 generic helpers and prevent inconsistencies when the persistence pattern evolves.</comment>

<file context>
@@ -0,0 +1,262 @@
+	return &state, true, nil
+}
+
+func (r *RedisRepository) SaveClientEnrollment(ctx context.Context, state *ClientEnrollmentState, ttl time.Duration) error {
+	if state == nil || state.ContainerID == "" {
+		return errThunderStateRequired
</file context>

Comment thread pkg/agent/thunder.go Outdated
Comment thread pkg/agent/thunder.go Outdated
Comment thread pkg/worker/thunder.go Outdated
Comment thread pkg/gateway/services/thunder/client.go Outdated
Comment thread pkg/worker/lifecycle_test.go Outdated
Comment thread pkg/gateway/services/thunder/service.go Outdated
Comment thread pkg/gateway/services/thunder/service_test.go
Comment thread sdk/src/beta9/abstractions/base/runner.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 24 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pkg/types/types.proto">

<violation number="1">
P3: When removing a field from a proto message, reserve its number (and name) so it can never be silently reused later. In the same PR you correctly did this in gateway.proto with `reserved 47;`, but here in types.proto the `gpu_virtualized = 37;` field was removed from `ContainerRequest` without a matching `reserved 37;` declaration. Since field 37 is now completely free, a future addition could accidentally take over tag 37, and any older SDK or serialized payload still carrying on-the-wire field 37 would then be decoded as that unrelated new field, silently corrupting requests. Because this is prototype wiring removed consistently on both sides the immediate impact is low, but adding the reservation now costs nothing and matches the pattern you already used in gateway.proto. Consider adding `reserved 37; reserved "gpu_virtualized";` after `task_id = 36;` and regenerating.</violation>
</file>

<file name="pkg/types/scheduler.go">

<violation number="1">
P2: Custom agent: **Prevent Redundant Code Duplication**

Removing `RequiresPhysicalGPU()` from `ContainerRequest` without adding an equivalent helper on the `Worker` duplicates the physical-vs-virtual GPU decision inline across multiple call sites. The expression `request.RequiresGPU() && !s.gpuVirtualizedForRequest(request)` now appears in `pkg/worker/lifecycle.go` at lines 298 and 1271. Extracting a shared `Worker`-level method such as `requiresPhysicalGPU(request)` would restore the centralized intent and avoid repeated inline logic.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 13 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread pkg/gateway/services/compute/agent.go
Comment thread pkg/agent/transport.go Outdated
Comment thread pkg/abstractions/pod/pod.go Outdated
gpuCount = 1
}

log.Info().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this log required? we log this in the scheduler too I think

Comment thread pkg/agent/transport.go Outdated
Comment thread pkg/gateway/services/thunder/db.go Outdated
return err
}

func (r *RedisRepository) DeleteZone(ctx context.Context, workspaceID, poolName string) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may make sense to push this into the repository layer. repository/thunder_redis.go or something

Comment thread pkg/runtime/startup_hook_wrapper.go Outdated
"github.com/opencontainers/runtime-spec/specs-go"
)

const startupHookShutdownTimeout = 5 * time.Second

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code can probably also live inside startup_hook.go maybe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants