Latest Posts (20 found)

FBI Arrests Founder of Ransomware Negotiation Firm

Agents with the Federal Bureau of Investigation (FBI) on Thursday arrested the co-founder of a Canadian cybersecurity firm in connection with an investigation into the ShinyHunters hacking group that recently relieved the FBI of sensitive data on thousands of agents, multiple sources tell KrebsOnSecurity. The New York Times reported today that the FBI has arrested a Canadian man in Pennsylvania on suspicion of assisting ShinyHunters. The Times story did not identify the man, nor did a statement on Twitter/X about the arrest from FBI Director Kash Patel . One source close to the investigation told KrebsOnSecurity the Canadian person arrested this week was visiting Pennsylvania for a cyber insurance conference, and that the suspect’s company specialized in handling ransomware negotiations with cybercrime groups. Another shared that control over the ShinyHunters investigation has been centralized at an FBI field office in Texas. An online search reveals the Cyber Risk Summit was held at the Loews Philadelphia Hotel between Oct. 5 and Oct. 7. The conference had several sponsors, but according to the summit’s website its biggest sponsor was a Canadian security company called Cypfer . According to LinkedIn, Cypher was co-founded by a Canadian man named Edward Dubrovsky , who is now associated with another Canadian security firm called CyberSteward . In a post to LinkedIn approximately one month ago, Dubrovsky said he had plans to attend the Cyber Risk Summit with the rest of the CyberSteward team. Edward Dubrovsky’s LinkedIn profile. “Looking forward to continuing conversations around strategy & compliant driven coercive (ransomware, extortion) advisory, negotiations and settlement services that are global and truly agnostic,” Dubrovsky wrote. Federal court records show that on October 8, an Edward Dobrovsky (note the slight misspelling of the last name) was arrested in Pennsylvania on cyber extortion and conspiracy charges. Several of those documents — including the core complaint — are now sealed. But a handful of them were indexed at Courtlistener.com , including a summary of the complaint, which charges the defendant with “conspiracy to threaten to impair the confidentiality of information with the intent to extort money,” and “interference with commerce by threats.” Image: Courtlistener.com The inmate locator at the U.S. Bureau of Prisons website reports that a 54-year-old Edward Dubrovsky is currently being held at a federal facility in Philadelphia. But the court records indexed by CourtListener include a notice filed on October 9 that moved the case to the Eastern District of Texas, which sources say is now the epicenter of the FBI’s ShinyHunters investigation. The FBI declined to comment for this story. Dubrovsky’s LinkedIn profile states he is the author of Cyber Extortion Strategic Response , a 252-page book that promises to “take readers beyond the ransom note and into the decisions that determine how an organization responds, recovers, and protects what matters.” Edward Dubrovsky’s book, which centers on the intricacies of ransomware negotiations. “At the heart of the book is a critical distinction: communicating with a criminal is not the same as negotiating a payment, and negotiating is not a commitment to pay,” reads an excerpt from the book’s listing on Amazon. “Engagement can serve other objectives, including testing claims, gathering information, creating time, and preserving options while the organization evaluates its next move.” Mr. Dubrovsky could not be immediately reached for comment. KrebsOnSecurity also sought comment from the other co-founder of CyberSteward, and will update this post in the event they respond. The available court records in Dubrovsky’s case show that he does not currently have an attorney and has yet to be appointed a public defender by the courts. ShinyHunters typically uses phishing and stolen credentials to siphon data from corporate accounts at software-as-a-service companies, and then threatens to publish the stolen data online unless a ransom demand is paid. According to the FBI, the group has extorted more than $70 million from victims so far this year. Sources tell KrebsOnSecurity the FBI has been poring over devices that were seized last month when the Dutch police arrested the convicted cybercriminal Pepijn van der Stap in connection with the ShinyHunters investigation, and that charges against principals at other companies that specialize in ransomware negotiation may be forthcoming. Immediately after Van der Stap’s arrest, another member of ShinyHunters named “ Rey ” assumed control over the group and began taunting the FBI over data the group stole from the agency’s online recruitment portal, which included each’s person’s unit and specialization, as well as medical and psychiatric records. Last week, Reuters reported that Rey — identified as a teenager named Saif Al-din Khader — had been detained and was cooperating with FBI investigators. On October 7, we detailed how Rey was apprehended as the cybercrime group allegedly sought to extort a navigation and digital aviation unit that was divested by Boeing in late 2025. This is likely to be a fast-moving story. Updates will be noted along with timestamps.

0 views

Justice for CSS

Fun seeing CSS be a topic in WIRED . Pete Millspaugh shouting out some greats: One programmer recently re-created Doom in CSS . Others have used it to build holographic Pokémon cards and 3D-rendering engines . Cassidy Williams, who competed in this year’s inaugural March MadCSS tournament , built an experimental database-query engine entirely in CSS. I mean, hell yeah. Allow me to just… And I think Adam did it first!

0 views
Unsung Today

Subtitles on demand

Nice moment in iOS: when you reduce the volume to zero, subtitles come up automatically – and they go away the moment you increase the volume again. This is from YouTube on the iPad, but it appears to be system-wide: (Feels extra meaningful in the face of so many people playing their media on speaker in public these days.)

0 views

2026.41: It’s Not You, It’s Me

Welcome back to This Week in Stratechery! As a reminder, each week, every Friday, we’re sending out this overview of content in the Stratechery bundle; highlighted links are free for everyone . Additionally, you have complete control over what we send to you. If you don’t want to receive This Week in Stratechery emails (there is no podcast), please uncheck the box in your delivery settings . On that note, here were a few of our favorites this week. This week’s Stratechery video is on Apps, Agents, and Aggregation . Drifting Apart . For the last 25 years, Apple has been in a position where its computers were not only thought of as the best choice for everyday consumers, but also the most useful tools for technologists and hackers. However, I think those paths are diverging: in Apple and a Hacker’s Future I explain why I feel myself growing increasingly frustrated with Apple in the age of AI; that means that Apple partnering with LG to dramatically expand its home offering is not for me, which paradoxically means it’s a great idea. The future of consumer technology is likely completely custom on one hand, and completely integrated on the other. — Ben Thompson It’s Complicated. As someone who was born in D.C. and therefore takes an interest in politics as a birthright (or birth defect), I loved this week’s Stratechery Interview with author and political consultant Katie Harbath. Ben and Katie trace the early innings of tech’s impact on politics, including several flashpoints I’d forgotten (John Kerry and dolphins!), but then turn to ten years of Katie’s experience at Facebook. Her tenure coincided with seismic political shifts and subsequent sentiment shifts toward tech companies, and both Ben and Katie — who’ve been friends for 20 years — reflect on that era with an honest and fun look at evolving business imperatives, angry backlashes, and the explanatory power (or lack thereof) of tech as a political force. — Andrew Sharp Friends Without Benefits.  Speaking of politics… Sharp China was off this week, but two weeks after Xi Jinping’s visit to D.C., I wrote about the delightful absurdity of U.S.-China dynamics in the current moment , and the quiet effectiveness of his administration’s campaigns to counter Beijing. One year after I wrote that China threatening to cut off the Western world from rare earths would do lasting damage to its global standing, Germany and France are now lobbying for new trade barriers and a “China kill switch” that could shut off Chinese imports entirely. Trump, meanwhile, is friendlier than ever with Xi, as the U.S. attempts to preserve stability while its policies complicate Beijing’s relationships everywhere else.  — AS Apple and a Hacker’s Future — I was happy for years in Apple’s walled garden; with AI, however, their protections feel like limitations. Game Decompilation, Is This Legal?, A Well-Trodden Path — Games are being decompiled, but the real risk to gaming is new games and increased personalization. Apple and LG, The House For Everyone Else, Agent Standards and Amazon — Apple is taking a smarter approach to the home than I expected, leaning into integration (with partners); then, what Amazon should do about agents. An Interview with Katie Harbath About Disrupting Politics at Facebook — An interview with former Facebook Head of Global Elections Katie Harbath about her new book Disrupting Politics, and how things change for the company in the 2010s. Friends Without Benefits — The Trump methods are often maddening, but the current administration’s strategy on China continues to be quietly effective. Ben Gets Hacked Apple’s 2010s Mistakes The Fall of Brazil’s Armored Vehicle King TSMC is Coming to Texas? Fraud Watch 2026, Sixth Men and 32 Teams and KAT, All Eyes on the Heat and the Southeast Two Paths for Apple and Its Users, More on Amazon and Advertising, OpenAI and 722 Mathematical Manuscripts

0 views

Fun with low-rank vocab matrices (and a bonus test loss reduction?)

