Posts in Tutorial (20 found)
Martin Fowler Yesterday

Build your slide template

Building a slide template starts with three foundational choices — colours, fonts and layouts. Sumeet Gayathri Moghe covers the basics of color theory and how to build a reusable palette, the essentials of typography, including serif, sans-serif, handwritten and monospace fonts, plus the finer points of kerning and leading, and why a near-blank layout might serve you better than the prescribed layouts that ship with your slideware.

0 views
neilzone 3 days ago

Blurring part of a video using ffmpeg

Hello, future Neil! You are probably trying to remember how you blurred part of a video. You blurred the bottom left portion of a circus video, using . : this is the size of the boxblur, 440px wide, 80px high : this is the position of the boxblur, starting from left top. i.e. 0 pixels from the left, 370 pixels from the top : this is how dense the blurring is. 2 is quite weak; 10 is strong : this needs to match the position of the boxblur

0 views
Blargh 1 weeks ago

UniBalun dipole setup

Until now, my HF antennas have mostly been off the shelf. I’ve gone out with an AlexLoop (hmm, alexloop.com seems to be down. I don’t know if that’s permanent), and the Elecraft AX1 . They’ve both been fine, but have some drawbacks. The AX1 is a bit finicky about getting its radial(s) good enough, and the AlexLoop is a bit tricky to set up in a temporary but stable way. I have not have good experience with the half wave G5RV, yet, for some reason. So since all HF antennas are compromises, why not try an inverted V dipole? I had the UniBalun recommended to me, so I went with that. There are some minor things to think about, which is what this blog post is about. To become an antenna, I also needed antenna wire , and a way to put the middle of the dipole high up, with the antenna wire extending straight diagonally down. For a 20m (14MHz) antenna, I cut two 5.2m pieces, expecting to need to either cut or fold them back a bit, for the right resonance. On the UniBalun side I could just crimp an M4 ring to attach the antenna wire, but on the other end of the wire I needed to be more creative. It needed to be able to attach to paracord, and also be fairly easily adjustable in how much should fold back. Even if I were to cut it, I’d probably still want it folded back a bit, to adjust for anything from exact band location to moisture in the ground. I ended up using a butt splice crimp, without crimping it, as a kind of zipper to create the attachment loop. I then used a ziptie to make sure the fold is along the main wire as much as possible. I think an improvement here would be to use a second butt splice crimp instead of the ziptie, and glue the folded tip to it. That way it can move and be adjustable, and also straight. If wind starts moving it, there’s always gaffa tape. I had an extendable window washing pole, so that sorted height. I added a guying collar to secure it with paracord, though a hitch knot appeared to be stable enough too, and is more flexible about how high up you want the guying wire. The guying collar is the same diameter as the pole. Which means while I got it on, it ain’t never coming off. I probably should have bought a slightly bigger guying collar, and secured it with a shelf of tape below it. But it works. My bike’s wheel held the base in place. My antenna analyzer has bluetooth. I thought I’d check that out, to prevent me going back and forth too much. But the Android app is not compatible with the current Android release. And the Linux app didn’t seem to work either. So I told AI to build one: After a few more minor user improvement prompts, I ended up with something pretty usable . Here’s two example sweeps from my testing today: It saves everything from the session, so I can inspect the return loss and other graphs after the fact, too. I’m treating this tool as any official tool I find online; I didn’t look at the code at all. I would not have spent the time to actually build this manually. I would have given up and only used the rigexpert directly. Well, I did use it directly, too. It went well. The UniBalun is rated for 5W in data modes, so that’s what I ran. Not bad at all. Unreasonably effective , in fact. I also dialled in to my BBS over the mercury HF modem . This is my checklist for going out and playing radio. I don’t bring everything, only what I’ll need on the day. But I do go through the list every time because it’s easy to forget something. UniBalun — £9 100m lightweight antenna wire (just over 10m used) — £14 Guying collar — £3 Paracord — less than £1 worth used 2x M4 crimps 2x (or 4x) AWG 16-40 butt splice crimp Tent stakes — £13 USB hub (my Elecraft requires two USB ports to get audio and CAT), and one of my two USB-A ports has a yubikey nano in it. HF radio CAT cable External battery Baofeng + AIOC (convenient modem) Coffee thermos Picnic blanket Set home station frequency & power (if dialing in back home) Headphone cable Arrow antenna AX1 antenna Antenna stand (for AX1 or AlexLoop) Dipole pole Camping stakes Paper towels Lighter (for sealing paracord after cutting)

0 views
iDiallo 1 weeks ago

Why My Asus Laptop Kept Rebooting After Sleep and How to Fix it

Three years ago, I bought an Asus laptop, specifically the Zenbook Pro 17. It's a powerful machine and works just fine for my needs, but it had one problem that made me want to either return it or throw it out the window. I would work on projects for hours on end and close the lid when I was done. I haven't manually shut down a computer in more than 10 years. I only do so after a forced update or during a debugging session. So I would close the lid, and a couple of hours later I would open it back up. Instead of waking from sleep, the computer would start from scratch as if it had been turned off and the battery was nearly dead. That meant everything I had open was closed. I'm lucky that most of the applications I run can restore a previous session, but it was extremely annoying. I had a similar issue with my previous Asus, and I had blamed Windows for it. The computer often woke from sleep just so Microsoft could perform an update, which used up all my battery. Sometimes I would open my backpack to find a dead laptop that was warm to the touch. I've written this blog post at least five times. Each time I thought I had solved the issue, but then it came back. This time, though, I think I have finally resolved it. So if you have an Asus laptop running Windows and you experience this very annoying issue, you are not alone. In my case, the culprit was the Wi-Fi adapter, specifically the MediaTek Wi-Fi 6E MT7922. On Windows: That's it. After three years of annoying restarts, this finally solved my problem. Now let me explain what was going on. When the laptop lid is closed, Windows initiates "Modern Standby." In this state, the screen turns off to save power, but the system remains partially active to maintain background network connectivity. Shortly after, Windows attempts to transition into a deeper low-power state and disconnects the network adapter to conserve battery. However, the MediaTek Wi-Fi driver ( ) fails to handle this low-power transition properly. About a minute later, it generates Event ID 1033 errors referencing a network device path (specifically in my logs). Because the driver's error message resource is missing or corrupted, Windows Event Viewer cannot interpret the error and displays a generic warning instead: "The description for Event ID 1033 from source mtkwlex cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted..." Because the Wi-Fi driver fails to sleep, it keeps the system partially awake. This causes abnormal background battery drain and heat buildup, which is exacerbated when the laptop is in an enclosed space like a bag. In response to this abnormal drain, Windows triggers Event 507: "Austerity Battery Drain Budget Exceeded." It's important to note that this event is Windows intentionally waking the laptop up to prevent the battery from dying completely. However, because the Wi-Fi driver is already in a crashed or unstable kernel state, the system fails to recover from this forced wake-up. The result is a silent system crash or thermal shutdown, followed by Event 12 ("The operating system started") , which confirms the laptop rebooted. With the fix in place, the Wi-Fi device no longer attempts to enter its power-saving sleep mode, so it never crashes. The ultimate solution would be a fixed MT7922 driver, but try as I might, I haven't been able to find one online. It's been three weeks so far, and I've been able to resume my work without coming back to a crashed computer. I hope this helps someone else. Open Device Manager . Expand Network adapters . Right-click the MediaTek device and click Properties . Click the Advanced tab. In the Property box, select Power Saving . In the Value field, select Disabled .

0 views
Julia Evans 2 weeks ago

Replacing the old battery on rechargeable bike lights

Hello! Recently I needed bike lights for my bike. And I remembered that I already had rechargeable bike lights that I bought ten years ago, that I hadn’t tried in a long time. I tried to recharge them, but after fully charging them, they only worked for maybe 5 minutes before they turned off again. I don’t know much about electronics, but I’ve been curious about whether it’s possible to fix old electronics for a long time, and this seemed like the perfect repair project because I might just need to replace the battery. So I went to the local queer makerspace where I’m a member to use the soldering iron and try to do it! I don’t know much about electronics and this post does not contain any safety advice because I don’t know much about safety. I think it’s nice to do projects in a community space where you can get help. The bike light felt like it was made of silicone, so I cut open the silicone in a haphazard way along something that vaguely looked like a seam. I definitely ripped some silicone in the process and it was pretty messy but I got it open and found the circuit board. I don’t know the model number of the bike lights but there’s a photo of them at the end of the post. There were some screws attaching things together so I removed them so I could get the circuit board out. Mostly I tried to remove as few screws as possible because I was worried about losing them or not being able to put them back after. I probably put the screws in a bag or something. I took out the circuit board. Here’s what it looked like: You can see where the battery is attached, I think it’s left of RI3 and above Q2. Here’s what the battery looked like: I’d never desoldered anything before, so I found the iFixit guide to desoldering and read it. Also I asked my friends Lee and Lauria for advice. Here were the steps I ended up following based on the guide & the advice I got: In the picture of the battery in Step 3, you can see it says something like “3” and “LI???77”. There’s a piece of metal that I think is welded or something to the top of the battery. It seemed impossible and also maybe not smart to try to remove so I wasn’t sure how to find out what an “LI????77” was or how to order another one. I’ve been trying to avoid using LLMs (though I will not get into that because I am exhausted by LLM discourse and I’m sure you are too), but I really had no idea how to figure out what the battery was so I asked an LLM. It gave the response “LIR2477”, which (when I looked it up) looked exactly the same as my battery so I figured that was plausible. I would be interested to learn non-LLM ways to figure this out though. There must be a way. Lauria showed me how to use DigiKey’s search which was very cool though DigiKey didn’t have that part. (edit: someone in the replies told me that this kind of coin cell battery is named according to its dimensions, and you can use plastic calipers to measure the dimensions of the battery. So I guess a non-LLM way would be to measure the battery with calipers and try to match it to something on the List of battery sizes Wikipedia page, though that page only mentions CR 2477 and not LIR 2477. It’s a good example of what’s fun for me about trying to avoid LLMs, this “List of battery sizes” page is super interesting and if I use an LLM I might never find it) I went to AliExpress and ordered: I think the batteries were $3 each and the glue was $8. The parts took maybe 2 weeks to arive, and once they arrived, I went back to the makerspace and: Then after waiting some amount of time for the glue to dry I took it home and waited 24 hours for the glue to cure. Also I took the old batteries to somewhere nearby that accepts old batteries. The lights work! I have used them to bike at night! I still haven’t needed to recharge them (and tragically I had to order a new Mini USB cable because I got rid of all my Mini USB cables, so I’m still waiting for that), so I still don’t know for sure how long the lifetime of the new battery will be. Here’s what the light looks like after re-gluing. You can see that I didn’t glue very carefully. It didn’t really go back together that well but I’m hoping it’ll be good enough. I thought it was really cool that I was able to do this with extremely minimal electronics skills! It cost about $20 CAD to buy the parts, and (whether or not the repair holds up, I’ll try to update this post in the future!), it was fun to try to repair something and learn something new. Use a desoldering pump to remove most of the solder Once most of it is gone, kind of pull them apart to try to separate them Also try to avoid getting the battery too hot in the process by taking breaks to let it cool down. I’m not very good with a soldering iron so it took a while. The battery has an attachment that is welded to the top. For a while I thought I needed to remove this and it seemed impossible, but it turned out the replacement battery comes with that part so actually I was supposed to leave it alone. 2 batteries (I had 2 bike lights and I wanted to fix them both) some silicone glue to glue things back together soldered in the new batteries put the screws back in. The screws were very small and hard to hold, so at this point I dropped some screws on the ground and couldn’t find them because they were too small. So I just used fewer screws and hoped for the best. used the glue to try to put everything back together. Make a somewhat halfhearted attempt to clamp the parts I was gluing together

0 views
alikhil 3 weeks ago

My MikroTik workaround for LaLiga Cloudflare blocks

Spain loves football. Spain also has plenty of people watching football on pirate streams. Those streams often sit behind Cloudflare, where one IP address can belong to a lot of completely unrelated websites. LaLiga spent a while trying to get Cloudflare to take the pirate streams down. That did not work out. Then LaLiga got a court order that lets Spanish ISPs block IP addresses during matches. So they block the IPs used by the streams, and legitimate sites behind Cloudflare go down with them. Message shown by a Spanish ISP when you try to load a website hosted on an IP address it has blocked. The result is annoying. A random site does not load. An API starts timing out. You can spend an hour debugging your own setup before noticing that there is a LaLiga match on. I used to fix it by enabling a rule on my MikroTik that sends Cloudflare traffic through a VPN. Every match, same small ritual. Recently I found ¿Hay ahora fútbol? , a site with one useful question: is football on right now, and is Cloudflare probably broken again? It also exposes its signal over DNS. That was enough to automate the whole thing. The router keeps a list of Cloudflare IP ranges. When Hayahora reports a large set of blocked IPs, it enables one disabled mangle rule. New connections from my LAN to Cloudflare then go through my existing VPN. When the DNS answer goes back to normal, the rule turns itself off. Only Cloudflare traffic takes the detour. The VPN is not my default route, and the rest of the network keeps using my normal ISP connection. My setup uses RouterOS 7 and an existing policy-based IPsec connection marked . If your VPN uses a different connection mark, replace below. Get the VPN working first; this post only adds the automatic switch. First, refresh a firewall address list named every day. I use Davie3’s list , but any maintained Cloudflare IP-list import will do. Then add this rule. It starts disabled on purpose. Finally, this script checks Hayahora every five minutes. More than ten DNS answers is my signal that a blocking event is active. The same threshold is used by the TRMNL LaLiga plugin . You might wonder why this does an HTTP request to Google DNS instead of using RouterOS’s native . returns only one record. I need the full answer set to keep the threshold, so one record does not send all Cloudflare traffic through the VPN by accident. This is a blunt workaround. While the rule is enabled, every new connection from my LAN to a Cloudflare IP goes through the VPN. That is fine for me during a match, but it sends all Cloudflare traffic through the VPN, not just traffic to the site that failed. The DNS data is an observation, not an official blocklist. And connections that already exist keep their old route until they reconnect. Still, it has saved me from one more round of “why is this website broken only tonight?” If football is on and the block is active, I am French automatically.

