← All AI Engineer talks

AI Engineer World's Fair 2025

Challenges in High Performance Robotics Systems

Rishabh Garg· Robotics Engineer, Tesla Optimus12:43

Read the talk

When a Robot’s Policy Is Right but Its Timing Is Wrong

A simple CAN-connected robot reveals how bus delays, stale sensor inputs, logging and scheduling can turn correct policy outputs into erratic motion.

From a talk by Rishabh Garg

Before you start: Basic familiarity with control loops, threads and sensor-to-actuator data flow will help; no CAN experience is required.

Between the controller and the wire

A robot’s motor does not move. Did the policy fail to produce a command, or did the software fail to deliver it? A controller depends on two journeys: sensor data must reach the policy, and the resulting commands must reach the actuators. A failure anywhere along those paths can look like a bad policy.

Rishabh Garg builds a small toy robot around this diagnostic problem. The question is not simply whether the policy computes the right answer, but whether the surrounding system supplies the right input and delivers the answer when the motor needs it. That distinction becomes increasingly important as mechanical, electrical and software components interact.

Slide listing robot complexity, coordination of mechanical, electrical and software systems, and critical data flow, followed by “How to Root Cause unexplained behavior?”
Why this talk? Finding the root cause of unexplained robot behavior.
0:150:27
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:15 · section reference included

The missing millisecond

The toy architecture contains actuators, a CPU, an optional hardware accelerator and a sensor. Garg chooses CAN for communication because it is affordable, compatible with many components and fast enough for the example. His description of CAN as open source is best understood as enthusiasm for its accessible ecosystem; the protocol and particular open-source implementations are distinct.

The initial control loop is sequential: receive data, run the policy, transmit its output. With approximately 2 ms allocated to policy execution, it is tempting to expect a new output every 2 ms. Deployment introduces an extra gap, however. The receive and transmit operations at the edges of the loop also consume time.

In the toy budget, ten 100-bit messages at 1 Mbit/s require 1 ms of serialization. Five messages go out and five come back:

Totalbits=100bits/message×10messages=1,000bitsTimepermessage=100/1,000,000seconds=0.1msTotalbustime=1,000/1,000,000seconds=1msTotal bits = 100 bits/message × 10 messages = 1,000 bits Time per message = 100 / 1,000,000 seconds = 0.1 ms Total bus time = 1,000 / 1,000,000 seconds = 1 ms

Communication is now on the same order as computation, explaining the additional millisecond. This is a simplified budget: actual CAN frame structure, stuffing, arbitration, intermission and retries affect bus occupancy. The 100-bit assumption is not a universal frame size.

1:231:34
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

1:23 · section reference included

Overlap communication with computation

Accepting the communication delay gives an approximately 3 ms sequential loop; the proposed pipeline instead targets a 2 ms iteration cadence. The bus work does not disappear. The design changes when that work happens, separating transmission (TX), reception (RX) and policy execution, with communication running on a different thread from the policy.

The staggered schedule proceeds as follows:

  1. Receive initial sensor data to seed the first policy iteration.
  2. While that policy executes, begin receiving the data intended for the next iteration.
  3. At the next iteration, transmit the previous policy’s output while the next policy computation runs.

Pipelining recovers cadence by overlapping work. It does not establish a 2 ms end-to-end sensor-to-actuator latency, and the proposed schedule still depends on tasks completing at the intended boundaries.

New Design timeline with three consecutive Policy blocks, yellow RX blocks and blue TX blocks arranged around their boundaries, above a milliseconds axis.
The new design overlaps RX and TX with successive policy executions.
3:203:30
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

3:20 · section reference included

Watch what actually reaches the bus

After deployment, the actuators stutter, make catching-up sounds or move strangely. Again, the policy looks suspicious. Before changing it, inspect the commands that actually reach the wire. Attach an external CAN transceiver to the bus and connect it to a separate host, such as a laptop. This gives a view of traffic outside the robot’s control process.

On that host, candump from can-utils can display timestamped CAN traffic. For an already configured interface named can0, a basic capture is:

bash

candump can0

The measurement’s timestamp source matters. The current candump options include -H to request hardware rather than system timestamps; Garg does not specify the adapter or timestamp mode used in his example.