I was nerdsniped ! On the HF discussion page for one of the models I created for my previous post , asked if I'd considered trying out factorised embeddings -- something he's using for his model, Maba, and which was previously used in some other models, like ALBERT . It's a really nifty idea; you use a similar trick to LoRA as a way of reducing the number of parameters used for your embeddings and your output head. And for small models, those can be a disproportionate number of the total. For example, with my 163 million parameter GPT-2 small-style models, the embeddings are about 39 million of them ; the output head is the same size, so with those two taken together, that's half the model just getting stuff in and out rather than actually doing the thinking. Even if you use weight tying, like the original GPT-2 models did, you'll wind up spending 30% of your "budget" on the single matrix shared between embeddings and the output head. Similarly, while larger models have a smaller percentage spent that way, even medium-sized MoE models can wind up spending a lot of the active parameters on embeddings; I calculated that for Qwen 3.6 35B MoE, with 3B active parameters, there were about 1B of them used across both the input embeddings and the output head. So anything that can reduce that -- so long as it doesn't come at a high cost in terms of the model's capabilities -- is worth considering. You save on your parameter count, which either means smaller models and potentially quicker training, or allows you to "invest" the savings in more thinking parameters -- that is, a wider network with a higher embedding dimensionality, or a deeper one with more layers. Andrew reported that the impact of using this trick was minimal: At rank 128 on a 50k vocabulary, the penalty on validation cross-entropy is negligible (typically within +0.02 to +0.04 loss, or <0.5 perplexity delta). Now, that suggests that he's getting a loss of around 2.5 (that being the point where a loss increase of 0.04 implies a perplexity change of 0.5), which is much lower than I typically get with my GPT-2 architecture (3.5 is more like it for me), but still, perhaps I'd get good numbers too? I decided to train a few models to see what happened. The results were interesting! I found that using this trick on both the embeddings and the output head caused a somewhat larger increase in loss than Andrew described -- about 0.07 in my first test, 0.10 in the second, and 0.14 in the third. Those are relatively large, though potentially recoverable from further training or larger models. But more surprisingly, I found that using the trick on the input embeddings only seemed to reduce test loss. Before I'd started, I'd expected it to be less harmful on the input side than it was on the output side, but seeing an improvement was certainly unexpected. Getting loss down by reducing your number of parameters is not what normally happens! So, it's definitely worth digging in a bit. Let's start with the theory: what are these factorised embeddings, and how do they work? Like LoRA, they rely on low-rank factors, so firstly I'll define those. Our models are made up of a large set of matrices -- embeddings, output heads, attention weights, FFN linear layers, and so on. Making them smaller obviously reduces the number of parameters for the model, though you would expect it to come at a cost. The idea behind low-rank factors is that you can replace a larger matrix with two smaller ones that will contain most of the important information. Imagine that you've got a matrix of size m × n . Now, from matrix multiplication, we know that if you multiply an m × r matrix by an r × n one, you'll get a result that is m × n . If we do that, then we've factorised the matrix (in the same way as we might factorise 12 into 3 and 4 because 3 × 4 = 12 ), and the value r is referred to (logically enough) as the rank of the factorisation. If r is sufficiently smaller than m and n , then these two matrices -- the low-rank factors -- combined will contain fewer parameters than the full matrix. Let's make this specific, using the output head of a GPT-2 style model without weight tying. It takes in the embeddings that result from the Transformer layers (after normalisation), and converts them into logits across the vocabulary. For the GPT-2 small size, our embeddings are 768-dimensional, and the vocab size is 50,257 tokens. So when we use a linear layer to do that mapping, it has 768 × 50257 = 38597376 parameters. If we were to replace it with two matrices -- say, one of 768 × 128 and one of 128 × 50257 , then combined they would take up 768 × 128 + 50257 × 128 = 98304 + 6432896 = 6531200 parameters. That is almost six times smaller! So: the idea is that instead of creating our model with an m × n matrix, we create it with a pair, m × r and r × n , and train those instead. If all goes well, that pair will be almost as capable of learning what we want as the full one would have been, so we'll get results that are close enough. The maths and the implementation work out simply, too. For a linear layer with no bias, we can write the matrix multiplication that takes our inputs X and a weight matrix W , and produces an output Z , like this: 1 Now, if we're using a pair of low-rank factor matrices A and B instead of a full one, W , we can say the "virtual" weights we want to use for the calculation are W ′ such that: So that means that our calculation for this neural network is this: Matrix multiplication is associative, which means that you can rewrite that as: ...which hopefully you can see is the same as feeding X through a layer using A as its weights, and then feeding the result through a second one using B . In code, you've taken something like this: ...and replaced it with this: It's probably intuitively obvious, though, that this comes at a cost. If you have fewer parameters then you can store less information, so this part of your model is "dumber". If it's not clear, though, imagine that in the code above was one. You would be taking the (for GPT-2 small) 768-dimensional embedding, converting it to a single number, and then expanding that single number out to a 50,257-dimensional set of logits. Stuff is going to get lost -- the idea behind low-rank factors is that, so long as you choose an appropriate value for r (which is often referred to as the width of the low-rank bottleneck ), you won't lose the important stuff. This works surprisingly well in many cases -- in particular, LoRA, which allows you to fine-tune models that are too hard to fully train on your hardware, or to fine-tune them faster, uses it to easily train something like low rank factor "diffs" to your weight matrices 2 . But for this post, we'll try the simplest version: what if we replace both the input embeddings and the output head -- the vocabulary matrices -- in their entirety with low-rank bottlenecks? Low-rank factorisation of the vocabulary matrices is a bit of a mouthful, so based on the name "LoRA", I decided to call this trick LoRE, for low-rank embeddings. It's not strictly accurate (after all, we're doing it both to the embedding matrix at the start of the LLM and to the output head at the end), and I don't expect it to take off, but I rather like it and will use it in this post :-) The argument behind it is that the embeddings are just a simple lookup table, so are exactly the kind of place you'd expect to be able to make savings by only considering the important parts of our huge matrix; the same might apply to the output head at the end, though personally I was less convinced by this part. It's worth looking into that asymmetry a bit. My intuition was that while embeddings really are a lookup table, the output head is doing something a bit more subtle. It's projecting from the continuous embeddings that come out of our Transformer layers into logits, which we interpret (via softmax) as a probability distribution over possible next tokens. That's a significantly less simple job than mapping from "cat" to the appropriate embedding: as a (simplified) example, if you have an embedding that means something adjacent to "cat", "dog", "gerbil", "household pet" and so on, then projecting that to the appropriate values for the next token is non-trivial. 3 Still, implementation-wise, it was really simple. I decided to extend the PyTorch code that I had from Sebastian Raschka 's book " Build a Large Language Model (from Scratch) ", by adding an optional section to the model config JSON. This would allow us to switch on LoRE mode for the input embeddings and the output head independently, and would specify the rank -- that is, the r in the section above, which says how wide the low-rank bottleneck between matrices A and B would be. So, in code, my first cut changed this: ...to this: ...and this: ...became this: That in itself was a perfectly reasonable implementation of the underlying concept. But it had a problem. I wanted to compare the loss with and without LoRE, and also find out what the effects of input embedding-only and output-head-only LoRE would be. But I've found in other experiments that the initial random weights that my models start with can have a significant effect on the loss that the resulting model gets. The code above would give quite different random weights with different configurations. The fix was simple, though: With that code, so long as there is any LoRE config, we create the same weights regardless of whether it's used in the input embeddings or the output head. I would need to train a baseline which had config for LoRE but just had both and set to there -- that would actually be a normal, non-LoRE model but would have the same initial random weights in any parts that it shared with an equivalent model that did use LoRE. Additionally, Andrew had said that we need to be careful about initialisation: The main failure mode to watch for during training is early gradient spikes: scaling the bottleneck projection initialization to 1 / sqrt(r) keeps variance uniform and ensures training dynamics match the full-rank baseline from step 1. ...and later : Keep standard GPT-2 init on the embedding table, but scale the inner linear projection matrices by std = 1.0 / math.sqrt(128). (The 128 in there is because we were talking about a setup with r = 128 , which is actually what I wound up using.) Now, I was using PyTorch's default initialisation rather than the classic GPT-2 setup, but I went ahead and added a flag to handle the scaling as Andrew described: if it was set, then right at the end of the model creation I applied the initialisation to the low rank factor matrices that were innermost in my model (as in, closest to the Transformer layers): Even though that was not run for every configuration, because it happened right at the end of the model creation, it would not affect the other weights, so I figured that it would be safe. You can see the full diff from the normal GPT-2 code here . Those changes looked correct to me, and I asked GPT-6 Astra (on "extra high" thinking) to confirm that it looked right to it in the context of the discussion, which it did. That done, I double-checked with Andrew , as I really wanted this to be a clean attempt at a repro of his results. He confirmed, so it was time to train some models! I decided that I wanted five training runs to check this out thoroughly: To save time, I decided to train them in the cloud: the first three, and then the last two, in two batches of parallel runs. I used 8x A100 machines with 40 GiB VRAM per GPU on Lambda . I'll break with my normal tradition of giving detailed runthroughs of each training run, as there are (spoiler) quite a lot of them in this post! However, for reproducibility, the commands I ran for each one looked like this: ...with the run ID, , replaced with the appropriate one for the given run settings . Here are the results from the first three runs: the baseline, and the ones with LoRE at both ends, with and without smart initialisation. The model name links go to the uploaded models on Hugging Face. You can see that I had some issues with the last run; there were shortages of available instances, and I wound up starting one in Japan. Connectivity to it from my home in Portugal was really slow and unreliable; when I tried to download the model after the training run, was predicting 10 hours (which at the machine's cost of US$15.92/hour would have been insane). However, I was able to copy it to my PythonAnywhere account quickly, perhaps because that is in a US Amazon datacenter and had better connectivity to Japan, and then I could copy it from there to my machine, again quite quickly. Unfortunately while scrabbling around with that I didn't capture all of the output. Still, the important data at this stage was in the test loss column. As expected, using LoRE made the test loss worse. And smart initialisation did seem to help reduce that penalty. The loss delta, at +0.07, was worse than the +0.02 to +0.04 that Andrew described in his message, but not wildly different -- certainly small enough that it seemed plausible that you could recover it, and perhaps more, by investing the 64M parameter difference in a deeper or wider model. Promising! The cost and time savings were real, too: the run in Japan had its cost bumped up by all of the woes I had copying the model down, so the one is the best indication: US$38.55 rather than US$43.96, so a saving of more than 10%. And while going down from a bit more than 2h30m to 2h14m might not seem like much, with a larger model or a longer training run, an almost 14% reduction could be valuable in and of itself. (And that's not even considering the alternative of "reinvesting" the saved parameters in larger models.) So that was pretty good news! It was time to do the ablation runs to see whether having LoRE just on the token embeddings, or just on the output head, made things any different. I switched the setting for the remaining two models to , and kicked them off. Now, there's a bunch of interesting stuff in there, but let's focus on the test loss. My lab notes for this bit say "well, that's a bit of a shocker!" Quite amazingly -- at least to me -- we got lower test loss with the input-only LoRE, with 3.526610, than we did with the no-LoRE baseline, which got 3.599611. Things get even more interesting when you compare the different versions (excluding the non-smart-init) against each other, in terms of their difference from the no-LoRE baseline (which I'll call the delta for conciseness below). Rounding to 3dps: If you add the two single-LoRE loss deltas together -- the 0.138 and the -0.073 -- you get 0.065, which is surprisingly close to the delta of 0.072 for the dual-LoRE option, just 0.007 away. Could we be looking at two almost independent effects, one for each option of where to apply the LoRE? And was that apparent benefit of the input-only LoRE a real thing? Or was it due to chance -- perhaps we had "good luck" when initialising the LoRE weights, and "bad luck" when initialising the non-LoRE ones. If there was an overlap between the best loss you could get with LoRE and the worst loss you could get without, then this result could be within the noise. I decided I was going to do a second batch of training runs to see if I could get evidence one way or the other. But before we get into that, it's worth taking a look at some of the other numbers in those two tables. Firstly, let's take a look at the training time. The training machines looked like they were running at close to 100% during all of the runs, so tentatively let's assume that time taken is proportional to compute. Unsurprisingly, the baseline run came in with the longest time, at 9,339s. And equally unsurprisingly, the full-LoRE run that we have timing for was much faster, at 8,057s. It certainly makes sense that replacing big matrices with pairs of (much) smaller low-rank factors will mean fewer calculations on both the forward and the backward pass, meaning that the training run is faster. There was something that initially surprised me with the single-LoRE runs. Using LoRE on the output head sped things up quite a lot, but input embedding LoRE had much less effect. However, after thinking about it a bit, it became clear what was going on. An embedding module in a network is actually really quick to run, both on the forward and backward pass. While I like to think of embeddings as conceptually being a projection of a one-hot vector in vocab space into embedding space via a matrix multiplication, and I still think that's an excellent model of interpreting what's going on, in practice it's just something much more like an array lookup, with almost zero cost. Likewise on the backward pass, we only need to work out gradients for the chosen embedding, so that should be faster too. Indeed, with that all in mind, it might not have been surprising if input-only LoRE had turned out to be a bit slower than the baseline, as it, at least, needed to do forward and backward passes through actual matrices, even though they were fairly small. Doing another of those delta tables, the time difference in seconds (using the "ordinary init" run for the both-on number): You can see that if you add the delta of the single-LoRE options, you get -1,251, which is pleasingly close to the delta of the dual-LoRE one. And that is much less surprising than the loss result -- with training compute, you really would expect improvements like this to be additive. Finally, there was something interesting in the initial training loss. Because my training code prints out the loss it got on the first global step, I decided to note it down. There's something in this data that will become important later: in both of the training runs with LoRE on the output head and smart initialisation, it went up noticeably. Normally, the initial training loss on my GPT-2-style models is a bit less than 11. This fits in well with the architecture; you'd expect that a model that was predicting tokens purely randomly (well, strictly, uniformly) would have a perplexity roughly equal to the vocab size; the vocab size for the GPT-2 tokeniser is 50,257, and loss is the natural logarithm of perplexity, so that implies a loss of roughly 10.82. But both of those smart-init models with LoRE on the output head had an initial loss about one point higher, at 11.865 and 11.817, meaning that they were significantly worse than random. What might be going on here? We'll find out later :-) Anyway, for now, that was an interesting set of results. I wanted to dig in more; in particular, would that surprising improvement of test loss for the input-only LoRE model reproduce if I tried again with a different random seed? I had two working hypotheses at this point; either A solid and simple test to try to distinguish between these was just to train another set of models, with a different random seed. If I got the same kind of results, input LoRE beating non-LoRE, that would be evidence in favour of (2) rather than (1), whereas if things changed, it would work the other way. I decided not to bother with a non-smart-init model this time, so I set things up for four new models. My training code uses a random seed of 42 by default, but allows it to be overridden in the config file, so I decided to use 123 for these, and created: Once I had the config together, it was time to train the models -- but it was hard to get available instances on Lambda. I wound up having to use my script to start things up, but after a few hours I got an alert on my phone that I had instances running, and could kick things off. The second training run with input-only LoRE came in with a test loss that once again beat the baseline, so I did the full set of runs, and here are the results: Another matrix of deltas against the baseline: So, once again, input-only LoRE improved test loss (though at -0.013, it was a smaller effect than the -0.073 the previous set of models had). And once again, the sum of the input-only and output-only LoRE deltas was close to the combined-LoRE model's -- indeed it was even closer, 0.102 being just 0.001 away from 0.101. The training time results and the higher initial training loss for models with LoRE on the output head also held up, which was good to see. So at this point it was time to wrap things up -- or was it? While I was doing the second batch of training runs, I started writing up this experiment. As I normally do , I ran a draft past various AI models, and Claude Opus 5.5 on max thinking spotted something. Andrew had written : Keep standard GPT-2 init on the embedding table, but scale the inner linear projection matrices by std = 1.0 / math.sqrt(128). I'd interpreted that "inner" as meaning that the "innermost" matrices in our low rank factors -- the ones that were closest to the Transformer layers -- should be initialised as he said, with values drawn from a normal distribution with a standard deviation of 1 / r , and a mean of zero. So that implied the second matrix in the embeddings, and the first one in the output head: Claude felt that it should be the second low-rank factor in both cases -- that is, we should change It made a very good case for doing things that way, and digging into that is useful because it changes this "smart" initialisation from being a blindly-implemented "secret sauce" into something actually meaningful. Let's imagine that we're not using LoRE, and consider the output head only. It is a PyTorch , which means that: ...the values are initialized from 𝒰 ( − k , k ) , where k = 1 in_features So, we have a matrix, which we can call W , that is full of numbers drawn from that probability distribution -- a uniform (flat) distribution within the range from − k to k . For LoRE, we're replacing W with two separate matrices, which we'll call A and B : Let's say that W is shaped m × n , which means that A is m × r , and B is r × n . We want the combined effect of those two matrices W ′ = A B to start off with random numbers that look like -- in terms of their randomness -- the full matrix W . But what do the numbers actually look like in our "virtual" matrix W ′ with the code above? On the face of it, it sounds unlikely to be anything like the probability distribution for W . After all, W was initialised based on a formula that used in_features . You can see that A will have been initialised using the same distribution, as it has the same number of input features, but the number of input features for B is r , so its random numbers will have been based on that instead. Their combination into W ′ will presumably blend something from each of those two different probability distributions. We can actually work out what that will be. Maths incoming: click here to skip . Per Wikipedia , the variance of the product of two independent random scalar variables (so, not matrices, but we'll come back to that) X and Y is this: ...where σ X 2 is X 's variance, μ X is its mean, and likewise for Y . Now, let's say that the mean is zero for both X and Y (like it is for PyTorch's distribution for the initial weights, 𝒰 ( − k , k ) ). That simplifies the above: How does that apply to our matrices? Well, let's write out how we calculate W ′ by multiplying A and B , assuming that m = 3 , n = 4 , and r = 2 : By normal matrix maths, the value at position 0 , 0 in the result will be the dot product of the zeroth row in the first matrix (taken as a vector) and the zeroth column in the second: We can rewrite that for an arbitrary low rank bottleneck r like this: ...and even more generally, we can define all of the items in W ′ like this: So, every element is the sum of r products, where each product multiplies a number from whatever distribution was used to initialise A 's elements by a number from whatever was used for B 's. Using the notation from above, A 's elements have variance σ A 2 and B 's have variance σ B 2 . So that means that the variance of each component of that sum is σ A 2 · σ B 2 . There are r of them; if you add together independent variables, their variances add, so that means that the variance of the elements of our resulting W ′ is: Now, let's remember that our matrix A actually already was initialised in the way we would have liked our non-LoRE version W to have been done. The distribution of the random initial weights for an is dependent entirely on the number of input features -- and of course A has the same number of input features as W . So how can we make Var ( W ′ ) be σ A 2 ? We need to make σ B 2 = 1 / r , and then the equation above drops out like this: Now, let's look at the code (with Claude's correction in there): We're setting the weights on our B matrix to be drawn from a distribution with a standard deviation of 1 / r . SD is, of course, the square root of the variance (which is why we've been using things like σ B 2 with that squaring in there to represent variance -- σ on its own is used for standard deviation). So the variance of the matrix after we've called that on it will be 1 / r as desired, and W ′ will have random initial weights with the same variance as W would have done if we'd created it directly. That's rather satisfying :-) So that's the output head. How about the input embeddings? They're s, which are initialised somewhat differently: initialized from 𝒩 ( 0 , 1 ) That's a normal distribution, with a mean of 0 and a variance (and thus also a standard deviation) of 1. The maths above applied to any distribution with a mean of zero -- so the smart initialisation should work for those too. Our A matrix would have the correct variance, 1, and a mean of zero, and therefore so would A B , given that (for embeddings) B already had the smart initialisation. So does that mean that our W ′ "virtual" matrix created by multiplying A and B is identical in terms of randomness to the matrix W that it's replacing? Well, not quite. Remember that the output head had numbers drawn from a uniform distribution, and the embeddings from a normal distribution. We're multiplying the A matrices that are the "input" low rank factor matrices by B s that are initialised from a normal distribution. The variance of the A matrices is maintained by the smart initialisation, as we've shown, and the mean (being zero) is going to come through cleanly too. But the shape will change. I did a bit of digging around and it started to feel like a bit of a rabbit hole, but the one thing I was able to be certain of is that a variable drawn from a uniform distribution multiplied by another from a normal distribution gives a result that is neither uniform nor normal, and that normal times normal gives something called a normal product distribution (or also a product-normal distribution), which is also neither uniform nor normal. Remember that each element of our W ′ matrix is defined by this: So each one is a sum of r numbers, each of which is drawn from a product normal distribution (for embeddings) or from whatever the uniform × normal distribution is called. What shape would that be? Again: rabbit hole, but the central limit theorem states ( per Wolfram MathWorld ): the normalized sum of independent random variates with finite variances approaches a normal distribution So, for large r , we can say that the result is going to be close to a normal distribution. 4 What the smart initialisation does is make sure that our "virtual" weights, W ′ = A B , have the same mean and variance as the ones we're trying to approximate, W . But it won't keep the same shape for the distribution that they're drawn from -- certainly not for the output head, which goes from uniform to normal-ish. The embeddings are less impacted (though not completely untouched) because they were normal and are still almost so. Still, it's a neat trick! Was it what Andrew had actually meant? Having convinced myself that the smart initialisation code I'd been using was wrong, I decided to check with Andrew. My first thought was that perhaps he was using weight tying -- remember, with weight tying, the embedding matrix is just re-used in transposed form for the output head. That would mean that the embeddings would be W ′ = A B , and you'd use the smart initialisation on B , but the output head -- being that transpose -- would use W ′ T = B T A T for the same B and A , so the smart initialisation would be the other way around. But as it turned out, it was just a miscommunication . He said that he keeps his weights untied, and had been using the word "inner" in "inner linear projection matrices" to mean something different to what I'd interpreted it as. He confirmed that the real smart init code should always be applied to the second matrix, as the maths (and Claude) suggested. So that was a simple change. You can see the diff here , but in short: Now, the good news (both for my own sanity and my wallet) was that testing this fix was not going to require another four training runs. My training code is essentially deterministic -- no dropout or any other randomness is used after the model is created 5 . Because the smart initialisation code was at the end of the model creation, and smart initialisation of the output head was right at the end, that meant that changing it would not affect any other code that used randomness. So if I did two training runs -- with LoRE switched on for the output head only, and with it switched on for both embeddings and output -- then those would be "compatible" with earlier training runs that had no LoRE at all, and that only had it on the embeddings. You can see the configs here (with no explicit random seed, so they'd use my default of 42): Here are the results; I've put them into a table with their counterparts from the original seed-42 training run: Before we look at the test loss, check out that "Initial training loss" column. The weirdness that I'd noticed earlier, where that value was much worse than uniform, is gone! That actually makes some kind of sense. With the smart initialisation on the wrong part of the low-rank output head, our variance was completely wrong, and so the model was initially producing particularly bad results. 6 So that was promising! But the test loss results were even more interesting. In our original seed-42 results with the incorrect init, the output-head-LoRE-only model got a test loss of 3.737128, but here we got 3.785209. It was actually worse with the corrected smart initialisation! And that carried through to the result for models with LoRE on both input and output; the original model with the incorrect init got 3.671585, and here we got 3.741209 -- an even bigger worsening. Combining those two together, an image I like in terms of the loss landscape is that previously we were starting on a mountain that was near a deep valley -- we started with high loss, but there was a low-loss place nearby. But with the smart initialisation fixed, we were now starting on a hill with a less-deep valley nearby. So what happens if we do one of our test loss delta tables showing how each model performed against the baseline? If we add up the effect of the input-only test loss delta of -0.073 and the output-only delta of 0.186, we get 0.113 -- quite different to the delta of 0.142 that we actually got with them both. That nice "additive" property that we had, where the delta from input LoRE only plus the delta from output LoRE only summed to almost exactly the same as the delta for LoRE on both ends, appears to have gone away :-( Still, an interesting set of results! Let's bring everything together. Here are all of the training runs together in one table. I've removed the columns for the cost, the end training loss, and the training time to keep things manageable, and I've sorted it by the test loss. A few things stand out: So what does that all tell us? The numbers above suggest three things -- with important caveats below: Even more caveated, we might say: Neither the larger of the two apparent improvements from input-only LoRE nor the worsening from output-only were small -- when I was trying various interventions into my GPT-2 models (like removing dropout, scheduling the learning rate, gradient clipping and so on), many of them had a similar or smaller level of effect. Similarly, training a comparable non-LoRE model on double the number of Chinchilla-optimal tokens (ie. on 40 per parameter rather than 20) improved loss by 0.09, while investing the same compute resources to do a Chinchilla-optimal training run on a larger model improved it by roughly 0.13 over the baseline (see this post for the raw numbers). So, is input-only LoRE One Weird Trick To Improve Your Test Loss? Of course not. Are output-only LoRE and both-ends LoRE Bad Things? Not necessarily. The first thing to remember is that this is a really limited set of experiments. Although I wound up spending about US$450 on training runs (I really should be investing in building my machine up as a better training box to save on Lambda costs), I was only able to test: Even in order to say anything definitive about whether LoRE works for this specific architecture, it would be necessary to do tests over a much larger number of random seeds and different lengths (in terms of number of tokens) of training run: To get solid results on whether smart initialisation is a good thing, you'd need to try a number of seeds to see whether the poor performance of the "correct" smart initialisation against the "incorrect" one worked out as a general result. And if it did, why might that be? Beyond that: the apparent improvement in the loss from input-only LoRE is something that came up in these experiments. Maybe it would turn out to be a real thing, maybe not, but the real benefit that Andrew mentioned in his original message was about something quite different: LoRE saves a lot of parameters and only (as he said later) costs a small amount of loss. If you reinvest those saved parameters in a deeper network with more layers, you can hopefully gain back the loss that you, um, lost, and then some -- and wind up with a more capable model. So important further tests (which I think I will do, but later on) would be: It's worth noting that in my Chinchilla experiments, I scaled up the model by 2 . That's about a 41% increase, which is quite close to the 39% we save by using LoRE on both ends. So if the loss improvements carried over, perhaps it might work? On the other hand, those models were also trained on 41% more tokens, so... So there's a huge number of further experiments that would be needed to build out these results -- and that's even before we move beyond this specific architecture. Andrew reports solid results with his setup, and his model Maba does appear to be quite different. But would it extend to a Qwen-style one? Or Mistral? Next, there's what happens with scaling. LoRE is valuable in small models because embeddings and the output head are such a large proportion of the total parameter count. My guess is that it would become increasingly unhelpful as models got larger. To take an extreme case, looking at the Kimi K3 paper , it has a vocab size of 160k, and its hidden dimension (which I'll assume is what the embeddings are using) is 7,168. That means that the embedding layer uses something like 1,146,880,000 parameters, as does the output head, for a total of ~2.3B parameters (assuming they're not using weight tying, which seems likely for a monster like this). It has 104.2B active parameters per token, so if it were to use LoRE you'd save something of the order of 2% of the active parameter count. But for purposes of "reinvesting" in further layers, the real comparison point is the total number of parameters, not the active ones, and with about 2.8T total, the number used for embeddings is less than 0.1%. It doesn't sound like it would help much. Unless, of course, the benefit of the input-only LoRE loss improvement actually turned out to be a real thing, and didn't weaken as the model was scaled... But anyway, that's speculation. I think a good set of experiments for that would be to try the same training runs as before, but for larger models. Maybe just trying GPT-2 medium and large sizes would be a good start? Lots of work. If I had access to Google's resources then I think I'd be kicking off a bunch of training runs. OTOH if I had access to Google's resources I probably wouldn't be blogging about this... I think that there's one thing I'm going to take away from this. LoRE is an interesting intervention -- and I think is well worth trying out on any new model where vocab matrices are a significant fraction of the parameters, just to see if it helps. I'll be doing that in future. And if anyone else out there wants to try LoRE-style training runs, I'd love to hear from you. Any results, positive or negative, would be really interesting. Thanks for reading! And many, many thanks to Andrew for suggesting this as an idea to play around with -- it's been fun :-) This is often written Z = X W T to reflect the fact that weights are generally kept in d out × d in format, at least in PyTorch, but that's really an implementation detail .  ↩ I'll write more about LoRA in the future -- I've been playing with it a bit -- but essentially, what you do is: For each matrix that you want to train (which might not be all of them -- for fine-tuning, people often only train a subset), you add on a low-rank adapter , which is a pair of matrices like our A and B above, and which sits "beside" the existing one, like this: Gradient tracking is enabled on the matrices in the low-rank adapter, and its results are added to the results of the original weights. The idea is that the adapter pair learns to create a "diff" to the results of the original weights, which allows a certain amount of training of that part of your model. And it works surprisingly well. Again: I'll write about this in more depth in the future.  ↩ When I was discussing this with an LLM -- sadly I've forgotten which -- the softmax bottleneck came up here, as it did in later correspondence with Andrew. That's a reference to this paper . I'll need to work through it carefully at some later point, but the relevant point appears to be that the rank (which we can loosely read as how many independent ways the predictions can vary from one context to another) of the values coming out of softmax is limited by the rank of what is going into it. If we force our (in my GPT-2 small case) 768-dimensional embeddings through a 128-dimensional bottleneck, we're reducing their inherent rank, and it's that reduced rank that will drive softmax's, so the results will be worse than what you'd get with the original 768. Seems logical enough. Linkrot-proof reference: Yang, Z., Dai, Z., Salakhutdinov, R., & Cohen, W. W. (2018). Breaking the softmax bottleneck: A high-rank RNN language model. In International Conference on Learning Representations (ICLR) .  ↩ Thanks to Claude Opus 5.5 for pointing me at the central limit theorem. I definitely need to brush up on my statistics, and I'm sure I knew about it at some point back in the 90s...  ↩ Modulo CUDA jitter of various flavours -- though even then, a while back I did two training runs on 8x A100 machines with the same config, months apart, and the Safetensors files were identical bit-for-bit. So I think the effect of that in my setup is minuscule.  ↩ The temptation to dig into the details of why, and exactly what the mistaken smart initialisation did to the variance of the "virtual" weights W ′ , is strong. But I will resist.  ↩ When I ran a draft of this post past my editorial board of LLMs , Grok made the point that perhaps the apparent benefit of having LoRE only on the input embeddings might have been due to that model seeing significantly more tokens per parameter. It's an interesting thought! If we do the maths, the input-only and output-only LoRE models were trained on ~25 tokens per parameter and the both-ends ones were trained on about 33, vs 20 for the non-LoRE ones. However, in my Chinchilla experiments, I only got 0.09 improvement in loss by training on 40 tokens per parameter rather than 20. Now, I'm sure that gain mostly happened early in the run -- overtraining is subject to diminishing returns -- but the result of 0.073 for input-only LoRE would imply that almost all of it happened in the first extra 5 tokens per parameter, which seems dubious. Those are different training runs with different seeds, and were using my JAX code, which has a better baseline loss than my PyTorch code, so we can't compare them directly. But it feels unlikely that the effect would be drastically different. Also, if we use reductio ad absurdum on Grok's logic, it does sound a bit weak -- if we get rid of parameters but train on the same number of tokens, the higher tokens per parameter will not in general counteract the damage from the smaller model. Imagine, for example, removing 32M parameters worth of Transformer layers and training on the Chinchilla-optimal tokens for the size of the model before you did that. I'm not going to run the experiment, but I'd be... somewhat more than surprised if it improved the model.  ↩ A baseline. As I said earlier, the LoRE stuff would have an effect on the randomness used for the initial weights, so I wanted to train one model with LoRE "enabled" but not actually used. Config: , . A model trained with LoRE enabled on both the input embeddings and the output head, but without the smart initialisation of the low-rank matrices. This was simply to confirm to myself that it was required. Config: , . A model with LoRE on both ends, with the smart initialisation -- essentially, what Andrew had described. Config: , . A model with LoRE on the input embeddings only, using smart initialisation if that had turned out to be necessary, and not using it if it had not. I deliberately made the config invalid, using instead of or for the flag, so that I'd be reminded to fill those in later. Config: , . A model with LoRE on the output head only, with smart initialisation the same as with (4). Config: , . What we were seeing was due to luck of the draw with the random initialisation of the weights -- good luck with LoRE, bad luck without. There really was an effect here: input-only LoRE actually improved test loss in the resulting model. A new baseline with no LoRE. Config: , . An input-only LoRE. I wanted to run this one as the first non-baseline one, because if it got worse (higher) loss than the baseline, then it would look rather like the effect I'd previously seen was due to random weight initialisation, so I might want to consider stopping there. Config: , . If I decided to continue, the next one would be LoRE on both the input and output sides. Config: , . Finally, I'd do an output head LoRE only one. Config: , . I changed the config parameter so that instead of just being or , it could be , for the original incorrect interpretation, or for the fixed one. I updated all of the existing training configurations that had set to to be . I added on yet more config for some more training runs to test this, setting to the new option. Both: , . Output-only: , . The input-only LoRE models lead the pack -- though it's a close-run thing, and the gap between and could well be within the noise at 0.013. In that post, I trained three models with different weight initialisation seeds, and got models that differed by up to ~0.017. The two models with the corrected smart initialisation are the worst -- even worse than the model with no extra initialisation, . LoRE can reduce the number of parameters in these models dramatically even if only applied to the input embeddings, and that comes with potentially an improvement in test loss (0.073 in one test, 0.013 in a second). When applied to the output head, it has the same size of reduction in the parameter count, and causes a worsening in test loss (over three tests, 0.115, 0.138 and 0.186). The results with LoRE on both ends seemed to come in somewhere around (in two of the three cases, very close to) the sum of the input-only and output-only deltas, though it doesn't seem to be a strictly additive relationship. The mathematically cleaner implementation of what I've been calling "smart initialisation" actually worked worse than the mistaken version I used originally, though this was only tested with one seed, so the evidence is much weaker. Against my own GPT-2 style model (important differences from the original: no weight tying or bias on the QKV weights, no dropout, and PyTorch default weight initialisation). With two seeds. With two options for smart initialisation, and one of them only with one seed. Over a number of tokens chosen to be Chinchilla-optimal for the baseline non-LoRE model. 7 Maybe the effect was just limited to the seeds I tested? Two is better than one, but three would be better still, four even better, and so on. Maybe the effect disappears in longer training runs? Perhaps LoRE causes some kind of loss in capacity that stops models from improving past some point. Intuitively that could make sense -- if you imagine training as being in some sense compressing information from the training data into the parameters, having fewer parameters is clearly going to limit your capacity in some sense. Isoparameter tests: Input-only LoRE saves 32M parameters, so what happens if you "spend" that on extra layers? Or alternatively make the model wider (in terms of its embedding dimension)? Or some combination of both? Output-only LoRE likewise. And, of course, both input and output. Isocompute tests. Notably, input-only LoRE doesn't save you much compute, as I mentioned earlier -- embedding layers are cheap in terms of compute, if not in terms of parameters. So here perhaps it would be more interesting to see what happens if you did output-only LoRE, but trained the model on more data so that the total training compute budget matched. Would you gain back the lost loss? This is often written Z = X W T to reflect the fact that weights are generally kept in d out × d in format, at least in PyTorch, but that's really an implementation detail .  ↩ I'll write more about LoRA in the future -- I've been playing with it a bit -- but essentially, what you do is: Freeze all of the matrices that make up your parameters (eg. in PyTorch, set to ). For each matrix that you want to train (which might not be all of them -- for fine-tuning, people often only train a subset), you add on a low-rank adapter , which is a pair of matrices like our A and B above, and which sits "beside" the existing one, like this: Gradient tracking is enabled on the matrices in the low-rank adapter, and its results are added to the results of the original weights. When I was discussing this with an LLM -- sadly I've forgotten which -- the softmax bottleneck came up here, as it did in later correspondence with Andrew. That's a reference to this paper . I'll need to work through it carefully at some later point, but the relevant point appears to be that the rank (which we can loosely read as how many independent ways the predictions can vary from one context to another) of the values coming out of softmax is limited by the rank of what is going into it. If we force our (in my GPT-2 small case) 768-dimensional embeddings through a 128-dimensional bottleneck, we're reducing their inherent rank, and it's that reduced rank that will drive softmax's, so the results will be worse than what you'd get with the original 768. Seems logical enough. Linkrot-proof reference: Yang, Z., Dai, Z., Salakhutdinov, R., & Cohen, W. W. (2018). Breaking the softmax bottleneck: A high-rank RNN language model. In International Conference on Learning Representations (ICLR) .  ↩ Thanks to Claude Opus 5.5 for pointing me at the central limit theorem. I definitely need to brush up on my statistics, and I'm sure I knew about it at some point back in the 90s...  ↩ Modulo CUDA jitter of various flavours -- though even then, a while back I did two training runs on 8x A100 machines with the same config, months apart, and the Safetensors files were identical bit-for-bit. So I think the effect of that in my setup is minuscule.  ↩ The temptation to dig into the details of why, and exactly what the mistaken smart initialisation did to the variance of the "virtual" weights W ′ , is strong. But I will resist.  ↩ When I ran a draft of this post past my editorial board of LLMs , Grok made the point that perhaps the apparent benefit of having LoRE only on the input embeddings might have been due to that model seeing significantly more tokens per parameter. It's an interesting thought! If we do the maths, the input-only and output-only LoRE models were trained on ~25 tokens per parameter and the both-ends ones were trained on about 33, vs 20 for the non-LoRE ones. However, in my Chinchilla experiments, I only got 0.09 improvement in loss by training on 40 tokens per parameter rather than 20. Now, I'm sure that gain mostly happened early in the run -- overtraining is subject to diminishing returns -- but the result of 0.073 for input-only LoRE would imply that almost all of it happened in the first extra 5 tokens per parameter, which seems dubious. Those are different training runs with different seeds, and were using my JAX code, which has a better baseline loss than my PyTorch code, so we can't compare them directly. But it feels unlikely that the effect would be drastically different. Also, if we use reductio ad absurdum on Grok's logic, it does sound a bit weak -- if we get rid of parameters but train on the same number of tokens, the higher tokens per parameter will not in general counteract the damage from the smaller model. Imagine, for example, removing 32M parameters worth of Transformer layers and training on the Chinchilla-optimal tokens for the size of the model before you did that. I'm not going to run the experiment, but I'd be... somewhat more than surprised if it improved the model.  ↩

0 views

Premium: The Hater's Guide To Junk

Ladies and gentlemen, we are in a glorious, splendiferous, outrageous era of crap!  Some call it “high-yield,” some call it “speculative-grade,” but to those of us living in the real world, we know them as junk bonds, the formerly-sleepy world of below-investment grade debt that’s exploded in the last six years.  Since the beginning of 2011, junk bond issuance has increased by seventy-five times , from a mere $3 billion issued against $60.8 billion in investment-grade bonds in the first half of 2011 (around 5%) to an astonishing $229 billion of junk against the $805.9 billion in the first half of 2026 — making the high-yield market about 28.4% the size of the investment-grade market. The high-yield market has ballooned over the last few years thanks to the vagaries of the global economy and, in recent years, the arrival of an entirely new category of crap.  And “junk,” in the case of today’s newsletter, is going to extend so much further than simply “high-yield,” and into the murky world of leveraged loans that have been used to fund dodgy software deals and an increasing amount of data center debt deals. So why should you give a shit about junk?  As I covered in this week’s free newsletter , Anthropic and OpenAI are two of the single-worst businesses in the history of capitalism, with negative EBITDA cashflows and massive customer concentrations, making them likely to be rated toward the lowest tiers of junk — CCC, or the equivalent rating in Moody’s system, Caa: While you probably know this, “default” refers to when a company has missed a payment on its debt and the credit agencies don’t believe that it will ever make that payment.  The AI labs will require $50 billion to $100 billion of annual issuance across the worlds of high-yield, private credit, leveraged loans (high-interest loans with floating rates issued to companies with a ton of existing debt), and revolving credit lines that, according to a source in fixed-income, are the kind of thing you only use as a last resort. They’d also be the first parts of the AI industry to truly touch the world of junk other than the disgraceful AI neoclouds, companies that raise oodles of debt with the vague promise of building AI data centers some time in an indeterminate future, but serve the more immediate benefit of pumping NVIDIA’s revenue numbers by buying GPUs years before they’ll ever dance with the power grid.  Regardless of how shitty it is, this industry needs hundreds of billions of dollars to fund the theoretical buildout of these potentially-possible data centers — CoreWeave alone, per UBS, needs to raise $102 billion in debt through 2030 — at a rate with no historical comparison. Even the railroad bubble of the 1800s had, based on rough calculations of inflation, only around $250 billion in total investment compared to the $400 billion in issuance expected from hyperscalers alone in 2027 . Yet the vast majority of companies building AI data centers range from low credit to no credit, with whatever creditworthiness they may have based near-entirely on their proximity to either NVIDIA or one of the hyperscalers that is invariably going to be their customer. The problem, you see, isn’t really the AI of it all, but the uniquely stinky business of being in AI. Even under the best possible circumstances, an AI data center will need billions of dollars up front, years in advance of any potential return on investment, buoyed only by the hype around the potential payoff at the other end.  All of this exists in a time in history when more and more companies are demanding more and more debt from the markets — including the large, wet son of Larry Ellison and his $52 billion bond/loan sale to fund their takeover of Warner Brothers Discovery , $12.4 billion of which was funded with junk due to the sheer scale of the buyout, with Paramount/Skydance/Whatever paying as much as 9.1% on the 10-year end of the deal.  And as I’ve discussed in the past , Larry Ellison’s Oracle now sits on the very last rung of investment-grade debt , with any further downgrades dropping it into the junk markets and leading to investment and pension funds legally having to dump its debt en masse, which would be a big problem considering the $30 billion or more of personal loans Ellison has collateralized using Oracle stock will face brutal margin calls in the event the stock dumps…which it will absolutely do in the event of a downgrade. Its last downgrade came from the most obvious of places, per S&P Global: Today’s exploration of junk is a culmination of multiple different threads of premium research, ranging from the AI data center bubble to the SaaSpocalypse , with massive amounts of leverage powering some of the worst deals in history, creating a setup for private credit and other investors to lose billions of dollars as a result. Welcome to the Hater’s Guide To Junk, or Enter The Crapverse.

0 views

A new feature for my blog, built using my voice

I shipped a new feature for my blog today: the Newsletters page, which offers an index of all of the newsletters I've sent out, both my free weekly Substack and my monthly sponsors-only updates. I built the feature almost entirely using my voice, chatting away to my laptop while I cooked dinner. I used the ChatGPT desktop app for this, in the Codex tab, using the voice conversation mode, running against a local development environment. Here's what that looks like: I started the session against my local simonwillisonblog checkout by typing: This gave me a preview of the site that it would be working on, and meant that I could later ask it to show me the new pages so I could visually track its progress. Then I clicked the "Start new voice chat" button - that's not the microphone button, it's the one to the right of it - and set my laptop up in the kitchen so I could talk to it while I cooked. I had a pretty good idea of what I wanted to build, and it's a simple enough Django feature that I was certain the model (in this case GPT-6 Astra High) would be able to do it. A new model, a migration, some view code, templates, and a couple of import functions to populate the database from external sources. Here's an extract of my voice transcript that was captured by Codex: Um, they do not. Um, this is going to be a new type of content. Um, it's not going to show up... Oh, hold on. Yeah, no- I do not want this to show up in my, um, tag pages and date archive pages and... Actually, no, I think... I don't want it on the tag pages. I don't want it on the, um, blog index page. But I think I do want it to show up on the date-based pages. You know, if you navigate to September the 19th, and I sent a newsletter on that page, I think I want that to show up. So... this is- so I think we probably need a new model. The other thing is that I want them searchable, uh the Substack ones are not searchable, because those are actually just copies of other s- on content on my blog. These monthly ones do contain unique content, and spe- and once they're... published, like once they're made public a month after they've gone out, I want them to show up on my search results. Apparently this was clear enough that the model knew what I wanted to build! You can read the full transcript, disfluencies and all, in this Gist . We went on like this for about half an hour (the time it took to cook dinner). The model would reply and occasionally ask clarifying questions, then get to work modifying the code. We got a surprisingly long way entirely by voice: It was almost ready to ship. The catch was the imports: Astra offered to export data from my local copy so I could import that into production, but I wanted it to work like my other import scripts. Since some of the data lived in a private GitHub repository, this would involve creating a new API key, and for that I knew I'd have to sit at the keyboard for a while. Once I had finished cooking and judged it mostly feature-complete, I had Codex create a branch and open a pull request. I reviewed the code in the GitHub PR interface. It was nearly what I needed, except it had chosen to use Git in a subprocess for one of the import scripts. I needed one of the imports to pull from a private Git repository, so I figured the API would be a better bet. I switched to typing and had Codex swap that out for an API-based import instead. You can see the changes I made during the review in the extra commits on the PR . I fixed the import mechanism and made a few tweaks to the display of those public pages. It took an additional half hour of typing-based prompting to get to the point where I was happy to deploy it to production by landing the PR. You can see the end result at the new newsletters index page , or view the page for a previous monthly newsletter . The index page shows my most recent Substack weekly newsletters and GitHub sponsors monthly newsletters mixed together in reverse chronological order. Further down the page are links to my by-year archive pages. GPT-6 Astra designed the page, and then tweaked that design based on my vocal feedback from glancing at the local preview across the kitchen. OpenAI love using voice-driven demos like this one for things like DevDay - and they do work well in that environment. I don't think this is going to be a daily driver for me though. I've written before about how much "work" I get done using ChatGPT voice mode on my phone while walking the dog - mostly research and brainstorming, but occasionally actual development work by having ChatGPT write and test out snippets of code. This feels different. The addition of the visual preview, plus being able to type or paste things in via the keyboard when I need to communicate something that doesn't work vocally, makes this a much more powerful way of interacting with a coding agent. I still switch back to typing once I get down to the details of things though. Being able to paste in examples and error messages, or directly highlight the code or feature that needs changing, remains more efficient than trying to describe it in words. I mainly work from home, which is good because there's no way I'd want to talk to my computer like this in a shared workspace! The killer feature for me is the ability to multi-task. I usually cook with a podcast or TikTok running; now I can actually build stuff instead. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options . A new model and migration to represent imported newsletters in Django, plus Django Admin configuration for that Four working imports: The most recent Substack items via RSS Every other Substack item via their undocumented API, which GPT-6 Astra knew about (it tried directly) and then ran a search to figure out how to paginate it and found this article by Karen Spinner All of my published monthly newsletters from my simonw/monthly-newsletter-archive GitHub repository My most recent private sponsors-only newsletter from a private repository The /newsletters/ and /newsletters/2026/ public archive pages Newsletters showing up on day and month archive pages too, but not on tag pages or my homepage Weekly Substack newsletters link to Substack; archived monthly newsletters have their own pages Integration with my site search engine

0 views
Unsung Yesterday

Deeper dive: Keyboard differences between Windows and Macs

Over the years, I learned about many gotchas and strange differences between keyboard handling on Mac and Windows – pertinent especially to web apps, which use the same codebase to cater to both. I thought it might be helpful to someone if I compile them all in one place. This post is meant to be a reference, and there aren’t any cute or riveting stories hiding inside – if this seems boring to you, feel free to skip to the next one! What Windows calls Backspace, Mac calls Delete. What Windows calls Delete, Macs call Forward Delete. (This means saying “Delete” without specifying the platform might actually be confusing.) What Windows calls ⏎ Enter, Mac calls ⏎ Return. However, Macs also have an ⌤ Enter, although only on a numeric keypad (previously, it was even there on laptops !). On keyboards without the physical Enter key, you can simulate it via Fn+Return. In most Mac software, Enter and Return do the same thing. There are a few exceptions – for example, Photoshop enters a new line on Return but commits on Enter, and some classic pro apps like Cubase or Pro Tools do different things, too – but it seems to be a dying tradition. (If you are curious about the history of Return and Enter, I wrote about it once .) Windows customarily calls the secondary key Numpad Enter. I don’t believe Fn+Enter works to simulate it. Generally, third-party keyboards use Windows verbiage – so, Backspace and Enter. If a keyboard offers Mac conventions at all, they usually only extend to modifier keys. ⌃ Control on Windows is the equivalent to ⌘ Command on a Mac. (Paste, for example, is ⌃V on Windows and ⌘V on a Mac.) But, confusingly, Apple devices still have a Control key, too. This was originally meant for terminal applications, but these days many GUI apps use it as an extra modifier key. This means that for apps, Windows devices offer Ctrl, Alt, and Shift – and Macs offer ⌘ Command, ⌥ Option, ⌃ Control, and ⇧ Shift. That’s one more modifier key, and thus more space to breathe. (We’re not counting the Windows key or the 🌐 Globe/Fn key since those are technically reserved by the operating system.) Some keyboards offer extra key caps you can swap to match the platform, for example: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/1.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/1.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/2.1600w.avif" type="image/avif"> Others cover all the bases on fixed key caps, in an awkward way: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/3.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/4.1600w.avif" type="image/avif"> Many stick with Windows-only legends. Apple devices rely more on symbols on their keys and in their menus, although they don’t do so consistently. Here are all of them: Actually, I lied about the last four. On modern Apple keyboards, they are: Reusing the regular arrows for Page Up and Down is one of a few perplexing decisions from Apple’s keyboard designers. The arrow key symbols look like this – ◀▶▲▼ – but only on Apple keyboards. Here is an older and a newer Apple keyboard showing what happened: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/5.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/6.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/6.1600w.avif" type="image/avif"> (Luckily, the confusing dual arrow key situation only happens on less popular, full-size keyboards.) Historically, Apple keyboards used symbols more on non-US keyboards, but starting with the 2026 models, they unified when they show symbols and when they show symbols and legends, across all keyboards. (The only key with just a text legend is Esc, making the appearance of ⎋ in menus extra puzzling.) Windows keyboards typically use words – this is why in tight quarters you sometimes see shortenings like Ctrl, Bkspc, PrtSc, Del, PgUp, Win, and so on. (I don’t think Apple ever abbreviates their legends with the exception of Esc for Escape.) In some countries, the legends are translated – as an example, in Germany, Ctrl sometimes appears as Strg. The symbols that crossed over to Windows side are: I have not seen any other popular symbols on the Windows side of the aisle, and I am not even sure if the above would be widely understood. But here’s an example: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/7.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/7.1600w.avif" type="image/avif"> ( I wrote a bit more about Mac symbols before , and also about the rare Canadian symbols .) In menus and other places, Windows joins the key combinations/​shortcuts with a plus, but Apple just glues them together: Here is the same Chrome menu on Windows and on macOS: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/8.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/8.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/9.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/9.1600w.avif" type="image/avif"> (In other words, you’d never say “⌃V is Paste on Windows” like I did above.) Both Windows and Macs have function keys. On Windows, traditionally those were claimed by the operating system or the apps as shortcuts. Here are some examples of well-known function key assignments: On Macs, traditionally the apps didn’t reach for function keys, as those were reserved solely for the users to do stuff with. (However, some combination of ⌃ and function keys are used by the operating system for accessibility options, and I occasionally see apps use function keys these days.) Windows keyboards top off at F12, but some Mac keyboards reuse the three special PC keys, and even take over the unnecessary Num Lock/​Caps Lock/​Scroll Lock island, and end up going up to F19: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/10.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/10.1600w.avif" type="image/avif"> Access to extra characters In text fields, Apple has a system where pressing ⌥ with printing keys outputs more characters, for example ⌥Q outputs œ, and ⌥7 outputs a ¶ pilcrow. Additionally, ⇧ works in this context, so for example ⌥⇧Q outputs Œ, and ⌥⇧7 outputs a ‡ double dagger. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/11.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/11.1600w.avif" type="image/avif"> Note that not only letters, but basically all printing keys have secret ⌥ and ⌥⇧ assignments. Also, ⌥ assignments vary per location! The above was U.S. English, here’s Polish with a lot of differences: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/12.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/12.1600w.avif" type="image/avif"> This means it’s best to avoid ⌥-based shortcuts in text fields, since they might conflict with some important character. By the way, while the above two are just mock-ups, on some physical keyboards (here: British English), some of the important ⌥ invocations are actually printed on keys: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/13.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/13.1600w.avif" type="image/avif"> Windows doesn’t have the above feature. However, Windows has an alternative feature where typing Alt+numpad keys allows to enter any character by its code. (For example, Alt+0128 outputs €. Num Lock has to be enabled. I don’t think it’s possible to do it without a numeric keypad present.) Mac has a version of this feature, but it requires adding Unicode Hex Input keyboard and switching to it beforehand. Then, pressing ⌥20AC outputs €. While Mac modifier keys are completely symmetrical, Windows makes an exception for Alt. On many non-US keyboards, right Alt is also known as AltGr , and does something similar to ⌥ on a Mac: it outputs letters when pressed in combination with printing keys. For example, on Polish keyboards AltGr+A = ą, AltGr+C = ć, AltGr+Shift+A = Ą, and so on with 15 more combinations. Many other keyboards do similar things. The way it differs from ⌥ on a Mac is that these are usually reserved for core letters and punctuation necessary for each language, rather than the typographical smorgasbord that Apple provides. For legacy reasons, and also for keyboards that do not have a physical AltGr key, you can also invoke all these using Ctrl+Alt combinations instead (so, Ctrl+Alt+A = ą). This means that just as you have to be careful about ⌥-based shortcuts in text fields on a Mac, so you should be of Ctrl+Alt-based shortcuts on Windows . Just like on Macs, on some keyboards, selected AltGr options will be printed in the corners or fronts of keys: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/14.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/14.1600w.avif" type="image/avif"> Text fields Text fields in Mac OS apps and web browsers/​websites use some of the standard Unix/​Linux shortcuts based on the ⌃ Control key: You can combine many of the above with holding ⇧ to extend the selection. A small group of vocal users love these. Windows still supports Ctrl+Insert for copy and Shift+Insert for paste. I am not sure if those are being used. On a Mac, in simple input fields, ↑ and ↓ jumps to the beginning and end (same as ⌘←→). This often conflicts with command line history, autocomplete pop-ups, and so on. Windows doesn’t have this convention, and Home and End serve this purpose instead. Macs support the iPhone-inspired convention of holding a key to show related accented characters, instead of triggering auto-repeat. Windows doesn’t have this feature. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/15.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/15.1600w.avif" type="image/avif"> Differing shortcut conventions worth knowing about On a Mac, redo is typically ⌘⇧Z. On Windows, it’s typically Ctrl+Y. On a Mac, refresh (in browsers, etc.) is typically ⌘R. On Windows, it’s typically F5, although some browsers now support Ctrl+R, too, presumably since function keys are harder to access than they used to be. Windows’s eponymous ⊞ Windows key is reserved solely for the use of the operating system, and so is Mac’s 🌐 Fn/​Globe key. (Although I am not 100% sure of that as I found one place you can create 🌐 shortcuts as a user – I’m just not certain if it’s intentional.) However, each platform also has a lot of shortcut combinations that are effectively unavailable, for example ⌘⇥ and ⌘M on a Mac, or Alt+Tab and Ctrl+Shift+Escape on Windows. These official lists can help: If you’re a web app, you will compete for shortcuts with the operating system (like any app would), but also with the browser itself. You can typically take over shortcuts like ⌘S or ⌘P, but depending on the browser or the platform, some might be sacred and unavailable. No Mac browser will allow a website to claim ⌘Q, ⌘W, or ⌘T. Safari won’t allow a web app to override ⌘R. Arc browser won’t allow to override ⌘⇧C. (This is not a complete list.) Tabbing and Shift tabbing is customarily used to jump between UI elements, but it behaves slightly differently in native apps. On Windows, tabbing jumps through all elements: On Mac, Tab jumps only to elements that require keyboard to operate (such as input fields). You can toggle a System Settings preference and ask macOS to mimic Windows behaviour – see the last toggle below – but it’s not supported well everywhere. = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/18.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/18.1600w.avif" type="image/avif"> PgUp and PgDn work differently: Similarly, Home and End work differently: On both platforms, Fn+↑↓ does PgUp/​PgDn, and Fn+←→ does Home/End. (Do you see how confusing it is to say that given that ↑↓ already mean PgUp/Dn on a Mac?) Windows has a tradition of enabling access to menus and visible controls by pressing Alt and letter keys. This has evolved over time and is not as consistent as it used to be, but it might be worth knowing about. On a 100-plus-key keyboard, Windows might have: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/19.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/19.1600w.avif" type="image/avif"> Macs have: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/20.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/20.1600w.avif" type="image/avif"> Macs never had arrow keys on a numeric keypad nor a Num Lock key to enable it. (I’m breaking my promise! Here’s a fun bug story about Num Lock .) In a strange twist of fate, it’s not only third-party keyboards that favor Windows legends. Some of Apple’s keyboards in between 1980s and 2000s showed PC legends, too! (This was to court PC compatibility of then-beleaguered Macs.) Here’s the classic Apple Extended Keyboard, showing legends for Insert, Delete (naming it Del to avoid confusion with its own), Print Screen, Scroll Lock, Pause, Num Lock, and Alt: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/21.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/deeper-dive-keyboard-differences-between-windows-and-macs/21.1600w.avif" type="image/avif"> (Did I miss anything or make a mistake? Let me know !) ⎋ Esc (not printed on keyboards, for some reason) ⌦ Forward Delete ≣ Contextual Menu (only on full-size keyboards) ⇞ Page Up (only on full-size keyboards) ⇟ Page Down (only on full-size keyboards) ↖ Home (only on full-size keyboards) ↘ End (only on full-size keyboards) ↓ Page Down ⇧ for Shift ⇪ or 🔒 or a similar symbol for Caps Lock (on non-US keyboards) ⏎ for Enter – note the same arrow as Mac’s ⏎ Return ← for Backspace – note a different arrow than Mac’s ⌫ Delete ⇥ for Tab (you can also sometimes see ⭾, which is a symbol representing Tab and Reverse Tab together; Reverse Tab used to be a separate key on some 1970s terminals) ⊞ for Windows key – the style of the logo will be different depending on the age of the keyboard, roughly matching the Windows logo at the time; some keyboards also use a more abstract shape, or call the key Start or System instead of Windows ≣ for contextual menu key (a.k.a. Application Key) – just as Apple added that key on its large keyboards, Microsoft started removing it in favor of emoji key , Office key , and then Copilot key ⌃ for Control (especially in nerdy contexts) Windows: Shift+A, Ctrl+Shift+G, Ctrl+F11, Alt+Enter Mac: ⇧A, ⌃⇧G, ⌘F11, ⌥⏎ Alt+F4 – close the app F11 – maximize window F12 – dev tools in browsers ⌃A – move to the beginning of the line ⌃E – move to the end of the line ⌃F – move to the right, or forwards (hold ⌥ to jump through words) ⌃B – move to the left, or backwards (hold ⌥ to jump through words) ⌃N – move down or to the next command ⌃P – move up or to the previous command ⌃L – scroll the window so that the text cursor is in the vertical middle ⌃H – delete (backspace) ⌃D – forward delete ⌃K – delete (kill) to the end of the line ⌃T – swap (transpose) the adjacent characters ⌃O – insert new line, but (contrary to Return) do not move the cursor there Mac keyboard shortcuts Keyboard shortcuts in Windows On Windows, they move the text cursor a screen up or down. On a Mac, they scroll the view up and down, but they do not move the text cursor if it’s present (so, they are more an equivalent of clicking on the scrollbar chute). On Windows, they scroll the contents (and move the cursor if present) to the top or the bottom of a view, or move the cursor to the beginning or the end of an input field. On a Mac, in views they scroll to the beginning or end without moving the cursor. In input fields, Home/End do nothing, but you can use ⌘←→ to accomplish the same. Insert – still used in some contexts for overtyping Print Screen – today taken over by screenshotting Num Lock and Scroll Lock Pause/​Break Help – rarely used, and now abandoned in favor of… ≣ Contextual Menu – shows the same menu as right click would ⌧ Clear – a deterministic backspace , these days seldomly different than the actual Delete key

0 views
Jim Nielsen Yesterday

“Getting off the Modernization Treadmill”

My notes from this talk by Alexander Petros at Big Sky DevCon 2026 . Alex starts by noting how “modernize” used to mean something along the lines of “update this thing that was made before I was born”. But now “modernize” means something more like “update this thing from 5-10 years ago” (hence the framing of the talk, the “modernization treadmill”). Using a real-world example of an incredibly slow website that was required to access state-sponsored programs for welfare, Alex points out the disparity in conditions between those of us who make software and those who have to use them: The people who develop these websites are usually doing so on high-powered internet connections and high-powered devices, but they're not using them in the conditions that the people who most need those benefits are going to be. Then he shows a Reddit thread where somebody essentially posted, “I’m having problems with this website. I’ve been waiting for months for my application to go through. Any suggestions?” And one Reddit user responded, “The best thing you can do is go into the physical office, get a case worker, and your problems will be solved within the hour.” I guess we've come full circle now. It used to be: “Don’t talk to anybody. It’s faster and more convenient to use the website!” But now it’s: “Don’t use the website. It’s faster and more convenient to talk to somebody!” Have we failed at making websites? And is our failure, at least in part, rooted in the fact that we don’t leverage the basic tools for making websites: HTML, CSS, and (in a distant third) JavaScript? Alex goes on to argue that the technologies of the web have an ideological bent and, if used as designed, can solve so many of the performance, accessibility, and usability issues that plague so many websites. The grain of the web’s technologies are rooted in these values: Which means if you use them as intended, they are optimized to deliver outcomes rooted in those same values. So if you like those values and you want those outcomes, use the platform. Take HTML, for example. Here’s Alex: HTML does [performance improvements] for you for free. If you've coded your website in a proper, semantical, structure HTML style, it will just get better over time at zero cost to the people who built that website HTML is your friend. HTML won’t give you up or let you down . HTML will make it difficult for you to make a bad website. Write it in to the requirements of the project you’re doing that it work without JavaScript. Not necessarily that it doesn’t have any JavaScript, but just that the core functionality of the website can happen without JavaScript. If you do this […] you will find that it’s very hard to deliver a bad web page because the structure that HTML requires is one that fundamentally is good for the user, performant, and cost effective. Technologies are imbued with culture, which influences what you do and how you do it. If you can align your ideological beliefs with your technological choices, you might end up with an outcome that aligns with your values — who would’ve thought, eh? A lot of modern software developers come from websites like Facebook, they come from big tech companies [who] fundamentally have a different set of priorities. Their job is to keep you on the website as long as possible so that you consume more ads. But that’s the opposite set of requirements and priorities that the government needs to be doing, which is to build something that is clean, quick, efficient, and gets you in and out as fast as possible. So [I tell people] that the technology they use comes from [an] ideological place. But there are different ideological places that produce different technological results and if we start from those through lines then we can produce services that help people who need them. The ideological principles of the web are well established : users over everything else. If you use HTML as much as possible, you’ll make something that’s as user-friendly as possible on the web. Reply via: Email · Mastodon · Bluesky User-friendly Backwards- and forwards-compatibility Long-term viability Universal accessibility

0 views
fLaMEd fury Yesterday

Open Tabs September 2026

What’s going on, Internet? If you don’t follow my Bookmarks through the feed , then here’s the bookmarks from September. Enjoy. For more, check out the bookmarks archive, and subscribe to the feeds if you want these as they happen. Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website. MVNDY — THE POWER OF VISION Spend more time on the idea than the tooling. Craft on its own doesn’t change much. I don’t like passkeys I’m not a big fan of passkeys either. I’ve tried them and don’t particularly care for them. Rubenerd: Ban infinite scrolling I’ve never liked infinite scroll. I prefer pagination. I always lose my place in infinite scroll websites. This is my top hate of Discourse. Give me pagination. The Art of Brütal: Inside the Creative Legacy of Sam “Samwise” Didier | MadmadeLabel Fantastic interview with Samwise Didier, the original artist of Azeroth FINDING MUSIC WITH A LITTLE LESS BIG TECH Ways of discovering music when you’re not on DSP’s or social media. I follow local music venues, local online music mags and music writers. Simplest single file webring implementation : Garden of Learning by Juhis Juhis shares how to create and manage a simple webring in a single page. Celebrating One Year on the Small Web Small wins on the small web 😃 Start a Blog Shannon shares how to start a blog 😃 Harry Potter and the Teenage Websites of Yore GeoCities, FrontPage, and diving into creating websites with HTML. Love it. FrontPage was a crutch before Dreamweaver came around. A Creative’s Guide to Personal Websites (Zine) - with words, wonder James hits us with a neat zine angled at those who think they can’t make a website. Such a cool little project!

0 views
Andre Garzia Yesterday

BlogCat got a new Youtube subscriptions page

Made a really nice feature today after a friend requested it, added a new page to BlogCat that will list all the Youtube Channels you subscribed as a thumbnail grid, much like a non-algorithmic version of their home page. That means that you can use BlogCat to subscribe to channels, use the new page to view latest videos from the channels you subscribed, and use an alternative front-end to view the said videos such as invidious or nadeko. By combining these features, one doesn’t even need to have a Youtube account. It is quite neat and it skips algorithms and so on.

0 views
Andre Garzia Yesterday

First Photowalk of October

Went on a wee photowalk here with my Ricoh GR III camera. I am trying to get used to the 28mm focal length and think I am getting better with it. Decided to shoot digital for a while to save money. I prefer film but it is expensive to shoot. For this walk, I had nothing in mind, I was just listening to an albumn I like and going around town on a kind of extended circle route that ended up back at mine. I’m using some film simulation recipes on these shots, I got them from the Ricoh GR Recipes app. Think the super saturated shots are Royal Supra and the black and white are Dramatic B&W, the other shots are just my standard configuration. I don’t shoot raw, these are all straight of camera JPEGs, life is too short for editing.

0 views
マリウス Yesterday

Inbox Zero for Privacy, not Productivity

The concept of Inbox Zero has been worn out by the productivity crowd over the years and you’ve most likely come across the genre yourself. The type of ideas that tell you to empty your inbox in an effort to attain a state of serene focus, and ascend to become the kind of knowledge worker who “processes” correspondence rather than merely reading it. To be fair, ever since “AI” has been widely adopted to write and even read e-mails, I haven’t heard much from the former Inbox Zero fans, but I never found their framing compelling to begin with. What does interest me, however, is that the average mailbox has grown into one of the most complete, most sensitive, and probably least defended archives of private data a person owns, and that this archive is stored on someone else’s computer . So, with the tinfoil hats firmly back on, let’s look at Inbox Zero not as a ritual of productivity, but as a practice of privacy. Let’s first consider what is in your inbox right now. Probably password reset links, maybe invoices bearing your full legal name and home address, likely a dozen booking confirmations that double as a travel itinerary, maybe even medical exchanges and the occasional scan of an identity document that you sent “just this once” to this company that likely got breached at least once since you began reading this post. For most people an e-mail account amounts to a more thorough dossier than anything an intelligence service could have ever assembled by hand a few decades ago. And all of it is stored… where, exactly? Oh, right, on a mail server you do not control, operated by a company you are obliged to “just trust, bro” , reachable from anywhere on the planet by whoever happens to have the IMAP password (or exploit). I’ve written before about why none of us can trust any online service to safeguard our data anymore, and e-mail is the textbook example of the problem. A mailbox holding a decade of correspondence is a target that turns a data breach into a catastrophe. The productivity flavor of Inbox Zero does nothing to help here. When the productivity gurus instruct you to “archive” a message, what they really mean is clicking a button that relocates it from to a folder named , on the same server. From a privacy and security standpoint an inbox holding five messages alongside an archive of fifty thousand is no safer than the cluttered inbox you started out with, as it is the very same data, on the very same machine, exposed to the very same risk. But that’s because the productivity crowd doesn’t give a flying flock about privacy or security to begin with. However, it turns out that we can build on top of the Inbox Zero principle, but actually make it make sense from a data safety perspective by implementing a single, simple change in the process! The privacy-minded take on Inbox Zero is fairly simple: Once you are done with an e-mail, you don’t just move it to another folder, but you move it off the mail server and onto storage that you physically control. The server keeps your along with whatever you haven’t dealt with yet, but nothing more. Everything you have already handled is stored in a local mailbox on your own disk, ideally one that is encrypted at rest, backed up, and not reachable from the internet at all. This simple change in behavior is enough for most people to greatly decrease the damage they would suffer from a data breach of their mail service, and it doesn’t cost much to implement. Rather than treating the server as the one place where your mail is stored and your laptop as a viewer onto it, you treat your laptop as the place where your mail is stored, and the server as a temporary drop box for incoming and outgoing messages. It’s literally as easy as that, and everyone no matter their technical abilities can implement this change. Before getting to the actual implementation and the necessary configuration, be aware that this approach is not a silver bullet. Don’t get tricked into a false sense of security! Moving your archive onto local storage protects you against one specific and common class of disaster, namely your mail provider being breached, compelled, acquired, or simply deciding one day to mine your mailbox for iMpRoViNg SeRvIcE qUaLiTy . Once a message has been moved off the server, no future compromise of that server can expose that one message, at least unless the provider stores a backup copy of that mail, which, these days, is assumed. This approach, however, does not un-send mail , as every message you archive locally still has a copy in the mailbox of whoever you were corresponding with, on their provider’s server. It does not erase the metadata either, since the mere fact that you and a given person exchanged messages at a given time may well remain visible in server and transport logs long after the bodies of those messages have left your account. The metadata problem is one of e-mail’s big issues, and no amount of local archiving fixes it. Finally, moving the mails off-server relocates the risk rather than removing it. Your own storage is now the dossier, and should that storage be neither encrypted nor backed up you have traded a remote risk for a local one. Full-disk encryption and an encrypted backup strategy are therefore advisable. With that established, and an encrypted laptop with sane backups assumed, let’s look at how this is actually done in the clients people tend to use. Thunderbird ships with this capability built in, which makes it the easiest place to start if you are coming from a graphical client. Every Thunderbird profile already contains a special account called Local Folders that stores mail on your own machine. In the folder pane you locate the Local Folders account, usually pinned to the bottom, right-click it and create a new subfolder named something along the lines of , mirroring your server-side folder structure if you care to. To archive a message you then select it in your IMAP inbox and drag it onto that local folder, or use the right-click Move To menu. Keep in mind that only the move action removes the message from the server, while copy leaves a duplicate behind, so you want the move . If you feel extra fancy you can even set the directory in the Local Folders account as a destination for archived e-mail in your main account. Alternatively you can also use Message Filters , found under Tools , and build a rule that, say, relocates everything older than thirty days from the inbox into the local archive on demand. I prefer moving things by hand as I finish with them, since it forces me to decide what “done” is supposed to mean, and it also lets me structure my with subfolders for individual areas, but a filter can make for a reasonable safety net nevertheless. Warning: Thunderbird refers to its Local Folders storage as “maildir” , and Mozilla lets you select it as such. It is not a real maildir : Note this is NOT full maildir in the sense that most people, particularly linux users or mail administrators, know as maildir. Having stored years of mail in Thunderbird’s Local Folders myself, I eventually found that I had locked it all into a format no other maildir -aware tool could read, and no clean way to get it out. Apple Mail offers the same concept under a different name, with local mailboxes in the On My Mac section. You create one through Mailbox , New Mailbox , setting the location to On My Mac and giving it a name such as . From there you select messages in your primary account and reach for Message , Move To , On My Mac , Archive , or simply drag them across. As with Thunderbird, Move To removes the message from the server while Copy To leaves a copy behind, so the move is once again what you need to use. Note: When moving a large backlog across for the first time, Apple’s own documentation recommends doing so in batches of a few hundred messages rather than selecting ten years of mail in one go, as the transfer can otherwise time out somewhere in the middle. Outlook is where things become weird , for the simple reason that there are now two Outlooks, and they behave differently. Classic Outlook , the long-standing Win32 application, supports exactly what we are after by way of Outlook Data Files , the familiar files. A is a local, on-disk mail store, which you create through File , Account Settings , Data Files , Add , after which you select messages in your IMAP inbox and drag them into a folder inside that . This is a move, so the messages leave the server, and it is the decades-old way of taking mail offline in the Microsoft world. New Outlook , the cloud-first rewrite that Microsoft has been pushing onto everyone, is different. Having been re-architected around Microsoft’s cloud bs, it has had its local support largely gutted . At the time of writing, new Outlook can neither create nor write to a the way classic Outlook can. The supposedly “modern” client makes it harder to keep your mail on your own machine, and if local archiving matters to you while you are stuck on Windows with Outlook, you currently have to fall back to classic Outlook . Note: That a brand-new flagship mail client in 2026 makes it harder to keep your own mail on your own computer shows where the incentives are. The cloud is not being offered to you because it serves you better, but because your mailbox and the data it contains is very precious to Big Tech . Now we get to the setups I use myself. aerc is a terminal mail client that speaks IMAP directly and lets you configure several accounts of differing backend types at once. It lets us define one account for the IMAP server and a second account that is nothing more than a local maildir on disk, and then bind a key to move messages from the former to the latter. The relevant part of looks something like this: The matching binding in : With that in place the workflow reduces to reading a message, dealing with it, and pressing . The message is gone from the server and is now stored in as a standard maildir , one file per message, readable by , , , , or anything else that understands the format. There is no proprietary bs and no lock-in, and because it is a maildir you can point a full-text indexer at it and search a decade of mail instantly and entirely offline, and it will very likely work better than Google Mail’s search. aerc’s performs the cross-account move for you, copying the message into the destination account’s backend and removing it from the source. neomutt treats IMAP folders and local maildirs as the same thing, namely a mailbox , and lets you move messages between them with a single keystroke. , bound to by default, copies a message into another mailbox and leaves the original in place, while , bound to , copies it into another mailbox and marks the original for deletion. The save function is really a move . A local archive in neomutt is therefore nothing more than save -ing a message out of your IMAP inbox and into a local maildir folder. A minimal illustrates the idea: You tag a few messages with , press , and they are moved off the server into your local maildir . Running , or quitting the mailbox, syncs the deletions back and the messages are gone from the provider. Many people pair neomutt with /isync or for offline access. However, those tools follow a different philosophy, because they mirror the server locally, which by default means the mail exists in both places at once. If your goal is to get mail off the server, you need to configure the sync tool to expunge after fetching. As we can see, the technology here is trivial and (almost) every client mentioned above can do this today, most of them with a single keystroke or a drag of the mouse. The difficulty is the discipline, in treating “I have finished with this e-mail” and “this e-mail is no longer on the server” as the same thing. Now, funny enough, this is where the productivity framing of Inbox Zero turns out to be useful after all. The habit it cultivates, of touching each message once, deciding, and moving on, is the very habit that keeps your server empty. You merely point the “moving on” at local storage rather than at a server-side folder. If pressing twenty times a day becomes a burden, a periodic filter or a small cron ’d move script can take anything older than a given number of days off the server on your behalf. I prefer to do it by hand, but automation is a fine fallback. Run things this way for a while and your provider’s copy of your life shrinks to a rolling window of recent messages. At least if we disregard the fact that every message is probably copied by the provider and fed into some machine-learning system upon retrieval anyway, but that however is more of an issue with your choice of providers. However, were your provider to be compromised tomorrow, with a server-side archive every mail going back years would leak. With this setup it’s at least only a handful of recent e-mails. But there is more to this approach than just the breach scenario. The archive is yours, stored as real maildir or mbox files on storage you control, readable for as long as you like by ordinary tools, with no vendor able to lock you in or out. It costs nothing besides a little bit of storage, and it introduces no new dependency, given that it’s plain old IMAP paired with a local folder , which is something that has worked for over thirty years. As a small bonus you also stop worrying about storage quotas, since the server stays nearly empty regardless of how much you correspond. Inbox Zero was always marketed as a route to feeling calmer, but for me the calm has little to do with a tidy inbox, and much more to do with knowing that there is nothing on that server worth breaking in for.

0 views
Stratechery Yesterday

An Interview with Katie Harbath About Disrupting Politics at Facebook

An interview with former Facebook Head of Global Elections Katie Harbath about her new book Disrupting Politics, and how things change for the company in the 2010s.

0 views
Unsung Yesterday

Playdate’s update interface takes charge

Here’s what happens when you try to update your Android phone if it’s low on battery (even if it’s connected to a charger): = 3x)" srcset="https://unsung.aresluna.org/_media/playdates-update-interface-takes-charge/1-framed.1600w.avif" type="image/avif"> It’s a pretty cheap error UI, a misaligned string thrown carelessly in between existing interface pieces. It doesn’t even bother explaining what “too low” means. Compare this to what the Playdate does in the same situation: = 3x)" srcset="https://unsung.aresluna.org/_media/playdates-update-interface-takes-charge/2-framed.1600w.avif" type="image/avif"> If the device battery is depleted, the software simply charges if first to the necessary minimum (spelled out on screen), before automatically proceeding to the actual update – all without any user intervention. We previously talked about fire-and-forget states, or interfaces which give you confidence that once the process starts , it will finish on its own . (If you don’t like military references , we could call these tap-and-walk-away.) This is a really good example. There is even a nice corresponding version for when your Playdate needs to be plugged in first: = 3x)" srcset="https://unsung.aresluna.org/_media/playdates-update-interface-takes-charge/3-framed.1600w.avif" type="image/avif"> Once you do, the process again continues by itself right until the end.