0 views
Blargh 3 weeks ago

The magic of OFDM

I’ve been playing a bit with OFDM in software defined radio lately. And after watching a video that wasn’t even close to correct , I thought I’d write down my understanding in a blog post. I wouldn’t call myself an expert, so worst case someone will correct me, and I’m happy to be corrected. In my experiments with OFDM my initial goal is understanding, so e.g. I won’t blindly use some hierarchical GNU Radio block. I think that in order to internalize OFDM, we first need to remind ourselves how a purely “real” wave becomes a complex number, at a high level. The real signal is a value that’s a function of time. Glossing over details, this not feasible for most of our signals, as we would need to provide a new value to the transmitter at a rate twice as high as the highest frequency we care about. For 2.4GHz experiments, we’d need 4.8 billion samples per second. Quadrature sampling allows us to represent a slice of bandwidth at a managable rate. We can then provide the I/Q data to hardware, instructing it to hold that “until further notice”. If we provide one sample per second, for example, we can change the phase or amplitude of a pure sine wave (the carrier) once a second. Between our samples, it continues as-is in that phase and amplitude. “As-is” is here left deliberately vague. I only phrase it this way because it’s a helpful analogy further down. Now some background on QPSK , since that’s the encoding in the subcarriers in my OFDM experiments. This should apply equally to QAM , QAM being a generalization that allows more bits per symbol. At first glance QPSK seems simple. Symbols become . But you can’t really transmit that kind of instant transition without infinite bandwidth. An individual symbol instead gets turned into a “pulse” towards its coordinate. A pulse will spread out, but the pulse shape is designed such that it should have minimal effect where it matters; at the peak of neighboring pulses (symbols). So if the receiver samples at exactly the right point, it’ll get a perfect symbol with no inter-symbol interference. Of course that’s only strictly true in theory (because of multipath and various nonlinearities). But minimizing the symbol interference helps. Turning symbols into pulses is called pulse shaping . GNURadio has a nice guide to QPSK . The explanation of OFDM still makes me feel like it shouldn’t work. It’s too weird of a tool to use. It feels like cheating. The idea is that instead of having one high symbol rate with as many constellation points (and therefore bits per symbol) as you can, you instead split your data into multiple slower carriers of narrower bandwidth. How would you generate all these smaller slower signals? Well, you could create many virtual transmitters and add them up into one spectrum. That’s one way to turn many constellation diagrams (series of complex numbers) into one signal. Then on the receiver side you’d do one band pass filter and translation to baseband per carrier. You know what else takes a complex time domain signal and gives you a set of discrete complex numbers, one per frequency? An FFT. So you just stuff QPSK symbols into (e.g.) 64 FFT bins, and do an inverse FFT? Well, almost (see below). But the fact that the answer is “yes almost!” is what’s magic. This is the thing that blew my mind. A receiver can then just do a 64 bin FFT, and there you have the I and Q for your 64 subcarriers. This means that both transmitter and receiver will process the incoming I/Q in chunks. Chunks that are the size of the FFT. Ok, so “OFDM symbol” doesn’t make so much sense. I mean the collection of symbols being sent at any given point in time across all subcarriers. An OFDM “aggregate symbol” is several input symbols, distributed into the FFT bins / subcarriers. Transmitting turns these sets of symbols into I found it useful to think of OFDM in the time domain within a single “FFT chunk”. The original I/Q samples had a start time and an end time. Between the start time and the end time, the true analog signal was a mix of (for example) 64 fixed tones. The individual tones had a particular individual phase and amplitude. Here’s an example of a four subcarrier OFDM, for three symbol periods: And this is where the “hold this until further notice” analogy is relevant. It doesn’t matter how many cycles a given subcarrier goes through. It just matters for a given subcarrier, what amplitude and phase it was. And yes, this is the same thing as having four independent transmitters. But because the “transmitters” are just the output of an inverse FFT, they are exactly synchronized. We generated this four transmitters by cheating! Taking the illustration above literally, we should not transmit that exactly. The phase and amplitude transitions are too sharp. An input symbol is only part of one FFT, and for the next FFT period we have another input symbol for that FFT bin. So how should we soften that up? Should we pulse shape the subcarrier QPSK before it goes into the inverse FFT? No, because then that means each symbol takes multiple FFT chunks, slowing down the transmission. Do we pulse shape the whole FFT chunk? No, these aren’t pulses but instead multiple frequencies; there isn’t really a “pulse” to shape. Fixing this involves windowed OFDM/WOLA, smoothing out the FFT period transitions, and I’ve not looked into it yet. OFDM is good in the presence of multipath for two reasons: Whereas QPSK needs to lock on based on peaks in the signal, and can therefore easily lock in on small frequency differences between transmitter and receiver, OFDM doesn’t have the same luxury. The pulses are inside the subcarriers, and you can’t extract the subcarriers until you have the frequency. This is usually solved by sacrificing some subcarriers to pilot signals. In my testing I made the center bin a pure sine wave, and used a feedback loop to perform a primitive frequency lock based on quadrature demod. Any residual frequency error will result in cross-subcarrier interference, and frequency offset (quadrature rotation) inside the subcarrier. You can also use a Jacobsen CFO estimator to estimate the frequency error from the pilot signal FFT bin. My GNURadio flowgraph uses that only for diagnostic, not for tuning feedback. On the plus side, because the subcarrier is merely the values of the FFT, if the outer carrier is synchronized, then the inner is too, and you can just pluck the QPSK symbols directly after Symbol Sync, without a Costas loop or other subcarrier frequency lock. In my example graph, the default frequency locking is coarse, trading stability for speed of convergence, and the clocked symbols (under “Clocked”) shows this instability as a subcarrier frequency offset, correctable with a Costas loop. If I change the to a lower value, it becomes stable: (yes, that’s an animation. It’s just very frequency locked) In a better implementation, frequency locking should probably switch to fine locking automatically when the signal is found. While my GNURadio experiment does add the Cyclic Prefix, and removes it, it does not yet actually attempt any timing synchronization. This means that the start and end of an “FFT chunk” can be wrong. I think this is a likely main cause of why it’s very sensitive to other subcarriers being assigned noise: I think this timing synchronization may be best done by having the pilot signal do… something. Maybe the (or one of) the pilot signal should be BPSK triggered by FFT chunk transition? This is how my OFDM test looks over the (virtual) air. A pilot in the middle, silent bins around it, a slow (each symbol repeats 10 times) signal in FFT bin / subcarrier 5, and the rest of the carriers full of noise. This blog post was written when the github repo with my flowgraph was at commit 1e8fbd79d465cf94f6407f4677375ac088158619. The symbols are slow, and the speed of light is fast. The slower a symbol is sent, the less the chance a bounced signal from a different symbol has to interfere. OFDM also encourages adding a “Cyclic Prefix”. Basically extending the transmitted “OFDM chunk” and discarding the initial part, under the assumption that it could be polluted with multipath echoes of the previous symbols.

0 views
Luke Hsiao 3 weeks ago

Turning a $40 Surface Go into an always-on camera monitor

TL;DR I wanted an always-on screen showing a camera feed. Something that boots straight into the stream with no prompts, survives Wi-Fi drops and camera hiccups, and can be maintained remotely. A used Surface Go 3 running Omarchy turned out to be a great fit, with only a few setup steps. I have my home kitted out with UniFi PoE cameras. Inspired by Casey Neistat’s studio door camera , I made a similar setup: a camera pointed outside with a screen mounted on a wall inside. In practice, this is meaningfully more convenient than a peephole (we also don’t have any windows with a good view of the entrance). When someone knocks, we can glance at the monitor from just about anywhere in the room and see who it is. Until now, I was just using a cheap Amazon Fire HD 8 tablet. My thinking at the time was to make this as simple as possible: get a cheap Android tablet, run VLC for the RTSP stream, attach it to the wall with Command Strips, leave it plugged in, done. This had some downsides. The Fire HD 8 is underpowered, so we could only use a medium-resolution stream (720p), and the stream would lag behind by a few seconds. The simplicity of the VLC setup meant that anytime our network went down for an upgrade or the stream cut out momentarily, we’d have to reconnect manually. That worked for about three years. Then, I suspect, being permanently plugged in at 100% charge took its toll. Over the past week, the stream grew increasingly unreliable, mostly because the tablet failed to connect to my home Wi-Fi. I suspect the Wi-Fi card is dying. So, this presented an opportunity for an upgrade. My main goal was reliability. I wanted anyone in my family to be able to turn this on and get it working with as little interaction as possible. That meant I wanted them to literally just turn it on, walk away, and be greeted by the stream playing in full screen. I also wanted the stream to recover automatically from network and camera feed drops robustly. In addition, I wanted something better suited to this unusual always-on, always-streaming, always-plugged-in design. This eliminated OLED screens; this would be a worst-case scenario for burn-in. It also made devices with battery charge limits more appealing, since those limits could mitigate battery deterioration from always being plugged in. I also wanted something that didn’t feel awful to use, unlike Amazon’s bloated, ad-riddled version of Android for its Fire tablets. I wanted something I could keep getting software updates for forever. That pushed me toward a device I could run Linux on, rather than another Android tablet with an unavoidable end-of-life date. Finally, I still wanted this to be cheap. After doing some research, I settled on something like a Microsoft Surface Go 2. Powerful enough hardware and a nice enough display to handle a high-resolution HEVC stream. Able to run Linux. And, perhaps most importantly, BIOS-level support for battery limiting —when set, the battery stops charging when it reaches 50% of its capacity. Then, the stars aligned even more: I found a base (4 GB, 64 GB eMMC) Surface Go 3 on my local Facebook Marketplace for just $40. Score. Updating the BIOS Whenever I buy a used device I intend to put Linux on, I always update the BIOS using the Windows installation first. Most manufacturers do not have a way to update the BIOS without Windows. In the case of this tablet , released in 2021, I was able to bump my BIOS from a 2022 version to a recent release. BIOS updates often improve system stability and performance. Next, I prepared the UEFI settings for installing Omarchy and using the tablet as a kiosk. To get into the BIOS, hold Volume Up when hitting the power button. For this install, I have no data on the device. I specifically want it to be easy to boot, so that means skipping Omarchy’s default full-disk encryption. At the time of writing, this meant hitting at the disk-formatting confirmation prompt . This tablet only has a single USB-C port, so I needed a USB-C hub to plug in my mouse, keyboard, and USB drive simultaneously. I didn’t have the Surface keyboard (part of why I got a good deal on Marketplace). This one bit me. Omarchy always logs in automatically on encrypted installs because the LUKS prompt is the auth gate. On an unencrypted install, it logs in automatically exactly once. Then a one-shot service deletes , and SDDM shows a login screen forever after. To re-enable autologin, we just put the file back under a filename nothing will remove: Next, in the Omarchy menu, we set up . With that, the boot chain is fully unattended. Turning the machine on will take us straight to the desktop. is already installed in Omarchy. We can use a user service to launch the stream. We can keep the secrets out of the unit. Then, in : Then enable and check it: We can run on a different workspace, since the original workspace is now playing full-screen video. The recovery logic is all in . makes exit on a stalled connection rather than show a frozen frame, and makes it exit when the stream ends. relaunches it, starting with a 2-second delay and backing off toward 30 seconds during a long outage. Wi-Fi needs no special handling: reconnects on its own, and simply fails until it does. stops taps on the touchscreen from pausing or seeking, and uses VAAPI, which matters for heat on a fanless tablet. is there because Omarchy ships a default rule that floats the app ID in an 875×600 window. So, we can give the kiosk its own rule instead, appended to : A simple or a click on the “Stay Awake” icon in the taskbar will do the trick. This can also be done via the CLI: These settings are useful despite our Hyprland rule because the rule only applies while is playing. Without them, the tablet might still lock or sleep during downtime. These settings persist across reboots by default. Once the tablet is deployed, SSH is the intended way to interact with it. Omarchy has easy ways to set up SSH and Tailscale . The SSH setup installs OpenSSH, opens port 22 in (with a rate limit), handles key authorization, and disables password auth. Then, once Tailscale was set up, I disabled key expiry for this node. I like Omarchy for this because it is a rolling distro (based on Arch), which makes it easy to keep up to date. Just SSHing into the machine and running periodically does the trick. When a reboot is necessary, that’s no problem since the RTSP service will reconnect to the stream automatically on boot. Demo of the unattended boot. This turned out as nicely as I had hoped. All my goals were met, for cheap! Enable the battery limiter (Boot configuration -> Battery Limit: Enabled). Disable Secure Boot (Security -> Secure Boot: Off) (currently required to install Omarchy). Change the boot order to put the USB drive first.

0 views
Hillel Wayne 1 months ago

A Crash Course in Predicate Logic

