Designers republic and memphis were the only designs i couldn't immediately see were AI generated.
The only reason i couldn't tell memphis is because it's aping a known design language already, it's just a perfect copy so me not being able to tell it wasn't AI isn't a free pass in this case.
I think if there was a little less "grid" feeling to the layout details then i wouldn't be able to tell on a few of the other designs either.
Edit: the designers republic version is quite a lot different than the others, it's not just that it's the only design to avoid pairing every feature with an icon, it's the whole layout variety - there's different font sizes, the positioning of elements is very well done to my eye. I'm gonna say this has to be another case like memphis, there's some pre-existing design language that i don't know of and it's just aping that. It's much more human than the others which i can believe are straight ai.
I'm not the GP but if you look at actual 1980s zines the difference is obvious, even if it's not easy to put into words. The absence of photos or hand-drawn graphics in particular.
The origins of the punk movement are in counterculture and DIY, so there's something particularly perverse about using an AI model to generate a poster in that style.
Why couldn't you express all those as test cases rather than instructions?
In test cases i can do anything, a test framework is just a way of discovering and then scheduling functions to run. I can emit useful instructions to the agent from the failed test case: "After walking the AST of all use of state machine X, a branch was found at Y which reused stale state. Ensure stale references are dropped..."
I can force the agent to pass the test suite before it considers itself done. I can reject edits of such test cases to partially mitigate reward hacking. etc etc
You can express them as tests, but you also need a feedback mechanism that creates the rule that when the LLM generates some net new code or performs some refactor, that there are these CAPAs that it needs to cover with test cases.
The CAPA is a learning that sits outside of the mechanism of verification; it is a record of problem:root_cause:preventative_action. I see it as the instruction that would be required to generate the test case to prevent the next occurrence of a class of failures.
In a real-world process, for example, there is usually a QA lead that is verifying that the process is followed by looking at the paperwork and evidence.
> Why couldn't you express all those as test cases rather than instructions?
How would prevent removing the test case and the code that it tests? That's a legitimate thing to do when you're modifying a codebase. My intuition is "corrective and preventative actions (CAPA)" is a level or two up from test cases.
This isn't ai slop. It's fascinating and well written.
But I learned something really basic - i didn't know that the ANE (and the data pipeline around it) was designed for CNN rather than transformers. It's always been an open loop in my head, wondering why the ANE was less impactful than i understood it should be.
>i didn't know that the ANE (and the data pipeline around it) was designed for CNN rather than transformers.
Multiple stories have reported that ANE came from Apple's self-driving car project that got canceled. (Makes sense since CNN is used for vision-related machine learning and enables cars to analyze their surroundings.) They spent 10 years and ~10 billion on research & development on a product that never got released so Apple is probably happy they're able to salvage some of that ai technology and put it in iPhones and Macs.
This is pure sunk cost fallacy. CNNs were from the deep learning ImageNet heydays, but now everything in that domain is equally done better by transformers. All you are doing is wasting area and saddling software with outdated hardware, and myopic PMs insisting on its use will create inferior products. Now that sounds a lot like the Apple AI efforts..
A lot of neural engine, particularly in the embedded domain (ARM/RISC MCUs) have the same problem. Designing other models means on top of this means a lot of profiling to get convolution blocks right to get good speedups. (We optimized this in the past e.g. using Neural Architecture Search on super networks)
I designed and deployed several custom CNN-based AI models to ipad hosts around 2018-2020. It was a finicky process - CoreML didn't give you much control (or even feedback) on what parts of your model would run on the CPU, GPU, or ANE - but you could usually tell right away when they didn't, just based on big latency differences. Performance to power (and size) ratio was pretty good and I was impressed at the time, despite being generally not really happy about working with the apple ecosystem. I would've preferred something Android (or Linux)-based but there wasn't really anything comparable out there back then. (SBCs like the Jetson would've worked, but we would've had to figure out how to add an interface for human operators which would've been a big lift compared to just using off the shelf hardware).
Also, just imagine being the group at Apple responsible for designing this section of the chip, starting probably almost a decade back – under the constant uncertainty of not knowing what direction ML workloads would develop in…
> ML research was a rather known quantity, or the separate "Neural Engine" CPU explicitly aimed at existing ML pipelines wouldn't exist.
No, not really. Transformers were just one of the possible directions. Silicon design does not have the same time scale than software. Now, everyone is using transformers so it becomes harder to do anything else, and it’s been the case long enough that hardware had some time to align (but is still lagging). But who’s to say that a different architecture published last year won’t take the world by storm 2 years from now?
It’s easy to say it in hindsight, but transformers took a bit of effort to get where they are now.
> Silicon design does not have the same time scale than software.
"Neural Engine" has been a part of iPhones since 2017. So, in development since at least 2013, possibly earlier. And it targeted the rather well established, known, and widely used ML practices.
GPT-like models didn't become even remotely useful until at least 5 years later.
I'm all for compassion, but engineers knew the NE was empty when it sat idle for 10 years on our computers.
- when you're given no usecase for your engineering piece, apart from "detour characters in pictures". It's an exageration but AI's contributions in iOS aren't visible; Meanwhile Google has features that people actually notice like removing tourists from your holidays photos — worse: it's mostly a simple collage feature working on the main CPU, and it has the same social effect as green bubbles in iMessage ("ah. Tourists on your photos. iPhone user?")
- and you tout it as "16 Neural Engine cores" during the sales, with no associated software, no listed material feature, just hand-waving,
- Siri maxxes out at "There is no contact named 'What's the weather today' in your agenda",
Then can't really claim that Apple engineers' problem was really the bad luck that ML wasn't the determining part of the future. It's more like misreading the room for 5 to 10 years straight.
Apple engineering's excellence on vertical integration and supply chain control gave them absolute power over our world (with merit), it just failed at that particular project. Which occupies 40% of our CPUs.
> an exageration but AI's contributions in iOS aren't visible
If you want to ignore them, that’s right. In the real world, they’ve been talking about ML and how it’s making pictures or such-and-such aspect of the OS better for about a decade now. It might not be flashy, but it is used throughout the OS.
> Meanwhile Google has features that people actually notice like removing tourists from your holidays photos — worse: it's mostly a simple collage feature working on the main CPU, and it has the same social effect as green bubbles in iMessage ("ah. Tourists on your photos. iPhone user?")
The feature to do this has been in the Photos application for years, what are you talking about?
Yank on that thread harder, developers and engineers are not safe just because they rely on different patterns.
But once you've toppled the house of knowledge work, you'll find you can rebuild it easily because the foundation still exists, and it's the thing missing in this analysis; judgement.
Optimal judgement is worth a premium... in some cases. In other cases, it's sufficient to merely keep trundling. Numerically more companies are boring than exciting.
MBAs are just components, and the cost of that component just went down.
I don't think it's common knowledge how much they dropped imports since the Iran / straight of hormuz crisis.
Back at the start of that conflict there were a lot of breathless takes about what catastrophe was in store in a few weeks time, and what materialised was bad but not in anything like the same league as what was predicted.
The thing that wasn't accounted for in the analysis at the time was just how rapidly and by how much China could suppress its appetite for oil imports.
They had basically been planning for this shortfall for ever as a likely outcome of sanctions/blockades during an invasion of Taiwan.
Curtailing imports so much is a smart soft power move in not only does it ingratiate countries that would suffer most from the price spike but it could soften the resolve of Taiwan in showing that one of the largest penalties that would result from the invasion isn't nearly as punishing which in turn makes Taiwan's porcupine strategy less viable.
Along with US own goals like the thoroughly bungled and unplanned invasion, tariffs, corruption (both business crimes and more moral crimes like pedophilia or disregard for following legal process) it's made most of the west rate China above USA in favourability [latest Pew research poll].
System 1: a system for recording an image of a location at a given place over time.
System 1 can refer to the dashcam or the mk1 eyeball + notepad in your example.
System 2: a system for tracking the presence of a person across both time and location
An example of system 2 would be the facial id system being trialled on the london underground currently.
These are a difference in kind not in degree. It doesn't matter how many system 1's you deploy, you cannot unlock the capability of querying where any given face was observed across time and location.
Where did you learn that? It doesn't reflect the structural volumes present. Central banks make interventions all the time in line with little stabilisation programs. Those are almost always deemed success.
Maybe you deduced it by yourself? If so, fx is weird despite traditionally being seen as the simplest area in finance. E.g. It's counter-intuitive but we tend to think trade make up most FX volume globally. It's in the area of less than 3%. The majority by far is speculative and hedging.
The other trap is fx volume, people assume the know what volume is but then they learn expressions of fx volume is almost always actually tick volume.
It works very well in rich countries, but it is expensive, especially if the volume of exchanged currency is low. This is one of the reason behind the Euro, countries realised it would be way less expensive to defend the currency if they banded together. This is also a big why ex french colonies keeps colonial money, because that currency is pegged to the Euro and that peg protect it (the only way to get out cheaply is to do a West African Union or something similar, which is why anti-colonialists are big on the subject. They are right).
There are so many footguns[1] in async python but it really has stolen the zeitgeist of modern python web. Synchronous django has a lot to commend it. If you deploy it reasonably well (workers, behind a caching reverse proxy etc) it's easy to operate in production even under duress.
It's not going to be the right stack for long lived websocket connections or whatever but for a CRUD-ish or enterprise app, often very productive.
[1] buffer bloat is too easy to sleep walk into
queue = asyncio.Queue() # oops
unbounded concurrency
await asyncio.gather(*(fetch(item) for item in items)) # look mum! no outbound sockets left or maybe even no file descriptors
accidental blocking
data = requests.get(url).json() # oops! we're blocking the event loop
and sure you can setup a separate task to measure the event loop latency and alert on that but this is the kinda thing you'll never find in a tutorial, you just need to experience this stuff and figure out a solution you like
I can go on and on about async python (cancellation bugs, leaking a task - that has a cataclysmic failure mode where if you forget to hold a reference to your asyncio.create_task() then it's all weak refs in that machinery so your task can get garbage collected before it ran or completed in production! super tricky forensics. Then there's all the obvious stuff like race conditions, new ways of creating deadlocks, blah blah i really can go on for days.
Everytime I have to deal with websockets in Django, I want to rip my eyeballs out.
Most of the time what I really want is just push support, and don't need bidirectional communication, i.e. SSE. But it's the same thing: both are a pain to work with in Django. I find django-channels so cumbersome.
So, for my latest projects, I've ended up offloading this push functionality to a service like Centrifugo. Django talks to Centrifugo, and clients connect to it as well. It makes Django still be django, without worrying about django-channels, ASGI and all of that complicated setup. I've been very, very, very happy with this decision, even if I do have some additional complexity to articulate the two. It is nowhere near as confusing and convoluted as django-channels and uvicorn/daphne, though.
As for async python in general, fully agree. Every couple of years I decide to give it a go with some new tool or framework, thinking surely now it'll finally just click and I'll get with the times. Every time I feel disappointed with footguns and absolutely terrible ergonomics. No, thanks.
Please go on. I also feel like asyncio is a big hack. Even just accidentally blocking the event loop is way too easy. And I couldn't believe it when I read that you need to store a reference to a task in a set to keep it from accidentally getting canceled. How did this become the main stack of AI backends? It's like node but slower AND much easier to mess up because of synchronous IO.
Well references in python are mutable by default, so you essentially combine an asynchronous model with mutable shared state. Combine that model with unknown caller exceptions in any subfunction and you're in for a world of hurt.
I'm not sure i see it as a hack, but i do feel unduly burdened as a developer by async in python. I feel like there's a lot that i have to think about and i'd appreciate more help from the language.
I don't want to be overly critical, there's languages that people complain about and then there are languages that no one uses... If i compare it to js/ts, some stuff is genuinely better in Python - e.g. if you missed an await. While both ecosystems have lint tools available for this, but the behaviour is just friendlier in python.
Structured concurrency is better in python, but even if TaskGroup is nicer to use than AbortController, it still has its own foibles which means i'll usually advocate for AnyIO.
But the js/ts ecosystem just generally benefits from being async from the get-go where python you're just a time.sleep() away from a bug that will slip through dev envs and ci pipelines undetected and only rear its head under load in prod.
If i have a tip to share its the debug flag for asyncio run:
asyncio.run(f(), debug=True) # find some more issues before prod
I agree that there's plenty of footguns with async python, but I think the hype is a bit overblown. A few sensible defaults in the standard library would go a long way, e.g. requiring a maxsize argument to Queue (or None if you really need it to be unbounded). The "accidental blocking" thing seems like less of design issue - it has much more to do with python's super long history as a sync-first language. Taking a step back, though, async is popular because it is genuinely extremely useful! If you need to do a bunch of work and that work is mostly IO-bound, then it makes sense to live in an async world.
I think people in the Python world just don't yet have a good mental model of async either. I recently have had conversations with a number of people who have switched to FastAPI because "it's faster" even when they're using it to serve ML models which are compute bound and there's nothing you can await on.
I think the mental model is the right thing to focus on. I'm not denying there are cases where async at the edge of an app can make sense but there's no free lunch and i that doesn't appear to be well understood.
Maybe i will write a blog post, if nothing else than to get my own thoughts in order.
async python is always slower than just naively using threads. I don't think there is any application in production anywhere that would not prove me right on this. the complete lack of any kind of scheduling is a disaster. unless you know exactly what I mean by a scheduler please don't reply to this.
I don’t think there’s anything Python specific about that criticism. Most languages with cooperative concurrency have simple or no schedulers and run coroutines pretty much directly (or unpredictably, if they’re scheduled into parallel executors). Even Java and Tokio’s scheduling, such as it is, isn’t that directly controllable or observable and doesn’t magically fix loop-blocking or cache efficiency or whatever.
I think you might just not like cooperative async IO multiplexing as a programming model. Which is fine, but has nothing to do with Python.
I don't like it, but I don't like it because it is impossible to make it performant.
What if I told you there was one of the greatest engineering achievements of mankind ready to make sure your threads run whenever they can, but also fairly and carefully balancing context switching cost and throughput, and instead you use python async and get 1/10th the performance, weird impossible to avoid pauses an order of magnitude longer than the threaded version's median response time, and just constantly have to fix bugs about callbacks firing on canceled blah blah blah.
Those are all fair points! They apply more or less equally to, say, nodejs or Perl's AnyEvent.
I'm totally onboard with "use the OS thread scheduler when you can"; it's a brilliant piece of technology. And if you are in a situation where
> pauses [are] an order of magnitude longer than the threaded version's median response time
...then fully cooperative, single-thread async programming isn't for you!
But async programming was made for the inverse use-case of that: backend functions which spend the vast majority of their time waiting for I/O--slow databases, HTTP requests, and whatnot--and which needed to serve very high concurrency demands on very cheap servers. If, say, your web route handler wants to probe or parallel-fetch from 100 unique-per-request HTTP endpoints, and you expect to handle 100 web requests concurrency, using threads is going to cost a lot of resources (the portion stack space that's allocated eagerly, start/stop time, and so on). For some folks, those resources might be readily available, in which case threads may still be the way to go! But for other folks (tiny servers, or more orders of magnitude than 100/100--use cases like proxies or CDNs) cooperative async might be a better way to go. Similarly, if your concurrent code is sharing a lot of data between routines but is primarily waiting on I/O, single-thread async concurrency might be a less race-condition-bug-prone model to use.
I'm generally with you that it's a tool that's prioritized above threads in a lot of cases when it shouldn't be. But it is still definitely valuable for a lot of common challenges.
You clearly know what you are talking about and I agree with what you are saying 100%, if you are spending almost all your time waiting around, you should address that. Generally the OS has a better solution there as well, in fact linux has several. Python also has several, and solutions like Stackless or Twisted are just a joy compared to the cancer of python async.
Even if you buy into callback-style async style programming like node or python async, Python async is not just bad because the general idea of it is bad, it is also bad-at-what-it-is-trying-to-be. It's a bad, hack, incompetent implementation of async programming, which in this style (cooperative callback style) is a bad architecture. It's unforgivably bad. Look in the stdlib and read the code for it, it's full of gotchas and confusion and patch after patch after patch putting in exceptions (like the "we want to do W but if this task is x then actually don't do this but if it's y do this other thing and if it's z then do a third thing kind of exception) and checks and double checks every 3rd line because the design itself is broken. Whole features (like un-cancel task) were put in because one influential person demanded it because the library they wrote can't work without it, even though it breaks a fundamental invariant that should never be violated.
Fundamentally, when you choose a tool or architecture, it should not be the thing you spend 50% of your time debugging. Every Python async project I've ever worked on required me to spend half my time tracing around in the guts of the async library itself to figure out what weird edge case we were running into that made something completely broken. This is like the hallmark of bad software.
I wish Stackless/Twisted had succeeded, I really do, but they unfortunately lost to what I agree is a worse implementation. Like you, I've spent probably thousands of hours in my career debugging bizarre Python async internal machinery and writing tools that shave off some of the rough edges, and you're right that it's bad out there.
I think the main things that cripple the Python stdlib/asyncio design and the ecosystem that arose around it are:
1. Thread:loop locality. The design of event loop-per-thread is ... admirably flexible, but doomed to smack into the reality that many folks aren't in control of what threads their code is invoked on, and what may or may not have kept a reference to an event loop, which brings me to...
2. The fact that event loop objects can be replaced/started/stopped. That's a terribly confusing API with lots of inherently global state at the edges (open files/pipes used for self-polling, signal handlers), which trips up all sorts of code--including the stdlib itself at times.
A better API design would have, I think, had loops be utility objects without a notion of "running" or not, ideally with the ability to be addressed across threads (note that I'm not asking for coroutines to be auto-scheduled onto multiple threads, just for "asyncio.run"-style entry points and loop objects to work on first access in any thread context). That'd save on the wacky complexity needed for libraries like asgiref (which does a very good job at something that should not be this hard) and reduce "you can't run this coroutine because it's loop is stopped" and "you can't run this coroutine because it's owned by another loop"-type bugs.
Swapping in other loops like uvloop should be a stdlib call that has to happen before any async functionality has run anywhere in the program, and doing it after that should be an error. Or just ... don't support other loops. Realistically there are only two, and the internal "loop" abstraction is so low-level that making it swappable can only speed up certain other kinds of code; a lot of perf is left on the table behind those ugly and expensive "introspect the loop/task/passed-in-awaitable types and states on every single asyncio function in the stdlib" checks you correctly complain about.
The ability to lapse into/out of "blocks" of async code in a largely-synchronous codebase (or between tests) could have been provided with a few simple utilities that asserted things like "crash if any timers/sockets/etc. are still registered with the event loop" or "give me a weakref-tracked list of all live Future and Task handles that the loop has ever manipulated". A ten-liner I use in most async Python projects I touch is basically "write a wrapper class for concurrent.Future and asyncio.Future which stores a weakref from the Future to any async Task that was active when anything wrote to the Future", which makes "is there anything async-condvar shaped that hasn't been GC'd yet?"-type checks trivial.
3. Cancellation is a tarpit, as you pointed out. I'm a big fan of the ability to observe cancellations (the Nodejs model and the Rust model both leave a lot to be desired here), and it's useful to be able to intercept and take action in response to them within reason. However, modelling them as a regular exception runs afoul of too much "except:"-using code, and the idea of "sheild", uncanceling, and accidentally swallowing cancellations is a poorly designed API. Ideally, cancellation handlers would be used in the same way that signal handlers are used--constant-time, minimally-side-effectful code with a guaranteed jump back to resume cancellation propagation when they're done. I understand what making cancels preventable/handleable enables, and it's indeed valuable ... but it's not worth it compared to the pervasive bugs that emerge around the choice of making them a regular exception. Like, sure, it is technically the programmers' fault for misusing CancelledError, but past a point, it's easier to fix the tool than the users.
4. Future objects (especially concurrent.Future's ability to serve as a sync/async "bridge") are treated as low-level tools users should rarely reach for directly. The stdlib them wraps them with some pretty questionably behaving and poorly performing code. I wish the original APIs around asyncio had made the creation and use of Futures the main language things speak, rather than endless utility methods that end up "turducken wrapping" Futures/tasks internally. Relatedly, it's frankly insane the amount of hoops you have to jump through to customize exception propagation between a pair of Future-ish things when chaining them (if A fails with X, sometimes I want B to fail with the same thing, sometimes I don't. If X is CancelledError, the best-practices tools in the stdlib require creating no fewer than six nested/intertwined futures to express "if A fails with a real error, B should fail; if A is cancelled, B should return/do nothing/whatever").
5. I wish the asyncio stdlib had given us more tools to bridge thread/async computation early on (run_coroutine_threadsafe and and run_in_executor are simultaneously too high- and too low-level). Honestly, a ton of the asyncio stdlib feels like it was written as if some of the held-for-constant-time synchronization locks involved in e.g. "call_soon_threadsafe" evoked more fear in the maintainers than the 8 stack frames of chunky pure-python code involved in doing normal thread-local operations. Unconditionally taking out some usually-uncontended coordination locks whenever communicating between different sync stacks/async coroutines would not break the bank--hell, this is what they did for every data object in the program when they removed the GIL. It's fine. Again, modelling an "event loop" as something fixed and cross-thread without a running/stopped state would make this moot, but here we are.
6. There are a lot of preforking systems in Python, from multiprocessing to gunicorn to mod_python. Async code should have been fork-safe--at least as fork-safe as Python's threads, which are hellishly unsafe to fork in the presence of on paper, but which the GIL and interpreter behavior made fork-safe in practice in a huge number of situations (the evidence of which is that thousands of users didn't even know they were in those situations and things kept working fine).
On the other hand, some things that a lot of people rail about in Python async are, I believe, good ideas that should be emulated:
1. Python's got a giant legacy of synchronous code, and (at least when asyncio was created), didn't have the ability to effectively spread work across threads. Given that, the refusal to make async stuff automatically schedule onto multiple threads when a coroutine blocks was a good idea. Loop blocking was just always going to be more common and frustrating in Python async than in JavaScript (with no synchronous legacy), Java (where everyone's used to their code being plopped onto random thread pools anyway), or Rust (with compiler support for expressing that a chunk of code can be moved between threads or not).
2. The taxonomy of awaitables from regular "async def" (fast, no permanent tracked stack) to Futures to Tasks (promises) is a good design. A good async system needs something in all those niches. I just wish the situations where one was automatically promoted to another were handled better.
3. PYTHONASYNCIODEBUG is very useful.
4. Tricky as they are to work with, async generators are very powerful.
5. The decision to implement asyncio itself on top of the original interpreter's generator machinery was a good one (caveat the flaw in the original generator machinery that StopIteration is a regular exception that users can subclass and raise--just like CancelledError, that was not a good decision).
Things like Trio can definitely help shave off some of the rough edges of Python's async (and the stdlib has adopted structured concurrency too nowadays), but even using them inevitably causes some problems since it's too easy to mix and match asyncio and Trio.
At the end of the day, I think Python did pretty well here: it's a big synchronous language with a million deployment targets adopting an entirely new control flow primitive without changing any invariants. The fact that they did as well as they did without industry backing is due in large part both to careful design and the willingness to compromise/ship and improve on incomplete solutions by folks like Victor Stinner. They deserve props. There's still a lot that could be done better.
I think you are right on every point, which is why we should agree that using python async is a bad decision and anyone starting a new project should not use it. Threads might not be what all the cool LLMs are doing, but I have never spent half of all my work time for a month debugging a threading bug, much less half of my day every day forever like with python async. Anyone who says they don't spend half their time debugging python async internals either doesn't use it or doesn't realize they are spending 500x too much on hosting.
I’m not sure I’d go as far as “always a bad decision”, but Python async is definitely something that shouldn’t be adopted without carefully considering alternatives and being aware of the requisite expertise needed. It’s an advanced and sharp-edged tool, and I wish people forced to use it (e.g. due to the use of libraries that only expose an async API) had better tools to make its limited adoption in an otherwise-sync codebase easier.
Especially in Python, threads and blocking code should definitely be encouraged as a first resort.
Yep, if you are going to make asynchronous programming, you need either a small, plain, and obvious layer that calls insulated synchronous code or some stack with high quality assurances.
The only reason i couldn't tell memphis is because it's aping a known design language already, it's just a perfect copy so me not being able to tell it wasn't AI isn't a free pass in this case.
I think if there was a little less "grid" feeling to the layout details then i wouldn't be able to tell on a few of the other designs either.
Edit: the designers republic version is quite a lot different than the others, it's not just that it's the only design to avoid pairing every feature with an icon, it's the whole layout variety - there's different font sizes, the positioning of elements is very well done to my eye. I'm gonna say this has to be another case like memphis, there's some pre-existing design language that i don't know of and it's just aping that. It's much more human than the others which i can believe are straight ai.
reply