Plot the outgoing command timestamps. The expected spacing is 2 ms. In the illustrated failure, the gap from message two to message three grows to 4 ms, then message four follows message three with almost no gap. Message three is late; message four is on time. The actuator experiences a pause followed by closely spaced commands, producing the apparent catch-up motion. Messages seven and eight repeat the pattern. CAN frames still serialize: almost no gap here means small relative to the intended cadence, not simultaneous transmission.

A cycle-time plot makes this pattern easier to see. For each message, subtract the preceding message’s arrival time:

Cycletime[n]=arrivaltime[n]arrivaltime[n1]Cycle time[n] = arrival time[n] − arrival time[n − 1]

Healthy spacing produces a line around 2 ms. A delayed message produces a rise to 4 ms, followed by a near-zero interval when the next on-time message arrives. The pair reveals a missed interval followed by a burst, rather than a uniformly slower stream. With the symptom characterized, the next step is to find which task missed its timing boundary.

4:324:45
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

4:32 · section reference included

A late policy result becomes a burst

Policy execution time can vary. If an iteration runs long, its output misses the scheduled transmission opportunity. In Garg’s illustrated implementation, that result is queued. At the next opportunity, the system sends both the queued result and the current result, placing two commands on the bus in quick succession.

Desynchronization between communication threads can produce the same symptom. Synchronization is therefore part of the control system’s behavior, not merely an implementation detail. Fixing the transmit schedule improves the motion—but does not necessarily fix everything. Even a clean outgoing timing plot says nothing about whether each command was computed from fresh sensor data.

6:577:07
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

6:57 · section reference included

Regular commands can still use stale inputs

Now delay the receive thread instead. The next policy iteration starts before its new sensor data is available, so it reuses the previous input. In iteration two, the command is therefore based on older data. Iteration three then jumps ahead, skipping an intermediate data-processing step. The motor can again appear to catch up or jitter, even though outgoing messages arrive regularly.

The system must preserve the pairing between received data and the policy iteration that consumes it. Garg proposes two approaches:

  • Synchronization primitives: Use condition variables or semaphores to coordinate task handoffs.
  • Timing padding: Where the particular target lacks the needed primitives, leave a cushion so reception can finish before the intended policy iteration consumes its input.

The second option is conditional on the environment, not a general limitation of real-time operating systems. Garg specifies no padding duration; the purpose is to keep the intended RX data, computation and timely output together.

8:008:10
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

8:00 · section reference included

Logging becomes part of the timing problem

Logging received data and policy outputs seems harmless until accumulated logs must be written to storage. If the main control loop performs that write, it can stop controlling the robot while storage catches up. Garg reports a 30 ms robot freeze on a Raspberry Pi using an SD card when logging wrote to storage. The specific board, card and workload are not given.

His remedy is to assign asynchronous logging to another CPU core, described in the architecture as a third CPU. The design progressively separates work: policy execution, communication and now logging each have different timing needs. Keeping storage work away from the control path protects the deadline the robot is trying to meet.

A microcontroller can suffer a related failure without a disk or filesystem. Its logs may go directly to a peripheral such as UART, where transmission can take milliseconds depending on the amount logged. A diagnostic message can then create the very error it reports:

  1. A packet is dropped.
  2. The system logs the dropped packet.
  3. Sending that log takes enough time to miss the next packet.
  4. The next drop triggers another log, repeating the cycle.

The result can be a complete CAN data blackout while diagnostic logs continue. That combination is especially misleading: the system is visibly reporting errors, but reporting them is sustaining the failure. Observability work must fit within the system’s timing budget too.

Logging slide describing disk synchronization delays, up to 30 milliseconds on a Raspberry Pi with an SD card, and asynchronous logging on another CPU core. A second panel shows a Message Drop and Log feedback cycle.
Logging delays and the case for asynchronous logging on a different CPU core.
9:269:35
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:26 · section reference included

Let the entire receive path run

The final failure lies below the application. Received data passes through interrupt handling and kernel work before reaching a user process. Raising the robot application’s priority too aggressively can starve schedulable receive-path work that the application itself needs. The process urgently waiting for data prevents the work that would deliver it.

Garg calls this priority inversion and describes receive dropouts approaching seconds at a time. More precisely, the example concerns starvation of required receive-path work; it does not mean a user process can block every kind of kernel or interrupt execution. The remedy is to identify the participants in the complete pipeline and assign priorities that let all necessary work progress.