I started writing Logic for Programmers because there weren’t any good resources on logic for, uh, programmers. Now that the book’s out, the new problem is that there aren’t any good free resources on logic for programmers. So, to solve that problem (and maybe hype the book a little), I converted the second chapter of LfP into a blog post. All footnotes are editorial comments not present in the book. 1 Enjoy! Formal logic is a very powerful tool, but it’s also very simple. Over this chapter, we’ll motivate and explain the basic concepts and syntax. This includes predicates, the implication operator, sets, and set quantifiers. Much of it may already be familiar to you from programming experience! To a first approximation, a predicate is a function that returns a Boolean. You’ve probably written dozens of predicates as a programmer. These are all predicates: I say “to a first approximation” because predicates are a mathematical concept, not a programming construct. A program function needs to come with a way of computing the answer, while a predicate simply defines what the answer is. Take : the software implementation would depend on the programming language, operating system, and possibly even the physical hardware. But the predicate? True if the computer has the RAM, false if not. That’s it. This means predicates can be more abstract than programming functions, expressing things that we can’t even compute, or at least don’t yet know how to. These are all valid predicates, too: On the other hand, is easy to compute: just check if . The power of predicates is that they can span the full range of abstraction. So let’s introduce some syntax to distinguish between abstract predicates and concrete predicates . If a predicate is abstract, I’ll wrap the body in `backticks`: This isn’t a common mathematician convention, but it’s clear enough for our purposes. To distinguish predicates from “ordinary” functions like , predicates will always be and functions will always be . Find some predicates in a program you wrote. Are these abstract predicates or concrete predicates?[^exercises] Solution Predicates tend to be functions that don’t change program or world state and return a Boolean. One I recently wrote was . Whatever predicates you find should all be concrete, as it’s impossible to actually code up an “abstract” predicate. You may see an abstract predicate or two in a design document, though. Since predicates return Booleans, now’s a good time to get some Boolean operations out of the way. Different programming languages have different symbols for AND, inclusive OR, and NOT. Mathematicians use ∧, ∨, and ¬. I’m not going to use these because they’re not found on the keyboard. Instead, I’ll use , , and as our symbols. So means “X is true and Y is false”. 2 On top of the three usual Boolean operators, mathematicians recognize a fourth, . But before we get into what that means, let’s try practicing what we just learned. Predicates act as a bridge between how we talk about systems in a human language and how we encode them in a programming language. Let’s come back to . I once saw a program with these requirements: The computer must have enough RAM and a fast CPU or a good graphics card (GPU). I find this confusing. The sentence sounds natural enough in English, but we can find a problem by formalizing with logic. We’ll start by first writing predicates for each subrequirement, like so: These predicates are abstract because we don’t know the specifics of what these mean. Is 64gb “enough RAM”? Is 32gb? The specifics don’t matter for us, because this is already enough to write as a concrete mathematical expression. Now the problem is clearer: is supposed to be read as or as ? The predicate is malformed and we have two different ways of making it make sense: Both interpretations make sense in English! But they have different outputs for some inputs. We can see this by listing every single possible combination of values for RAM/CPU/GPU, and see what they give for . This is called a truth table . 3 There are two combinations of inputs where one interpretation gives “false” and the other gives “true”. It’s possible that the vendor meant the first interpretation when writing the requirements, but I read it as the second interpretation. I’m sure that the program will run on my computer, it fails from insufficient RAM, and I think the vendor lied to me. Much better to express the requirement mathematically! Expressing properties with formal logic is less ambiguous than with informal English. For the purpose of teaching, we’ll assume the intended predicate is . We will use truth tables for case analysis in the chapter Decision Tables . 4 If you ever have trouble generating a truth table, you can try to use a truth table generator. I provided a simple one here . Try and experiment from there. Make a truth table for and . Solution P Q !P && !Q !(P OR Q) T T F F T F F F F T F F F F T T These are the same. This is called De Morgan’s law. Let’s now make a variation on our predicate. For our example: Some programs have a native version and a web version. The native version uses the local computer’s resources, while the web version does most of the processing on some cloud computer somewhere. So the native version requires a beefy computer, but any computer can run the web client. If a computer is running the native version, it must have enough RAM and a fast CPU or a good graphics card (GPU) to use this program. But if it’s not running the native version, you’re fine. To model this, we’ll need a new predicate, . is a property of the program, not the computer, so then depends on both: I used backticks here because half the predicate is still in informal English. It turns out that we already have the tools we need to make it concrete. Whenever is false, should be automatically true: we don’t need to even look at the computer specs. How does this work? It’s easier to see if we pull out the right hand side into a new predicate, like , so we have . Here’s the truth table for that expression (using for and for ): When is false, is true, regardless of the value of . When is true, then the expression is equal to the value of . So we’re only checking the computer specs if we’re running the native version, and ignoring it otherwise. This trick of writing to mean “check only if is true” is incredibly common in math. So common that mathematicians use a special operator for it: , or the implication operator. (“P implies Q”) is the same as writing . Expressed this way, our predicate is: binds less tightly than and : is , not . The implication operator is incredibly powerful and comes in handy in lots of different places, like writing specifications or making system models. Among other things, we can use it to say that one Boolean statement is “stronger” than another. 5 For example, “this code crashes when passed a 0” is a stronger statement than “this code contains a bug”. If it crashes on some input, it definitely contains a bug! But even if the program doesn’t crash, it can have a bug like an off-by-one error. Or, written mathematically: Implication is also useful because it’s transitive . If and , then we know , regardless of what P, Q and R actually are. If , then . Say we add two more conditions, so that is instead Write this using . Then write this without using . Which is easier to read? Solution I personally find (1) easier to read, since we don’t have as many nested expressions. means that “computer has sufficient RAM”. Modify it to mean “computer has enough RAM to run program ”. Make similar changes for our other predicates and write . Solution show all Solution show all Take the predicate: Assume c, x, and y are all booleans. Solution is equivalent to . If you work through the cases, you should find that is true when is true and is true, or when is false and is true. As hinted by the name, is simulating a conditional. Predicates have untyped inputs by default. In , can be a computer, but can also be a robot, or the number 26, or the string “the number 26”. In programming, we’d want to give it a type to make it clear that we should only pass in computers. Something like: Now, even if we glue a good GPU to a poodle, will still be false. To make the concept “c is a computer” mathematically representable, mathematicians use sets . A set is an unordered collection of unique values, like “all computers”, “all webpages under 500 kilobytes”, or “all strings that are valid Java programs”. Conventionally, we write the elements of a set like this: Then “c is a computer” is equivalent to saying “c is an element of the set ”. We’ll write this as . To make our predicate definitions more concise, I’ll borrow a common programming syntax and write to mean “ must be an element of ”, like this: This will make writing predicates with several constrained parameters easier. Mathematicians treat sets as a mathematical bedrock they can use to build out more complex concepts. For example, they might define pairs in terms of sets by writing as , 6 and then define the list to be the set of pairs . 7 Then, having a set-based implementation of the list “abstraction”, they can throw away the sets and just work directly with lists. As programmers, we don’t need to write formal definitions of lists before we use them and prefer to work with that more complex abstraction anyway. Even so, sets are still a useful programming data type. We’ll see this in the next chapter. Just like we have an arithmetic of numbers and an arithmetic of Booleans, we also have an arithmetic of sets. Given sets and , the basic things we can do are: We can also test if one set is a subset of another. is a subset of because every element of is also an element of . The value is not a subset of , but the set is. Subsets are similar to how programming languages have subtypes . 8 If a language says that “ is a subtype of ”, it means that the set of all rectangles is a subset of the set of all shapes. We’ll look at subtyping in more detail as part of the broader topic of contracts. Use the sets , , and to construct the set , the set of all computers that pass . Given the sets and , express the statements “nobody is both a child and an adult” by saying the sets do not overlap. You can use to mean the empty set. The symmetric difference of two sets is the set of all elements in exactly one of the two sets. For example, the symmetric difference of and is . Using just the basic set operations, find the symmetric difference of arbitrary sets and . . Another way would be . One way is ; another is . It’s also quite useful to map and filter sets. The standard math notation is , but I find that beginners get confused about which side is map and which is filter. So for this book, I’ll use a more explicit syntax: For example, the set of all even numbers is and the set of all square roots of even numbers is . This is sometimes called a set comprehension or set builder notation . Later, set comprehensions will form the bedrock of how we understand database queries. Let be a set of images, where each image is a record containing fields for name, height, width, and size in kilobytes. Write set comprehensions for: Solution show all Let’s move away from software requirements and switch to a different problem. Software development teams often require changes to the main code to be first proposed as part of a pull request, which must be reviewed by another team member. More concisely: A pull request must be reviewed by a team member before it can be merged. Let us assume that we have two sets, and , that we can use in our predicates. I can start with these abstract predicates to express the rule: Both of these predicates are abstract, but it seems like we should be able to make concrete by defining it in terms of . For this we need a quantifier , or a logical expression that acts on a whole set. There are two common quantifiers in predicate logic. The first, the one we’ll use here, is called . means that is true for at least one in the set . I would read this as “ is true for the Pull Request element if there’s at least one element in the set of Developers where is true”. Or, as just “there is some developer that reviewed the pull request”. Quantifier expressions translate to regular English. 9 The value is called a variable . The token is quantifying over the set or, alternatively, is scoped to that set. This makes our use of it a scoped quantifier . More rarely, an expression is true for any value we care to name. For example, the statement is the same as , regardless of what is. In this case, we can choose to leave out the sets and write: This use of is not scoped to a set, so we call it an unscoped quantifier . Almost all quantifiers we use will be scoped. So as to prevent eldritch math horrors, we can only quantify over sets and values, not predicates.[^function-sets] If you want to know more about eldritch math horrors, check out the appendix Beyond Logic . As it stands, is too permissive. What happens if the reviewer found a major security flaw? What if five developers review the pull request and two find flaws? Most companies use a stricter merge requirement: A pull request must be reviewed by at least one team member, and all reviewers must approve the request , before it can be merged. As is our habit, we start by writing the requirements as abstract predicates. This gives us an opportunity to introduce the other quantifier: all . says that is true for every in our set. With this, it seems like our new predicate can be written like this: But this is wrong: it requires every single developer to approve the pull request, including developers out sick or on parental leave. We only want to require that every developer who reviewed the pull request approved it. We can fix this with implication. Recall that means . Then means that either approved the pull request or did not review it at all. We often use to only an on a subset of elements. Most programming languages have built-in quantifier functions, as we’ll discuss in a later chapter. If your language of choice does not, you can usually approximate quantifiers with a loop. For example, you can write like this pseudocode: Why do we need at all? Isn’t it true that if everybody who reviewed the PR approved it, then someone must have reviewed it? Find the edge case where is and is . Solution If not a single developer has reviewed the PR, then is true (all zero reviewers approved!) while is false (nobody reviewed it). As a rule, is always true (regardless of what is) and is always false. Define as the set of natural numbers: 0, 1, 2, etc. Solution show all In both cases you’ll need to put one quantifier inside a different quantifier. Solution show all Now that we’ve seen both and I want to point out something important: is more likely to be true for large sets, and is more likely to be true for small sets. If we have two distinct sets and is a subset of , we should expect to find some predicate where and . We can say that guarantees . Let’s look at three examples: The set “all ASCII characters” is a subset of the set “all Unicode characters”. ASCII guarantees/assures that every representable character fits in exactly one byte, so I can look at the string and immediately know it’s three bytes. Unicode doesn’t guarantee this, and the string could be six bytes if I use Cyrillic characters. The set “things we can do with read-only access to a file” is a subset of “things we can do with full access to a file”. Read-only access guarantees that a program won’t change the contents of a file. Whereas with full access, any buggy program could overwrite our data. The set “all logical formulae that only use booleans, AND, OR, and NOT” is a subset of “all logical formulae”. The former guarantees that every formula can be turned into a truth table. How do you make a truth table for ? You can’t. Mathematicians still don’t know if it’s true or not! At the same time, we need Unicode to represent emoji, write access to update our data, and set quantifiers to express most interesting predicates. This is the ability-guarantee tradeoff : the more things a language or format or tool is able to do, the fewer things it guarantees us. 10 We will see this tradeoff in almost every chapter of this book. In the beginning of the book, I said that logic is the mathematics of Booleans just as arithmetic is the mathematics of numbers. Knowing arithmetic lets us simplify numerical expressions. For example, here’s how we can simplify the function : In logic, these simplifications are called rewrite rules . You may have already used one rewrite rule as a kid: Are you sorry? No? Well are you not not not not sorry? The rewrite rule here is . This means is the same as . Some common rewrite rules we use in logic are: The implication operator also has rewrite rules. One of them, contrapositive , will be very useful to us. And there are rewrite rules for quantifiers, too: Constant extraction works for any quantifier with or . Distribution only works for and . Some rules come up more often than others. We’ll be using De Morgan’s law, contrapositive, and quantifier duality a whole lot going forward. A larger list is in Rewrite Rules . Even niche rules can be quite useful for refactoring code. Use rewrite rules to simplify . Solution show all Give a real-world example of each distribution rule. Solution Here are two I came up with: only distributes over and only distributes over . Find predicates where: HINT: In both cases, make the left side true and the right side false. Solution There are many answers, here are just two (assuming is the set of all people who have ever lived): You may have heard that mathematicians try to prove theorems . A theorem is just a mathematical statement that is always true, and a proof is just a clear set of steps that gets you from what you already know is true to showing that the theorem is true. Every rewrite rule I listed is a theorem, and we can prove they always work. Take contrapositive, for example. To show that we can always rewrite into , we: Tada, we just wrote a proof! Try going the other way, starting from . Start from and rewrite it into . Solution First rewrite it as . Then replace with to get . Then rewrite that as . Most theorems can be proved in more than one way. Here’s a totally different proof of the contrapositive rewrite rule: Theorems are the foundation of mathematics. A theorem is what tells us if a logical tool (like the contrapositive or De Morgan’s law) actually works or not. We can use the machinery of logic without knowing the theorems that support them, just as we know without having to write a proof first. That said, we can also prove theorems about code, a specialty topic we’ll cover in its own chapter. Mathematicians like to say that logic is a “language”. The point of language is to communicate complex ideas clearly, and sometimes the best way to do that is to come up with new words and grammar. In logic, too, we can come up with new constructs and ways of writing formulae, as long as 1) it’s consistent and 2) we explain the notation clearly. In fact, this is encouraged. For example, the normal way of writing “the set of integers between 1 and 10” takes up a lot of space: If I want to be more concise, I can come up with a shorthand: If I want to be even more concise than that, I can define new syntax: This isn’t completely unambiguous: what is ? I’ll define it as the empty set: if , then is empty. Similarly, is empty whenever . Rewrite that rule (that if , then is empty) using the quantifier. Assume both and are in the set of integers. Solution show all Write using set filter notation. Filter on the set . Solution show all Write , which is true if is evenly divisible by . Use and . Solution show all Another notation I find very useful is conjunction lists . Complicated systems often have complicated requirements: That’s hard to read! To make it easier, let’s instead write it like this: 11 Numbers like and letters like will always mean AND. If I want a list of ORs, I’ll always use . Here are all the symbols and syntax we learned: And that’s it! That’s all the basics of formal logic. Really not that much, considering. The difficulty, of course, is in the application. It’s one thing to know division but quite another to realize that “scale a recipe with 5 eggs to use only 3 eggs” is a division problem. The rest of the book is about software situations where logic is useful, and how to make it useful. Let’s use logic to understand the world. By necessity this chapter is only a very broad overview of the basics of mathematical logic. More thorough and comprehensive treatments include (in increasing order of complexity) Robert S. Wolf’s A tour through mathematical logic , Michael Huth and Mark Ryan’s Logic in Computer Science , and Richard Epstein’s Classical Mathematical Logic . The logic we covered is called first-order logic because predicates cannot be values in sets or passed to other predicates. Higher order logics have more abilities and fewer guarantees; see appendix Beyond Logic for more information. Logic without predicates and quantifiers (only booleans, AND, OR, and NOT) is called propositional logic . The formal names for and are the existential and universal quantifiers, respectively. Mathematicians use the symbols ∃ and ∀. There are a few syntactic variants on the quantified expression; see appendix Math Notation for more. “Ability” in the ability-guarantee tradeoff is sometimes called “power”, as in the rule of least power (prefer the “least powerful” programming language that solves the task). I’ve also seen “guarantees” called “power”. As the “power-power tradeoff” is unclear, we’ll avoid the word “power” in this book. Logic for Programmers is now available in ebook and tree book formats. Check out the official site to learn more ! is true if x is greater than 0. is true if x plus y equals z. is true if the computer has at least bytes of physical RAM. is true if the computer is capable of running our program, whatever “capable” ends up meaning. is true if on , it rained somewhere in Canada. is true if aliens are real. Using , write the expression “if is true then is false, and if is true then is false”. Using , write the expression “ and are not both true”. Using , write the expression “ is false or is false”. When is true? When it is false? What common code construct does this look like? is equivalent to . If you work through the cases, you should find that is true when is true and is true, or when is false and is true. As hinted by the name, is simulating a conditional. Union them together, or smush them into one big set: Intersect them, or find the common elements: Take the set difference , or subtract one set from the other: Use the sets , , and to construct the set , the set of all computers that pass . Given the sets and , express the statements “nobody is both a child and an adult” by saying the sets do not overlap. You can use to mean the empty set. The symmetric difference of two sets is the set of all elements in exactly one of the two sets. For example, the symmetric difference of and is . Using just the basic set operations, find the symmetric difference of arbitrary sets and . . Another way would be . One way is ; another is . The set of all image names. The set of all images larger than 10 kb. The set of all heights for images that are squares. Write the logical statement “every natural number is smaller than itself plus 1”. Write the logical statement “0 is less than or equal to every natural number”. Write the logical statement “for every PR, there is a developer that approved it”. Write the logical statement “there is a developer that has reviewed every single pull request”. The set “all ASCII characters” is a subset of the set “all Unicode characters”. ASCII guarantees/assures that every representable character fits in exactly one byte, so I can look at the string and immediately know it’s three bytes. Unicode doesn’t guarantee this, and the string could be six bytes if I use Cyrillic characters. The set “things we can do with read-only access to a file” is a subset of “things we can do with full access to a file”. Read-only access guarantees that a program won’t change the contents of a file. Whereas with full access, any buggy program could overwrite our data. The set “all logical formulae that only use booleans, AND, OR, and NOT” is a subset of “all logical formulae”. The former guarantees that every formula can be turned into a truth table. How do you make a truth table for ? You can’t. Mathematicians still don’t know if it’s true or not! is the same as . is the same as . The first two terms are opposites, so they cancel out. So we have just . “All days this week are warm and sunny” is the same as “all days [this week] are warm and all days are sunny”. “Someone has blue eyes or green eyes” is the same as “someone has blue eyes or someone has green eyes”. is true, is false. is true, is false. Start with . Apply the definition of implication to get . Remove the double negative to get . Apply the definition of implication again to get . Draw the truth tables for and . They are the same. A predicate is a Boolean function which can be defined over any input. A set is an unordered collection of unique elements. Sets can contain any type of value, except predicates. A quantified expression is an expression checked for every (or any) element of a set. Math notation is flexible. We can come up with new notation, operators, and grammar, as long as we’re clear and consistent. Logical formulae can be rewritten and simplified. Predicates are always . Functions are always lowercase and . AND, OR, NOT: , , Set union, intersection, difference: , , Set map and filter: Quantifiers: and Various syntactic sugar. Just to set expectations up-front: the book is based on first-order classical logic, because that’s all you need to drive a couple hundred pages of applications. There’s no constructive logic, no lambda calculus, no Curry-Howard, etc. The target audience is people who don’t know any math. [return] One really important design decision I had was “all math used in the book should be typeable on standard US keyboard”. Most “official” math symbols are meant to be handwritten, which makes them a lot harder to use on a computer! [return] The blog post uses instead of in tables because breaks markdown table formatting. [return] Not shown: the book is heavily indexed and interlinked; this and all the other “this will be useful for XYZ” comments have page numbers of other chapters. [return] I first explored this idea in the newsletter Some tests are stronger than others ; chapter 4 of the book uses it to motivate property testing. [return] Yeah uh okay in reviewing this post I just realized I got this wrong, the actual mathematical convention of pairs is actually . Sorry if this ruined the whole book for you. At least it’s now in the errata . [return] The book uses 0-based indexing for lists (unless discussing a language which does otherwise) and starts the natural numbers at 0. Different branches of math have different conventions and I just stuck to what most programmers are used to. [return] One thing the book does not cover is type theory. There’s a few reasons for this, mainly that I couldn’t find the right balance of simplicity, practicality, and actually-using-logic-ity. It does cover some basic things like Make Illegal States Unrepresentable and Liskov subtyping. [return] Extremely cursed fact I learned while making this figure: The SVG standard defines a “point” as 1 / 72 inches, while TeX defines a “point” as 1 / 72.27 inches. I wrote more about the history here . [return] Coming up with the dang name for this took forever. I used to call it the capability-tractability tradeoff but the book was pretentious enough as-is. Thanks to Chelsea Troy for finally coming up with something workable. [return] This is the best idea in the book. I love conjunction lists. [return]

