☆ Bonus - Debugging

Debugging used to be an important thing to learn in the far past of 2023, but LLMs have gotten good enough at writing code that they can usually just debug the code for you at this point. I myself had not used the debugger since coding agents got good in late 2025, and would scarcely believe I would use it again to solve a problem a coding agent could not, as it can run and debug the code itself. None-the-less, we've included a older tutorial here should you be interested in learning the now broadly obsolete skill of how to debug by hand.


Debugging code ~~is~~ was an extremely important thing to learn. Just like using LLMs to assist your coding, it's something that used to ~~can~~ significantly speedrun your development, and stop you getting hung up on all the frustrating things!

To give you practice debugging using VSCode's built-in debugger*, we've provided an example function below. This is an implementation of raytrace_triangle which has a few mistakes in it. Your task is to use the debugger to find the mistake and fix it. (Note - you're encouraged to actually use the debugger, rather than comparing it to the solution above!)

If you're using Colab, you'll have to use different strategies for debugging. See this page for one suggested approach.

def raytrace_triangle_with_bug(
    rays: Float[Tensor, "nrays rayPoints=2 dims=3"],
    triangle: Float[Tensor, "trianglePoints=3 dims=3"]
) -> Bool[Tensor, " nrays"]:
    """
    For each ray, return True if the triangle intersects that ray.
    """
    NR = rays.size[0]

    A, B, C = einops.repeat(triangle, "pts dims -> pts NR dims", NR=NR)

    O, D = rays.unbind(-1)

    mat = t.stack([- D, B - A, C - A])

    dets = t.linalg.det(mat)
    is_singular = dets.abs() < 1e-6
    mat[is_singular] = t.eye(3)

    vec = O - A

    sol = t.linalg.solve(mat, vec)
    s, u, v = sol.unbind(dim=-1)

    return ((u >= 0) & (v >= 0) & (u + v <= 1) & ~is_singular)


intersects = raytrace_triangle_with_bug(rays2d, test_triangle)
img = intersects.reshape(num_pixels_y, num_pixels_z).int()
imshow(img, origin="lower", width=600, title="Triangle (as intersected by rays)")

You can debug a cell by clicking on the Debug cell option at the bottom of your cell. Your cell should contain the code that is actually run to cause an error (rather than needing to contain the function which is the source of the error). Before you run your debugger, you can set breakpoints by clicking on the left-hand side of the line number (a red dot will appear). You can then step through your code, using the toolbar of buttons which will appear when you run the debugger (see here for an explanation of what each of the buttons does). When you reach a breakpoint, you can use the following tools:

  • Inspect local and global variables, in the VARIABLES window that appears on the left sidebar.
  • Add variables expressions to watch, in the WATCH window that appears on the left sidebar.
    • You can just type in any expression here, e.g. the type of a variable or the length of a list, and this will update as you step through the function.
  • Evaluate expressions on a one-time basis, by typing them into the DEBUG CONSOLE (which appears at the bottom of the screen)

Note, when you run the debugger, it will stop before the breakpoint line is evaluated. So if you've run a cell and got an error on a specific line, then that is the line you want to set a breakpoint on.

This all works basically the same if you're in a notebook in VSCode, except for a few changes e.g. the debug button is on the dropdown at the top-left of the cell (if it doesn't appear for you then you'll need to go into user settings and add the line "notebook.consolidatedRunButton": true).

There's much more detail we could go into about debugging, but this will suffice for most purposes. It's generally a much more efficient way of debugging than using print statements or asserts (although these can also be helpful in some situations).

What are the bugs, and how can we fix them?

Bug 1 Location
NR = rays.size[0]
Bug 1 Explanation

This should be rays.size(0) (or equivalently, rays.shape[0]). size is a method which takes an int and returns the size of that dimension; shape returns a torch.Size object which can be indexed into.

This would have been pretty easy to realise without using the debugger, because the error message is informative.

Bug 2 Location
O, D = rays.unbind(-1)
Bug 2 Explanation

We're unbinding from the wrong dimension. rays has shape (nrays, points=2, dims=3), and we actually want to unbind along the points dimension. So we should use rays.unbind(1).

We could have found this by inspecting the rays object in the variables pane (you can click on the dropdown next to the variable name to look at its attributes, including shape), or you could just type rays.shape into the debug console. However, this error should also have been apparent given the type signature of the function (a good case for using typing!).

Bug 3 Location
mat = t.stack([- D, B - A, C - A])
Bug 3 Explanation

This should have had the argument dim=-1 on the end, because torch.stack by default stacks along the first dimension.

This error is a bit harder to diagnose, because the line causing an error is after the line containing the mistake. Again, we could have diagnosed this with the debugger by inspecting the shape of our mat tensor in the variables pane.

Bug 4 Location
return ((u >= 0) & (v >= 0) & (u + v <= 1) & ~is_singular)
Bug 4 Explanation

The final condition is also missing the check s >= 0, which checks that the intersection is in the direction of travel of the ray from the origin towards object it hits, rather than behind it. For our example this doesn't actually change the output image (all rays point in the positive x direction, and when we rendered Pikachu, there was nothing behind the camera for rays to strike in the wrong direction), but it's still a bug relative to the correct implementation above, and a hard one to find as our current code wouldn't trigger it.

These were all relatively easy bugs to diagnose (not all bugs will present as actual errors in your code, some might just be an unexpected set of results). But hopefully this has given you a sense for how to use the debugger. It's a very powerful tool, and can save you a lot of time!