LLMs are mortally terrified of exceptions

twitter.com

301 points by nought 4 days ago


https://x.com/karpathy/status/1976082963382272334

karpathy - 3 days ago

Sorry I thought it would be clear and could have clarified that the code itself is just a joke illustrating the point, as an exaggeration. This was the thread if anyone is interested

https://chatgpt.com/share/68e82db9-7a28-8007-9a99-bc6f0010d1...

comex - 4 days ago

This is a parody but the phenomenon is real.

My uninformed suspicion is that this kind of defensive programming somehow improves performance during RLVR. Perhaps the model sometimes comes up with programs that are buggy enough to emit exceptions, but close enough to correct that they produce the right answer after swallowing the exceptions. So the model learns that swallowing exceptions sometimes improves its reward. It also learns that swallowing exceptions rarely reduces its reward, because if the model does come up with fully correct code, that code usually won’t raise exceptions in the first place (at least not in the test cases it’s being judged on), so adding exception swallowing won’t fail the tests even if it’s theoretically incorrect.

Again, this is pure speculation. Even if I’m right, I’m sure another part of the reason is just that the training set contains a lot of code written by human beginners, who also like to ignore errors.

metalcrow - 4 days ago

Given that the output describes the function as being done "with extraordinary caution, because you never know what can go wrong", i would guess that the undisclosed prompt was something similar to "generate a division function in python that handles all possible edges cases. be extremely careful". Which seems to say less about LLM training and more about them doing exactly what they are told.

fkyoureadthedoc - 4 days ago

Not sure why but it made me think of FizzBuzzEnterpriseEdition https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...

jampekka - 4 days ago

I've noted that LLMs tend to produce defensive code to a fault. Lots of unnecessary checks, e.g. check for null/None/undefined multiple times for same valie. This can lead to really hard to read code, even for the LLM itself.

The RL objectives probably heavily penalize exceptions, but don't reward much for code readability or simplicity.

criemen - 4 days ago

It's also logically incoherent - division by zero can't occur, because if b=0 then abs(b) < sys.float_info.epsilon.

Furthermore, the code is happy to return NaN from the pre-checks, but replaces a NaN result from the division by None. That doesn't make any sense from an API design standpoint.

sarchertech - 4 days ago

Expert beginners program like this. I call it what it driven development. Turns out a lot of code was written by expert beginners because by many metrics they are prolifically productive.

In go all SOTA agents are obsessed with being ludicrously defensive against concurrency bugs. Probably because in addition to what if driven development, there are a lot of blog posts warning about concurrency bugs.

falcor84 - 4 days ago

That code has many issues, but the one that bothers me the most in practice is this tendency of adding imports inside functions. I can only assume that it's an artifact of them optimizing for a minimal number of edits somewhere in the process, but I expect better.

CGamesPlay - 3 days ago

I dealt with this in my AGENTS.md by including a recap of the text of "Vexing Exceptions" [0], rephrased as a set of guidelines for when to write a throw or catch. I feel like it helped; and when it still emits error handling I disagree with and I ask about it, it will categorize it into one of the four categories, and typically rewrite it in an appropriate way.

I think the Vexing Exceptions post is on the same tier as other seminal works in computer science; definitely worth a quick read or re-read once in a while.

[0] https://ericlippert.com/2008/09/10/vexing-exceptions/

paulhodge - 3 days ago

Agree that LLMs go too far on error catching..

BUT, to play devil's advocate a little: Most human coders should be writing a lot more try/catch blocks than they actually do. It's very common that you don't actually want an error in one section (however unlikely) to interrupt the overall operation. (and sometimes you do, it just depends)

dgan - 3 days ago

I spent more time dismissing various popups than reading this post. I hate twitter links

furyofantares - 3 days ago

A couple thoughts.

One is that often I do want error handling, but also often I either know the error just won't happen or if it does, something is very wrong and we should just crash fast to make it easy to fix the bug.