1 views
neilzone 1 months ago

Automating local backups of UniFi OS Server on Linux with uos-backup

Earlier today, I migrated my self-hosted UniFi controller from Network Manager to UniFi OS Server . One of the annoyances of the new setup is that it does not allow automated local backups - just automated backups to Ubiquiti’s cloud. Fortunately, one can work around this. In the UniFi interface, I set up a new local user, , to use for this automated backup. I am using . is a simple Python scripts which someone has kindly written and shared. I did the following, on the machine I wanted to use to take and store the backups. Get the code: Change to the directory with the code: Edit the python script, for the correct URL, username for my new backup user, and password. Check that the requirements are met: Copy the script to : Make it executable: Create the directory to store the backups. This is the directory specified in the script; you can create a directory with a different path, and then just update the script according Test that the script works: Even though I had just set up a new user, I had managed to get the username and password wrong in the script, and this step helped me debug it. I checked in /var/lib/uos-server/ to check that I had backup files. Set up the systemd services: I then added the backup directory path to restic, so that it gets picked up with my automated restic backups too.

0 views
neilzone 1 months ago

Migrating my self-hosted UniFi controller from Network Manager to UniFi OS Server

One of the jobs that has been on my list for a while is to migrate my UniFi controller installation from the self-hosted network manager tool to the new UniFi OS Server tool. The only reason that it was a job at all is because UniFi has decided to discontinue support for the UniFi network manager. Which is probably for the better, as it contained outdated packages anyway. Frankly, I’m not massively impressed with UniFi any more. If I were starting again, I am not sure that I would pick UniFi kit, but I don’t know what I would go for instead. I simply want to run my own controller, without external access or access by anyone else, to control the network infrastructure at home. I did the migration, and it mostly worked. Here’s what I did: I read the Unifi OS Server installation instructions . I also read the Backups and Migration in UniFi instructions. My UniFi controller is running in a virtual machine, so I took a snapshot of that first. If all else failed, I could roll back the snapshot. I backed up the configuration of my existing UniFi network manager configuration. I downloaded it to my local machine. I also backed up the ssh configuration information for my UniFi devices, in line with the instructions: It is also recommended to copy the SSH username and password from Devices > Device Updates & Settings > Device SSH Settings, in case any devices need help later when connecting to the new instance of UniFi Network. I stopped the UniFi network manager with . I followed the Unifi OS Server installation instructions . It will be interesting to see how updates work. The instructions say: Captive portals will be served on port 8444, changed from port 8843 on Network Server. It did not mention that there was also a change to the port to the controller. However, the final line of the set up information showed that it was port 11443. So I changed my nginx proxy config from 8443 to 11443, and reloaded nginx. I could now access the new UniFi OS Server interface. It went downhill from here. I was intending to restore from backup, so I clicked the option for this. It then prompted me to - forced me to - sign in with a ui.com account. I’ve no idea why. It is a local controller, and I don’t want any remote access facilities. Nevertheless, I could not find a way around it. So I did, but I can’t say that I am impressed by this. It then said: We’ve discovered that you already have a self‑hosted UniFi Network installation. Would you like to import your current network settings into UniFi OS Server? But the options were not “Yes” and “No”, but rather “Continue without importing” and “Next”. This was a surprise anyway, as the instructions say: On macOS and Windows, the installer will automatically detect and offer to migrate your existing Network Server setup (if installed in the default location). On Linux, or if auto-migration doesn’t occur, you can manually migrate by installing UniFi OS Server and using the Site Export tool I am running it on Linux, so I did not expect any migration. I guessed that “Next” means “yes”, so I selected “Next”. It took me to a url ending . This was a blank screen. Nothing at all. I waited a couple of minutes, then refreshed the page. It then showed me a page showing that it was “restoring backup”, but the progress bar remained blank for quite a while. It also said that it was restoring to settings from January 2026, not last night’s backup, which surprised me. After a couple of minutes, the progress bar flashed by, and it was done. The import/migration appears to have correctly imported all my devices, and is set up to talk to them. But other aspects of the migration were underwhelming. It did not restore the settings for my mailserver. It was preset to use the “UI Mail Server”. I set it up to use my own mailserver, and it failed, with a useless error message. When I logged in to my mailserver to see what was going on, I saw . It appears that I am not the only person with this issue , albeit with a slightly different setup. They seem to have resolved it by disabling TLS, which is not an option for me. I have not yet got this to work. Even though I had configured automatic backups on the previous Unifi Network Server, they were not enabled on the new UniFi OS Server. I tried to set it up, but I was prompted for my “Ubiquiti SSO account password”. I tried the password for my ui.com account, but I got an error message of “Something went wrong. Please try again later.” Which was no use at all. Having turned off Remote Access (below), I went back to the Backups dialogue. Now, there was an option to download, or upload & restore, but nothing about automation. The info box says that I can schedule backups here, but there is no user interface for that. I took a manual backup. I cannot see a way to do automated backups to my local file system. If this is correct, this is absurd. I may see if I can do something using the command line. *Edit: yes, I can, with python and systemd. See Automating local backups of UniFi OS Server on Linux with uos-backup . “Remote access” is enabled by default, even though I am confident that I did not have remote access enabled before. When I attempted to untick it, it showed a dialogue box: So I disabled it. https://help.ui.com/hc/en-us/articles/220066768-Updating-and-Installing-Self-Hosted-UniFi-Network-Servers-Linux It did not restore my preferred time format (24 hours). I had to turn off analytics, which was on by default. It worked better than I was expecting, but that’s mainly because my expectations were very low. Why the email server and automated backups do not work, I do not know. I will need to investigate these. But at least I am now running a supported controller again. Once I’ve done a scan of the new system with greenbone, I’ll be interested to see what it reports.

0 views

Verifying (simple) C in Isabelle/HOL with AutoCorres