0 views

Monte-Carlo simulations

Monte Carlo simulations (or methods ) is the technique of applying randomness and the Law of large numbers to the solution of various scientific and engineering problems. One of its first documented uses was by Stanislaw Ulam and John von Neumann for nuclear weapon simulations after WWII [1] . In this post I want to provide examples of some simple uses of Monte Carlo simulations. We'll start with the classical example of calculating the value of \pi by throwing darts. Suppose we take a square board and inscribe a quarter of a circle into it. We then proceed to throw darts at the board and record whether each dart hits inside or outside the quarter circle. Having thrown many such darts, we calculate the ratio of the darts inside the circle to the total number thrown. Assuming our darts are distributed uniformly over the square, by the Law of large numbers this ratio should approach the ratio of areas of the quarter circle A_{circ} to the full square A_{square} . With a square side length of 1, we have: Here's a visualization: Change the number of samples (dart throws) and click "Run" to regenerate. The code is very simple - here's a slightly sanitized version: You'll notice that the estimate is relatively poor - even with 1000 samples - if you click "Run" several times, some numbers will be way off mark. While this method does estimate \pi , it's not a particularly good estimate. I find that running ~10 billion samples is necessary to estimate it to 4 digits after the decimal with reasonable reliability. In general, for independent trials like these, the typical sampling error decreases in proportion to 1/\sqrt{N} , where N is the number of trials. This means that halving the error requires four times as many samples. While the \pi estimation may seem whimsical, it's an example of an important class of problems to which Monte Carlo simulation is applied: numerical integration. Our simulation estimates the area under the quarter-circle curve, which is a definite integral. Many integrals are very difficult to solve analytically, and much research has been done in the area of numerical analysis to develop methods to calculate integrals. Monte Carlo methods are particularly useful for high-dimensional integrals, where other numerical methods can become prohibitively expensive. A common use of Monte Carlo methods is estimating complex combinatorial calculations. These often don't have analytical solutions, and enumerating all options is intractable due to the scale of the numbers involved. As an example, let's consider the game of SET . Each SET card has four attributes: And the goal is to find a "set" - three cards that are either all different or all the same for each attribute separately . As an example, here's a hand with a single set; see if you can find it [2] : And the next hand doesn't have any sets: Here's a question: given a freshly shuffled SET deck, what are the odds that the first 12 cards drawn will have no sets among them? This question is difficult to answer without using a computer. It's easy to calculate the number of ways to deal a 12-card hand from a deck of 81: But how many of these hands have no sets? Enumerating 70 trillion SET hands and checking each one can take quite a while, and there is no straightforward counting formula to answer this question. Some clever methods can be employed to leverage symmetries and other mathematical properties of SET to cut down this search space considerably. Donald Knuth himself worked on this problem and came up with a neat program ( setset-all on his programs page ) that found 2,284,535,476,080 such hands. Therefore, the answer to our question is: There's a 3.23% chance that a randomly drawn hand of 12 cards from a full deck of SET will have no set in it. Let's see how we can use a Monte Carlo simulation to answer this question with relatively small effort, without deep knowledge of the mathematical properties of SET that enable cutting down the search space Knuth-style. We can use the following pseudo-code: After running 10 million simulated draws, I got an answer of 0.0323, which matches the real answer very closely. The Monte Carlo approach lets us solve rather complicated problems in a very simple way. Suppose we want to answer the same question for a hand of 15 cards; this would blow up the search space considerably - there are about 100x more ways to select 15-card hands than there are to select 12-card hands. But for a Monte Carlo simulation, we adjust one small parameter and get a very reliable [3] answer (about 0.00037, in case you were wondering). One domain where Monte Carlo simulations are ubiquitous is projections for retirement portfolios. Suppose someone prepares to retire with a total sum of 1 million dollars in their portfolio; they'd like to be able to draw $30,000 a year from the portfolio for their living expenses. Would that work? There's a large number of factors to take into account when analyzing this question, but for simplicity let's focus on just two: portfolio return and inflation. We can run a naive estimate, assuming average values: suppose an average yearly portfolio return of 4%, and average yearly inflation of 2% [4] Let's denote our portfolio return as r=0.04 , and inflation as q=0.02 . Then the real return each year is: Starting with $1,000,000, at the end of the year we'll have $1,019,600 and then draw $30,000 for living expenses [5] , ending with $989,600. If we continue this way, the money runs out after ~55 years, which means that a person retiring at the age of 65 should be reasonably safe, right? But this is very simplistic ; assuming just average returns is risky, because they do a poor job of representing reality, and many factors have uncertainty. For example, the sequence of returns matters a lot; a bad year (-10%) followed by a great year (+18%) would still count as "4% on average" but produces significantly less money than two consecutive +4% years. Inflation is also unpredictable, and sometimes correlated with portfolio returns; there could be bad years of high inflation and low / volatile returns that can wreak havoc on a portfolio. As we add factors (variance in yearly draws, mixed portfolios of stocks, bonds, real estate, life expectancy, unexpected events, changing tax laws etc.), relying on a single average estimate becomes increasingly more fraught. This is why Monte Carlo simulations are very popular in this domain: by drawing from reasonable distributions based on historical data, a Monte Carlo simulation can easily run a million different scenarios and provide estimates: for example, what are the odds of money running out before death. Here's a useful chart from a simulation I ran: In the top chart: In the bottom chart: Looking a this simulation, under the current assumptions the plan sounds much riskier than the average assumption makes it appear. Assuming that a 65-y.o. person would plan for 25 years of retirement until death, the ~30% odds of not having sufficient funds for this duration of time are sobering. Perhaps a change in plans is needed (such as a more frugal lifestyle or securing additional funds in some way). Retirement projection is only one of may ways in which Monte Carlo simulations are used for financial and economical applications; given the high uncertainty of these domains, it's very difficult to plan using analytical calculations. Company sales projections, growth projections, stock offering prices and much more uses Monte Carlo simulations to arrive at estimates with reasonable error bars. All the code for the explorations in this post is available on GitHub . A quarter circle of radius 1 inside a unit square. Samples 200 10 1000 Run π ≈ — Change the number of samples (dart throws) and click "Run" to regenerate. The code is very simple - here's a slightly sanitized version: You'll notice that the estimate is relatively poor - even with 1000 samples - if you click "Run" several times, some numbers will be way off mark. While this method does estimate \pi , it's not a particularly good estimate. I find that running ~10 billion samples is necessary to estimate it to 4 digits after the decimal with reasonable reliability. In general, for independent trials like these, the typical sampling error decreases in proportion to 1/\sqrt{N} , where N is the number of trials. This means that halving the error requires four times as many samples. While the \pi estimation may seem whimsical, it's an example of an important class of problems to which Monte Carlo simulation is applied: numerical integration. Our simulation estimates the area under the quarter-circle curve, which is a definite integral. Many integrals are very difficult to solve analytically, and much research has been done in the area of numerical analysis to develop methods to calculate integrals. Monte Carlo methods are particularly useful for high-dimensional integrals, where other numerical methods can become prohibitively expensive. Combinatorial simulation - the game of SET A common use of Monte Carlo methods is estimating complex combinatorial calculations. These often don't have analytical solutions, and enumerating all options is intractable due to the scale of the numbers involved. As an example, let's consider the game of SET . Each SET card has four attributes: Number of shapes (1, 2 or 3) Color (Red, Green or Purple) Shape type (Oval, Diamond or Squiggle) Shading (Empty, Striped or Solid) The dashed line shows the constant assumptions mentioned before: what happens when yearly return is always 4% and inflation is always 2%. The shaded blue areas demonstrate the outcomes of 1,000,000 simulations where inflation and return numbers are drawn from reasonable normal distributions based on historical data. We see that in 25% of the cases, all money ran out by roughly 22 years. It's even easier to see how long the funds last; if we're interested in knowing, say, what are the odds that this plan will have enough money for 30 years - the chart shows it's about 60% (since in 40% of the simulations the funds were depleted at this point).