But I am not really sure I would expect someone to know the difference in all cases just looking at some code. This is often an about holistically knowing how the app works.

A second thought - remember the experiment where an LLM was fine tuned on bad code (exploitable security problems for example) and the LLM became broadly misaligned on all sorts of unrelated (non-coding) tasks/contexts? It's as if "good or bad" alignment is encoded as a pretty general concept.

Error-handling is good aligned, which I think is why, even with lots of instructions to fail fast, it's still hard to get the LLM to allow crashing by avoiding error checking. It's gonna be even harder if you do want it to do some error checking, and the code it's looking at has some error checking

exasperaited - 3 days ago

Why do LLMs do it for real: because you trained them by stealing all of Stack Overflow?

Less sarcastically but equally as true: they've learned from the tests you stole from people on the internet as well as the code you stole from people on the internet.

Most developers write tests for the wrong things, and many developers write tests that contain some bullshit edge case that they've been told to test (automatically to meet some coverage metric, or by a "senior" developer who got Dilbert principled away from the coalface and doesn't understand diminishing returns).

But then the end goal is to turn out code about as good as the average developer so they can be replaced more cheaply, so your LLM is meeting its objectives. Congrats.

cess11 - 3 days ago

Now this is a toy example because usually you never do division this way, but in mature code in commercial applications this is usually what it looks like. It's a sliver of business logic that in itself seems trivial, and then handlers of edge case upon edge case upon edge case, mirroring an even larger set of unit tests.

One reason for this is that you typically lack a type system that allows 'making illegal states unrepresentable' to some extent, or possibly lack a team that can leverage the available type system to that effect due to organisational pressure, insufficient experience or whatever.

ziml77 - 3 days ago

That's funny but definitely not far off from reality. I have instructions from my agent to use exceptions but they only help so much.

I really dislike their underuse of exceptions. I'm working on ETL/ELT scripts. Just let stuff blow up on me if something is wrong. Like, that config entry "foo" is required. There's no point in using config.get("foo") with a None check which then prints a message and returns False or whatever. Just use config["foo"] and I'll know what's wrong from the stack trace and exception text.

constantcrying - 4 days ago

If you are dividing two numbers with no prior knowledge of these numbers or any reasonable assumptions you can make and this code is used where you can not rely on the caller to catch an exception and the code is critical for the product, then this is necessary.

If you are actually doing safety critical software, e.g. aerospace, medicine or automotive, then this is a good precaution, although you will not be writing in Python.

glitchc - 4 days ago

But what's the prompt that led to this output? Is it just a simple "Write code to divide a by b?" or are there instructions added for code safety or specific behaviours?

I know it's Karpathy, which is why the entire prompt is all the more important to see.

never_inline - 3 days ago

I think too much of RLHF is done on small scale tutorial-ish examples.

LLMs often write tutorial-ish code without much care how it integrates with rest of codebase.

Swallowing exceptions is one such example.

wffurr - 4 days ago

Turns out computer math is actually super hard. Basic operations entail all kinds of undefined behavior and such. This code is a bit verbose but otherwise familiar.

iagooar - 3 days ago

This issue has been one of the biggest issues with the Claude models, not so much with GPT-4 or GPT-5.

I even had this Cursor rule when I was using Claude:

"- Do not use statements to catch all possible errors to mask an error - let it crash, to see what happened and for easier debugging."

And even with this rule, Claude would not always adhere. Never had this issue with GPT-5.

stuaxo - 3 days ago

My wishlist top item is to stop creating a class with Service on the name and having things come off it, when all I needed was functions and methods, the dev I was working with submitted a lot of these and in testing I could get the LLM to do it easily myself.

shiandow - 4 days ago

Is there a way to read the rest?

bobogei81123 - 4 days ago

This is just AI trying to tell us how bad we designed our programming languages to be when exceptions can be thrown pretty much anywhere