This post details the first steps of verifying a C function in Isabelle/HOL using AutoCorres. It'll go over the basic setup for Isabelle and AutoCorres, what AutoCorres gives you, and how we can verify some basic properties of a function (here, the sum of a list.) I use the latest versions of Isabelle and AutoCorres available at time of writing (24/08/26). This is not official documentation for AutoCorres, nor may it be 100% correct in all places. All the proofs go through, but I do not work on AutoCorres, nor have I used it professionally; most of my experience is hobby verification. However, I have found resources on it are woefully lacking, so I wished to introduce some more. Much of this information has been gleaned from the official documentation (which can be found at the aforementioned,) and this course . When it runs, the slides/similar may be removed for some time - they exist on the internet archive also. This article assumes a little either Isabelle/HOL or general verification knowledge, but I try to explain wherever feasible. A bit of C knowledge is required as well, and so is a little knowledge about program verification - a little Hoare logic, and the like. I'll try to explain as much as I can without being excessively verbose, and much of it is very searchable. You can find Isabelle here . Install as is appropriate for your platform. You can decide whether to put in your path or not. First, pick a directory for your project. It may be feasible to use AutoCorres globally, but I wouldn't recommend it for versioning reasons. Then, AutoCorres can be found by scrolling down here . (It will probably take you here , whereupon you should scroll down to the latest AutoCorres release). You only need the AutoCorres download, as it bundles the C parser. Take that file, and extract it in your directory of choice. Then we need to build AutoCorres. From the directory where you unpacked it, run This'll take a second. Replace paths as appropriate. Note that is not the architecture you are on - it determines how the tool translates various C sizes into Isabelle. This will need to be the same architecture you launch with later. There are some others, and on a related note: It may not all make sense yet, but it may answer some questions. I recommend making a little shell script for this step ( perhaps.) You'll need to invoke Isabelle in a manner similar to the following: Remember that needs to be the same as earlier. Now that we have Isabelle running, we can use AutoCorres to generate Isabelle versions of C files. The manner in which this is performed is long, complex, and interesting - I recommend a rabbit hole evening - but in short, the C parser first translates it to a deep embedding in a language called Simpl, and then AutoCorres takes this Simpl representation and turns it into a monadic shallow embedding best it can. Here's what we'll be verifying today: The use of unsigned will be expanded on later. We'll put it in a C file named . At this point, your directory should look something like or similar. Begin a regular Isabelle theory, importing AutoCorres (and whatever else you want): We then need to let AutoCorres perform its magic. First, we "install" the C file with the C parser: You can use to see what this defines. Of particular interest is . Then, AutoCorres: The idea the C parser and AutoCorres use for verifying C is that it is reasonable to do a very direct translation of C to Simpl, and then a refinement to the monadic representation. The C parser is correct through inspection, and extensive testing. However, the translation of Simpl to the monadic form is verified - there exist Isabelle-checkable proofs that show that the monadic representation, however different it may be, behaves identically to the Simpl equivalent. This makes the monadic form a refinement of Simpl, and is why the things we prove about the monadic forms translate back down to C. This might also take a second. We choose to use . This makes unsigned integers perform modular arithmetic instead of using overflow checks; this has positives and negatives. Read the README for more. Again, you can use to see what this gives you. Of particular interest is . This is the monadic embedding of our function. We then need to enter the locale (think of it as an environment) defined by the C parser and AutoCorres, so your file should look something like: We do our work in this locale. If you haven't already, and . The former is the deep embedding produced by the C parser, and the latter is the result of AutoCorres's shallow embedding. These can be unfolded with and respectively. You can examine the types of things with ctrl-hover (cmd-hover on Macs.) Also examine . The type for the monadic state used by AutoCorres is called (You may see it displayed as in some places.) It's a record containing fields for each type of pointer used; ours only uses , so it only contains information for 32 bit words. We can examine the extract and update functions used in with and . Which monad AutoCorres chooses to embed a function into depends on the function, and can also be configured. This function is simple enough that it can be encoded with purely , but others include , (option with state,) and . Yes, this is a reasonable question to ask. What does it actually mean to verify this function? Generally, there are a few reasons to verify something: We'll look at all three. The not failing example will go into quite a lot of depth, whereas the correctness example will go into much less, only covering broad strokes. This is because the proofs are quite similar, and if you find the former excessively verbose, you might want to skip to the latter. Let's consider what we need for this function to not fail in C. The obvious constraint is that must be defined for all . Using , we can state this as a definition: Now we have our suitable precondition, let's set up our "doesn't fail" lemma. We expect that: We can state this using the combinator . The NF stands for , and it adds the additional condition that our program does not fail in some way during execution. Precisely what we want! It takes three arguments: The arguments are: Here, is used because of the choice of state monad AutoCorres makes. Note that our postcondition takes both the state and the return value . When we use with , will be our state . So, our lemma then becomes: If we have our list defined properly and some property Q, and we run our program, then it does not fail and Q is still true. If you're familiar with Hoare logic, you'll know we probably want to use some sort of weakest precondition reasoning. A weakest precondition is roughly "what is the smallest amount of information we need to know for this to be true", which allows us to simplify our proof obligations. Indeed, AutoCorres provides us with a family of tactics such as and . However, I find it nice to start these proofs by unfolding the function at hand, and applying or similar to get some simplification going. Then we can apply to apply relevant weakest precondition rules automatically. This should leave you with a state something like: I highly recommend using the Query tab of jedit throughout (or equivalents like .) will come in handy, and it's a nice fuzzy search. will do what's on the tin, and will find theorems that could apply. As we have a while-loop, a reasonable step is to add an invariant. An invariant is something that is invariant over the loop - it is always true, at the top of every loop cycle, and right after the loop finishes. This is how we conclude things about what a loop does. Indeed, we can see a theorem of use: Most of the time, the prefixes can be omitted. So first, we add an invariant, and then we can use to transform our into theorems we can work with. The invariants we care about right now are: We also care that this loop terminates, so let's add a suitable measure. is our invariant, and is the termination measure for the loop. Note the type conversion in . Then, again: This'll probably give you a few more normal looking goals. can come in handy again to chunk these down. This leaves me with: The first we talked about earlier - we can solve it by unfolding via , and basic reasoning. For the second, it seems obvious - why hasn't solved it? (If it were on s, it certainly would have.) Alas, it's on s, which as we have chosen to use modular arithmetic, are slightly less nice. It's hard to search for theorems involving as it's so overloaded, but luckily here finds . The final goal is more interesting. The initial intuition might be to use again, but this leaves us with nasty metavariables because of the chaining nature of the binds, and the fact that AutoCorres here is slightly too general. With a little searching we have the following: But we only really need to be identical to . We could instantiate manually each time, or we can make a little helper lemma (which I will do.) Above this lemma, we add Then we can apply twice (There are two binds.) We hence have: It's finally time to start unfolding the definition of so we can crunch down these last few goals. If we hit the first goal with: we're left with which takes out nicely. Tip: I fiddled with this for a bit manually, but if it looks fiddly and obvious, there's a good chance sledgehammer can do it. Finally, the last two are easy: We have a proof of non-failure! This is very exciting. The final proof is as follows: Then, what does it mean for our function to be correct? Well, a reasonable definition is that it produces the output we expect. We then need to figure out what "output we expect" means. We could have also defined the sum of a list as similar to the following recursive function: Note that we explicitly check for 0 to ensure the recursion terminates (We can't pattern match on 0/Suc, as we're working with s.). Unfortunately, this function's termination can't be proven automatically due to the use of a , which Isabelle isn't as good with as its own s. This is why we use a instead of , and we must do the termination proof ourselves: We also delete from the default simp set because it seems to cause some solvers to loop. We can set up our lemma like before, but this time, we use the parameter: We want the result to be equivalent to summing the entire list with our recursive function. We also don't bother to prove that it doesn't fail here. We can begin as before, omitting the step. We then need to annotate with a suitable invariant. A hint: We want the sum at the end to be correct, so a good invariant should capture correctness at every step , which gives us full correctness when the loop finishes. We have to manually a few times as we deleted it from the simp set, but otherwise the proof is very straightforward. Mine came out to be: We could take this one step further if we wanted, and define a bijection between and assuming our precondition, and then also show that our own spec is equivalent to the sum of that list. That'd give us even more confidence our function is correct. I'm personally pretty convinced, but you're welcome to try this yourself! We could then finally prove that if some property is true for the sum of a list, it's true for the result of our program. I won't detail this one; it should follow reasonably easily from the former two. So, what have we (hopefully) learnt? I hope this has been informative! (With less fixing of indentation for the web, sorry): A bit underwhelming for how long it took to explain, perhaps! To prove it never "fails" (what failing means is another question) To prove it produces some desired output To prove it holds some desired property is a , as perhaps expected. has been converted into a . is our state for the function, and carries information as mentioned about the heap. We use the perhaps confusing for pointer addition. You can write this in jedit with . We also need an explicit type conversion , as you can add negatives to a pointer, so the argument is an (this is not unsigned int; that's . here corresponds to Isabelle , which is signed.) If: The list is defined properly Some other property Q is true Then: Our function does return successfully, so That property is still true. A precondition function. A computation function. A postcondition function. (for termination) (for no-failure) (for simplified correctness) How to install and setup Isabelle and AutoCorres What it means to prove things about programs How to set up appropriate proofs How to prove them What tools we have available and how we can search for more

0 views
Giles's blog 1 months ago

Adding diagrams to my static site generator with D2

