Skip to main content
SYS.ONLINE

Augment Stage: NVIDIA Cosmos3 Transfer on an NVIDIA Jetson AGX Thor for the Seeed Studio reBot Arm B601-RS

· 10 min read
Chiwai Chan
Tinkerer

For the GR00T N1.7 fine-tune I borrowed someone else's dataset, prateeks-iu/rebot_b601_rs_sugar: 103 episodes of a robot arm picking up a sugar cube. All of them were recorded in the same room, on the same white table, under the same light.

A model trained on that learns the table as much as it learns the task.

The usual fix is to record more data somewhere else. The other fix is to repaint the data you already have — keep the arm's motion exactly as it was, and change everything behind it. That is what Cosmos3 Transfer does.

I had a Jetson AGX Thor on my desk and wanted to know whether it could run Cosmos3 Transfer on that dataset. Six days later: 309 episodes.

One dataset, three rooms​

Four frames from the source episode at 0, 6.7, 13.3 and 20.0 seconds: a lime-green and black robot arm on a white table against a grey wall, reaching toward a sugar cube beside a white cup and lifting away

The source. Grey wall, white tabletop, flat light — prateeks-iu/rebot_b601_rs_sugar, 103 episodes in LeRobot format, the same dataset I fine-tuned GR00T N1.7 on.

The same four frames repainted as a domestic kitchen: timber cabinetry behind, a marble countertop, warm light

Kitchen: "marble countertops, stainless steel appliances and timber cabinetry behind", warm overhead spots and window light.

The same four frames repainted as a research laboratory: white epoxy bench, clinical even lighting

Lab: "white epoxy workbenches, metal shelving and precision instruments", flat fluorescent panels, minimal shadows.

The same four frames repainted as an industrial workshop: heavy timber bench, grey wall, crisp overhead shadows

Workshop: "heavy timber workbenches, hand tools on a pegboard wall", industrial LED task lighting.

Those four strips are sampled at the same four timestamps — 0, 6.7, 13.3 and 20.0 seconds.

Compared properly, the arm is in the same pose at the same moment in all four. The cup has not moved, the sugar cubes have not moved. Only the room changed.

That is the point: the joint trajectories in the original parquet files should stay valid for every repainted video, because the motion is untouched.

How it holds the motion​

Three things constrain generation, and they matter in this order:

Canny edge controlAn edge map extracted from the source video, fed in at weight 0.9
Chunked generation121 frames at a time, each chunk conditioned on the last frame of the previous one
Scene promptA structured JSON description of the room, the light and the camera

The edge map is the leash. The arm's silhouette is in it frame by frame, so the model cannot invent a different motion without contradicting its own control input. Set the control weight too low and you get a prettier video of a different robot.

Chunking is how a 16B model generates an 800-frame episode in 122 GB of unified memory at all: 121 frames, keep the last one, condition the next 121 on it. I checked a handful of episodes at the chunk boundaries by eye and found no visible seam, which I would attribute to the edge control reasserting the true arm position on every frame regardless of what the previous chunk drifted toward. By eye, on a handful — not measured across all 309.

I skipped SAM2 segmentation entirely. The reference workflow uses edge plus segmentation — but it runs edge at weight 0.9 against segmentation's 0.1. For a tenth of the control signal you install a second model and give it a chance to fail. Edge alone is a first-class control.

The settings, in full​

Modelnvidia/Cosmos3-Nano — 16B, bf16, ~32 GB of weights
Modevideo2video
ControlCanny edge, weight 0.9, preset_edge_threshold: medium. No segmentation
SamplerUniPC, 35 steps, shift 5.0
Guidancetext 4.0, control 1.5
Resolution480 bucket at 4:3 → 736 × 544, 30 fps
Chunking121 frames, 1 conditional frame, 0 for the first chunk
Seed2026, fixed across all 309
Specs309 = 103 episodes × 3 scenes

shift 5.0 is the 480p value; 10.0 is for 720p. The wrong one is a quiet quality loss, not an error, so nothing tells you. The 480 bucket is what the model was trained for — asking for more on a Jetson runs you out of memory mid-episode.

Six days at a steady 419 seconds a chunk​

Line chart of seconds per 121-frame chunk across 1,144 chunks over six days: the line stays in a narrow band around 420 seconds against a dashed baseline at 429 seconds, with no upward drift

Every chunk the run produced, in order. 1,144 of them.

The first fifty average 426 s. The last fifty average 424 s. Mean across the whole run is 419 s, standard deviation 7.7 s, against a 429 s baseline I measured on ten clips before launching the full job.

There is one visible step, and it goes the wrong way to be a problem. Chunks 0–200 average 428 s, 200–1,100 sit at 417 s, the last 44 return to 424 s. An 11-second shift that happens once and reverses — ambient temperature over a week, not hardware degrading. Degradation is monotonic. This is not.