0 views
Justin Duke 2 days ago

The Sting

The Sting belongs in the pantheon of films I'm deeply embarrassed to have not watched earlier. Not just because it's a great film — and it is — but because it is so incredibly my shit that I feel retroactively spurned for not having watched it sooner. It's a movie to which the Ocean's franchise obviously owes so much, which is a funny thing to say given that the Ocean's franchise is itself a remake. But much like Ocean's, this is a hedonistic, smooth, and perfectly executed act of escapism, relying in no small part on your love and affection for two absolute behemoth actors. Much like Ocean's — and sometimes even more so — it beggars belief. Early in the film, Paul Newman snaps his fingers and assembles, at a moment's notice, a two-dozen-strong team of expert con artists who work in choreographed unison for the rest of the film, never failing or faltering. A part of my brain went: come on, are you expecting me to believe this? And that part of my brain was immediately silenced by the louder, happier part that whispered: shush, and just let it happen. The film ends with two little twists. One of them is so obvious that I think it barely merits mention; the other — the revelation of a certain assassin's identity — actually did take me by surprise, and kudos to them. But God, I challenge you to watch this film and not spend the final six or so minutes with a maniacal grin on your face. It is a movie faultlessly devoted to pleasure. I'm glad that not every movie is like that, but I'm glad that many of them are.