A lot of the time when I've been writing posts for this blog, I've felt that a diagram would really help. But they're a pain to produce well, and I think I underuse them as a result. I wanted to fix that, and wound up adding D2 support to my static site generator. I think it works pretty well! In the past, I've tried drawing my own diagrams in LibreOffice and exporting as SVG, but my complete lack of artistic skill doesn't help: Asking an AI to do it for me helped in simple cases: ...but with something less standard (there must be a million neural network diagrams in their training sets) it can be really fiddly to get something right. I did some investigations into the various diagram-generating tools out there, and decided to give D2 a go. It has a simple language for specifying what your diagram should show, and the output is pretty nice: Here's the source for that diagram: That looks pretty clear to me! So now, in the source for my blog posts, I have a directory. That contains subdirectories -- by convention, I create one for each post that needs diagrams -- and D2 files. These can be generated automatically when I publish: (Hat tip to Evan Hahn for the method on , which I wasn't aware of.) The flags on the command line took a little bit of fiddling; the just gets rid of the large margins that D2 puts around the diagram by default, but the others are to tell it to use the ELK layout package with particular formatting. Its default layout has curvy lines, and I prefer the closer-to-right-angle ones that ELK provides. Another awkward bit was in scaling; the file that is generated by that command comes out pretty large ( you can see it full-size here ). By default, I allow images inlined into my posts to be as wide as the text, but that would still be too large here. I use to convert the markdown source for my posts into HTML, and there isn't any way to tell it what size an image should be using markdown-ish syntax. So for now, instead of embedding images the normal markdown way, like this: ...for these D2-generated ones I'll just embed a normal tag like this: ...so that I can control the size. Perhaps more work needed there. At some point I may go back and update my old diagrams -- at least, the really ugly hand-drawn ones -- to use this. And a random thought: perhaps it might also make sense to include the D2 source somehow on the blog? I can imagine that it could help with accessibility in some situations, and perhaps also for any LLMs stopping by. Will have to ponder that a bit more. What do you think? Does the D2 diagram look good to you? Or is there a better diagramming package that might work better?

0 views

Forgejo hack: How to set a starting issue and pull request number

I'm currently working on migrating my open source projects from GitHub to a self-hosted Forgejo instance. As part of this effort I often end up looking through the Forgejo source code to figure out if there are hidden ways to configure certain things to my liking when I can't do it on the administration UI. I thought I'd start putting my discoveries in writing here, in case they can help others. So here goes the first one. One of the aspects of the migration that is tricky is how to transition issues and pull requests. What makes the most sense to me is to only use Forgejo to track issues and pull requests going forward, leaving all the issues and pull requests created up to the migration point on GitHub. Of course whether this is a good or bad idea is debatable, but considering all the options I have decided that this is the solution that is going to inflict the least pain on me. The one problem with this approach is that I would end up having duplicate issue numbers, because Forgejo would start creating issues and pull requests all the way back from , and all those low numbers have been used on the GitHub side. So I wanted to hack my Forgejo instance so that issues start from, say, 10000. That way when anyone references an issue by its number I would know that numbers below 10000 are on GitHub and only those above are on my own instance.

0 views
Kev Quirk 1 months ago

2026-08-24 11:01: A friend asked for help setting up a blog, so I went with #Pureblog (obviously)....

A friend asked for help setting up a blog, so I went with #Pureblog (obviously). We went from nothing to fully working blog, with all the customisations she wanted in about 25 minutes. I know I'm bias, but I love how flexible and powerful Pure Blog is becoming. Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️ You can reply to this post by email , or leave a comment .

0 views
Ahead of AI 1 months ago

How Claude Watermarks AI-Generated Text

I recently posted a Substack note about Claude’s new watermarking process and implementation. Since it’s such a popular topic and sparked such a lively discussion, I thought it might be interesting to go into a bit more detail when explaining how it works. Instead of the usual text article, I recorded a little lecture on the topic (to change it up a bit from my usual articles). So, below is the video along with a transcript. Originally, I planned to make 10 slides and record a short 10-min video. However, while putting it together, I added some crucial details here and there, resulting in >50 slides and a 48 min recording. I hope that this now explains it well, though! Happy watching! I also have a YouTube version if you prefer using the YouTube player And here is a link to the slides Subscribe now Note: The transcript below is slightly edited and cleaned up for readability but preserves the overall order and flow of the video lecture above. Slide 2 of 52, time stamp 0:00 Hi everyone. So, a few days ago, Anthropic announced that they will watermark the text outputs of their Claude models. I then did a social media post briefly explaining how that works. And yeah, this was quite the popular post. So not the watermarking itself was popular, but I guess the explanation or the mechanism behind it. Then, it might be worthwhile expanding this a bit to explain it in more detail, because this post only had one figure, and there were a lot of questions and discussions. So, I thought, well, let’s make a few more figures. I actually originally planned to do like 10 slides and walk you through it. It ended up being 50 slides, but I hope this really explains how this watermarking technique works well, how watermarking itself can fail or be removed, and so forth. So I think it might be an interesting topic because a lot of people use LLMs these days and also consume a lot of text on the Internet that might be generated by LLMs. And now there’s going to be this watermarking, and there’s this, I guess, fear of watermarking making text worse, or what’s actually the benefit of this watermarking? And so what does it mean? And I think if we understand a bit better what watermarking is, that goes a long way, and then we can make up our own minds about whether that’s a good thing or not, and so forth, like the pros and cons. So, my goal here is really to explain how the underlying mechanism works and how they are going to implement this type of watermarking, text watermarking. Slide 2 of 52, time stamp 1:41 It’s also a great example to illustrate why understanding things from scratch is actually quite useful. This watermarking technique is also a nice way to explain how conventional models or LLMs in general work under the hood. So yeah, you may know I like doing things from scratch. Like, I have my books: Build a Large Language Model From Scratch, Build a Reasoning Model From Scratch. I have some articles labeled from scratch. So, for me, “ from scratch often includes coding. So this one will not be coding-related, but coding from scratch is actually a very, very useful technique because it really helps you understand how something is implemented. And then from that we can derive our understanding, figures, concepts, because if we don’t really implement things, if there’s no code, it’s really sometimes ambiguous. And of course, you know, as I realized, not everyone is coding from scratch anymore. Like back in the day, coding something from scratch was all we had. I mean, there were only humans coding. Nowadays, coding can be done by LLMs. However, that doesn’t mean reading code is no longer useful, because it carries a lot of information. So in this case here with this watermarking, spending some time coding an LLM from scratch really makes you realize how this sampling inside is implemented. We still have some relevant code snippets. And then that really, in turn, helps us understand, oh, the watermarking is applied at this position, and this has so-and-so consequences and so forth. So I think even though people may not be coding from scratch, at least not all the time anymore, it is still useful being able to, let’s say, build something from scratch for educational purposes to understand something deeply and then also for research purposes to manipulate this in a transparent way that is not hidden away in tons of layers of abstraction. But that aside, I think it’s just a coincidental nice relationship here because, for this slide deck, I actually used a lot of figures from my from-scratch coding materials. Slide 3 of 52, time stamp 3:58 So a few days ago (this is August 14), there was this article, How Claude’s Text Watermark Works, and there was this article here; it’s just like a screen recording, so it can have everything in the slides, but there’s plenty of detail. They updated it actually a couple of times, so originally when I read this, it was a way shorter. Still, it is very, I guess, conceptual; there’s like this overview, and there’s, I mean, there’s not a single figure in there. And so it’s kind of still hard to understand what they’re trying to do. So they explain a lot about why they’re going to do it, but they don’t explain how. They’re linking to one paper somewhere there, which is very technical also. So I do think it makes sense maybe to take a step back and start at the beginning to kind of understand what they’re trying to implement here with this watermarking technique. And so the motivation, by the way, of watermarking is for them to identify if someone posts some text that they can say, oh, this text was generated by our Claude Opus 4.8 model, for example, so that they have a way to tell, OK, this text is AI-generated because it carries this watermark. And this watermark is invisible to users, so only they can decode it and find out whether the text has their watermark. Why can only they do it? We will get to that later in this (hopefully not too long a video), but one thing at a time. Slide 4 of 52, time stamp 5:35 So I wanted to start with a brief prelude to explain how text generation works in LLMs, because based on that we can then more easily understand how the watermarking works and that this is actually not a huge, expensive thing on top of it. It’s really just like a minor, I guess, tweak inside the regular text generation process. Slide 5 of 52, time stamp 6:01 So when we are using something like ChatGPT, for example, let’s say I ask the question, the capital of Germany is, and yeah, ChatGPT or other LLMs, so this is just like an example would, for example, answer “Berlin”. So here, in this case, it’s generating two tokens, like “Berlin” and the period. But for simplicity, let’s assume it’s generating one token. So the next token is the “Berlin” token. How is this token generated internally? What is happening under the hood when we type something here like the capital of Germany is and receive a token like “Berlin” back? What is actually going on there behind the scenes? Slide 6 of 52, time stamp 6:41 So in the next couple of slides, I want to briefly talk about what happens under the hood when this next token is generated. Slide 7 of 52, time stamp 6:50 So assume again that our prompt is the capital of Germany is. And the first step here is to convert this into token IDs. So tokenizing it and converting it into token IDs is one of the main steps at the beginning. This is outside. It’s not inside the LLM; it’s outside of the LLM. So we are simply converting the text into token IDs. It’s just a format that embedding layers can work with. Slide 8 of 52, time stamp 7:22 And then this passes through the LLM. And the LLM gives us a score distribution for the next token. Slide 9 of 52, time stamp 7:31 So again, this is just like a brief overview of how LLMs work internally. So I’m not covering the LLM machinery itself. I talked about it many times in my other From Scratch LLMs videos and books. The important part is that when we generate the next token (for example, “Berlin”), we have, at this point, a distribution of scores. So this is the output produced by the LLM. Here in this case, we’re looking at logit values. So these are just scores from minus infinity to plus infinity, like a range of scores. Here’s an example, ranging from about -8 or -9 to 20. We could convert these into a probability distribution, but technically, it’s not strictly necessary depending on how we sample. But so you can think of the logit values as the raw scores. And the raw scores go over the entire vocabulary. Slide 10 of 52, time stamp 8:39 That means every possible word that the LLM could generate. Now here, in the vocabulary index, a certain value (index position 19,846) receives the highest score. So I spread out the distribution. If you would run this prompt through an LLM, you would even see something more extreme: that everything is, like, very, very, very close to zero. And “Berlin” would probably be much, much higher even. But just to show you a few, you know, like peaks here so it looks a bit more interesting, I kind of zoomed in; in and spread out the distribution a bit. Now here, “Berlin” is the highest score because you can think of it as the most, I guess, probable or plausible next token if I have a very specific prompt like this. So the other ones, I mean, it could be something like Hamburg or Munich that the LLM might guess incorrectly. But nowadays an LLM should be fairly certain that “Berlin” is the correct answer here. You are also seeing here the vocabulary index. So that’s like over the whole vocabulary. Nowadays, LLMs have like 250,000 possible tokens as output. I’m truncating it here from 19,800 to 19,900 because there’s just so much space here on this slide. If I would have a very realistic vocabulary of 250,000 words, everything would be so narrow that we would barely even be able to tell or see anything on this distribution. So this is just truncated for educational purposes. The important point is that in regular text generation, we get this score distribution. Now, what we do is look at the highest score. Slide 11 of 52, time stamp 10:33 I will get into more detail later on how this is selected. So it’s not necessarily precisely the highest one, but for simplicity, assume we are taking the highest score here. And in this case, it’s 19,846. Slide 12 of 52, time stamp 10:52 And this score is then detokenized, and we get “Berlin” back. So that is the process here on this slide: from an input prompt to conversion into token IDs and tokenization, passing it to the LLM, getting this score distribution, getting the next token, and converting it back into text. Slide 13 of 52, time stamp 11:12 And then this text is appended to the input. So if we have a question that requires multiple output tokens, we keep going in this loop until the answer is complete. That usually means that the LLM generates an end-of-text token, for example, here. For simplicity, I’m showing you only one iteration where it generates one token. But yeah, as I said, it would kind of continue like that, where we are feeding back the modified input to the LLM for the next round. Now, how do we actually sample this next token here? Slide 14 of 52, time stamp 11:44 I briefly said, well, we could just technically select the highest one, the one with the highest score. This is called greedy decoding. That’s one way to do it. But most LLMs, like if you use them, they don’t do greedy decoding where they always pick the highest one. Because if you ask it on some other prompt, it might not be what we want to always have the highest score, because then it would memorize the training data. It would always kind of give the same response and so forth. So we actually often want some variation in the outputs, but not so much that it generates random stuff. So how it works is that, when we sample here from this distribution, we first typically convert it into probability scores. Slide 15 of 52, time stamp 12:28 So here I just have these scores shown in this plot. I’m just using NumPy for simplicity; whatever tool you use (e.g., PyTorch), the same concepts apply. But let’s assume we have the scores here in NumPy. So what I would do is I would compute the softmax. Technically, I would use a softmax function implemented in Torch or PyTorch, for example, that is numerically stable for both large and small values, including very high positive values, very low positive values, and very high negative values. Here I’m just writing it out like that. That’s the canonical softmax, just to make it a bit more readable. But the details don’t matter here. Slide 16 of 52, time stamp 13:20 What matters is that after this conversion, the scores here, I mean, there’s only so much space on the slide, but the scores here, they would add up to one. So it’s essentially like a renormalization. So they would be normalized to sum up to one. That’s all that the probability conversion does: the softmax conversion. So then once we have these probabilities, we can use a random number or, like, a random sampling algorithm. For example, here in NumPy, we could use the choice function or method. So this is with a specific random seed we are passing to the vocabulary indices. And then, and that’s the important part, we are passing the probabilities as the weights. So, these, essentially, yeah, are like: “How likely is a certain token to be selected?” So, for example, if “Berlin”, after this normalization step, the softmax step, has a 99% probability and the other ones together have a 1% probability, then if we would sample 100 times, 99 of the times, we would get “Berlin”. In realistic LLMs, for example, that are well trained, “Berlin” might receive a probability of 99.999999 or something like that. So you’re almost certainly always sampling “Berlin” because it’s very confident that the answer is “Berlin” in this particular case. So yeah, that is how we would sample from this distribution. There are modifications like top-k sampling or top-p sampling where, let’s say, just for simplicity in top-k sampling, we would select the top 100 tokens and then apply this random choice only to the top 100, the 100 highest-scoring ones, so that we don’t get nonsense tokens in there. For this example, it doesn’t really matter. I mean, it’s just like another thing to explain, so I’m skimming over this. So you can maybe assume that this is already the top 100 tokens using top-k or something like that. Slide 17 of 52, time stamp 15:37 And so, for example, here’s an example. If we sample 10,000 times with a probability of “Berlin” being very high, 99.9, we would sample “Berlin” 9,997 times, sample the word “Hal” twice, and one “Moh”. And these are basically nonsense tokens. It rarely happens that, in this case, the LLM might produce nonsense because, as I mentioned before, I spread out this distribution a bit to make it more interesting. A real LLM would probably, 10,000 out of 10,000 times, sample “Berlin” because the probability of “Berlin” is so high. But this is for illustration purposes. Slide 18 of 52, time stamp 16:21 Now we briefly talked about how LLMs work under the hood, which I think is kind of an interesting concept in itself. But I’ve talked about this many times before, so I don’t want to bore you. I just wanted to set up some context for now, explaining how this watermarking works. Slide 19 of 52, time stamp 16:41 So, we mentioned that we select the highest-scoring token when sampling. Or we use this probability sampling, which will lead to one of the highest-scoring tokens being selected most of the time. Now here’s another example without watermarking. I changed the prompt slightly. Now the prompt is: today’s weather is “cold,” and a possible answer could be, for example, “gray” or “overcast”. So in contrast to the “Berlin” example, I would say “gray” and “overcast” kind of are interchangeable. They are both reasonable next tokens for this prompt, given the goal of completing this text or writing the next token. So it’s almost like a coin flip which one we want to select. There is not really an objectively worse one of one or the other. So when we do the random sampling, because they also have relatively high scores and their scores are similarly high since they are both plausible tokens, we might get one or the other. So almost half of the time we would get “overcast”, and almost half of the time we would get “gray” if we repeat the sampling multiple times. And that’s how LLMs often end up with different answers if you provide the same prompt. If you use the same prompt and you ask the LLM multiple times, you often get slightly different answers. And that’s because at certain positions, two possible tokens are almost equally likely, so it will choose one or the other. And that token would then influence all subsequent tokens, and so forth. Slide 20 of 52, time stamp 18:25 Now, I wanted to briefly talk about random number generation. So, for example, if we use a random number generator like this, it will generate a random sequence of numbers. If I run it again, the sequence of numbers is different here. So you can see every time we produce five numbers, they are different. If I set the random seed here, like one, two, three, and I run this multiple times, we still get random numbers, but they are now all the same, right? So they are still random. If we use a random seed, we still get random numbers that are different from each other, but they are reproducible. So whether we use a random seed or not, we still get random numbers. But with a random seed, we get a reproducible sequence of numbers. So keep this in mind: this is just like a little primer, and we will use this concept in a few moments. Slide 21 of 52, time stamp 19:26 So, for example, I mentioned before that we might get either “gray” or “overcast” if we randomly sample. Now, if we use a specific random seed like 42, we would always, for example, select “overcast”. I mean, it’s still a random selection, but we make it deterministic. In this case, given this prompt, the model will always select “overcast”. Slide 22 of 52, time stamp 19:49 If we use a different random seed, the model might select “gray”. Every time we sample, it will always select “gray” as the next token. So it’s still random sampling, but we are making it deterministic based on the random seed. Slide 23 of 52, time stamp 20:04 So, in watermarking, Claude watermarking is kind of like the idea that it sets a random seed. But this random seed, instead of being like a number that is fixed based on, I don’t know, someone writing down a fixed number, they’re using a secret key that is essentially like an API key, a secret key, and from that key, together with the four previous words, they derive this random seed essentially. But the idea is that if I go back one slide, it’s the same as here: there’s essentially a fixed random seed, and that random seed always selects the same next token. Okay, so instead of using random seed 99 here, for example, they have a secret key and also use information about the previous tokens to derive this random seed. But more on that later. Slide 24 of 52, time stamp 21:06 So the idea is that watermarking makes the text generation more deterministic in certain positions. So, for example, if we have these plausible texts on the left side. So if I have a text that says, > The weather today is cold and I may either pick “overcast” or “gray”. And the next sentence could be, > and then “light” or “gentle” They’re both interchangeable again. > And then breeze is “moving” or “blowing” through the trees, and the streets seem “quiet” or “still”. Which means basically I could say either “quiet” or “still”. So there are certain positions in the text where we have token choices where they are almost equally likely, like we have seen before. So that means if we are, this is without watermarking, if we are running the prompt, or given the prompt through the LLM, we might sometimes get this answer here, sometimes this answer, and so forth. And based on the number of positions, we might have 128 possible answers here. And of course, the longer the text, the more positions we have where we can have terms interchangeably, the more combinations, or the more output texts, there are. So, for example, again, one possible output text could be > The weather today is cold and overcast. A light breeze is moving through the trees, and the streets seem quiet. I think I’ll stay home and read a book with a cup of tea. So that is one possible text. Another possible text is > The weather today is cold and gray. A gentle breeze is blowing through the trees, and the streets seem still. I think I’ll stay inside and read a novel with a mug of tea. By the way, it’s also actually raining outside. I don’t know how good this microphone is, but it’s kind of a very fitting context here. But yeah, the bottom line is that you can see there are two very reasonable texts here being generated, and there are more combinations. So they are all reasonable. There isn’t one that is necessarily better than the other. They’re just, you know, slight variations. And if we don’t use watermarking, we might get either one, or it’s just random, right? Because of the random sampling, we might get one or the other. Slide 25 of 52, time stamp 23:31 Now, if we fix the random seed, as I mentioned before, for example, if the random seed is 99, we might always get this text here. So, using a random seed, we can kind of fix which answer we get, because then the random sampling is still random, but it’s deterministic in the sense that it’s reproducible. It’s always going to be the same then. Okay, so that is still without watermarking, now with a random seed. Slide 26 of 52, time stamp 23:59 And the watermarking is essentially doing the same thing. Now, instead of just using a simple random seed, they have a so-called random key, where this random key is involved in selecting the text, essentially. But what we can already say is that, in the Claude blog post, they say the watermarking shouldn’t make the text worse. If we look at this mechanism, yeah, it makes sense why it would not make the text worse. By the way, I’m not defending watermarks here. I’m just trying to explain. So please don’t kill the messenger here. But what I’m trying to say is that the watermarking is nothing else for the end user than fixing a random seed and making this sampling kind of deterministic, if that makes sense. Slide 27 of 52, time stamp 24:47 Okay, so the summary so far is without watermarking. We often sample without a random seed because I know most people don’t even use one. I honestly don’t think you can necessarily do it with the Claude and OpenAI APIs. I know you can do it in Ollama, but I also always had some problems with that because I used Ollama in one of my books for the bonus material to generate some texts. I was fixing the random seed, but it still wasn’t always deterministic, and so forth. So it’s tricky. Your mileage may also vary, depending on the software version and so forth. Anyways, so without watermarking, we have this random sampling. With watermarking on the right-hand side, we still have the random sampling. But in addition to just a random sampling being fully random, we have this watermarking key. And this watermarking key is passed to the random seed generator to set a specific random seed, making this deterministic. But it’s essentially very similar, and like I mentioned, there’s a lot of benefit in terms of understanding things from scratch. And now we know essentially where this watermark is applied to. So this is essentially applied to the sampling. It’s not applied inside the LLM, which is actually cool knowledge. So they don’t need to train a new LLM for that. They can just use an existing LLM, and they just apply it at this sampling stage. They don’t have to retrain anything or anything like that. So yeah, that is actually interesting, right? Slide 28 of 52, time stamp 26:21 But we are not quite done yet. I would also like to talk about how we can understand or see whether text is watermarked. So detecting the watermark is only possible if we have access to the key. So, for example, if we have these different texts, and essentially, after the text was generated, you find some random text on the internet (for example, you find this text number four here on the internet somewhere), you want to know: is this watermarked? Well, it’s impossible to know because, in order to know, you would need the watermarking key. You need this scoring function, and then you have to score basically the text with a scoring function. And then the idea is that if the score is above a certain threshold, then the text is watermarked. Otherwise, it’s not watermarked. But as the end user, we can’t do this because we don’t have this key. So the key is not available to us. Only Anthropic will have the key. However, in this blog post, they mentioned that they are providing it, of course, or they’re going to develop an API for that that they will make available. I don’t know. Honestly, I’m not affiliated. I don’t know the details. I was just reading this in this blog post. That’s all I know. So that API might as well be private for some companies, like, let’s say, X or Substack Notes, when they want to label AI-generated posts. They may make it public for end users to use. Who knows? We will have to wait on that. But yeah, so the bottom line here is that watermark detection is only possible if we have this watermarking key or, of course, the API that they are going to develop. Slide 29 of 52, time stamp 28:03 Now, removing the watermark is interesting. So now that we know how the watermarking works, we also know the shortcomings. I mean, this is really highly dependent on specific tokens in certain positions. So, for example, in this given text, if these colored words or tokens are the watermarking positions, we know that we could remove the watermark by editing this, right? If we change all the words at these positions, we would be 100% able to defeat this watermark. Now, the problem, though, is that we don’t know, right? Slide 30 of 52, time stamp 28:41 So we don’t know where these words are because we haven’t generated the watermark. So we don’t know which positions to look at. So the practical scenario here is that we could just randomly edit the text. So we would randomly change a few words and hope that we change enough positions to edit the watermark. So that would be one way to remove it. And since we also don’t know which are the highest-scoring ones, because that would require us to have access to the LLM and rerun the prompt through the LLM to find out which words are the highest-scoring, we can kind of only guess. So for example, we might say, oh, we replace “overcast” with “cloudy” because we don’t know that “gray” was high-scoring, you know? So in this case, it might be intuitive to say “gray”, but there might be cases where it’s not so intuitive. So what I’m trying to illustrate here is just some general text editing where we are modifying positions, but we are still kind of guessing what a watermark position is. So since we don’t know, we added just a few words here and there. And if we added enough words, that would also defeat the watermark. Slide 31 of 52, time stamp 29:59 So yeah, that was the watermarking in a nutshell. I mentioned that there is a scoring function to find out whether something is watermarked. And I want to do it as a bonus here. It’s already a long video, but as a bonus here, I wanted to briefly also explain how this scoring function works because that is also interesting information. It’s a bit complicated. It’s not essential to understand how the scoring function works. But the reason why they do it the certain way they do is to make the detection cheaper. Because otherwise, if I go back one slide or two slides, if you wanted to check if something is watermarked, if even they wanted to check, they would have to rerun the prompt to get these scores and then apply this watermarking random seed to get this text and then compare. And that would be very expensive because then essentially every text you want to compare, you would have to rerun the LLM. You have to know which LLM, and that would be really unfeasible because you often also don’t even know the prompt, right? So yeah, so they have like a trick that they use to, yeah, I would say, modify the sampling so that you don’t use or don’t need the LLM later on for the scoring stage. And in the blog post, they mentioned that they derived this method from a paper. It was a Nature paper, and this method is called SynthID-Text. So that was like a paper that came out maybe one or two years ago. It was by Google, and they use a similar technique they call Claude watermarking. I don’t know, sorry, I don’t know if they use exactly that technique, but that’s the one they mentioned. Slide 32 of 52, time stamp 31:39 So how does it work? So before we looked at the slides, we looked at the regular, let’s say, overview here, where we have some text. We put it through the LLM. We get this logit distribution and then we sample from the distribution and get the output token. And here, during the sampling, we use the watermarking key and the random seed generator. So this is still correct. This is still what’s going on, but there is a bit more, I guess, nuance to how this token is sampled. So they’re not just using, let’s say, NumPy’s random choice. They’re using something a bit more sophisticated here. Slide 33 of 52, time stamp 32:16 So assume, again, our context is “the weather today is cold,” and we want to generate the next token. So, for example: “gray”, “overcast”, “gloomy”, “cloudy”. “Gray” is 50% probability, “overcast” is 30, “gloomy” is 15. Let’s say “cloudy” is 0.05 and the rest is, let’s say, 0. Here it looks, of course, a bit different. Let’s say that’s “gray” and “overcast”. I’m just reusing this figure. But now imagine these are the most likely ones, like “gray” and “overcast”, and everything else is just very small, except “gloomy” and “cloudy,” maybe. So essentially, think about just a very small vocabulary for this example of four words instead of all these 50 words here, just to make it even simpler. Now, as I mentioned before, we could use ‘sNumPy’s random choice with these probabilities to sample the next token. Slide 34 of 52, time stamp 33:13 And we could use the watermarking key with this random seed generator to make it deterministic and get the certain watermark that we want. But as I mentioned before, this would be very expensive. Not the sampling itself. That doesn’t matter. This is pretty cheap. But the detection later on would be very expensive if we are trying to check random text on the internet. Slide 35 of 52, time stamp 33:35 So instead, what they use, they also use it during the sampling, during the generation, so that it can be reused later during detection. What they use is called tournament sampling. So this is instead of using something like random choice, they use a concept called tournament sampling. And so how does that work? It might look a bit complicated, but it looks really more complicated than it really is, to be honest. So you might have to, I guess, stop the video at some point and just sit with the figure a bit. But I think it is actually simpler than it looks like. It’s like once you get the hang of it, it’s pretty straightforward. But let me try to explain here. So what we have is we have still this context, and then we have these probable or plausible next tokens with these different probabilities. Now they have something they call random watermarking functions. Slide 36 of 52, time stamp 34:35 Here we have three watermarking functions, G1, G2, and G3. In reality, they might have 30, 50, or even more. Here I’m just using three because that is simpler on this slide. It’s just smaller, you know, like it fits better on the slide. Now, if we look at this word “gray”, this might give us a signature 101. With that, I mean, if we use this watermarking key to generate this random seed, and we have three functions, G1, G2, G3. If I put the word “gray”, what I’m skipping here is that usually you put the word “gray” together with the four or three previous words from the context. So it’s “cold” and “gray”. If I put that into G1 together with this watermarking key, I get the value one. Why? Well, that’s just how this function works. It’s like a random function. The random function either returns zero or one. In this case, with this random key and this token, it returns one. With the same key, but a different function, you get the value zero. And then here you get a one again. So if we have more functions (of course, 30 functions), this will be a very long string of ones and zeros. Slide 37 of 52, time stamp 36:00 It’s basically like a bit string, like if you have bits of zeros and ones. Okay. So this is for “gray”. So we get the signature 101 through using these watermarking functions. Now we can do the same thing for all the other ones. So we can do it for “gray”. We can do it for “overcast”, “gloomy”, and “cloudy”. So each one has a different signature here. So, for example, “overcast” is zero, one, zero. “Gloomy” has zero, zero, one. “Cloudy” has one, zero, zero. Okay. So we have these bits here now. The next step is a so-called tournament sampling where we just pair them. Slide 38 of 52, time stamp 36:39 Like, you know, like a soccer tournament, the knockout (KO) stages, or like the playoffs in American football, you always have two teams playing against each other. And that’s kind of like the same idea. We have a pair of tokens, and they’re playing against each other, essentially. And the scores, they come from these functions here. So we start with the first function in the first round. So we have “cloudy” and “gray”. So we look up here: “gray” is a one and “cloudy” is a one. Okay. So one and one. “Overcast” and “gray”. So “overcast” is zero, “gray” is one. So we have zero, one. “Gloomy” and “overcast”. So here we have “gloomy” zero, “overcast” zero. So zero, zero. And then we have “gray” and “gray” again, because we are running out. So we don’t have enough of the others. So we have one duplicate. So this is chosen randomly. And so you have one and one here. Now we look at the results. So this is a tie. In the case of a tie, we also select randomly using, you know, the random seed and the watermarking key. So here, “cloudy” survives. And from this one, G1 is, according to G1, “gray” is the winner because it has the one. So “gray” survives. And then here, “overcast” and “ gray “ are a tie, randomly selected, and “gray” also randomly selected. So we have now “cloudy” and “gray” and “overcast” and “gray”. And we play the next round in this tournament. So in this next round, we use G2. So according to G2, “cloudy” has a zero here. “Gray” also has zero. “Overcast” has one. And “gray” also has zero, sorry. And so, the next stage of the tournament again. Slide 39 of 52, time stamp 38:24 So we have a tie. We randomly select “gray”. And here we have “overcast” as the winner. And so we have “gray” versus “overcast” in the final. And then we look again at the scores. So “gray” has a one. “Overcast” is a zero. So “gray” is the winner. And that’s how the token “gray” is sampled. What is the watermarking key doing here? So the watermarking key, if I go back a few slides, is selected for generating these scores using these random watermarking functions. So the watermarking key determines essentially what values we get at these stages. So the watermarking key is still very important. Otherwise, these signatures would look different. Slide 40 of 52, time stamp 39:11 So we now have sampled the next token. And that’s just how this modified sampling procedure works. We could have used NumPy’s `random.choice`. But the shortcoming of that is that if we want to score random text on the internet, we would have to rerun the LLM. With this technique, we don’t. I will show you in a moment. So this technique sounds like really weird and cumbersome, but it has the advantage that we can now score random text more easily without having to rerun the LLM. So it’s essentially just to make the detection easier and cheaper. Slide 41 of 52, time stamp 39:43 Slide 42 of 52, time stamp 39:48 So, for example, if we have a new text. So I’m just using the same text here, but let’s assume it’s new text. So this is after the sampling, when we are scoring. And let’s say we are discovering this text on the internet. And the text is the weather today is cold and “gray”, and we want to know if this is LLM-generated or not. So we would, or Claude/Anthropic would, have the watermarking key and these functions: G1, G2, and G3. And it would put this text through these functions. For the one position here for “gray”, we would get 101, similar to what we got during the generation process. So this is the same as before. And this has, if we add up these bits, two bits, right? One and one here. So it has two bits of information, let’s say, for simplicity. This is just a really simple illustration. But let’s assume we get a score of two here for the “gray” in this position. If we had a different word here, “overcast,” in this position, we would get one if we get “gloomy,” like we also have one, and “cloudy” one. So I’m just summing over each row here, right? So that’s just like a score we would get at each position. And here I’m only looking at the last position. If I would do this at other positions, I would get a different score at different positions. So, for example, let’s assume at the first position I get a two. Here I get a two. For “today”, I get a three. For “is”, I get a two. “Cold”, two. And “gray”, three. So here I’m applying these watermarking functions as I’ve shown on the previous slide. Slide 43 of 52, time stamp 41:33 And I’m just adding up these numbers across the three functions. And the watermarking functions are very cheap. So you can just quickly run them on the whole text and get these scores. And then based on that, I can compute the average bits. So if I just average over all these values here, let’s say I get 2.23. Slide 44 of 52, time stamp 41:55 Now, if I have slightly different text, so here I swapped “today” with “now” and “gray” with “overcast”. These now get a score of one and one. And if I average over this whole string, then I get a 1.71. And so for that, I don’t need an LLM. All I need is the watermarking key, the random seed generator, and these functions, G1, G2, and G3. And that’s all I need. I don’t need the LLM. And I can get this score here. And what they do is apply a threshold. Slide 45 of 52, time stamp 42:27 So, for example, I mean, they don’t use this exact threshold. But for example, we can say if the score is greater than two, then the text is watermarked. If the score is smaller than two, it’s not watermarked. So here, if the score is greater than two, it’s a yes. So yes, this is watermarked. In this case, 1.71 is not greater than two. So this text is not watermarked. Okay. So that’s just the way we can then detect whether random text on the internet is watermarked or not. It’s essentially just applying these watermarking functions and then averaging over the scores and applying a threshold. Okay. Slide 46 of 52, time stamp 43:13 So again, the tournament sampling is mainly to make detection easier and cheaper. We could also use something like NumPy’s random choice with a random seed or to make the sampling deterministic. But then again, it would be hard to score any text on the internet. Slide 47 of 52, time stamp 43:30 So yeah, the summary is still the same, though. The thing that is different between no watermarking and watermarking is that we are controlling this sampling here with the watermarking key. And inside that, we have this tournament sampling. And yeah, as I mentioned before, detecting the watermarks requires the secret key and the watermarking functions G1 to Gn. Slide 48 of 52, time stamp 43:54 And again, removing the watermark, because I think that’s maybe interesting to some people, would ideally involve editing all the positions here. But since we don’t know which positions are watermarked and internally, they choose the positions so that they have equally likely tokens at those positions. And there might be positions where that’s not true. So here, for example, for “trees”, we might not even have an alternative word that is high scoring so they don’t watermark that position. So they only do the watermarking at certain positions essentially. Since we don’t know which positions to kind of defeat or remove the watermark, we would... Slide 49 of 52, time stamp 44:29 ...have to edit several places in the text. So what I think that means for the future of AI-generated text is that this actually... Slide 50 of 52, time stamp 44:36 …might result in worse AI-generated text. So I think if there’s a person who likes to use AI-generated text everywhere on the internet, let’s say there’s a news website that likes to use AI-generated text to write the news, I don’t think watermarking will necessarily stop them from doing that. They will probably still want to generate AI-generated text because that’s part of their workflow. So I think my guess is that they’ll use another model. Slide 51 of 52, time stamp 45:08 They’ll just use a second model to edit the text to get the so-called edited AI-generated text. So it’s complicating the pipeline. Instead of getting the text directly from Claude, it’s now using Claude to generate AI-generated text, passing it through a local model, and then having edited AI-generated text, which is likely not watermarked anymore. So why a local model? I just think a local model because I think all the providers- the proprietary LLMs, not only Claude, but also Google— I mean, Google wrote this paper, right? So I’m thinking that they are also watermarking Gemini text. And I think OpenAI is probably already doing it or will do so as well. I mean, I’m just speculating, but I’m imagining everyone will probably do something like that because there’s like an EU regulation that requires that. And that’s, according to the blog post, apparently why Claude is doing it. Yeah, so I’m thinking local models may not, at least not yet, implement this watermarking. So I think people will just use a local model and then generate edited AI-generated text. And my guess is it will be slightly worse than the original text because for the local model, you might now be using a smaller model. So, I mean, you could also technically just use the local model directly to generate text. But in my view, editing text is simpler than generating text. So for the generation of the text, you might use a very expensive high-end, I don’t know, like the highest, most expensive Claude model for complicated text. And then you use a cheaper local model to make these surgical edits, essentially. That’s probably what’s going to happen. And why worse? So if we look back at this graphic where we just added random positions, you might be just changing words for the sake of changing them. And then it risks making the text worse. So you might still have generated text, but it’s kind of like it’s edited awkwardly. Slide 52 of 52, time stamp 47:20 But anyway, so my goal here was to explain how the watermarking works and not, let’s say, the worldwide ramifications of that. But I hope this kind of behind-the-scenes, under-the-hood look is useful. The watermarking is not as complicated as it might seem, but I think it was still 52 slides, so it was also not super trivial. So I hope you found this little lecture useful. And yeah, until next time, see you then. PS: If you like more explainers in this style, I don’t post videos to YouTube regularly, but I have accumulated over 300 videos over the years, which you can find on my YouTube channel here . I also have a YouTube version if you prefer using the YouTube player And here is a link to the slides

0 views
Jeff Geerling 1 months ago

Hands-on with Raspberry Pi's CM5 Programming Jig

In the before-times, when Raspberry Pi CM5s were (relatively) affordable, I built a number of Pi clusters ( example ), and one of the most annoying parts of the build was flashing Raspberry Pi OS to all the Pis. One, two, or even three Pis isn't a big deal, but once you hit 4+, the process of plugging the Compute Module into a carrier board, plugging that into a computer, managing Raspberry Pi Imager, and trying to match up details like a hostname, MAC address, and the physical Pi itself, gets annoying.

0 views
Unsung 2 months ago

“If nothing happened, you probably did not press the button quickly enough the second time.”

I like learning new things, and I like learning new old things. I was looking at Apple Human Interface Guidelines from 1987 and this passage caught my attention: The most common use of double-clicking is as a shortcut way to perform an action. For example, clicking twice on an icon is a faster way to open it than clicking once to select it, then choosing Open from the File menu; clicking twice on a word to select it is faster than dragging through it. I knew that double click an icon was a shortcut to the first action (typically Open), but I never really thought of double-clicking a word as a faster way to drag across to select it – even though, in hindsight, it makes perfect sense. Another vintage thing I learned of recently from a coworker is this, also covered in the 1987 HIG: If the user begins a double-click sequence, but then drags the mouse between the mouse- down and the mouse-up of the second click, the selection becomes a range of words rather than a single word. This doesn’t feel (to me) like a very pleasant gesture to perform repeatedly, but what feels nice about it is that it automatically snaps the selection to the endings of the words: Part of me would prefer this to be the default behaviour when selecting more than 3 words, or so, so you could be less precise. Anyway. The double clicking to perform default action applies to a lot of lists of things. Here are some examples from Scrivener, Word, and Lightroom – you can double click on each of these items to proceed, without having to select and click the button: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/2.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/2.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/3.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/3.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/4.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/4.1600w.avif" type="image/avif"> But sometimes the creators of such dialogs forget. Here’s Screen Sharing in MacOS, and a notification in Chrome where only the slow path is available – double clicking on items doesn’t do anything: = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/5.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/5.1600w.avif" type="image/avif"> = 2x) and (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/6.2096w.avif" type="image/avif"> = 3x) or (width >= 700px)" srcset="https://unsung.aresluna.org/_media/if-nothing-happened-you-probably-did-not-press-the-button-quickly-enough-the-second-time/6.1600w.avif" type="image/avif"> The tricky part about not being a good citizen of a shared user interface is that those omissions aren’t just local to your app – they can ruin the gesture in other places, as people’s fingers learn to distrust it in not just your app, but in general. (The quote in the title is from Apple Macintosh User’s Handbook .) #flow #mouse #text editing

0 views
xenodium 2 months ago

Focus windows across Emacs frames

Historically, I've only ever known two great ways of focusing Emacs windows: the built-in command, which cycles through all the available windows, and the ace-window package, offering random window access. It's not that there aren't more or better ways, I just didn't look any further as these were enough for my needs. While I really wanted to make my default choice, for whatever reason, it never stuck. with a custom binding always felt like the smoother fit for my limited needs. You see, I hardly ever have more than two visible windows, so whenever kicked into action (for 3 or more windows), it often took me by surprise. The one area didn't fit my needs revolved around focus requiring visual feedback, but I eventually solved that with winpulse (a little package I wrote). As you can see, I'm a simple man using few Emacs windows, and when it comes to frames, I almost never use more than one. That is until somewhat recently, when I built ytr , a tiny YouTube radio player that sits in the corner of your frame. While it all feels fairly integrated into your frame, under the hood, renders in a separate frame. This broke my trusty flow. I couldn't just focus my radio window using my well-internalized binding. Turns out, actually caters for focusing windows across frames, but only when invoked programmatically. Sure, I can wrap it with my own custom command, but Emacs already had me covered. I found the built-in command. All I had to do was bind it to and Bob's your uncle . I can now switch between my current window and my YouTube radio, with my dear binding. Balance restored.

0 views
Ahead of AI 2 months ago

Building an AI Text Detector From Scratch

Substack recently launched its AI detector feature in the UI, which is super interesting. Separately, lots of people asked me about interesting local do-it-yourself LLM projects as demos to show what small language models (SLMs) are capable of. Putting one and one together, I thought it would be interesting to show how an AI detector can be implemented. I will also use it as a verifier to train a small language model to produce text that avoids detection. This is a small educational project for studying the limitations of AI detectors and exploring a verifier-based LLM application beyond regular reasoning models trained on math and code. Figure 1: Substack now features a built-in AI detector. So, as mentioned above, the intended goal of this tutorial is to explain how AI detectors work by building (a simple) one. In practice, such a detector can be used to filter out spammy content, but also to potentially improve your personal writing without turning it into AI-generated text. For example, if you wrote a lengthy article and want to improve spelling and grammar, it is tempting (and actually useful) to use a grammar checker to polish it and improve readability. There are different services for that, including general-purpose LLMs like ChatGPT. However, this also runs the risk that these tools turn your writing, even though it’s still your own writing, into something that is then overpolished and now sounds like AI and gets flagged as spammy content. For example, with an AI checker, one could say, “Fix my grammar while ensuring that my text still scores 0% AI-generated.” Anyway, while we are building a fully functional checker here, the goal is to explain 1) how AI checkers (can) work and 2) use this as a case study for a more general topic on how to build a scorer or verifier that can be used with LLMs. Disclaimer: AI checkers are essentially a cat-and-mouse game. AI checkers may learn to detect a certain pattern that is indicative of AI-generated content. Then, the next LLM may incidentally or deliberately not exhibit that pattern and avoid detection. The AI checker then has to be updated to detect said LLM, and so forth. Plus, it’s also likely to encounter false positives (human written text flagged as AI-generated), but more on that later. There are several goals of this project. The overarching goal is, of course, to illustrate how AI detectors work and show an applied end-to-end LLM project including evaluation, training, and local deployment for real-world use. The outcome of this is an AI-detector API that can be used by humans and agents, and a user-friendly UI. Figure 2: Preview of the local browser interface developed later in this project. It returns a whole-text AI score and can also highlight the scores for individual text chunks. Here, we are going to develop a method similar to Pangram models, which, as far as I know, are behind Substack AI detection feature. I wrote a short article about AI-text detection a while back in 2023: What Are the Different Approaches for Detecting Content Generated by LLMs Such As ChatGPT? And How Do They Work and Differ? In essence, there are different ways to detect AI-written text, from supervised classifiers and perturbation-based probability tests to perplexity measures and watermarking. In this tutorial, we will build a model that returns a 0-100 score. It’s essentially a classifier with an estimated probability score. The probability score will denote how likely a text is AI-generated according to the classifier. (Or, to be precise the score is the classifier’s estimated probability for the AI-generated class based on its training distribution. However, we shouldn’t interpreted it as a general probability that the text was written by AI.) For this, we are going to fine-tune a DistilBERT classifier (similar to what I described in one of my early Substack articles, Finetuning Large Language Models ), but more details on that later when we get to that stage.

0 views