This is the chart I cared about. Thermal throttling costs wall-clock time and nothing else — it does not log, it does not warn. A run whose per-chunk time on day six is no worse than day zero was not losing time to heat, and that holds without reading a single temperature.

Terminal: tr and grep pulling the last three completed sampling bars out of the run log, showing 7:04, 6:59 and 7:02, against a comment noting the 429 second baseline

Six days at full load: 93.5 °C peak, no throttling​

Line chart of junction temperature across the whole six-day run, 16,199 samples: it cycles between roughly 60 and 93 degrees, far below dashed reference lines at 100 and 109 degrees

Sampled every 30 seconds for six days. The sawtooth is the gap between episodes: model unloads, board cools, next spec loads.

Peak junction was 93.5 °C. Thor's passive throttle is 109 °C and the fan steps are at 80, 86, 91 and 100 °C. Six days of continuous 16B diffusion sampling put it in the third fan step and left it there.

Bar chart of peak values against hardware limits: junction 93.5 of 109 degrees, GPU 92 of 109, memory 80 of 122 GB, CPU load 3.4 of 14 cores

I pointed a desk fan at it for part of the week. Six-hourly mean junction temperature under load stayed between 81.9 °C and 84.5 °C for the entire run — there is no step where the fan went on. Same result as the ten-clip benchmark: external cooling bought nothing, because at 89 °C the device is already in its active fan band and losing no performance.

The desk fan on the Thor.

The fan was me being nervous, not the hardware needing help. It's an expensive box to cook: it launched at US$3,499, NVIDIA now lists it at US$5,499, and it's NZ$10,754 here in New Zealand.

What you actually get​

Each of the 309 specs produces a directory:

FileSizeWhat it is
vision.mp440–80 MBThe augmented video, 736 × 544, 30 fps
control_edge.mp4~20 MBThe edge map that constrained it
sample_args.json~20 KBEvery argument, the full scene prompt, the source paths
sample_outputs.json~20 KBThe same, plus what was produced

The two JSON files mean every clip carries its own provenance — how it was made sits beside it, not in a spec file elsewhere that may have moved. Frame counts matched their own specs on all 309. Worth checking: a short video fails silently in training rather than loudly here.

What it cost​

Terminal: counting 309 vision.mp4 files, running the verify script to get valid 309 bad 0, grepping the log for zero errors and the watchdog for zero wifi bounces, and du reporting 13 GB

Wall clock144.7 h — 6.03 days (I predicted 132.7 h, so 9% over)
Specs309 of 309, 0 errors
Output13.3 GB, 1,239 files
Frame count vs spec309 match, 0 bad
Mean draw under load75 W GPU rail, 119 W total board
Energy17.3 kWh — about NZ$5
Peak memory80 GB of 122
Same job in the cloud (estimate)~47 GPU-hours on an AWS g6e.xlarge (L40S) — about US$80 on Spot, US$87 on-demand

Five dollars of electricity, and the machine was never unavailable for anything else — it is a box on a desk, not a meter running. On AWS it would finish sooner, but my estimate for the cloud compute is about US$80 (a g6e.xlarge on Spot), against NZ$5 of electricity here.

It was not uninterrupted. Six days is long enough that something will go wrong, and on 26 October the job stopped. The driver skips any spec whose output already exists, so restarting cost one batch of five and nothing else. That property was worth more than any amount of tuning: the correct response was a single command, not a decision.

I would keep the resumability unchanged.

The one thing I would flag for anyone sizing their own run: the progress CSV lies if you read it carelessly. Field four is batch seconds, not per-spec seconds, and the driver runs batches of five, so summing the column gives 715 hours for a 143-hour run.

What I was aiming for, and what I got​

I wanted more varied training data from the borrowed sugar-cube dataset. Same motion, same action labels, a different room. Each repainted video had to drop straight into the dataset in place of the real scene camera.

After the run I checked the outputs against the source, not just against their own specs. They don't match.

SourceGenerated
Frames, episode 01,380690
Frame rate60 fps30 fps
Resolution640 × 480736 × 544
Field of viewfull473 of 480 rows
  • The frame rate is half. My prep step downsampled to 30 fps before generating, so the video no longer lines up frame for frame with the action data.
  • The size is wrong. The model wrote out its own 736 × 544.
  • The view is slightly smaller. The default centre crop kept 473 of the 480 rows.
  • It is video only. There are no state or action arrays with it, and no LeRobot dataset yet.

My check compared each output to its spec, so all 309 passed anyway.

So this run is not the augmentation I set out to get. I'm not calling it complete or a success.

I'll work through these in a later post.