0 views

Swemak: Colemak for Swedish

I have written before about my Swedish Colemak hack, but I never explained what makes it so good. This is a better image what it looks like: If you’re struggling to find the difference against normal Colemak, look at the cluster of five keys next to the return key. This layout makes room for the three Swedish letters , , and , by only changing five keys in total, and they have changed very thoughtfully: (Continue reading the full article on the web.)

0 views
Unsung 2 days ago

“Konami looked at its restrictions and designed an entire level based around it.”

A fun little video by Bofner talking about how two Castlevania games on the original Nintendo Game Boy achieved a certain visual effect that wasn’t technically possible: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/konami-looked-at-its-restrictions-and-designed-an-entire-level-based-around-it/yt1-play.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/konami-looked-at-its-restrictions-and-designed-an-entire-level-based-around-it/yt1-play.1600w.avif" type="image/avif"> What I really liked was this sequence of events: Game Boy introduces a special immovable layer to accommodate HUD /GUI elements within games (such as score displays or life meters), Castlevania programmers come up with a creative hack to use the HUD layer draw a movable spike wall instead, then, they use another hack to draw the HUD anyway, outside of the HUD layer.

0 views
Simon Willison 2 days ago

Claude Haiku 5.5

As previously promised , here's Anthropic's new fast, low cost model: Introducing Claude Haiku 5.5 . The previous Haiku, 4.5, was very much showing its age. It came out almost a year ago , and was priced at $1/million input and $5/million output - relatively expensive even back then, and a full 10x the price of OpenAI's GPT-6 Luna , released last month. The new Haiku exactly matches the price of GPT-6 Luna - $0.10/$0.50 - up to 100,000 tokens. Beyond 100,000 tokens the price increases 5x to $0.50/$2.50. Luna itself has a price increase at 272,000 tokens but only to $0.20/$0.75. Haiku 5.5 also uses a new, less generous tokenizer. My Claude Token Counter tool shows that the same long prompt uses around 1.25x as many tokens with Haiku 5.5 compared to Haiku 4.5, so there's a hidden price increase there. If your workloads fit in 100,000 tokens, Haiku is the same price as Luna and reports higher benchmark scores. Above 100,000 tokens, Luna looks like a much better deal. The most recent release of llm-anthropic finally fixed it so I don't need to ship a new version of that plugin for every new model. I tested the new model like this: Here are pelicans for low, medium, high, xhigh, and max . The new Haiku doesn't let you disable reasoning, and defaults to . I got a good bicycle frame for everything beyond . The low effort pelican cost 0.0936 cents and took 7 seconds. This effort pelican (with a reasoning trace that starts "This is the classic pelican-on-bicycle SVG test...") took 5 minutes 9 seconds to generate, but still only cost me 3.3826 cents : (Since the reasoning trace exhibits awareness of the benchmark, here's Generate an SVG of an armadillo in fishnet tights jaywalking on Mars (on xhigh) , and the same prompt against some other recent models . Background on that .) For comparison, here's the pelican I got a year ago from Haiku 4.5 (for 0.7583 cents - Haiku 4.5 did not support reasoning levels). It sucked at drawing pelicans: In addition to Haiku 5.5, Anthropic announced today that they are halving the price of cache reads for Sonnet 5.5. They've also added API credits to subscription plans: Second, this week, we’ll roll out a new monthly API credit to all Max and Team subscribers for use on the Claude Platform . Max 5x users will get $100 in credits per month, Max 20x users will get $200, and Team subscribers will receive up to $500, pooled across their users. Claiming this is pleasantly easy: navigate to Settings -> Billing and select the API organization that should benefit from the credits every month: The API credits exactly match the cost of the subscription itself. This is really generous - it makes it much easier for subscribers to use the API. Anthropic also let you disable auto-reload for the API, with the consequence that "API requests will stop when your balance runs out" - exactly what you want if you're planning to burn through those API credits without risk of a nasty billing surprise . Note that the monthly credits do not roll over - use them or lose them. OpenAI still allow you to use your Codex subscription for personal API use, which works out as a better deal for heavy API users. This new credit scheme goes at least some way to overcoming that difference. You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options .

0 views