Years ago I shipped a Laravel app to a box that didn't have ext-mbstring, and nothing broke. That was the problem. Composer had happily pulled in symfony/polyfill-mbstring, every string function kept working, and our CSV importer spent the next eight months running noticeably slower than it did on my laptop because userland PHP was doing work a C extension should have done. Nobody saw an error. There wasn't one to see. I bring this up because the PyTorch team just shipped version 2.14, and buried under 2,995 commits from 487 contributors is a decision I'd like to frame and hang above every PHP maintainer's desk.
Here it is. PyTorch 2.14 publishes wheels for Python 3.15, including the free-threaded build, but torch.compile doesn't work on 3.15 yet. Instead of quietly dropping you back to eager execution and letting your training step take twice as long while you wonder whether it's the data loader, it raises an error. You asked for compiled code, you can't have it, here's why. That's the whole feature, and I think it's the most mature thing in the release.
It's even more striking when you read the rest of the notes, because a lot of the headline performance wins on Apple Silicon are cleanups of exactly the opposite pattern. Linear algebra on the MPS backend used to slide back to the CPU without telling anyone, paying for memory copies in both directions. In 2.14, SVD, eigh and lstsq run natively on the GPU for float32 and complex64, LU factorization got custom Metal kernels with speedups above 100x on small batched matrices, and CTC loss no longer escapes to the CPU. There was also a single-token decoding shape that missed the fast path and cost an 8.5x slowdown in bf16 and fp16. Every one of those is a silent fallback that somebody eventually had to go find with a profiler. The numbers tell you how much a polite degradation can cost before anyone notices it exists.
Now the fair counterpoint, and it's a strong one. Fallbacks are why software survives contact with real machines. The new NVGEMM matmul backend in torch.compile falls back to Triton for any shape it can't handle, and that's clearly correct: you opted into a faster path, and losing it on one odd shape shouldn't take down a run. Same with float64 on Metal, which still runs on the CPU because the hardware has no 64-bit float type. Nobody wants a model that crashes on a Mac because one tensor is double precision. The polyfill that hid my missing extension also kept a production site up on a host I didn't control. Slow and working beats fast and dead, most days.
So where's the line? I'd put it at intent. NVGEMM falling back to Triton is the system choosing between two compiled kernels you already agreed to, both inside the mode you asked for. torch.compile on 3.15 silently becoming eager would have been the system discarding the thing you explicitly requested, and that deserves a loud no. The same release turns TorchScript deprecation warnings visible, which fits the pattern: if the ground is moving under you, the tool's job is to say so, even if the log gets uglier for a while.
PHP has both kinds of fallback and we rarely tell them apart. Swapping a C extension for a polyfill is fine when you never asked for the extension. Loading the opcache JIT and having it switched off because some extension overrides the executor is a case where you did ask, and PHP 8 does at least emit a warning there, which I'd count as the right instinct. What I'd like is more of it in userland: libraries that pick a slow path and stay quiet about it, ORMs that drop to N+1 queries when a relation can't be eager loaded, cache drivers that degrade to array storage when Redis isn't reachable and only say so in a debug log nobody tails. Each of those keeps the request alive and leaves you with a performance mystery to solve later, on someone else's deadline.
The cheap fix lives in our own code more than in php-src. Composer's platform requirements already let you declare ext-mbstring or ext-intl as mandatory, and a lot of projects simply don't. A boot check that throws when a requested driver isn't the one actually serving traffic is about ten lines. PyTorch spent a release cycle undoing hidden slowdowns on one backend and chose, for another, to fail early. It's cheaper to write the error now than to go hunting for the slowdown later.
Where do you draw this line in your own stack? Is there a fallback in your app right now that you'd rather turn into an exception, or one you're glad stayed silent because it saved you at a bad moment? I'd like to hear about both, especially from anyone running PHP on hosting they don't fully control.




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.