That brings the toy robot back to its original diagnostic question. Hardware, profiling and software scheduling must be considered together: overlap communication to reduce cycle time, synchronize handoffs to prevent jitter, keep logging from blocking control, and preserve the priorities needed to deliver data. These are foundational design concerns, but they determine whether a carefully constructed policy can actually control a physical robot.

Recap slide listing pipelining for lower cycle time, synchronization for jitter, logging strategies, and priority to avoid starvation.
Recap: pipelining, synchronization, logging strategies and priority.
11:1011:23
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

11:10 · section reference included

Resources

From the talk

Read the complete timestamped transcript
  1. 0:00

    [upbeat music] Right.

  2. 0:15

    Good afternoon, everyone. And really excited to be here today. Really exciting stuff so far. So many models, so many new ideas. And today, I wanna talk about what happens between the controller and the wire.

  3. 0:27

    Now, we have seen so many policies that work that control robots, but again, with that, we need to get that data to the actuators. We need to get that data from sensors and feed the whole system.

  4. 0:37

    And what happens if your carefully crafted policy does not work as expected? Like, is this issue in the policy, or is it in the software system? So today, we look at a lot of instances where the issue will look like it's the policy, but it's actually the software system.

  5. 0:51

    And along the way, we'll try to design a very, uh, small toy robotics robot.

  6. 0:56

    So why this talk? Again, well, robots are complex, so many systems, so many different software components, and yet we're focused on, like, one big question. When things go wrong on the robot, when you don't see that motor move, what's the root cause?

  7. 1:10

    Is the policy that is not giving the command, or is it the software system? And this is a question that I grapple almost every day, and so I wanna talk about what I've seen so far, and how to diagnose these issues on the robot.

  8. 1:23

    So let's go to the buildup. Let's try build a very small, uh, toy robotics general architecture, right? Like, this is what a general robot would look like. You'd have some actuators, a CPU, maybe a hybrid accelerator, and then a sensor.

  9. 1:34

    Perfect. Now, one of the most critical aspects is the communication protocol. So for our, our talk, we'll use CAN. CAN is great. CAN is open source. Everyone can use CAN.

  10. 1:44

    It's cheap, it's affordable, and it has enough data rate and enough compatibility for a lot of components out there. So we'll stick to CAN, and, uh, we'll see how that influence a lot of the design, design decisions down the line.

  11. 1:57

    All right. So let's also start simple with the code. We'll start with receiving the data, giving that to the policy, and basically sending it back out. Nothing, nothing happening, nothing fancy, right?

  12. 2:08

    And let's assume that we have approximately two milliseconds for our policy, and this is what we should expect to see, right? Our loop's running every two millisecond, we are able to see our policy output.

  13. 2:20

    We read data, we send it out. Standard. But as soon as we deployed on the robot, this is what happens. There's a gap. Every two milliseconds, there's a gap.

  14. 2:30

    Wait, what's going on? Well, let's look at the loop again. So at the edge of the loop, we have question marks. We see that we are transmitting and receiving CAN data.

  15. 2:39

    So let's look at the CAN bus. Maybe we'll find some hints there.

  16. 2:42

    Okay, so let's say we have a hundred bits per message, and we have about ten messages, five to be sent out, five to be received. That gives us a total of thousand bits.

  17. 2:52

    And for a CAN bus that's operating at one megabit per second, that's about point one milliseconds per message or one milliseconds for ten message. You can see, like, how even a small number of messages are saturating the CAN bus to the point that the s- loop time, the how much our system takes to run, is on the

  18. 3:08

    same order as the transmission time. And this explains the one millisecond gap. So great. But then what to do about it? It's like, it's almost unavoidable, right? Like, we cannot go around this one millisecond gap.

  19. 3:20

    Well, that's solution number one. You just accept the delay. Hopefully, it's three milliseconds, and that's not too bad. But again, a system would not be high performance if we let that stop us.

  20. 3:30

    So we'll multithread, and we'll pipeline. We'll try to figure out how we can work around that one milliseconds, and see how we can sort of organize our tasks differently to still get that two millisecond loop time.

  21. 3:42

    So here, we'll take a, take a moment to pause and see that, you know, the loop, it has multiple components broken down into three now: TX, RX, and the policy, and we're running the communication in a different thread and the policy in a different thread.

  22. 3:54

    And now we'll see how we'll take this simple building block and stagger it so that we can actually achieve faster loop times.

  23. 4:00

    And this is it. So what we do, we seed the policy the first time. We get some data, we feed it to the policy. But before we conclude the policy, we start receiving the next set of data, and that's for the next iteration.

  24. 4:13

    When the next iteration starts, we tr- transmit the data from the last policy, and we continue resuming the ne- this iteration of this policy. Essentially, we have parallelized our RX and TX, but we're still receiving data for the same policy at the same cadence.

  25. 4:28

    So this is great. We might have solved our problems.

  26. 4:32

    Let's move on. So we deployed the system on the robot, and now we see new problems. Our system is stuttering. Our actuators are making j- sounds like catching up or like we are seeing weird motions on the actuator.

  27. 4:45

    This has to be policy. There's no way this can be software. Well, let's investigate more. Let's get some more data from the CAN bus.

  28. 4:52

    So again, like here, we have our CAN bus again, and we see our CPU, GPU, all our accelerators. And what we'll try to do is get an external transceiver.

  29. 5:02

    These are, again, very cheap, very open source products that you can get anywhere, and we connect it to the CAN bus, and we get data off the CAN bus.

  30. 5:10

    We take this data, we feed it to another host computer, let's say a laptop, and on there, we can run utilities like candump, which will actually give you a timestamp data of what message was seen at what time.

  31. 5:20

    So once we get this raw data off the bus, we can start plotting it. And this is what we should expect, that every two milliseconds, we have a message on the bus that is being sent out.

  32. 5:31

    Right? It should be very nicely spaced, and it should reach the actuators in time. And this, if we see this on the bus, we're really happy. Now, what happens a lot of the times in systems is you'll not see this, you'll see something like this.

  33. 5:45

    Here, we'll see, like between message number three and four, there's almost no gap. What happened there? And between two and three, there's four milliseconds of gap. It's almost like message number three was just late, and four was on time, and because of that, we had this weird, weird jitter where the actuator would try to catch up or

  34. 6:04

    have tried to command, like try to follow two commands at the same time.

  35. 6:08

    Okay, same thing happened with seven and eight. So let's take a deeper look, but first let's try plot this differently. So there's this plot called a cycle time plot, and where what we plot here is the time since last message.

  36. 6:21

    Time since last message is just a way to say like, "Hey, last message came in at two milliseconds interval. This one should also come at two milliseconds, so we should see a straight line around the two millisecond mark."

  37. 6:32

    But here we see some messages jump at four milliseconds, and the one after that comes to zero. This is expected because if a message is delayed, the cycle time for that would be late.

  38. 6:42

    But then for the next one, it would be much closer to zero because that one was not late, and the difference between the last message and the current one is basically nothing.

  39. 6:51

    Okay, so now we characterize the system, we know what's going on, and we can start solving it.

  40. 6:57

    But this is what's going on with the TX side. So let's see. So we missed sending the data and queued it. Why would that happen? Well, policies are not very real time.

  41. 7:07

    At times they can take longer, at times they can take shorter. And what happens if a policy takes longer? Well, you miss the time when you were supposed to send it out.

  42. 7:16

    So all you can do is just queue it somewhere, you can store it. But that cannot be sent out anymore. And when the next iteration comes around, that's when you send both the last message and the current message.

  43. 7:27

    So you'll see two messages just go on the bus at the same time. And this can also happen if our TX and RX threads start desynchronizing. But this is one of the issues that is very commonly seen with like a multi-threaded system, and it's very important to have, uh, synchronization in the systems.

  44. 7:41

    But let's say we do synchronize it, and we are able to fix our TX side. Well, we see some improvement. We don't see that like everything is solved, we see some improvement.

  45. 7:53

    Okay, but now this has to be policy. Our graphs are looking fine, everything is on the bus is fine. This has to be policy. There's no way this systems.

  46. 8:00

    Well, there's one last, one last issue that we have to check, and that is what happens if we desynchronize in the RX side. What happens if a thread is delayed?

  47. 8:10

    Well, now our policy will, will not get the new data and it will work with the last data. And because of that, the output will also be based on the last data.

  48. 8:18

    And so in policy number two or iteration number two, we'll actually have an old command still, like, which is relatively older. And in policy number three, we'll directly jump.

  49. 8:27

    We'll skip one of the data processings. And because of that, we'll see a sort of skip of caching up behavior on the motors, which will sound like almost like a jitter.

  50. 8:36

    Okay, so how do we resolve these two things? Well, there are synchronization primitives. Look, you can with condition variables, semaphores. These are like very low level system things that are widely used in robotics and should be used as well for this TWiSE system.

  51. 8:51

    But again, if these are not available, which is sometimes the case, like we're not working with Linux-based system, we'll work with like a real-time OS or like a microcontroller where we may not have all these semaphores, we can just add padding.

  52. 9:03

    Just have some cushion, right? Like have some cushion so that if some desynchronization happens, you still have the same RX going into the right policy and coming out the other way in like, in a timely manner.

  53. 9:13

    We don't miss messages. Okay, perfect. So this, this, this makes our system fairly robust, fairly high-performing, but there are a few other relative problems which will happen with a system like this, which we should also talk about.

  54. 9:26

    So let's talk about logging. Logging is benign, right? We just log that, hey, that message is coming in. We wanna just log that this is the data that we got, this is the output.

  55. 9:35

    It's fine, right? But if we log too much, at some point, we have to send those logs to disk, and that is very costly. Imagine what happens if your main control loop starts logging and decides just one day that, "Hey, I'm done.

  56. 9:47

    I'll just start putting this on the, on the hard disk." Well, your robot would stay frozen for thirty milliseconds, as we saw on the Raspberry Pi with an SD card.

  57. 9:55

    So, well, that's bad. How do we fix that? Well, we just throw more CPU at it. We just add another CPU, and now all our logging is handled by that third CPU.

  58. 10:04

    Cool. Okay, so now we have like-- we're seeing how multi-threaded is slowly getting baked into the system, how the robot is operating in a real-time deadline guarantee, and how we are able to, like, avoid the pitfalls.

  59. 10:15

    Perfect. Let's talk about something a little more low level again, like microcontrollers. Microcontrollers are fairly simple, and their logging doesn't actually go through a whole disk and file system way.

  60. 10:26

    They just log to some other peripheral. That takes time. In fact, for UART, it can be on the order of millisecond, depending on how much we are logging. So here's an interesting problem.

  61. 10:36

    Let's say we drop a me- packet and we log that, hey, we dropped a packet. Well, that log itself would take enough time that will drop the next packet, and then you will keep drop-- Because you drop the next packet, you log again.

  62. 10:48

    And so basically, just keep logging, and you see a complete blackout on the CAN bus. And it's very hard to debug, like, why am I getting logs and seeing packet drops but no data?

  63. 10:57

    So these are mysterious things that, and in my experience, like it's really good to, like, know about the pitfalls beforehand before we dive in the system and really figure out that, hey, this can also be a problem, just a log statement.

  64. 11:10

    Finally, there's also priority inversion. So in the kernel, in the Linux kernel, there are ways in which data is received by the user process. It's not direct, like it takes a while between the interrupt, the kernel process handling, and then it goes to the user process.

  65. 11:23

    In robotics, we tend to just boost the priority of all our processes so high that we start just blocking the kernel almost. Like if the kernel doesn't run, we won't get the data, but we're trying to get the data, and we're blocking the very thing that will give us the data.

  66. 11:38

    Well, this is inversion in action, and it will see your system again drop out for like seconds almost at a time.

  67. 11:44

    So again, this is something we, we fix by just making sure we know the parts of the pipeline, we fix the right priorities, and we make sure that our whole system as a whole, like it works together well.

  68. 11:54

    So this is how like software and s- robotics have to work together. We have to talk about hardware, the various profiling, the various priority stuff, and actually just take a recap from the top.

  69. 12:04

    So we went over pipeline. We saw how to reduce cycle time beat, how the communication delays. We saw how synchronization can actually cause some unexpected jitter, which are hard to diagnose.

  70. 12:15

    Could be the policy, could be the system. So we wanna make sure that that doesn't happen. Logging strategies, so that we don't block the system while we're trying to tell the user that, "Hey, this is happening."

  71. 12:24

    And finally, priority inversion to avoid starvation. And that's how we start designing high-performance robotic systems, at least on a very basic level. And that's my talk for today, and thank you so much for being here listening. [clapping] [outro music]