<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Gota Gando</title>
	<link href="https://ggando.com/feed.xml" rel="self" type="application/atom+xml"/>
    <link href="https://ggando.com"/>
	<updated>2026-09-02T00:00:00+00:00</updated>
	<id>https://ggando.com/feed.xml</id>
	<entry xml:lang="en">
		<title>Escaping the Tokyo Summer: Melbourne and Bali</title>
		<published>2026-09-02T00:00:00+00:00</published>
		<updated>2026-09-02T00:00:00+00:00</updated>
		<link href="https://ggando.com/travel/melbourne-bali-2026/" type="text/html"/>
		<id>https://ggando.com/travel/melbourne-bali-2026/</id>
		<content type="html">&lt;p&gt;I tend to travel in August since Tokyo is very hot and humid during the summer, and I&#x27;m not productive if I stay here anyway. I usually procrastinate the planning until the last moment and feel annoyed about starting the travel, but for someone like me who works on a computer all day long, it always feels worth it after I&#x27;ve done it.&lt;&#x2F;p&gt;
&lt;p&gt;Last year, I first visited SF, where I learned that robotics is getting hot, and then vacationed in Vancouver and Seattle. I considered traveling to the east coast this summer, but the US is too expensive for yen earners these days. So I decided to travel south this year.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;melbourne&quot;&gt;Melbourne&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#melbourne&quot; 
    aria-label=&quot;Anchor link for: melbourne&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;First, I went to Melbourne for a week to escape the heat in the Australian winter. I mostly stayed in the Central Business District (CBD), and my first impression was that it&#x27;s pretty clean and safe despite the size of the city. Also, there were so many Asian tourists that I kept hearing Japanese around the CBD. Makes sense given how close Australia is to Asia, and hey, I was one of them.&lt;&#x2F;p&gt;
&lt;img class=&quot;photo&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_yarra_skyline_1280.jpg&quot; alt=&quot;Melbourne CBD skyline across the Yarra River&quot;&#x2F;&gt;
&lt;h4 id=&quot;remote-working&quot;&gt;Remote working&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#remote-working&quot; 
    aria-label=&quot;Anchor link for: remote-working&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;img class=&quot;photo&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_airbnb_sunset_1280.jpg&quot; alt=&quot;Sunset over Melbourne from my Airbnb window&quot;&#x2F;&gt;
&lt;p&gt;Another reason I chose Melbourne was that I thought it could be a good place for short-term remote working. I was able to work from my Airbnb since the room was pretty spacious and quiet, but I actually felt it&#x27;s not so good for nomading as 1. it&#x27;s relatively expensive to stay and 2. cafe seating tends to be compact and not suitable for laptops. So you&#x27;d end up going to libraries, but they&#x27;re a bit crowded as everyone else tries to do the same. However, I did find two cafes where I was able to work a bit:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;Q4Q5hSXyP5aLRiqL8&quot;&gt;Luna Coffee&lt;&#x2F;a&gt;: It&#x27;s a spacious hotel lounge with a small coffee shop. It&#x27;s not crowded at all and nobody cares even if you stay here for a few hours.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;KzNFnMLLGy4EaRbS6&quot;&gt;Krimper Cafe&lt;&#x2F;a&gt;: There&#x27;s enough seating that the staff probably don&#x27;t mind a laptop user for a while (probably). I went there in the morning when it&#x27;s not crowded and was able to work for like 1-2 hours.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For the library, I recommend the City Library since you can probably find a spot there, instead of the overcrowded State Library.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;food&quot;&gt;Food&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#food&quot; 
    aria-label=&quot;Anchor link for: food&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;As for restaurants and stuff, you can just research with Claude, Grok, etc., plus there&#x27;s a shit ton of travel content on the web obviously, but I&#x27;d recommend these two restaurants I really liked:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;LPGegbzPx6X7Uk8V6&quot;&gt;Vespa Rossa Degraves&lt;&#x2F;a&gt;: Beef Ragu Risotto&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;1ZU678K4rJD3zat97&quot;&gt;Claypots Barbarossa&lt;&#x2F;a&gt;: Salmon Belly&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;architecture-and-ngv&quot;&gt;Architecture and NGV&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#architecture-and-ngv&quot; 
    aria-label=&quot;Anchor link for: architecture-and-ngv&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Also I really liked the buildings in the CBD. Lots of towers with reflective glass, and my favorite was UNO Melbourne, whose facade shifts between purple and copper depending on the angle. At NGV I also found a funny oil painting from 1652 (A Mediterranean port scene by Jan Baptist Weenix) where the lady with the parasol looks straight up like an anime character.&lt;&#x2F;p&gt;
&lt;div class=&quot;photo-row&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_uno_facade_1280.jpg&quot; alt=&quot;UNO Melbourne dichroic facade shifting purple&quot;&#x2F;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_laneway_cafes_1280.jpg&quot; alt=&quot;Melbourne laneway cafes with towers behind&quot;&#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;photo-row&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_ngv_weenix_1280.jpg&quot; alt=&quot;Jan Baptist Weenix, A Mediterranean port scene (1652), NGV&quot;&#x2F;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_ngv_weenix_closeup_1280.jpg&quot; alt=&quot;Close-up of the parasol lady who looks like an anime character&quot;&#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;h4 id=&quot;tech-scene&quot;&gt;Tech scene&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#tech-scene&quot; 
    aria-label=&quot;Anchor link for: tech-scene&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Overall the climate is pretty chill and people are friendly and great, but I didn&#x27;t feel much impact in terms of networking. Nothing on the meetup scene looked that compelling to me personally, but I attended two anyway. I felt like I met more people who have actually built something and are actively working on it even in Vancouver, people you&#x27;d genuinely feel like you wanna connect with. Claude mentioned Melbourne kinda has a culture of indie hackers, but I didn&#x27;t see anything particularly strong on that front. I stayed in Van for two weeks compared to one week in Mel, and the timing obviously matters, so I&#x27;m pretty biased here. For tech events I believe North America clearly wins, but for tourism and chill remote working, Melbourne was definitely worth it.&lt;&#x2F;p&gt;
&lt;img class=&quot;photo&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;mel2026&#x2F;mel2026_tram_1280.jpg&quot; alt=&quot;Green tram in Melbourne CBD&quot;&#x2F;&gt;
&lt;h3 id=&quot;bali&quot;&gt;Bali&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bali&quot; 
    aria-label=&quot;Anchor link for: bali&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;After Australia, I visited Bali for another week, basing myself in Canggu, to 1. do something summery since I&#x27;m getting hella old and 2. check out the digital nomad scene. I had such low expectations of Bali because people online kept trashing it, like how overcrowded, dirty, and expensive it has gotten compared to the past. But I loved Bali!&lt;&#x2F;p&gt;
&lt;img class=&quot;photo&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_canggu_sunset_1280.jpg&quot; alt=&quot;Sunset reflecting on wet black sand, Canggu beach&quot;&#x2F;&gt;
&lt;p&gt;There are some spots that are dirty, and there were indeed lots of tourists in the peak dry season. But I enjoyed it. Beaches are absolutely beautiful and worth visiting just to view them, even if you don&#x27;t swim or surf.&lt;&#x2F;p&gt;
&lt;div class=&quot;photo-row&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_beach_palms_mirror_1280.jpg&quot; alt=&quot;Palms and clouds mirrored on the beach at low tide&quot;&#x2F;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_beach_clouds_mirror_1280.jpg&quot; alt=&quot;Golden-hour clouds mirrored on the sand&quot;&#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I couldn&#x27;t explore everything, but there are lots of temples and world heritage sites you wanna visit, like Tanah Lot, the Rice Terraces and Uluwatu Temple. I was planning to just work on coding projects at nomad cafes, but ended up doing full tourist stuff.&lt;&#x2F;p&gt;
&lt;div class=&quot;photo-row&quot;&gt;
  &lt;img class=&quot;landscape&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_uluwatu_cliffs_1280.jpg&quot; alt=&quot;Uluwatu cliffs framed by bougainvillea&quot;&#x2F;&gt;
  &lt;img class=&quot;portrait&quot; src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_kecak_dance_1280.jpg&quot; alt=&quot;Kecak fire dance at Uluwatu at night&quot;&#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I went to Uluwatu just for the temple this time. Monkeys were literally stealing iPhones and glasses from tourists right in front of me, and the Kecak fire dance was really good, honestly better than the cliff views. For Ubud I did the classic route: the Tegallalang Rice Terraces → Tirta Empul → Serayu pottery → luwak coffee tasting → the Campuhan Ridge Walk. And Tanah Lot was definitely the best spot to view waves, with the temple sitting on a rock just offshore and the surf crashing around it. However touristy it looks, it&#x27;s worth going at least once.&lt;&#x2F;p&gt;
&lt;p&gt;Also, things are very cheap even for those who carry yen. For example, even lunch&#x2F;dinner at a high-grade restaurant only costs like less than 3k yen, which is like half of what it&#x27;d be in Tokyo. Basically I felt like an American visiting Japan in terms of cost of living.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;getting-around&quot;&gt;Getting around&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#getting-around&quot; 
    aria-label=&quot;Anchor link for: getting-around&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;For transportation, if you have a license for scooters it&#x27;s definitely better to rent a scooter, though the traffic is pretty chaotic and you&#x27;d need decent skill to maneuver without hitting something. I technically have a Japanese driver&#x27;s license that comes with a small scooter license up to 75cc, but Bali scooters are usually 125cc. This means it&#x27;s technically illegal for me to drive them and travel insurance won&#x27;t cover it, so I opted for just using the Gojek &#x2F; Grab apps. I heard some people don&#x27;t care and drive them anyway without the license, which I&#x27;m not really willing to risk. I ended up getting along with one of the car drivers I met on Gojek, and used his private service throughout the rest of my stay. Gojek takes a 20% margin, so if you pay the driver in cash plus some tips, it&#x27;d be a great deal for the driver. For reference, Canggu to Uluwatu Temple was around 400k IDR one way, and Canggu to the Tegallalang rice terraces was about 332.5k on Gojek, so Ubud day trips end up a bit more. After exchanging WhatsApp, we just used the Gojek app to check the fare for each ride and I paid him that in cash plus some tips. I&#x27;d recommend doing the same, for example if you stay around Canggu and plan to do long day trips to places like Ubud and Uluwatu.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;the-nomad-scene&quot;&gt;The nomad scene&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-nomad-scene&quot; 
    aria-label=&quot;Anchor link for: the-nomad-scene&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;As for the nomad scene, I did enjoy the co-working cafes designed for laptops. It wasn&#x27;t overcrowded and I could always find a spot. Working on your projects while sipping tropical drinks has its own charm.&lt;&#x2F;p&gt;
&lt;div class=&quot;photo-row&quot;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_zin_cafe_1280.jpg&quot; alt=&quot;ZIN Cafe in Canggu, plenty of seating for laptops&quot;&#x2F;&gt;
  &lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;travel&#x2F;bali2026&#x2F;bali2026_noah_cafe_code_v2_1280.jpg&quot; alt=&quot;Code on screen next to an iced drink at NOAH Cafe&quot;&#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;But I felt like it&#x27;s kinda hard to connect with other fellow nomads &#x2F; remote workers, because everyone kinda works on their laptops quietly. People were polite though, like you could ask people to watch your stuff for example. You probably wanna be proactive if you want to socialize with nomads. I&#x27;d attend events hosted by coworking spaces like Tribal. I didn&#x27;t, since I wanted to focus on traveling around Bali. There was also a startup event on Eventbrite I wanted to attend, but it was sold out when I tried to buy a ticket! For the record, here are the ones I tried out:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;AwjCnHBR18RjyquD8&quot;&gt;IZE&lt;&#x2F;a&gt;: very quiet and suitable for actual working&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;KKDmjeiMpwcU3t3b9&quot;&gt;Tribal&lt;&#x2F;a&gt;: the vibe is great but it&#x27;s pretty busy. I suppose you&#x27;d need to come in the early morning to secure a spot&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;p8Xbi8DA8qArmEjB7&quot;&gt;NOAH Cafe&lt;&#x2F;a&gt;: there&#x27;s a little enclosed space with AC for working, and it&#x27;s pretty good&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maps.app.goo.gl&#x2F;myXLMSLEtVzMp4mz6&quot;&gt;ZIN Cafe&lt;&#x2F;a&gt;: arguably the best one in Canggu, plenty of seating, you can order great foods and drinks while working and they&#x27;ll bring them to you&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;people&quot;&gt;People&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#people&quot; 
    aria-label=&quot;Anchor link for: people&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;My impression from my limited stay was that tourists (also nomads?) are a bit indifferent to each other and just do their own thing alone or with their group, though most of the ones I encountered were polite. On the other hand, I had a much easier time connecting with local people, since people tend to be warm and friendly. Also, some people kinda speak Japanese and seem to like Japan too. Classic, but people seem to like anime and manga apparently (soft power is real). I think Balinese are friendly and they tend to speak good English, sometimes even Japanese. Communicating in English, Japanese, and my occasional bad Indonesian was pretty fun!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;next-time&quot;&gt;Next time&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#next-time&quot; 
    aria-label=&quot;Anchor link for: next-time&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Overall, I definitely enjoyed Bali and would like to stay again, preferably a bit longer, in the near future. For anyone wondering if Bali is worth it, I&#x27;d personally recommend it. It is crowded with tourists, but it&#x27;s famous for a good reason. I only briefly visited Uluwatu&#x2F;Ubud this time, so next time I&#x27;m hoping to explore a bit north like Mt. Batur&#x2F;Kintamani and toward the east like Nusa Penida, Gili, and Lombok.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Fine-tuning SmolVLA for Cube Pick-and-Place on SO-101</title>
		<published>2026-03-01T00:00:00+00:00</published>
		<updated>2026-03-01T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/smolvla-so101/" type="text/html"/>
		<id>https://ggando.com/blog/smolvla-so101/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;so101_smolvla_960.jpg&quot; alt=&quot;SO-101 SmolVLA pick-and-place&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#context&quot; 
    aria-label=&quot;Anchor link for: context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;For the past few months I&#x27;ve been exploring different algorithms for my SO-101 robot arm to do a pick-and-place task. Last time I trained SAC with HIL-SERL (Human-in-the-Loop Sample Efficient RL) on the real robot with a reward classifier and human interventions via the leader arm. After about 750 episodes of training, HIL-SERL achieved 80% success on reach-and-grasp. That&#x27;s good, but each episode required me to physically be there watching the robot, ready to intervene, and it&#x27;s very labor-intensive.&lt;&#x2F;p&gt;
&lt;p&gt;So this time I tried out VLAs (Vision-Language-Action models) to see how quickly I could train a working model. I decided to go with SmolVLA from HuggingFace, as it&#x27;s a compact VLA built on SmolVLM that you can fine-tune on consumer hardware (in my case, RTX 3090). The idea is simple: collect teleoperation demos, fine-tune the pretrained model, and deploy. No reward engineering, no sim-to-real gap, no sitting next to the robot for hours pressing buttons. Here&#x27;s what I tried out.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-setup&quot;&gt;The Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-setup&quot; 
    aria-label=&quot;Anchor link for: the-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Hardware: SO-101 follower + leader arm (Feetech STS3215 servos), Intel RealSense D405 as wrist camera, Logitech C920 as overhead camera, and my RTX 3090 for training. Software: HuggingFace&#x27;s &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;huggingface&#x2F;lerobot&quot;&gt;LeRobot&lt;&#x2F;a&gt; framework with a custom fork that adds placo-based IK for automated resets between episodes.&lt;&#x2F;p&gt;
&lt;p&gt;The task: pick up a small cube from a randomized position and place it in a bowl. 10-second episodes, 30fps, recorded via teleoperation with the leader arm.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;smolvla-vs-act&quot;&gt;SmolVLA vs ACT&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#smolvla-vs-act&quot; 
    aria-label=&quot;Anchor link for: smolvla-vs-act&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;SmolVLA is a 500M+ parameter model, but you only fine-tune ~50M (the action expert + projections). The vision encoder (SigLIP) and language model (SmolLM2) stay frozen. This means the model already has a strong visual prior from pretraining. It knows what objects look like, it just needs to learn what to do with them.&lt;&#x2F;p&gt;
&lt;p&gt;I also trained an ACT baseline for comparison. ACT is a solid architecture but had practical issues on my setup: no built-in image resize meant 640×480 frames produced 602 encoder tokens per forward pass, OOM&#x27;d at batch_size=64 on 24GB VRAM, and required double the training steps to match SmolVLA&#x27;s sample count. At 1.28M matched samples, SmolVLA&#x27;s L1 loss converged to 0.006 while ACT was at 0.046 (though the losses aren&#x27;t directly comparable since ACT includes KL divergence).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;three-rounds-of-data-collection&quot;&gt;Three Rounds of Data Collection&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#three-rounds-of-data-collection&quot; 
    aria-label=&quot;Anchor link for: three-rounds-of-data-collection&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I had to record three times due to hardware breakage and inconsistent grasp motions:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v1-30cm-workspace-50-episodes&quot;&gt;v1: 30cm workspace, 50 episodes&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v1-30cm-workspace-50-episodes&quot; 
    aria-label=&quot;Anchor link for: v1-30cm-workspace-50-episodes&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;First attempt. Cube placed randomly across a ~30cm area, two cameras (wrist + overhead). Training converged (loss dropped from 0.162 to 0.005 in 20k steps), but at inference the arm moved toward the cube and then grasped at air. 50 demos across 30cm just wasn&#x27;t enough density. The policy learned the general motion but couldn&#x27;t pin down precise grasp locations.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v1-redux-10cm-workspace-75-episodes&quot;&gt;v1 redux: 10cm workspace, 75 episodes&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v1-redux-10cm-workspace-75-episodes&quot; 
    aria-label=&quot;Anchor link for: v1-redux-10cm-workspace-75-episodes&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Narrowed the workspace to ~10cm and collected 75 clean episodes. Also swapped the wrist camera from InnoMaker RGB (which kept dying mid-session) to a RealSense D405. This version hit 80% success on the first eval run. The tighter workspace gave the policy enough demonstration density to learn reliable grasping. I should have recorded the demo video and posted it at this point, but I realized that the flexible finger of the gripper was half-broken. I replaced it with a new one, re-calibrated, and obviously the performance significantly dropped since the model was trained with the old calibration. I decided to redo the entire process again.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v2-the-nudge-trick-problem&quot;&gt;v2: The nudge trick problem&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v2-the-nudge-trick-problem&quot; 
    aria-label=&quot;Anchor link for: v2-the-nudge-trick-problem&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Collected 81 more episodes with a recalibrated arm. During recording I developed a habit of nudging the cube with the gripper&#x27;s static finger to rotate it into a better angle before grasping. Seemed clever at the time.&lt;&#x2F;p&gt;
&lt;p&gt;The overhead camera also died partway through recording (likely USB power brownout from sharing the bus with the RealSense and servo controllers), so I bought a new one. Training metrics looked identical to v1: same final loss, same convergence. But eval was pretty bad at 20-80% success rate with wild variance across runs. The policy learned a conditional behavior: sometimes nudge, sometimes grasp directly. It couldn&#x27;t consistently decide which to do.&lt;&#x2F;p&gt;
&lt;p&gt;This was because I got used to the grasp task and started doing the nudging motion: nudge the cube to correct the rotation without having to rotate the wrist roll joint, then grasp. I also included another way to grasp, which is rotating the wrist roll to align the gripper angle and then grasping. This mix unfortunately turned out to be pretty bad for model training.&lt;&#x2F;p&gt;
&lt;p&gt;Lesson learned: consistency in demonstrations matters more than quantity. One clean strategy beats a mix of tricks.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v3-clean-demos-with-wrist-roll-alignment&quot;&gt;v3: Clean demos with wrist roll alignment&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v3-clean-demos-with-wrist-roll-alignment&quot; 
    aria-label=&quot;Anchor link for: v3-clean-demos-with-wrist-roll-alignment&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Re-recorded everything with a strict protocol. No nudging. Lower the arm above the cube, rotate the wrist roll to match the cube&#x27;s angle, descend, grasp. Same strategy every time. Also replaced the dead C920 and moved the RealSense to a proper USB 3.0 port (it had been silently running at 480Mbps on a USB 2.0 port, and I found this with &lt;code&gt;lsusb -t&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;75 clean episodes after removing 5 bad ones.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;training&quot;&gt;Training&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#training&quot; 
    aria-label=&quot;Anchor link for: training&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;All SmolVLA runs used the same hyperparams: batch_size=64, 20k steps, cosine decay from 1e-4, checkpoints every 5k steps. Images get &lt;code&gt;resize_with_pad&lt;&#x2F;code&gt; to 512×512. Each run took about 10 hours on the 3090.&lt;&#x2F;p&gt;
&lt;p&gt;The v3 dual-camera run (wrist + overhead) converged to loss 0.005 at 20k steps. Loss was still dropping, so there&#x27;s room to push further. Gradient norm went from 0.18 → 0.11, stable convergence throughout.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Step&lt;&#x2F;th&gt;&lt;th&gt;Loss&lt;&#x2F;th&gt;&lt;th&gt;Grad Norm&lt;&#x2F;th&gt;&lt;th&gt;LR&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;5k&lt;&#x2F;td&gt;&lt;td&gt;0.011&lt;&#x2F;td&gt;&lt;td&gt;0.18&lt;&#x2F;td&gt;&lt;td&gt;~8.5e-5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;10k&lt;&#x2F;td&gt;&lt;td&gt;0.010&lt;&#x2F;td&gt;&lt;td&gt;0.19&lt;&#x2F;td&gt;&lt;td&gt;7.6e-5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;15k&lt;&#x2F;td&gt;&lt;td&gt;0.007&lt;&#x2F;td&gt;&lt;td&gt;0.13&lt;&#x2F;td&gt;&lt;td&gt;~4.8e-5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;20k&lt;&#x2F;td&gt;&lt;td&gt;0.005&lt;&#x2F;td&gt;&lt;td&gt;0.11&lt;&#x2F;td&gt;&lt;td&gt;2.7e-5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;results&quot;&gt;Results&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#results&quot; 
    aria-label=&quot;Anchor link for: results&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The v3 dual-camera model typically achieves 60-80% success across 5-episode eval runs. The arm approaches the cube, aligns its wrist, grasps, lifts, carries to the bowl, and releases. There&#x27;s some shaking during execution, but this is a common behavior with IL models. I feel the shaking and slightly lower success rate might also be due to the time I performed inference. I recorded the dataset in late afternoon toward 5pm in late February, but during the time of evaluation it was around 12pm, which can slightly affect the lighting condition even though I have the dominant lighting (desktop lamp).&lt;&#x2F;p&gt;
&lt;p&gt;Failure modes are mostly spatial: occasional off-center grasps where the cube slips, or slight misjudgment of the bowl position on placement. The policy clearly understands the task structure and it&#x27;s not randomly flailing.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s a demo video showing a successful run. One of the episodes had a funny moment where the robot fumbled the cube and then recovered, which was very rare.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Yet another VLA inference vid, but I fine-tuned SmolVLA on 75 demos for 20k steps on my RTX 3090. ~10.4 hours of training, usually 60-80% success rate. Way less painful than HIL-SERL from scratch, but now the performance is capped by human demos. Can you fine-tune VLAs with RL? &lt;a href=&quot;https:&#x2F;&#x2F;t.co&#x2F;qBkHjONibx&quot;&gt;pic.twitter.com&#x2F;qBkHjONibx&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&amp;mdash; Gota (@gtgando) &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;gtgando&#x2F;status&#x2F;2025427031102722300?ref_src=twsrc%5Etfw&quot;&gt;February 22, 2026&lt;&#x2F;a&gt;&lt;&#x2F;blockquote&gt; &lt;script async src=&quot;https:&#x2F;&#x2F;platform.twitter.com&#x2F;widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;&#x2F;script&gt;
&lt;h3 id=&quot;comparison-smolvla-dual-vs-wrist-only-vs-act&quot;&gt;Comparison: SmolVLA (dual) vs wrist-only vs ACT&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#comparison-smolvla-dual-vs-wrist-only-vs-act&quot; 
    aria-label=&quot;Anchor link for: comparison-smolvla-dual-vs-wrist-only-vs-act&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I trained all three variants on the same v3 dataset (75 episodes) for 20k steps each:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;&#x2F;th&gt;&lt;th&gt;Cameras&lt;&#x2F;th&gt;&lt;th&gt;Final Loss&lt;&#x2F;th&gt;&lt;th&gt;Grad Norm&lt;&#x2F;th&gt;&lt;th&gt;Train Time&lt;&#x2F;th&gt;&lt;th&gt;Params&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;SmolVLA (dual)&lt;&#x2F;td&gt;&lt;td&gt;wrist + overhead&lt;&#x2F;td&gt;&lt;td&gt;0.005&lt;&#x2F;td&gt;&lt;td&gt;0.11&lt;&#x2F;td&gt;&lt;td&gt;~10.4h&lt;&#x2F;td&gt;&lt;td&gt;~1.7B&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;SmolVLA (wrist)&lt;&#x2F;td&gt;&lt;td&gt;wrist only&lt;&#x2F;td&gt;&lt;td&gt;0.006&lt;&#x2F;td&gt;&lt;td&gt;0.11&lt;&#x2F;td&gt;&lt;td&gt;~10h&lt;&#x2F;td&gt;&lt;td&gt;~1.7B&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ACT (dual)&lt;&#x2F;td&gt;&lt;td&gt;wrist + overhead&lt;&#x2F;td&gt;&lt;td&gt;0.052&lt;&#x2F;td&gt;&lt;td&gt;3.92&lt;&#x2F;td&gt;&lt;td&gt;~10.5h&lt;&#x2F;td&gt;&lt;td&gt;52M&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Loss values aren&#x27;t directly comparable across architectures (different loss formulations). SmolVLA converges to ~10x lower loss and ~35x lower gradient norms, which is expected given the pretrained VLM backbone vs ACT training from scratch with only ResNet18 features.&lt;&#x2F;p&gt;
&lt;p&gt;On the real robot though, the difference is clearer:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Model&lt;&#x2F;th&gt;&lt;th&gt;Cameras&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;SmolVLA (dual)&lt;&#x2F;td&gt;&lt;td&gt;wrist + overhead&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;100% (5&#x2F;5)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;SmolVLA (wrist)&lt;&#x2F;td&gt;&lt;td&gt;wrist only&lt;&#x2F;td&gt;&lt;td&gt;80% (4&#x2F;5)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ACT (dual)&lt;&#x2F;td&gt;&lt;td&gt;wrist + overhead&lt;&#x2F;td&gt;&lt;td&gt;80% (4&#x2F;5)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;SmolVLA dual-cam remains the best at 100%. For this run, I recorded at night, but somehow the performance is more stable compared to the runs I did in the afternoon (mentioned in the Results section).&lt;&#x2F;p&gt;
&lt;p&gt;Dropping the overhead camera or switching to ACT both degrade to 80%. The overhead camera clearly helps SmolVLA. I also felt that the motions are much smoother with the dual-cam model, as if it has more confidence.&lt;&#x2F;p&gt;
&lt;p&gt;ACT matching SmolVLA wrist-only at 80% is notable given it&#x27;s 52M params trained from scratch vs ~1.7B fine-tuned. For the v3 ACT run I also added an aspect-ratio-preserving resize to 224×224 (which the earlier v1 ACT run was missing), so it could finally run at batch_size=64 without OOM.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;fde3ee16-e018-4c24-9f4d-79571d87da16?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;prompt-robustness&quot;&gt;Prompt robustness&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#prompt-robustness&quot; 
    aria-label=&quot;Anchor link for: prompt-robustness&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;So this is a VLA, which runs based on the input prompt. For this training I used the same task prompt: &lt;code&gt;&quot;Pick up the cube and place it in the bowl&quot;&lt;&#x2F;code&gt;. Technically it should pick up the cube regardless of its color, for example, and this is kind of the point of using VLAs instead of other IL models: it should generalize based on prompts. I ran a quick color generalization test with the SmolVLA dual-cam model, since it was trained exclusively on a red cube.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Color&lt;&#x2F;th&gt;&lt;th&gt;Result&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Orange&lt;&#x2F;td&gt;&lt;td&gt;Succeeded but hit the bowl on the way back (rare with the red cube)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Blue&lt;&#x2F;td&gt;&lt;td&gt;Grasped but dropped on the way to the bowl&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Green&lt;&#x2F;td&gt;&lt;td&gt;Grasped and moved toward the bowl but hit it with the gripper, failed&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;1&#x2F;3 success vs 5&#x2F;5 with the training color. Grasping generalizes across colors reasonably well. It did pick up all three, but the place trajectory degrades. The model seems to have learned color-specific visual features for the red cube rather than a general &quot;cube&quot; concept. The placing phase is somehow more sensitive.&lt;&#x2F;p&gt;
&lt;p&gt;I didn&#x27;t change the prompt to specify the cube color, so this is purely testing whether the vision backbone generalizes. It partially does for grasping but not for the full task. I&#x27;d probably need to add cube color variations in the dataset if I wanted to be robust w.r.t. visual appearance of the cube.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;4fbb873f-479b-4237-9efd-60506b04eb7c?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#what-i-learned&quot; 
    aria-label=&quot;Anchor link for: what-i-learned&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Data quality matters.&lt;&#x2F;strong&gt; You need to perform consistent motions for effective training. 75 clean episodes with a consistent grasp strategy outperformed 81 episodes with mixed techniques. The policy learns exactly what you show it, including your bad habits.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Workspace density matters.&lt;&#x2F;strong&gt; 50 episodes across 30cm failed; 75 episodes across 10cm succeeded. For small datasets, constrain the task space and increase coverage density.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Overhead cam helps.&lt;&#x2F;strong&gt; The trajectory of models trained with both wrist cam and overhead cam seems to be much smoother compared to the wrist-only model. Having another cam at a high angle might help.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Teleoperation lag degrades demo quality.&lt;&#x2F;strong&gt; LeRobot&#x27;s &lt;code&gt;record_loop&lt;&#x2F;code&gt; couples camera capture, dataset writing, and teleop in one synchronous loop. Camera I&#x2F;O blocks action forwarding, causing the follower to lag behind the leader. Had to move slowly during recording to keep demonstrations clean.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#what-s-next&quot; 
    aria-label=&quot;Anchor link for: what-s-next&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Obviously I could try to improve the success rate by collecting more data, adding more training steps, or using other tricks, but I&#x27;m more interested in these two points:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IL ability is capped by human demos&lt;&#x2F;strong&gt;: IL works, but it can&#x27;t outperform beyond the demonstrations in the dataset. Can we fine-tune a VLA with RL to push the performance further?&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;How to make VLAs more robust&lt;&#x2F;strong&gt;: Currently the trained model only works in the same environment at my apartment. If I performed inference at another environment (e.g., some exhibit venue), it probably wouldn&#x27;t work since it&#x27;s purely based on RGB and gets affected by different lighting conditions, backgrounds, etc. How do you train a more robust VLA? Domain randomization? Or should I ditch RGB-only and switch to a depth-oriented (RGB-D) RL model?&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For now I&#x27;m thinking of exploring point 1 and trying to fine-tune a VLA with RL or something like HIL-SERL, though I need to research more first.&lt;&#x2F;p&gt;
&lt;p&gt;All code is built on LeRobot with a custom fork. GitHub repo: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;vla-so101&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;vla-so101&lt;&#x2F;a&gt;. Datasets are on HuggingFace Hub under &lt;code&gt;gtgando&#x2F;so101_pick_place_10cm_*&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>HIL-SERL for SO-101: Real-World Grasping from Scratch</title>
		<published>2026-02-08T00:00:00+00:00</published>
		<updated>2026-02-08T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/so101-hil-serl/" type="text/html"/>
		<id>https://ggando.com/blog/so101-hil-serl/</id>
		<content type="html">&lt;h3 id=&quot;background-why-hil-serl&quot;&gt;Background: Why HIL-SERL?&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#background-why-hil-serl&quot; 
    aria-label=&quot;Anchor link for: background-why-hil-serl&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Before this, I&#x27;d already achieved 100% success in MuJoCo simulation for grasp-and-lift using state-based SAC, and had a working image-based DrQ-v2 policy that could grasp and lift in sim. However, the RGB sim2real gap was too strong and didn&#x27;t work at all (just hitting the ground, etc.). I also tried another setup with segmentation + depth that actually tried to grasp the cube, but even that was still unable to grasp in zero-shot setting. So, I realized that RL training in a real environment is necessary and I decided to pivot to HIL-SERL training to explore if it&#x27;s even possible.&lt;&#x2F;p&gt;
&lt;p&gt;The problem is that nobody seems to have done this on a grasp-related task for SO-101 before. The LeRobot codebase had HIL-SERL support, but it was built and tested around SO-100 and Koch arms. SO-101 is different enough that basically nothing worked out of the box. It took about three weeks to make it work, but I was able to achieve ~70% success rate on a grasp-only task with HIL-SERL from scratch. In this blog, I&#x27;d like to document my setup and what worked for this training.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;hardware-setup&quot;&gt;Hardware Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#hardware-setup&quot; 
    aria-label=&quot;Anchor link for: hardware-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s what you need:
Robot Arms&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SO-101 leader + follower arm pair&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Table clamps x 4:&lt;&#x2F;strong&gt; You need these to fix the arm bases to the table. The arm moves with enough force that it will knock itself over without them.
Workspace&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A dedicated desk:&lt;&#x2F;strong&gt; Strongly recommended. The arm can move randomly during training and will hit anything on the table. I&#x27;ve knocked over cups, sent objects flying, and scratched my desk. Dedicate a workspace you don&#x27;t care about.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Red wooden cube:&lt;&#x2F;strong&gt; Or whatever object you&#x27;re training on. Start with something easy to see and grasp.
Camera&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;InnoMaker 1080P USB camera x 2:&lt;&#x2F;strong&gt; I recommend buying two of them because my first one died in January after using it for about six months. During RL training, the arm sometimes impacts the table with enough force that it damages the camera over time. My second one arrived tilted to one side (manufacturing defect), so I needed a third one. Budget for camera mortality. Alternatively, just buy a realsense D405 which should be more durable.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;3D-printed camera mount:&lt;&#x2F;strong&gt; The camera needs to mount to the gripper. You&#x27;ll need to print this yourself or use a service like JLC3DP. The SO-ARM100 repo has mount STLs.
Lighting&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Desk lamp:&lt;&#x2F;strong&gt; This turned out to be critical. RGB-based RL is extremely sensitive to lighting conditions. Without a dominant, consistent light source, the reward classifier gets confused by shadows and ambient light changes throughout the day. If your room lighting isn&#x27;t consistent, buy a desk lamp and point it at the workspace. I used a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.amazon.co.jp&#x2F;dp&#x2F;B0FBRKQHPX&quot;&gt;Yamada Z-LIGHT Z-10R&lt;&#x2F;a&gt; since it was cost-effective in Japan ($100), but I heard from Grok that the BenQ e-reading LED desk lamp on amazon.com is pretty good (~$200)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Optional but Useful&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;External USB camera + clamp stand:&lt;&#x2F;strong&gt; For recording training footage from a third-person view. Useful for debugging and making demo videos.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spare USB cables:&lt;&#x2F;strong&gt; Things break.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;014_setup_640.jpg&quot; alt=&quot;HIL-SERL hardware setup&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;code-setup&quot;&gt;Code Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#code-setup&quot; 
    aria-label=&quot;Anchor link for: code-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The current LeRobot main branch doesn&#x27;t fully support SO-101 for HIL-SERL. I needed to fork and use it from my repo:
LeRobot fork: My fork with SO-101 HIL-SERL fixes&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;lerobot&quot;&gt;ggand0&#x2F;lerobot&lt;&#x2F;a&gt; (branch: hilserl-so101)&lt;&#x2F;li&gt;
&lt;li&gt;Key changes:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MuJoCo-based IK:&lt;&#x2F;strong&gt; SO101FollowerEndEffector class with damped least-squares IK (replaced URDF+placo path that had a state caching bug causing arm drift)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Gym environment rewrite:&lt;&#x2F;strong&gt; Rewrote upstream&#x27;s processor&#x2F;pipeline architecture to gym wrappers (FullProprioceptionWrapper, ImageCropResizeWrapper, leader-follower teleop wrappers)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Full proprioception:&lt;&#x2F;strong&gt; State expansion from 6-dim joints to 18-dim (joints + velocities + EE pose via MuJoCo FK)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Offline buffer conversion:&lt;&#x2F;strong&gt; MuJoCo FK for joint-to-EE action conversion&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IK-based reset:&lt;&#x2F;strong&gt; Positioning with configurable locked joints&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Human intervention:&lt;&#x2F;strong&gt; Leader arm joint mirroring&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;DrQ-v2 policy:&lt;&#x2F;strong&gt; Implementation for sim-to-real transfer&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hardware robustness:&lt;&#x2F;strong&gt; Camera auto-reconnect, motor bus retry logic, torque disable on exit&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The upstream HIL-SERL code uses URDF-based kinematics via placo for end-effector control. The reason why I used mujoco as a FK solver here was that there was a state caching bug where the IK code cached its own computed targets as the &#x27;current&#x27; position instead of re-reading where the motors actually moved to and the internal state drifted further from reality each step. I was working on sim2real RL inference at that time and the inference script was already working, so I rewrote the robot class to use MuJoCo as a pure kinematics solver instead in a similar way. This fix worked but definitely not clean, and I plan to fix it with the original approach next time I have a chance to work on HIL-SERL.&lt;&#x2F;p&gt;
&lt;p&gt;hil-serl-101: Config files and training scripts&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;hil-serl-so101&quot;&gt;ggand0&#x2F;hil-serl-so101&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Contains:
&lt;ul&gt;
&lt;li&gt;Environment configs for SO-101&lt;&#x2F;li&gt;
&lt;li&gt;Training configs (hyperparameters that worked)&lt;&#x2F;li&gt;
&lt;li&gt;Reward classifier training scripts&lt;&#x2F;li&gt;
&lt;li&gt;Evaluation scripts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;why-a-fork&quot;&gt;Why a Fork?&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#why-a-fork&quot; 
    aria-label=&quot;Anchor link for: why-a-fork&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;SO-101 was missing several things the HIL-SERL codebase assumed:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;No URDF-based kinematics:&lt;&#x2F;strong&gt; SO-100 has so100_follower_end_effector with URDF support. SO-101 didn&#x27;t. I implemented MuJoCo-based FK&#x2F;IK instead.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;State dimension mismatch:&lt;&#x2F;strong&gt; The SAC policy expected 18-dim state (6 joint positions + 6 joint velocities + 3 EE position + 3 EE orientation), but SO-101 only provided raw 6-dim joint positions. Added FullProprioceptionWrapper that computes velocities via finite differences and EE pose via MuJoCo FK.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Offline buffer didn&#x27;t handle action conversion:&lt;&#x2F;strong&gt; Recorded demonstrations had 6-dim joint actions, but the policy expects 4-dim EE delta actions. The upstream buffer had no FK-based conversion path. Added MuJoCo FK to compute EE deltas from consecutive joint states.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;IK-based reset:&lt;&#x2F;strong&gt; The upstream reset assumed manual repositioning or simple joint homing. SO-101 with EE control needs IK to move to a specific Cartesian start position.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hardware reliability:&lt;&#x2F;strong&gt; STS3215 servos over USB had intermittent read&#x2F;write failures. Added retry logic to the motor bus and camera auto-reconnect on timeout, plus atexit torque disable so the arm doesn&#x27;t hold position if the script crashes.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;reward-classifier&quot;&gt;Reward Classifier&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#reward-classifier&quot; 
    aria-label=&quot;Anchor link for: reward-classifier&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;HIL-SERL uses a learned reward classifier to detect task success from camera images. This replaces manual labeling during training.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;architecture&quot;&gt;Architecture&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#architecture&quot; 
    aria-label=&quot;Anchor link for: architecture&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;Encoder: ResNet10 (frozen, from helper2424&#x2F;resnet10)&lt;&#x2F;li&gt;
&lt;li&gt;Spatial embedding: 4x4 spatial features → learned embeddings&lt;&#x2F;li&gt;
&lt;li&gt;Classifier head: Linear → Dropout → LayerNorm → ReLU → Linear(1)&lt;&#x2F;li&gt;
&lt;li&gt;Output: Binary (success&#x2F;failure)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;data-collection&quot;&gt;Data Collection&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#data-collection&quot; 
    aria-label=&quot;Anchor link for: data-collection&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The HIL-SERL paper recommends ~200 positive frames, ~1000 negative frames from ~10 teleoperated trajectories, taking about 5 minutes. I collected significantly more data than this to improve robustness.&lt;&#x2F;p&gt;
&lt;p&gt;First, I recorded 23 episodes for the offline dataset with clean trajectories at 8 seconds per episode. Then, I recorded more positive and negative samples simulating how the robot would grasp or fail at 10–20 seconds per episode. For example, grasp frames with different gripper angles (positive) and scenes of different backgrounds without the cube (negative).
I recorded episodes with terminate_on_success: false to capture both successful grasp frames and the approach&#x2F;failure frames in the same trajectories. Then I labeled frame ranges in each episode (frames &amp;gt;= cutoff are success, frames &amp;lt; cutoff are failure).&lt;&#x2F;p&gt;
&lt;h4 id=&quot;dataset-stats&quot;&gt;Dataset Stats&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#dataset-stats&quot; 
    aria-label=&quot;Anchor link for: dataset-stats&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;div style=&quot;overflow-x:auto;&quot;&gt;&lt;table style=&quot;table-layout:fixed;width:100%;&quot;&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;&#x2F;th&gt;&lt;th&gt;V5 Lamp Total&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Episodes&lt;&#x2F;td&gt;&lt;td&gt;42&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Frames&lt;&#x2F;td&gt;&lt;td&gt;4,731&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Success&lt;&#x2F;td&gt;&lt;td&gt;1,034 (21.9%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Failure&lt;&#x2F;td&gt;&lt;td&gt;3,697 (78.1%)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
Since I didn&#x27;t have many frames, I split the data into train&#x2F;val with 0.15 val ratio rather than train&#x2F;val&#x2F;test. Note that I used frame-based split not episode-based. After training it for 43 epochs (2,666 steps), it achieved the best val accuracy of 97.3%. 
I also implemented an inference script using the live camera feed and confirmed that the trained model works fine.
&lt;p&gt;Here&#x27;s the inference demo using the wrist cam live feed:&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;22860539-c098-46de-b559-259d5b8c87c5?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;training-hil-serl&quot;&gt;Training HIL-SERL&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#training-hil-serl&quot; 
    aria-label=&quot;Anchor link for: training-hil-serl&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;architecture-1&quot;&gt;Architecture&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#architecture-1&quot; 
    aria-label=&quot;Anchor link for: architecture-1&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;HIL-SERL uses an actor-learner architecture:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;┌─────────────────┐     gRPC      ┌─────────────────┐&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│     Actor       │◄────────────►│     Learner     │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│  (Real Robot)   │   weights    │     (GPU)       │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│    10 Hz        │   data       │    SAC + UTD    │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└────────┬────────┘              └────────┬────────┘&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         │                                │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;         ▼                                ▼&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;┌─────────────────┐              ┌─────────────────┐&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│  Leader Arm     │              │ Reward          │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│  (Human)        │              │ Classifier      │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└─────────────────┘              └─────────────────┘&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;Actor: Controls the real robot, runs policy at 10 Hz, sends transitions to learner&lt;&#x2F;li&gt;
&lt;li&gt;Learner: Trains SAC on GPU with high UTD (update-to-data) ratio&lt;&#x2F;li&gt;
&lt;li&gt;Leader arm: Human can grab the leader to intervene and guide the robot&lt;&#x2F;li&gt;
&lt;li&gt;Reward classifier: Predicts success from camera images in real-time&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;hyperparameters-that-worked&quot;&gt;Hyperparameters That Worked&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#hyperparameters-that-worked&quot; 
    aria-label=&quot;Anchor link for: hyperparameters-that-worked&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I mostly used the same values from the original paper instead of LeRobot defaults. Initially I was using wrong utd_ratio, temperature_init, and target_entropy and it didn&#x27;t work — these are pretty important for determining how the agent explores during training.&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x:auto;&quot;&gt;&lt;table style=&quot;table-layout:fixed;width:100%;&quot;&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Parameter&lt;&#x2F;th&gt;&lt;th&gt;Value&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;batch_size&lt;&#x2F;td&gt;&lt;td&gt;256&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;utd_ratio&lt;&#x2F;td&gt;&lt;td&gt;20&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;discount&lt;&#x2F;td&gt;&lt;td&gt;0.97&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;actor_lr&lt;&#x2F;td&gt;&lt;td&gt;0.0003&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;critic_lr&lt;&#x2F;td&gt;&lt;td&gt;0.0003&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;temperature_init&lt;&#x2F;td&gt;&lt;td&gt;0.01&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;target_entropy&lt;&#x2F;td&gt;&lt;td&gt;-2.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;num_critics&lt;&#x2F;td&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;critic_target_update_weight&lt;&#x2F;td&gt;&lt;td&gt;0.005&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;latent_dim&lt;&#x2F;td&gt;&lt;td&gt;256&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;hidden_dims&lt;&#x2F;td&gt;&lt;td&gt;[256, 256]&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;vision_encoder&lt;&#x2F;td&gt;&lt;td&gt;helper2424&#x2F;resnet10 (frozen)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;image_encoder_hidden_dim&lt;&#x2F;td&gt;&lt;td&gt;32&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;action_scale&lt;&#x2F;td&gt;&lt;td&gt;0.02&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;fps&lt;&#x2F;td&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;control_time_s&lt;&#x2F;td&gt;&lt;td&gt;10.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
&lt;h4 id=&quot;environment-config&quot;&gt;Environment Config&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#environment-config&quot; 
    aria-label=&quot;Anchor link for: environment-config&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;ik_reset_ee_pos&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.25&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.07&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;random_ee_range_xy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;random_ee_range_z&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.01&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;reset_delay_s&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Critical: Match reset height &#x2F; position to your demonstration data. I wasted hours debugging because my reset height was z=0.03 but my demos were recorded at z=0.07. The policy was starting from states it had never seen.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;human-intervention-protocol&quot;&gt;Human Intervention Protocol&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#human-intervention-protocol&quot; 
    aria-label=&quot;Anchor link for: human-intervention-protocol&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;From the HIL-SERL paper:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;This intervention is crucial in scenarios where the policy leads the robot to an unrecoverable or undesirable state, or when it becomes stuck in a local optimum that would otherwise require a significant amount of time to overcome without human assistance.&quot;
The paper shows that without interventions, even with 10x more demonstrations (200 vs 20), the policy fails on complex tasks like dashboard assembly (0% success). So interventions are essential.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;avoid-long-sparse-interventions&quot;&gt;Avoid Long Sparse Interventions&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#avoid-long-sparse-interventions&quot; 
    aria-label=&quot;Anchor link for: avoid-long-sparse-interventions&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Direct quote from the paper:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;the policy improves faster when the human operator issues specific corrections while letting the robot explore on its own otherwise.&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;we should avoid persistently providing long sparse interventions that lead to task successes. Such an intervention strategy will cause the overestimation of the value function, particularly in the early stages of the training process; which can result in unstable training dynamics.&quot;
At first, I was making a critical mistake of intervening all the way to a success, but doing this every time seems to weaken the policy&#x27;s ability to learn autonomous success and recovery. The Q-function would learn that &quot;human intervention = guaranteed success&quot; and overestimates values for states where the human typically takes over. This destabilizes learning. Instead, we need to intervene frequently with short corrections. Many small nudges &amp;gt; few complete takeovers. For example you could bring the arm near cube when it started drifting away from it.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;At first, I was making a critical mistake of intervening all the way to a success. For example, when the policy struggled to grasp the cube on the left side of the camera view, I would take over with the leader arm and complete the entire grasp for it, thinking this would teach it. But the opposite happened: it never learned to grasp at those positions on its own. You need to let the policy make mistakes and learn from them by limiting intervention to short guidance and corrections, not full task completions.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;the-training-experience&quot;&gt;The Training Experience&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-training-experience&quot; 
    aria-label=&quot;Anchor link for: the-training-experience&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;It&#x27;s exhausting. The paper makes this sound straightforward, but you&#x27;re standing at the robot for hours, watching it attempt grasps, intervening when it fails, repositioning the cube between episodes. You can&#x27;t walk away because the policy might do something that needs correction.
I did ~3 hours of active babysitting across multiple sessions. The paper says 1–2.5 hours for Franka tasks, but those are ~$40k industrial arms with Berkeley&#x27;s robotics lab behind them. For a first-time SO-101 setup with all the debugging, expect longer.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;results&quot;&gt;Results&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#results&quot; 
    aria-label=&quot;Anchor link for: results&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;Sim2real with RGB didn&amp;#39;t work, so I trained a grasp-only model from scratch using HIL-SERL on the real SO-101. It took ~750 episodes and 3 hours, but wow manual training is so exhausting &lt;a href=&quot;https:&#x2F;&#x2F;t.co&#x2F;rl3KJwDGSy&quot;&gt;pic.twitter.com&#x2F;rl3KJwDGSy&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&amp;mdash; Gota (@gtgando) &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;gtgando&#x2F;status&#x2F;2019696686084571535?ref_src=twsrc%5Etfw&quot;&gt;February 6, 2026&lt;&#x2F;a&gt;&lt;&#x2F;blockquote&gt; &lt;script async src=&quot;https:&#x2F;&#x2F;platform.twitter.com&#x2F;widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;&#x2F;script&gt;
&lt;h4 id=&quot;training-stats&quot;&gt;Training Stats&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#training-stats&quot; 
    aria-label=&quot;Anchor link for: training-stats&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;div style=&quot;overflow-x:auto;&quot;&gt;&lt;table style=&quot;table-layout:fixed;width:100%;&quot;&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Metric&lt;&#x2F;th&gt;&lt;th&gt;Value&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Total episodes&lt;&#x2F;td&gt;&lt;td&gt;757&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Total steps&lt;&#x2F;td&gt;&lt;td&gt;~48,300&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Optimization steps&lt;&#x2F;td&gt;&lt;td&gt;9,500&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Final intervention rate&lt;&#x2F;td&gt;&lt;td&gt;5.9%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Episodes with 0% intervention (last 100)&lt;&#x2F;td&gt;&lt;td&gt;61&#x2F;100&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
The intervention rate dropped significantly after ~400 episodes. By the end, the policy was mostly autonomous.
&lt;h4 id=&quot;evaluation&quot;&gt;Evaluation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#evaluation&quot; 
    aria-label=&quot;Anchor link for: evaluation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;20 evaluation episodes with cube in varying positions:&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x:auto;&quot;&gt;&lt;table style=&quot;table-layout:fixed;width:100%;&quot;&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Position Type&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Center (left-biased)&lt;&#x2F;td&gt;&lt;td&gt;80% (8&#x2F;10)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Edges (distributed)&lt;&#x2F;td&gt;&lt;td&gt;60% (6&#x2F;10)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Combined&lt;&#x2F;td&gt;&lt;td&gt;70% (14&#x2F;20)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
The policy is stronger in the center where training data concentrated, weaker at workspace edges. The ±1cm randomization during training limited generalization to boundary positions.
&lt;h4 id=&quot;training-analysis&quot;&gt;Training Analysis&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#training-analysis&quot; 
    aria-label=&quot;Anchor link for: training-analysis&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;014_training_curves_1280.png&quot; alt=&quot;HIL-SERL training curves&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p style=&quot;text-align:center; color:gray; font-size:0.9em;&quot;&gt;Episode reward (top) and intervention rate (bottom) over 757 episodes. Dashed lines indicate session breaks.&lt;&#x2F;p&gt;
&lt;p&gt;757 total episodes across 2 sessions (~3 hours of real-world training).&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x:auto;&quot;&gt;&lt;table style=&quot;table-layout:fixed;width:100%;&quot;&gt;
&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Phase&lt;&#x2F;th&gt;&lt;th&gt;Avg Reward&lt;&#x2F;th&gt;&lt;th&gt;Success%&lt;&#x2F;th&gt;&lt;th&gt;Intervention%&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Early (1-100)&lt;&#x2F;td&gt;&lt;td&gt;17.6&lt;&#x2F;td&gt;&lt;td&gt;32.0%&lt;&#x2F;td&gt;&lt;td&gt;20.8%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Mid (101-300)&lt;&#x2F;td&gt;&lt;td&gt;16.3&lt;&#x2F;td&gt;&lt;td&gt;27.0%&lt;&#x2F;td&gt;&lt;td&gt;17.8%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Late (301-500)&lt;&#x2F;td&gt;&lt;td&gt;59.1&lt;&#x2F;td&gt;&lt;td&gt;69.0%&lt;&#x2F;td&gt;&lt;td&gt;22.0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Final (501+)&lt;&#x2F;td&gt;&lt;td&gt;95.5&lt;&#x2F;td&gt;&lt;td&gt;79.4%&lt;&#x2F;td&gt;&lt;td&gt;9.2%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
&lt;p&gt;Overall episode rewards increase monotonically. Episode 300 seems to be a breakthrough point where the policy started behaving better. The intervention rate rose around this point because the policy was overfitting to a specific motion that could only grasp cubes on the right side of the camera view, so I started placing cubes more toward the left side and positions where it struggled. Toward the final phase of training (ep 500+), MA20 stabilized around 95-107, indicating convergence.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;things-to-make-hil-serl-work&quot;&gt;Things to make HIL-SERL work&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#things-to-make-hil-serl-work&quot; 
    aria-label=&quot;Anchor link for: things-to-make-hil-serl-work&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s the TL;DR of critical points I think are very important to train HIL-SERL successfully:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lighting:&lt;&#x2F;strong&gt; Consistent lighting is critical for RGB-based RL. Use a desk lamp as a dominant light source. Without one, the reward classifier gets confused by shadows and ambient light changes.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Reward classifier extra samples:&lt;&#x2F;strong&gt; Collect positive&#x2F;negative samples for edge cases that occur during training. The agent might reward-hack without covering these.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Human intervention techniques:&lt;&#x2F;strong&gt; Short corrections, not long takeovers; let policy explore early.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hyperparams different from paper:&lt;&#x2F;strong&gt; Especially exploration-related ones like temperature_init, utd_ratio, and target_entropy. Use the same values as the original paper.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;USB reconnection logic:&lt;&#x2F;strong&gt; USB keeps dying and interrupts training without this. Handling servo disconnects mid-training.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;750 episodes not 500:&lt;&#x2F;strong&gt; Keep training if improving, don&#x27;t stop at arbitrary cutoff. Ideally finish training within the same time segment like morning, afternoon, night etc.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Accurate calibration:&lt;&#x2F;strong&gt; Actually positioning joints in the middle of their range during calibration startup.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Classifier preprocessing:&lt;&#x2F;strong&gt; Use 128x128px as in the paper, consistent logic; in my case I center-cropped 480p frame to 480x480px square image then resize to 128x128px while maintaining the aspect ratio.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Classifier: frame-based train&#x2F;val split:&lt;&#x2F;strong&gt; Not episode-based.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Reset pose matching demos:&lt;&#x2F;strong&gt; Reset height must match demonstration data. I wasted hours debugging because my reset height was z=0.03 but my demos were recorded at z=0.07. The policy was starting from states it had never seen.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Small position randomization:&lt;&#x2F;strong&gt; ±1cm works. Larger randomization caused failures and the policy couldn&#x27;t generalize.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#lessons-learned&quot; 
    aria-label=&quot;Anchor link for: lessons-learned&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;the-real-cost&quot;&gt;The Real Cost&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-real-cost&quot; 
    aria-label=&quot;Anchor link for: the-real-cost&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;strong&gt;Runtime errors:&lt;&#x2F;strong&gt; I fixed dozens of bugs across the LeRobot codebase before training even worked. Missing imports, path checks, state dimensions, keyboard handling, intervention logic, reset positioning. This took weeks.
&lt;strong&gt;Hardware failures:&lt;&#x2F;strong&gt; Cameras die. Servos drift. Cables break. Budget time and money for replacements.
&lt;strong&gt;Human time:&lt;&#x2F;strong&gt; 3+ hours of active robot babysitting, plus all the setup time. This is not a &quot;run overnight&quot; method.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;is-hil-serl-worth-it&quot;&gt;Is HIL-SERL Worth It?&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#is-hil-serl-worth-it&quot; 
    aria-label=&quot;Anchor link for: is-hil-serl-worth-it&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;For learning the full sim-to-real RL pipeline: Yes. I understand how these systems work at a level I never would have from just reading papers.
For getting a working grasp policy: Maybe not. ACT trained on 50 demonstrations would probably achieve similar results with less total effort. VLAs like π₀ or OpenVLA might work with even less data.
The 70% success rate is pretty good where the policy discovered its own grasping strategy through exploration and corrections, not just copying demos. But the marginal improvement per hour of human time gets worse as the policy improves. I&#x27;m not sure I want to grind another few hundred episodes to push to 80%, let alone attempt a more complex task.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;what-s-next&quot;&gt;What&#x27;s Next&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#what-s-next&quot; 
    aria-label=&quot;Anchor link for: what-s-next&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;VLAs: Try π₀ or OpenVLA with minimal fine-tuning. These are pretrained on internet-scale robot data and might work faster.&lt;&#x2F;li&gt;
&lt;li&gt;RGBD: I just bought a realsense D405 depth camera. Adding depth might reduce the visual domain gap enough for better sim-to-real transfer.&lt;&#x2F;li&gt;
&lt;li&gt;Full pick-and-place: The current 70% is grasp-only. Extending to lift-and-place is the actual goal.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Thanks for reading! Let me know if anyone managed to make HIL-SERL work with SO-101. If you&#x27;re interested, the code is here: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;hil-serl-so101&quot;&gt;ggand0&#x2F;hil-serl-so101&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Ubuntu 24.04 upgrade broke my GNOME desktop (ROCm leftovers)</title>
		<published>2026-01-13T00:00:00+00:00</published>
		<updated>2026-01-13T00:00:00+00:00</updated>
        <summary>&lt;p&gt;I finally upgraded my Ubuntu 22.04 machine to 24.04. The upgrade itself went smoothly, but rebooting into the new system greeted me with GNOME&#x27;s dreaded crash screen—the white &quot;X_X&quot; face with &quot;Oh no! Something has gone wrong.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;The culprit? Old ROCm 6.4.3 artifacts from the previous installation were incompatible with 24.04&#x27;s stricter parsing and newer libraries.&lt;&#x2F;p&gt;</summary>
		<link href="https://ggando.com/til/008-24-04-upgrade/" type="text/html"/>
		<id>https://ggando.com/til/008-24-04-upgrade/</id>
		<content type="html">&lt;p&gt;I finally upgraded my Ubuntu 22.04 machine to 24.04. The upgrade itself went smoothly, but rebooting into the new system greeted me with GNOME&#x27;s dreaded crash screen—the white &quot;X_X&quot; face with &quot;Oh no! Something has gone wrong.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;The culprit? Old ROCm 6.4.3 artifacts from the previous installation were incompatible with 24.04&#x27;s stricter parsing and newer libraries.&lt;&#x2F;p&gt;
&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h2 id=&quot;the-symptoms&quot;&gt;The symptoms&lt;&#x2F;h2&gt;
&lt;p&gt;After rebooting, I couldn&#x27;t even get to a TTY login prompt initially. The system would boot, show the crash screen, and that was it. Recovery mode was the only way in.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;issues-i-encountered&quot;&gt;Issues I encountered&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;1-postgresql-blocking-the-upgrade&quot;&gt;1. PostgreSQL blocking the upgrade&lt;&#x2F;h3&gt;
&lt;p&gt;Before I could even start the upgrade, &lt;code&gt;do-release-upgrade&lt;&#x2F;code&gt; complained about PostgreSQL packages being in the removal deny list. Had to purge them first:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt purge postgresql&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;2-invalid-udev-rule-syntax&quot;&gt;2. Invalid udev rule syntax&lt;&#x2F;h3&gt;
&lt;p&gt;Ubuntu 24.04 has stricter udev parsing. The ROCm rule at &lt;code&gt;&#x2F;etc&#x2F;udev&#x2F;rules.d&#x2F;70-amdgpu.rules&lt;&#x2F;code&gt; had swapped operators:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Broken (22.04 tolerated this)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;KERNEL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;kfd&amp;quot;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; GROUP&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-punctuation z-definition z-string&quot;&gt;=&amp;quot;video&amp;quot;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MODE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;660&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Fixed (24.04 requires correct operators)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;KERNEL&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-punctuation z-definition z-string&quot;&gt;=&amp;quot;kfd&amp;quot;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; GROUP&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;video&amp;quot;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MODE&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0660&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;KERNEL&lt;&#x2F;code&gt; field needs &lt;code&gt;==&lt;&#x2F;code&gt; (comparison), while &lt;code&gt;GROUP&lt;&#x2F;code&gt; needs &lt;code&gt;=&lt;&#x2F;code&gt; (assignment). This one showed up in &lt;code&gt;journalctl&lt;&#x2F;code&gt; as &quot;Invalid operator for GROUP.&quot;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-missing-drmmodeclosefb-symbol&quot;&gt;3. Missing &lt;code&gt;drmModeCloseFB&lt;&#x2F;code&gt; symbol&lt;&#x2F;h3&gt;
&lt;p&gt;GNOME Shell was crashing with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;usr&#x2F;bin&#x2F;gnome-shell: symbol lookup error: &#x2F;lib&#x2F;x86_64-linux-gnu&#x2F;libmutter-14.so.0: undefined symbol: drmModeCloseFB&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The symbol existed in the system libdrm, but ROCm&#x27;s old libdrm in &lt;code&gt;&#x2F;opt&#x2F;amdgpu&#x2F;lib&#x2F;&lt;&#x2F;code&gt; was being loaded first due to leftover &lt;code&gt;ld.so.conf.d&lt;&#x2F;code&gt; entries. The system was loading the wrong library.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-xorg-segfault&quot;&gt;4. Xorg segfault&lt;&#x2F;h3&gt;
&lt;p&gt;Even after fixing the libdrm issue, X11 wouldn&#x27;t start—Xorg was segfaulting in &lt;code&gt;radeonsi_dri.so&lt;&#x2F;code&gt;. Turned out there was an old &lt;code&gt;&#x2F;etc&#x2F;X11&#x2F;xorg.conf&lt;&#x2F;code&gt; from the amdgpu-pro days that was forcing incompatible settings.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-fix&quot;&gt;The fix&lt;&#x2F;h2&gt;
&lt;p&gt;All of these issues traced back to leftover ROCm configuration files. The solution was to back them up and let the system use its defaults:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Back up the problematic ld.so.conf files&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mv &#x2F;etc&#x2F;ld.so.conf.d&#x2F;15-amdgpu-pro.conf{,.bak}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mv &#x2F;etc&#x2F;ld.so.conf.d&#x2F;20-amdgpu.conf{,.bak}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mv &#x2F;etc&#x2F;ld.so.conf.d&#x2F;10-rocm-opencl.conf{,.bak}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mv &#x2F;etc&#x2F;ld.so.conf.d&#x2F;rocm.conf{,.bak}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Back up the old xorg.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mv &#x2F;etc&#x2F;X11&#x2F;xorg.conf{,.bak}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Rebuild the library cache&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ldconfig&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After this, Wayland worked fine. I later reinstalled ROCm for &lt;code&gt;noble&lt;&#x2F;code&gt; and everything came back up—PyTorch detects the GPU, compute workloads run, and the desktop is stable.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-preserve-during-upgrade&quot;&gt;What to preserve during upgrade&lt;&#x2F;h2&gt;
&lt;p&gt;These config files should survive the upgrade intact:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&#x2F;etc&#x2F;default&#x2F;grub&lt;&#x2F;code&gt; — kept my RDNA3 stability params (&lt;code&gt;amdgpu.gfxoff=0&lt;&#x2F;code&gt;, &lt;code&gt;amdgpu.tmz=0&lt;&#x2F;code&gt;, &lt;code&gt;amdgpu.runpm=0&lt;&#x2F;code&gt;, &lt;code&gt;amdgpu.ppfeaturemask=0xfffd7fff&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;&#x2F;etc&#x2F;security&#x2F;limits.conf&lt;&#x2F;code&gt; — custom resource limits for GPU workloads&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The upgrade process asked about these and I chose to keep my versions.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;lesson-learned&quot;&gt;Lesson learned&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;re upgrading from 22.04 with ROCm installed, expect breakage. The third-party repos get disabled automatically by &lt;code&gt;do-release-upgrade&lt;&#x2F;code&gt;, but the old libraries and config files stick around and cause conflicts with the newer system components.&lt;&#x2F;p&gt;
&lt;p&gt;My approach next time: completely purge ROCm before upgrading, then reinstall fresh for the new release. Would&#x27;ve saved me a few hours of debugging in recovery mode.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Teaching a Robot to Grasp from Pixels in MuJoCo</title>
		<published>2026-01-12T00:00:00+00:00</published>
		<updated>2026-01-12T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/image-rl-grasp/" type="text/html"/>
		<id>https://ggando.com/blog/image-rl-grasp/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;so101_image-rl_lift_1280.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;context&quot;&gt;Context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#context&quot; 
    aria-label=&quot;Anchor link for: context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;After achieving 100% success on state-based RL for my SO-101 robot arm&#x27;s grasp-and-lift task, I wanted to move to image-based RL. My IRL SO-101 setup has a wrist camera on a custom mount and I want to do sim2real. State-based RL relies on privileged information: exact cube position that doesn&#x27;t exist on a real robot. Image-based RL learns directly from camera pixels which makes the policy transferable to real hardware.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-setup&quot;&gt;The Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-setup&quot; 
    aria-label=&quot;Anchor link for: the-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;stack&quot;&gt;Stack&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#stack&quot; 
    aria-label=&quot;Anchor link for: stack&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;Algorithm: DrQ-v2 (Data-regularized Q-learning with random shift augmentation)&lt;&#x2F;li&gt;
&lt;li&gt;Framework: RoboBase (fork with custom SB3-style logging)&lt;&#x2F;li&gt;
&lt;li&gt;Simulation: MuJoCo with wrist-mounted camera&lt;&#x2F;li&gt;
&lt;li&gt;Hardware: AMD RX 7900 XTX, training at ~20 it&#x2F;s&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;task&quot;&gt;Task&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#task&quot; 
    aria-label=&quot;Anchor link for: task&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The agent needs to:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Observe the scene through an 84×84 wrist camera&lt;&#x2F;li&gt;
&lt;li&gt;Grasp a 3cm cube with a two-finger gripper&lt;&#x2F;li&gt;
&lt;li&gt;Lift it to 8cm height&lt;&#x2F;li&gt;
&lt;li&gt;Hold for 150 steps (~3 seconds)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;1-initial-attempts&quot;&gt;1. Initial Attempts&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#1-initial-attempts&quot; 
    aria-label=&quot;Anchor link for: 1-initial-attempts&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;the-initial-run&quot;&gt;The Initial Run&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-initial-run&quot; 
    aria-label=&quot;Anchor link for: the-initial-run&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I started with the v11 reward that worked well for state-based RL:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# v11 reward structure&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; reach_reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; grasp_bonus&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; lift_rewards&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; success_bonus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The components:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Reach reward: 1.0 - tanh(10 * distance) — guides gripper toward cube&lt;&#x2F;li&gt;
&lt;li&gt;Grasp bonus: +0.25 when both fingers contact cube&lt;&#x2F;li&gt;
&lt;li&gt;Binary lift bonus: +1.0 when cube_z &amp;gt; 0.02&lt;&#x2F;li&gt;
&lt;li&gt;Continuous lift: proportional to height progress&lt;&#x2F;li&gt;
&lt;li&gt;Success bonus: +10.0 for completing the task&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;results-0-success-at-2m-steps&quot;&gt;Results: 0% Success at 2M Steps&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#results-0-success-at-2m-steps&quot; 
    aria-label=&quot;Anchor link for: results-0-success-at-2m-steps&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Steps&lt;&#x2F;th&gt;&lt;th&gt;Eval Reward&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;400k&lt;&#x2F;td&gt;&lt;td&gt;~320&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1M&lt;&#x2F;td&gt;&lt;td&gt;301 ± 76&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2M&lt;&#x2F;td&gt;&lt;td&gt;326 ± 15&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The agent got around 326 ± 15 rewards after 2M steps, and when I checked the video I just saw the agent:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Nudges cube with the static finger toward the arm base&lt;&#x2F;li&gt;
&lt;li&gt;Supports cube against static finger at the arm base&lt;&#x2F;li&gt;
&lt;li&gt;Never attempts a proper two-finger grasp&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;e0adfc76-7985-48ca-8b02-eb4094f5d603?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;p style=&quot;text-align:center; color:gray; font-size:0.9em;&quot;&gt;The cube spinning on its corner vertex lol&lt;&#x2F;p&gt;
&lt;p&gt;But at this point, I realized that the wrist camera view was mounted backwards in simulation, and decided to correct this first:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- v1 camera: WRONG - Camera on back side, facing arm base! --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;camera&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;wrist_cam&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.0 0.055 0.02&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; euler&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0 0 0&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; fovy&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;75&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Y position +0.055 placed the camera on the back side of the gripper, so I just flipped the Y position and added 180° rotation:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- v2 camera: Correct - Camera on front side, facing cube --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;camera&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;wrist_cam&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.0 -0.055 0.02&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; euler&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0 0 3.14159&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; fovy&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;75&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After the fix, I trained again with the same v11 reward:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Steps&lt;&#x2F;th&gt;&lt;th&gt;Eval Reward&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;160k&lt;&#x2F;td&gt;&lt;td&gt;209&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;480k&lt;&#x2F;td&gt;&lt;td&gt;314&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;960k&lt;&#x2F;td&gt;&lt;td&gt;316&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.92M&lt;&#x2F;td&gt;&lt;td&gt;322&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Still stuck at the same 320-ish reward.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;a-new-exploit&quot;&gt;A New Exploit&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#a-new-exploit&quot; 
    aria-label=&quot;Anchor link for: a-new-exploit&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;This time, a different exploit emerged around 1.76M steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Press cube edge with static finger&lt;&#x2F;li&gt;
&lt;li&gt;Tilt cube at 45° angle&lt;&#x2F;li&gt;
&lt;li&gt;Cube center rises from z=0.015 to z=0.021&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;This triggered the binary lift bonus (+1.0 for cube_z &amp;gt; 0.02) without actually grasping. The cube resting height is ~0.015m, so tilting it was enough to cross the threshold.&lt;&#x2F;p&gt;
&lt;p&gt;Exploit reward: ~1.9&#x2F;step (reach + binary lift)
Proper grasp reward: ~2.33&#x2F;step&lt;&#x2F;p&gt;
&lt;p&gt;The 0.43&#x2F;step difference wasn&#x27;t enough incentive to learn the harder two-finger grasp.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-v13-breakthrough&quot;&gt;2. v13 Breakthrough&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#2-v13-breakthrough&quot; 
    aria-label=&quot;Anchor link for: 2-v13-breakthrough&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;the-fix&quot;&gt;The Fix&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-fix&quot; 
    aria-label=&quot;Anchor link for: the-fix&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The tilt exploit revealed the issue where the agent was receiving lift rewards without actually grasping the cube. I&#x27;m still new to RL, so it&#x27;s interesting to see how it learned to support the cube diagonally though.&lt;&#x2F;p&gt;
&lt;p&gt;Since I was already tracking the grasp status by a flag &lt;code&gt;is_grasping&lt;&#x2F;code&gt;, I just gated the lift bonus with this flag:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# v11 (exploitable):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; cube_z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.02&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Tilt exploit gets this for free&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# v13 (fixed):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; is_grasping:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; cube_z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.02&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Only with proper two-finger grasp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I called it the v13 reward. After the fix, training with v13 reward on the same v2 camera (simple flip, no angle tweaks):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;800k steps | 3:08:40 elapsed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ep_rew_mean: 650.17&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;success_rate: 0.10 (10%)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;New best model saved!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the first time, the agent was actually grasping the cube with both fingers and lifting it. The video showed proper two-finger contact, closing, and lifting behavior. Just fixing the camera direction and gating the lift bonus on grasping was enough!&lt;&#x2F;p&gt;
&lt;h4 id=&quot;shaking-behavior&quot;&gt;Shaking Behavior&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#shaking-behavior&quot; 
    aria-label=&quot;Anchor link for: shaking-behavior&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;However, I noticed that the agent peaked at 800k and then regressed:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Steps&lt;&#x2F;th&gt;&lt;th&gt;Reward&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;800k&lt;&#x2F;td&gt;&lt;td&gt;650.17&lt;&#x2F;td&gt;&lt;td&gt;10%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1M&lt;&#x2F;td&gt;&lt;td&gt;~400&lt;&#x2F;td&gt;&lt;td&gt;~5%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;1.5M&lt;&#x2F;td&gt;&lt;td&gt;~350&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2M&lt;&#x2F;td&gt;&lt;td&gt;~315&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Fortunately, I had implemented best-model saving that captured the 800k checkpoint automatically. Evaluating the saved best checkpoint:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Mean Reward: 632.27 ± 43.00&lt;&#x2F;li&gt;
&lt;li&gt;Success Rate: 0%&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The agent was grasping and lifting, but not succeeding. I set the success condition of this task to be lifting at z &amp;gt; 8cm for more than 10 steps. I watched the evaluation videos, and I saw it shaking after lifting. The agent lifted to ~4-5cm, then oscillated instead of continuing to 8cm. Possible reward hacking or policy instability at elevated positions.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;camera-calibration&quot;&gt;Camera Calibration&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#camera-calibration&quot; 
    aria-label=&quot;Anchor link for: camera-calibration&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The shaking issue was still not fixed, but I noticed that the camera view was still very different from my IRL wrist cam, so I wanted to calibrate it again to be closer to the real innoMaker camera specs. Changes:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Parameter&lt;&#x2F;th&gt;&lt;th&gt;v2 (simple flip)&lt;&#x2F;th&gt;&lt;th&gt;v3 (calibrated)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Position X&lt;&#x2F;td&gt;&lt;td&gt;0.0&lt;&#x2F;td&gt;&lt;td&gt;0.02&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Position Y&lt;&#x2F;td&gt;&lt;td&gt;-0.055&lt;&#x2F;td&gt;&lt;td&gt;-0.08&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Position Z&lt;&#x2F;td&gt;&lt;td&gt;0.02&lt;&#x2F;td&gt;&lt;td&gt;-0.06&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pitch&lt;&#x2F;td&gt;&lt;td&gt;0°&lt;&#x2F;td&gt;&lt;td&gt;+40°&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;FOV&lt;&#x2F;td&gt;&lt;td&gt;75°&lt;&#x2F;td&gt;&lt;td&gt;103°&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;It took some time since this process is manual and Claude is usually bad at spatial understanding. Always trust your own eyes. I ended up adjusting the camera again, but after this adjustment it improved in the sense that I no longer saw the gripper body in the view.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;3-single-finger-behavior-problem&quot;&gt;3. Single-Finger Behavior Problem&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#3-single-finger-behavior-problem&quot; 
    aria-label=&quot;Anchor link for: 3-single-finger-behavior-problem&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I had to go through a series of failed attempts to get the agent to actually grasp the cube with image-based setup. I&#x27;m documenting these here for the record, but if you&#x27;re interested in the reward function that succeeded, skip to section 4.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v14-reward-single-finger-behavior-returns&quot;&gt;v14 Reward: Single-Finger Behavior Returns&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v14-reward-single-finger-behavior-returns&quot; 
    aria-label=&quot;Anchor link for: v14-reward-single-finger-behavior-returns&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I defined a minor action penalty timing change to the v13 reward to fix the shaking behavior as the v14 reward and trained it with the new v3 camera. Interestingly, it showed the agent just nudging the cube with the static finger at 1.12M steps. I thought this was just a bug in the action penalty I just added, so I went through a systematic iteration process to fix this. I trained it with v15 and v16 rewards:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v15-gripper-open-penalty&quot;&gt;v15: Gripper-Open Penalty&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v15-gripper-open-penalty&quot; 
    aria-label=&quot;Anchor link for: v15-gripper-open-penalty&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The agent was stuck in a local optimum, positioning near the cube but keeping the gripper wide open. My hypothesis: penalize keeping the gripper open too long.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# v15: Penalty for keeping gripper open after grace period&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;grace_period&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 40&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # ~20 agent decisions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; gripper_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.3&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; grace_period steps:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    penalty = &lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;min&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.05&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; excess_steps&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 50&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.3&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Caps at 0.3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt;= penalty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Result: The agent learned to briefly close&#x2F;open the gripper every ~39 steps to game the penalty (cheating). Still no grasping.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v16-increased-grasp-bonus-devlog-046&quot;&gt;v16: Increased Grasp Bonus (Devlog 046)&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v16-increased-grasp-bonus-devlog-046&quot; 
    aria-label=&quot;Anchor link for: v16-increased-grasp-bonus-devlog-046&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The grasp bonus (+0.25) was too weak compared to reach reward (~0.9). I increased it 6x:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Behavior&lt;&#x2F;th&gt;&lt;th&gt;v14&#x2F;v15&lt;&#x2F;th&gt;&lt;th&gt;v16&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Hovering near cube&lt;&#x2F;td&gt;&lt;td&gt;~0.9&lt;&#x2F;td&gt;&lt;td&gt;~0.9&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Grasping&lt;&#x2F;td&gt;&lt;td&gt;~1.15&lt;&#x2F;td&gt;&lt;td&gt;~2.4&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Result: Still failed. The agent never discovered the grasp bonus, probably because discovering it via random exploration is hard. At this point I realized that I was just lucky with the v13 reward.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;camera-v4-recalibration&quot;&gt;Camera v4 Recalibration&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#camera-v4-recalibration&quot; 
    aria-label=&quot;Anchor link for: camera-v4-recalibration&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;While debugging the single-finger behavior, I discovered the v3 camera was still different from the real camera:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Pitch sign was wrong: +40° instead of -25° (tilted backward, showing skybox)&lt;&#x2F;li&gt;
&lt;li&gt;FOV was wrong: Used 103° (horizontal) instead of 86° (vertical, what MuJoCo expects)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- v3 (wrong): --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;camera&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.02 -0.08 -0.06&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; euler&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.698 0 3.14159&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; fovy&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;103&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- v4 (correct): --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;camera&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.02 -0.08 -0.02&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; euler&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;-0.436 0 3.14159&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; fovy&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;86&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;bootstrap-attempt&quot;&gt;Bootstrap Attempt&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bootstrap-attempt&quot; 
    aria-label=&quot;Anchor link for: bootstrap-attempt&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I was still struggling to make it grasp the cube, so I had Claude research online to search for good methods to fix this and found bootstrapping (QT-Opt&#x27;s approach), where you seed the replay buffer with scripted grasp demonstrations. So I did:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Created scripted policy (~80% success rate)&lt;&#x2F;li&gt;
&lt;li&gt;Collected 1000 trajectories&lt;&#x2F;li&gt;
&lt;li&gt;Seeded replay buffer with ~60k successful grasp transitions&lt;&#x2F;li&gt;
&lt;li&gt;Trained with v13 reward&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Result at 600k steps: Same single-finger, open-gripper behavior. The agent ignored the demonstrations entirely.&lt;&#x2F;p&gt;
&lt;p&gt;Why bootstrap failed: I&#x27;m still not sure, but QT-Opt used sparse rewards (+1 success only). Our dense reach reward (~0.9&#x2F;step) might create a local optimum that bootstrap seeding can&#x27;t escape. The agent can achieve high reward by hovering without ever sampling from the successful grasp demonstrations.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;4-v17-breakthrough&quot;&gt;4. v17 Breakthrough&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#4-v17-breakthrough&quot; 
    aria-label=&quot;Anchor link for: 4-v17-breakthrough&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;the-core-problem&quot;&gt;The Core Problem&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-core-problem&quot; 
    aria-label=&quot;Anchor link for: the-core-problem&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;After all these failures, I finally identified the root cause: the SO-101&#x27;s gripper is asymmetric and it breaks standard reward assumptions.&lt;&#x2F;p&gt;
&lt;p&gt;Standard reach reward: 1 - tanh(10 * gripper_to_cube)&lt;&#x2F;p&gt;
&lt;p&gt;This works for symmetric parallel-jaw grippers where both fingers move equally toward the object. But SO-101 has:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A static finger fixed to the gripper frame (same position as TCP)&lt;&#x2F;li&gt;
&lt;li&gt;A moving finger that opens&#x2F;closes: this one can be pretty far away from the cube when fully opened&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;When you measure &quot;gripper-to-cube distance,&quot; you&#x27;re measuring the static finger position. The moving finger gets no gradient! The agent can maximize reach reward by positioning the static finger close to the cube while keeping the gripper wide open.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v17-per-finger-reach-reward&quot;&gt;v17: Per-Finger Reach Reward&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v17-per-finger-reach-reward&quot; 
    aria-label=&quot;Anchor link for: v17-per-finger-reach-reward&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The solution: give the moving finger its own reach reward that caps when the gripper closes.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# v17: Moving finger reach with proximity gate&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;gripper_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; tanh(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;10.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; gripper_to_cube)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Static finger&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; gripper_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.7&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Far from cube&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reach_reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; gripper_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Standard only&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Close to cube&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span&gt; gripper_state&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.25&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Closed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        moving_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # Capped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    else&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        moving_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span&gt; tanh(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;10.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; moving_finger_to_cube)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reach_reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; (gripper_reach&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; moving_reach)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Key insight: When near the cube (&lt;code&gt;gripper_reach &amp;gt;= 0.7&lt;&#x2F;code&gt;), the agent gets ~0.4 more reward per step for closing the gripper. This gives explicit gradient toward closing that was missing in v11-v16.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;v17-results-breakthrough&quot;&gt;v17 Results: Breakthrough&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v17-results-breakthrough&quot; 
    aria-label=&quot;Anchor link for: v17-results-breakthrough&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Steps&lt;&#x2F;th&gt;&lt;th&gt;Grasping %&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;200k&lt;&#x2F;td&gt;&lt;td&gt;89-97%&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2M&lt;&#x2F;td&gt;&lt;td&gt;97.5%&lt;&#x2F;td&gt;&lt;td&gt;10%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;For the first time since the camera recalibration, the agent was actually grasping. At 200k steps, 5&#x2F;10 evaluation episodes already showed &amp;gt;89% grasping throughout the episode.&lt;&#x2F;p&gt;
&lt;p&gt;By 2M steps:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;9&#x2F;10 episodes: 97.5% grasping&lt;&#x2F;li&gt;
&lt;li&gt;Episode 6: Lifted to 0.15m and held (success!)&lt;&#x2F;li&gt;
&lt;li&gt;Remaining episodes: Grasped but plateaued at 0.03-0.06m lift&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;v19-100-success-rate&quot;&gt;v19: 100% Success Rate&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#v19-100-success-rate&quot; 
    aria-label=&quot;Anchor link for: v19-100-success-rate&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;v17 solved grasping but most episodes plateaued at z=0.03-0.04m. The problem: weak lift gradient.&lt;&#x2F;p&gt;
&lt;p&gt;At z=0.04m with v17, pushing 1cm higher only gave +0.31 reward. Meanwhile, dropping the cube costs ~227 reward (drop penalty + lost grasp bonus for remaining steps). The agent learned to play it safe.&lt;&#x2F;p&gt;
&lt;p&gt;v18 changes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Doubled lift coefficient: 2.0 → 4.0&lt;&#x2F;li&gt;
&lt;li&gt;Added linear ramp from 0.04m to 0.08m (+0.5&#x2F;cm bonus)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Height&lt;&#x2F;th&gt;&lt;th&gt;v17&lt;&#x2F;th&gt;&lt;th&gt;v18&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0.04m&lt;&#x2F;td&gt;&lt;td&gt;4.2&lt;&#x2F;td&gt;&lt;td&gt;5.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;0.06m&lt;&#x2F;td&gt;&lt;td&gt;4.9&lt;&#x2F;td&gt;&lt;td&gt;6.8&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;0.08m&lt;&#x2F;td&gt;&lt;td&gt;6.0&lt;&#x2F;td&gt;&lt;td&gt;9.0&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Result at 2M steps: Agent now lifts to 0.06-0.10m (vs 0.03-0.04m), but success rate dropped to 0%. The agent oscillates above&#x2F;below the 0.08m threshold, never holding long enough.&lt;&#x2F;p&gt;
&lt;p&gt;Success requires 10 consecutive steps above 0.08m. v18&#x27;s agent would go up, dip down, go up again - resetting the counter each time. Episode 7 had 63 total steps above 0.08m but they weren&#x27;t consecutive.&lt;&#x2F;p&gt;
&lt;p&gt;v19 adds escalating reward for holding at target height:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; cube_z&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.08&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # existing target bonus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    reward&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; hold_count&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # 0.5, 1.0, 1.5, ... up to 5.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;At step 9 of holding, dipping would forfeit the +4.5 bonus AND reset progress. This creates strong incentive to stabilize.&lt;&#x2F;p&gt;
&lt;p&gt;Result: 100% success rate at 2M steps. Every episode completes in ~20 steps (vs 200 step timeouts before).&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Version&lt;&#x2F;th&gt;&lt;th&gt;Success Rate&lt;&#x2F;th&gt;&lt;th&gt;Typical Height&lt;&#x2F;th&gt;&lt;th&gt;Episode Length&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;v17&lt;&#x2F;td&gt;&lt;td&gt;10%&lt;&#x2F;td&gt;&lt;td&gt;0.03-0.04m&lt;&#x2F;td&gt;&lt;td&gt;200 (timeout)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;v18&lt;&#x2F;td&gt;&lt;td&gt;0%&lt;&#x2F;td&gt;&lt;td&gt;0.06-0.10m&lt;&#x2F;td&gt;&lt;td&gt;200 (timeout)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;v19&lt;&#x2F;td&gt;&lt;td&gt;100%&lt;&#x2F;td&gt;&lt;td&gt;0.10-0.12m&lt;&#x2F;td&gt;&lt;td&gt;19-22 steps&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;5-the-cube-position-leak-bug&quot;&gt;5. The Cube Position Leak Bug&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#5-the-cube-position-leak-bug&quot; 
    aria-label=&quot;Anchor link for: 5-the-cube-position-leak-bug&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;So after achieving 100% success rate, obviously I tried deploying to my real SO-101, but it failed completely and the gripper moved vaguely toward the cube area. Debugging the checkpoint, I realized that the cube position in the simulator was leaked to the input state: the DrQ-v2 concatenates the image with robot joint positions (because they&#x27;re known even when using image as input)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; BUG&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt;: passes all 21 dims including cube_pos&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;return&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;rgb&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: img,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;low_dim_state&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: obs.astype(np.float32)}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The agent had been cheating. This is a very basic bug and I should have checked this way earlier.&lt;&#x2F;p&gt;
&lt;p&gt;But the good news is that it still achieved 100% success rate at 800k steps after retraining. I was watching the rollout rewards during that and the reward progression pattern looked identical to before (needs to double check though). It seems that the cube position was redundant and the agent mostly relied on the input images despite this privileged leak. Here&#x27;s the eval video after the retraining:&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;582c11d1-3c77-4cd5-aed6-a9994b546c42?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h2 id=&quot;6-next-steps&quot;&gt;6. Next Steps&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#6-next-steps&quot; 
    aria-label=&quot;Anchor link for: 6-next-steps&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;d like to make it work on my real SO-101 to perform a pick-and-lift task but I need to bridge the domain gap somehow. I was thinking of doing HIL-SERL to fine-tune it IRL initially, but then I found this repo demonstrating zero-shot sim2real pick-and-lift. Their approach is to make the simulation environment much closer to the IRL setup and perform heavy domain randomization. I&#x27;m lazy so if I can avoid doing manual robot manipulation at all I prefer that. Since the framework they use (ManiSkills) doesn&#x27;t support GPU physics calculation on AMD GPUs, I&#x27;m planning on porting the code to Genesis and trying to train a robust agent that can perform decently on real inference.&lt;&#x2F;p&gt;
&lt;br &#x2F;&gt;
&lt;br &#x2F;&gt;
&lt;br &#x2F;&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Training an SO-101 RL Agent to Grasp and Lift in MuJoCo</title>
		<published>2026-01-02T00:00:00+00:00</published>
		<updated>2026-01-02T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/so101-rl-lift/" type="text/html"/>
		<id>https://ggando.com/blog/so101-rl-lift/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;so101_lift_852.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;context&quot;&gt;Context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#context&quot; 
    aria-label=&quot;Anchor link for: context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;When I attended a robotics meetup in SF this summer, I realized that the era of robotics is on the rise, and now is a good time to start building robots. I&#x27;ve been working on imitation learning for my SO-101 robot arm setup, successfully training two ACT policies via LeRobot: green cube → white paper, and red cube → green bowl. Both worked… sometimes. Maybe 50% success rate on a good day.&lt;&#x2F;p&gt;
&lt;p&gt;This is the same problem as supervised deep learning, where every improvement requires more data in the form of manual demonstrations. I obviously need more demos to go from 50% to 80% success; it could improve with 50+ more episodes. But I&#x27;d need to keep collecting data every time I change the situation or object. Big corporations like Google and OpenAI have teams collecting millions of demonstrations, and I&#x27;m seeing the same situation happening in Tokyo. As a solo developer, I can&#x27;t really compete with IL-based methods.&lt;&#x2F;p&gt;
&lt;p&gt;So I decided to go the RL route. Ideally, I train a good model in simulation first and then fine-tune with HIL-SERL without much human intervention. Would the fine-tuning really bridge the sim2real gap? I don&#x27;t know yet. But I&#x27;ll give it a shot. I do believe that RL is the future over the long run.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;initial-setup&quot;&gt;Initial Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#initial-setup&quot; 
    aria-label=&quot;Anchor link for: initial-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I set up a MuJoCo simulation with the SO-101 arm, a 3cm cube, and a goal position on the ground for a pick-and-place task. I used SAC from Stable-Baselines3, following my recent experience training agents for a Bevy 3D dodge game where SAC outperformed PPO on continuous control.&lt;&#x2F;p&gt;
&lt;p&gt;My first attempt with naive reward shaping resulted in the arm pushing the cube toward the goal after 500k steps. I did a bit of research on the best practices for letting the agent actually grasp the target and found the robosuite repo (link) that has staged rewards such as giving rewards for grasping the object.&lt;&#x2F;p&gt;
&lt;p&gt;I adopted this and trained a few setups with reach reward + grasp bonus + lift bonus + place reward, and also implemented an IK-based controller instead of the default velocity-based one. However, it learned to keep holding the cube on the ground without actually lifting, even after 1M steps. I added debug logging for the cube&#x27;s position and it turns out that it was pushing the cube down (z coordinate was going negative). I also visualized the scene from different angles, and realized that the gripper&#x27;s fingers were clipping into the cube. I tried fixing this by adding collision boxes to the fingers, but the cube&#x27;s size was too big and prevented the gripper from grasping anything, so I paused this approach and removed them.&lt;&#x2F;p&gt;
&lt;p&gt;I tried tweaking reward weights, adding penalties, and different reward structures, but nothing worked and I decided to try training a simpler lifting task, starting from the state where the arm already grasps the cube. In order to do this, I needed to implement a reset motion with inverse kinematics to grasp the cube at the beginning of each episode.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-ik-struggle&quot;&gt;The IK Struggle&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-ik-struggle&quot; 
    aria-label=&quot;Anchor link for: the-ik-struggle&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The SO-101 has 5 arm joints plus a gripper. To do top-down grasping, I needed the gripper pointing down with fingers horizontal, then move to an XYZ target. I implemented a damped least-squares IK controller using MuJoCo&#x27;s Jacobian.&lt;&#x2F;p&gt;
&lt;p&gt;Then, I attempted to grasp the cube with the IK controller for environment reset, but it wasn&#x27;t working at all; it took a fair amount of time to debug this. I added visualization spheres at grasp points and realized there were two issues.&lt;&#x2F;p&gt;
&lt;p&gt;The first problem was that I mixed up &lt;code&gt;graspframe&lt;&#x2F;code&gt; and &lt;code&gt;gripperframe&lt;&#x2F;code&gt;. The MJCF model had two sites:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;gripperframe&lt;&#x2F;code&gt; at the fingertips (the TCP)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;graspframe&lt;&#x2F;code&gt; further back between the fingers&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;so101_011_spheres_closeup_1280.jpg&quot; alt=&quot;Debug spheres closeup&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;I was targeting the wrong one, wondering why the gripper kept overshooting the cube. It took time to realize this because I was letting Claude Opus 4.5 debug this at first, but it&#x27;s sometimes bad at debugging visual issues like this because it only sees coordinates and grasp state in the console log. I highly recommend visualizing the grasp point and seeing it for yourself if you encounter similar issues.&lt;&#x2F;p&gt;
&lt;p&gt;The second problem: even after fixing that, the gripper kept hitting the cube with the static finger before properly centering on it. The SO-101&#x27;s gripper isn&#x27;t symmetric: one finger is fixed, one moves. I needed to offset the target position to account for this asymmetry.&lt;&#x2F;p&gt;
&lt;p&gt;The third problem: the motion worked but looked janky. I found this excellent &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;maegantucker.com&#x2F;ECE4560&#x2F;assignment8-so101&#x2F;&quot;&gt;ECE4560 course material&lt;&#x2F;a&gt; that had a clean 4-step pick sequence for the SO-101. I adapted their logic: move above block → descend → close gripper → lift. Now the motion was much cleaner. The cube still wobbled, though.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;d6faa860-36c3-4759-b5a2-9c3a843435da?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;the-wobbling-cube&quot;&gt;The Wobbling Cube&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#the-wobbling-cube&quot; 
    aria-label=&quot;Anchor link for: the-wobbling-cube&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The IK motion was fixed, but there was another big issue where the cube kept wobbling all the time. I was using the default material settings (friction, etc.) similar to rubber in real life, but my goal is to transfer this RL agent to my real SO-101. So I changed the cube material to the wood equivalent, but then it slipped away from the fingers when I tried to grasp it with the IK controller.&lt;&#x2F;p&gt;
&lt;p&gt;I initially thought it was a friction problem and tried wooden cube parameters, cranked up friction coefficients, enabled elliptic friction cones, and added noslip iterations. This helped a bit with slipping, but the wobble persisted.&lt;&#x2F;p&gt;
&lt;p&gt;After more research, I found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google-deepmind&#x2F;mujoco&#x2F;issues&#x2F;239&quot;&gt;MuJoCo issue #239&lt;&#x2F;a&gt; where someone had the exact same problem with a Franka Panda gripper. The solution from a MuJoCo collaborator:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it is the actual mesh I would strongly recommend to disable the collisions for the mesh and add a layer of (possibly invisible) box primitives that overlap with the end effector.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The problem: mesh-to-mesh collisions generate unstable single contact points. The solution: add small box geoms at the fingertips for stable multi-point contact. This was the same approach as the one I tried earlier, so I made the boxes pretty small and carefully adjusted the positions to only protrude slightly inward from the fingers.&lt;&#x2F;p&gt;
&lt;p&gt;I added two 2.5mm box geoms (&lt;code&gt;static_finger_pad&lt;&#x2F;code&gt; and &lt;code&gt;moving_finger_pad&lt;&#x2F;code&gt;) positioned to protrude slightly inward from the finger meshes:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- Static finger collision pad --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;geom&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;static_finger_pad&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;box&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; size&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.00125 0.00125 0.00125&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt;      pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;-0.008875 0.0 -0.100&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1 0.5 0.5 0.8&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; friction&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1 0.05 0.001&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!-- Moving finger collision pad --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;geom&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; name&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;moving_finger_pad&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;box&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; size&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.00125 0.00125 0.00125&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity&quot;&gt;      pos&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;-0.01136 -0.076 0.019&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; rgba&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;0.5 0.5 1 0.8&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; friction&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1 0.05 0.001&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;so101_finger_pads_side_v2_640_annotated.jpg&quot; alt=&quot;img1&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;After this fix, the wobbling stopped! The cube now gripped cleanly and held steady during lifting.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;9786268f-ce6a-4356-a559-1922ca5ef2b9?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;training-the-lift-agent&quot;&gt;Training the Lift Agent&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#training-the-lift-agent&quot; 
    aria-label=&quot;Anchor link for: training-the-lift-agent&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;With the physics working, I returned to RL training. First, I trained an agent that just lifts the cube, starting from the state where the cube was already grasped and lifted at z=0.02.&lt;&#x2F;p&gt;
&lt;p&gt;The reward structure looked like this. I call it the V11 reward since I&#x27;ve been versioning different reward functions.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;&#x2F;th&gt;&lt;th&gt;Condition&lt;&#x2F;th&gt;&lt;th&gt;Value&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Reach&lt;&#x2F;td&gt;&lt;td&gt;Always&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;1.0 - tanh(10 * distance)&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Push-down penalty&lt;&#x2F;td&gt;&lt;td&gt;cube_z &amp;lt; 0.01&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-(0.01 - z) * 50&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Drop penalty&lt;&#x2F;td&gt;&lt;td&gt;Lost grasp&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-2.0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Grasp bonus&lt;&#x2F;td&gt;&lt;td&gt;Grasping&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;+0.25&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Continuous lift&lt;&#x2F;td&gt;&lt;td&gt;Grasping&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;lift_progress * 2.0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Binary lift&lt;&#x2F;td&gt;&lt;td&gt;cube_z &amp;gt; 0.02&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;+1.0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Target bonus&lt;&#x2F;td&gt;&lt;td&gt;z &amp;gt; 0.08&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;+1.0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Action penalty&lt;&#x2F;td&gt;&lt;td&gt;z &amp;gt; 0.06&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;-0.01 * ‖action_delta‖²&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Success&lt;&#x2F;td&gt;&lt;td&gt;Held at target&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;+10.0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I first trained it without the action penalty. It learned to lift the cube, but had this issue where the agent lifted the cube close to z=0.08 but oscillated rapidly, like twitching. The reward difference between 0.07m and 0.08m is too small (~0.31). The policy learned to oscillate around ~0.07m rather than hold at 0.08m. So I added an action rate penalty &lt;code&gt;-0.01 * ||a_t - a_{t-1}||²&lt;&#x2F;code&gt; for smooth movement.&lt;&#x2F;p&gt;
&lt;p&gt;The penalty only applies when z &amp;gt; 0.06, because the agent learned to hold still at z=0.054m when I applied it all the time. Also, I needed to reduce the penalty coefficient from 0.05 to 0.01, as it learned to hold still rather than lift because any action change incurred a penalty.&lt;&#x2F;p&gt;
&lt;p&gt;After introducing it, the agent achieved a 100% success rate after 500k steps of training. Then, I tried curriculum learning, starting with the cube already grasped and mid-air, but the pretrained weights didn&#x27;t transfer well and it kept moving away from the cube erratically. I fixed a bug with VecNormalize stats, but the transfer still failed.&lt;&#x2F;p&gt;
&lt;p&gt;So I trained from scratch with V11 on the full task: 200k steps, ~4 hours on my AMD GPU with ROCm. Final result: 100% success rate on evaluation. The agent grasps the cube and lifts it above z=0.08 every time.&lt;&#x2F;p&gt;
&lt;p&gt;One interesting emergent behavior: the agent learned to nudge the cube slightly before grasping. I never explicitly rewarded this, but it just emerged from the training dynamics. The nudge seems to adjust the cube into a better orientation for the top-down grasp, and I like it!&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;2e506b66-3f52-4f24-a811-36dadbee6b02?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h3 id=&quot;takeaways&quot;&gt;Takeaways&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#takeaways&quot; 
    aria-label=&quot;Anchor link for: takeaways&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mesh collisions in MuJoCo can be unstable for grasping&lt;&#x2F;strong&gt;: add box primitives at contact points for stable multi-point contact&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Visualization is essential&lt;&#x2F;strong&gt;: debug spheres showing target vs actual positions saved me hours of print-statement debugging&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cartesian action space &amp;gt;&amp;gt; joint space for manipulation RL&lt;&#x2F;strong&gt;: random exploration naturally covers the 3D workspace&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Reward hacking is inevitable&lt;&#x2F;strong&gt;: budget time for reward iteration (I went through 11 versions)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Conditional penalties&lt;&#x2F;strong&gt; can prevent reward hacking without killing task completion&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Next up: pixel-based RL with camera observations, transitioning from Stable-Baselines3 to vision-RL frameworks like DrQ-v2. The ultimate goal is a pick-and-place agent that can actually clean up a desk.&lt;&#x2F;p&gt;
&lt;p&gt;The code is available at &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;pick-101&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;pick-101&lt;&#x2F;a&gt;. If you&#x27;re working on SO-101 RL in MuJoCo, feel free to reach out. There&#x27;s not much published work on this specific setup.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Training a 3D Dodge Agent with Reinforcement Learning</title>
		<published>2026-01-01T00:00:00+00:00</published>
		<updated>2026-01-01T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/bevy-3d-dodge/" type="text/html"/>
		<id>https://ggando.com/blog/bevy-3d-dodge/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;bevy_rl_game_v2_640.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#intro&quot; 
    aria-label=&quot;Anchor link for: intro&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Inspired by @warehouse YT videos, I built a Bevy-based 3D dodge game designed as a reinforcement learning environment. The player must keep avoiding projectiles (red balls), and I exposed APIs to control the action and receive observations&#x2F;rewards. The environment exposes a standard Gymnasium interface over HTTP or gRPC, and also supports parallel environments to speed up training.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;level-1-the-baseline-setup&quot;&gt;Level 1: The Baseline Setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#level-1-the-baseline-setup&quot; 
    aria-label=&quot;Anchor link for: level-1-the-baseline-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I started with an easy level (projectiles are slow and predictable) as the baseline. I used discrete actions (move left&#x2F;right&#x2F;forward&#x2F;back) and DQN from stable-baselines3, because this was the only deep reinforcement learning model I knew (my knowledge stops at 2016 lol).
After the initial 300k training run, DQN managed a 30% success rate with high variance (mean reward 641 ± 325), but I felt a limitation here and switched to PPO. PPO achieved a 100% success rate in just 10k steps (about 10 minutes of training), and all 20 evaluation episodes reached the max 1000 steps with perfect consistency.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;level-2-ramping-up-difficulty&quot;&gt;Level 2: Ramping Up Difficulty&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#level-2-ramping-up-difficulty&quot; 
    aria-label=&quot;Anchor link for: level-2-ramping-up-difficulty&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;With Level 1 solved, I added Level 2 to increase the difficulty. Now projectiles travel 50% faster (4.5 units&#x2F;sec), spawn 4x more frequently (every 0.5 seconds), and come from a wider arc instead of straight ahead. I also switched from discrete to continuous actions, using a simplified 3D action space: [vx, vy, sprint] where sprint multiplies base speed.
I initially tried spawning balls from a ±60° arc, but this turned out to be too hard. The agent only achieved a best eval reward of 38.10 ± 68.65, essentially failing to learn anything useful. After narrowing the spawn arc to ±30°, training started working, hitting 130.59 mean reward at 500k steps with 2x sprint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ppo-s-limits-and-sac-breakthrough&quot;&gt;PPO&#x27;s Limits and SAC Breakthrough&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ppo-s-limits-and-sac-breakthrough&quot; 
    aria-label=&quot;Anchor link for: ppo-s-limits-and-sac-breakthrough&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Since the eval reward curve was still monotonically increasing, I extended the ±30° setup to 2M steps with stability tweaks: dropped learning rate to 0.0001 and tightened clip range to 0.15 to match the longer training duration.
However, the best eval reward only improved from 170.61 at 1M steps to 177.03 at 2M, and the agent still struggled to dodge balls consistently. The second million steps showed signs of PPO hitting its limits.
Then I switched to SAC, which is known to perform well on continuous control tasks in RL. At just 550k steps, SAC hit 355.24 mean reward, which is 2x better than PPO&#x27;s best at 2M steps. Training became stable with consistent improvement, and evaluation episodes looked promising.
I&#x27;m still learning this, but SAC&#x27;s replay buffer and sample efficiency seem to help here. The replay buffer lets it learn from each dodge pattern hundreds of times instead of once, and off-policy learning makes it more sample efficient. But 15% success meant the agent still died early in most episodes, and I needed to figure out why.&lt;&#x2F;p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;I&amp;#39;m training RL agents for a 3D dodge game in Bevy. Here&amp;#39;s SAC (left) vs PPO (right) after 2M steps. SAC performs much better, but still dies early sometimes. It tries to dodge one ball and runs into another one &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;hashtag&#x2F;reinforcementlearning?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#reinforcementlearning&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;hashtag&#x2F;bevyengine?src=hash&amp;amp;ref_src=twsrc%5Etfw&quot;&gt;#bevyengine&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;t.co&#x2F;mlyrEYFV8C&quot;&gt;pic.twitter.com&#x2F;mlyrEYFV8C&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;&amp;mdash; Gota (@gtgando) &lt;a href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;gtgando&#x2F;status&#x2F;2002722344041861496?ref_src=twsrc%5Etfw&quot;&gt;December 21, 2025&lt;&#x2F;a&gt;&lt;&#x2F;blockquote&gt; &lt;script async src=&quot;https:&#x2F;&#x2F;platform.twitter.com&#x2F;widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;&#x2F;script&gt;
&lt;h2 id=&quot;improving-observations-thrower-visibility&quot;&gt;Improving Observations: Thrower Visibility&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#improving-observations-thrower-visibility&quot; 
    aria-label=&quot;Anchor link for: improving-observations-thrower-visibility&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Watching the SAC agent fail, I initially thought it was struggling with balls spawning from unexpected angles in the ±30° arc. To help the agent anticipate incoming projectiles, I added a thrower indicator: an orange glowing sphere appears at the future spawn location for 1 second before each projectile spawns. I extended the state from 65 to 69 dimensions to include the thrower&#x27;s position (x, y, z) and time until throw.
I trained SAC with this extended observation space for 2M steps on the ±30° setup. The results improved: the agent achieved a 50% success rate (5&#x2F;10 episodes hitting 1000 steps), compared to 15% without the thrower indicator.
However, rewatching the eval episodes revealed the actual failure pattern: the agent dies when it tries to avoid one ball while running into another. The thrower indicator helped, but the agent seems to be optimizing for immediate local threats rather than global planning (a greedy, reactive policy). A possible next step is introducing an attention module so the agent can learn to weight multiple projectiles by importance rather than just reacting to the nearest one.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;5f0a0bb2-11c9-4b34-9d89-5eecfc9a9ca0?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;h2 id=&quot;observations-and-future-work&quot;&gt;Observations and Future Work&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#observations-and-future-work&quot; 
    aria-label=&quot;Anchor link for: observations-and-future-work&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The agent trained on the thrower setup learned to shuffle back and forth at the rear of the play area. This makes sense since the projectiles are aimed at the agent&#x27;s current position, so constant movement left and right is a good strategy.
But I&#x27;d like to get agents to learn visually interesting motions, not just optimal ones. I&#x27;m thinking about ways to encourage more dynamic dodging, for example introducing rewards for close-call dodges (near misses), or a two-player adversarial setup where the thrower is also an RL agent that learns to predict and counter the dodger&#x27;s movements.
&lt;br &#x2F;&gt;
&lt;br &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>CPU to GPU Particle Migration in Bevy: The Billboard Illusion</title>
		<published>2025-12-24T00:00:00+00:00</published>
		<updated>2025-12-24T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/bevy-gpu-particles/" type="text/html"/>
		<id>https://ggando.com/til/bevy-gpu-particles/</id>
		<content type="html">&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;&#x2F;h2&gt;
&lt;p&gt;Ground explosions in my RTS game were killing performance. Each explosion spawned 150-280 ECS entities - individual particles for sparks, flash sparks, debris, dirt, smoke, and more. During artillery barrages with 6-10 simultaneous explosions, FPS tanked from 100 to 40.&lt;&#x2F;p&gt;
&lt;p&gt;The CPU particle system was nice and simple where each particle was a Bevy entity with a mesh, material, and transform. But when I spawn them multiple times to create a barrage effect, it was crushing the FPS.&lt;&#x2F;p&gt;
&lt;p&gt;So I decided to migrate to bevy_hanabi GPU particles. The idea is simple: instead of spawning hundreds of CPU entities, you get a single draw call per emitter type. Should be straightforward, right?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-easy-wins&quot;&gt;The Easy Wins&lt;&#x2F;h2&gt;
&lt;p&gt;The first emitters migrated relatively smoothly. Sparks, flash sparks, and parts debris went from ~100-185 entities down to 3 GPU effects. Some gotchas along the way:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Additive blending is non-negotiable.&lt;&#x2F;strong&gt; GPU sparks showed dark&#x2F;brown backgrounds instead of glowing. bevy_hanabi defaults to &lt;code&gt;AlphaMode::Blend&lt;&#x2F;code&gt;, but sparks need &lt;code&gt;AlphaMode::Add&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Watch for hidden brightness multipliers.&lt;&#x2F;strong&gt; GPU particles appeared orange-red instead of hot yellow-white. Turns out my CPU shader had a 4x brightness multiplier that I&#x27;d completely forgotten about. GPU gradients need the final rendered values directly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Velocity axis conventions differ.&lt;&#x2F;strong&gt; bevy_hanabi&#x27;s &lt;code&gt;AlongVelocity&lt;&#x2F;code&gt; mode uses X axis along velocity, while my CPU &lt;code&gt;VelocityAligned&lt;&#x2F;code&gt; used Y axis. Swap your size gradient axes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Drag ≠ deceleration.&lt;&#x2F;strong&gt; &lt;code&gt;LinearDragModifier&lt;&#x2F;code&gt; applies velocity-proportional drag (exponential decay). My CPU used constant deceleration. Replaced drag with &lt;code&gt;AccelModifier&lt;&#x2F;code&gt; to match the original behavior.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-main-emitter-nightmare&quot;&gt;The Main Emitter Nightmare&lt;&#x2F;h2&gt;
&lt;p&gt;Then I got to the main emitters that spawn directional blasts from the explosion. What started as &quot;should be straightforward&quot; became 8 devlogs and eventually required forking bevy_hanabi.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-mirroring-mystery&quot;&gt;The Mirroring Mystery&lt;&#x2F;h3&gt;
&lt;p&gt;The core symptom was that particles appeared to travel &quot;inward toward center then fly out the other side&quot; - visually mirrored. I spent days investigating:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Cross product sign flips in orientation math&lt;&#x2F;li&gt;
&lt;li&gt;Quaternion vs axis-based rotation differences&lt;&#x2F;li&gt;
&lt;li&gt;Camera-space vs world-space coordinate systems&lt;&#x2F;li&gt;
&lt;li&gt;bevy_hanabi&#x27;s &lt;code&gt;AlongVelocity&lt;&#x2F;code&gt; implementation details&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I tried at least 9 different approaches to fix the orientation: Gram-Schmidt orthogonalization, world-up reference vectors, quaternion-based rotation, camera-right projection... all failed. The mirroring persisted no matter what I tried.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-debug-breakthrough&quot;&gt;The Debug Breakthrough&lt;&#x2F;h3&gt;
&lt;p&gt;After days of frustration, I spawned small colored quads (R=+X velocity, G=+Y, B=+Z) instead of large blasts with UV zoom to visualize actual particle velocities (which I should have done way earlier).&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The velocities were correct the whole time.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Particles clearly moved outward and the motion was 100% correct.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-actual-problem-a-visual-illusion&quot;&gt;The Actual Problem: A Visual Illusion&lt;&#x2F;h3&gt;
&lt;p&gt;The &quot;mirroring&quot; was a perceptual illusion caused by the intense UV zoom effect. The main emitters use a 500x→1x UV zoom over their lifetime to create the &quot;debris burst expanding from impact point&quot; look.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the math: with UV scale starting at 500x, the visible texture region at t=0 is ~0.016m (tiny center point). At t=1, it&#x27;s 20m (full blast). That&#x27;s an apparent expansion rate of ~13 m&#x2F;s radially outward from texture center.&lt;&#x2F;p&gt;
&lt;p&gt;Meanwhile, actual particle velocity was only 3-5 m&#x2F;s. The UV zoom&#x27;s apparent motion completely dominated, and because it expanded from center (not bottom-pivot like CPU), it created the illusion of incorrect movement direction.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-actual-fixes&quot;&gt;The Actual Fixes&lt;&#x2F;h2&gt;
&lt;p&gt;Once I understood what was really happening, the fixes were straightforward:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fix-1-uvscaleoverlifetimemodifier-bug&quot;&gt;Fix 1: UVScaleOverLifetimeModifier Bug&lt;&#x2F;h3&gt;
&lt;p&gt;My custom modifier had inverted math:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;wgsl&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F; WRONG: multiplying sends UVs out of bounds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_scaled&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_centered&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_scale&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; vec2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F; With scale=500: UV 0.0 becomes -249.5 (samples edge&#x2F;garbage)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F; CORRECT: dividing zooms IN on center  &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_scaled&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_centered&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; uv_scale&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; vec2&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.5&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;&#x2F;&#x2F; With scale=500: UV 0.0 becomes 0.499 (samples center)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This was sampling edge pixels (solid color) instead of zooming in on the center.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fix-2-uv-pivot&quot;&gt;Fix 2: UV Pivot&lt;&#x2F;h3&gt;
&lt;p&gt;CPU main emitters use bottom-pivot - the UV zoom expands upward along velocity. My GPU zoom expanded from center, fighting the particle motion. Added configurable pivot to the modifier.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fix-3-simulationspace-local&quot;&gt;Fix 3: SimulationSpace::Local&lt;&#x2F;h3&gt;
&lt;p&gt;bevy_hanabi defaults to &lt;code&gt;SimulationSpace::Global&lt;&#x2F;code&gt;, which ignores &lt;code&gt;Transform.scale&lt;&#x2F;code&gt;. CPU velocity was scaled, GPU was fixed. Switched to &lt;code&gt;SimulationSpace::Local&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fix-4-expression-re-evaluation&quot;&gt;Fix 4: Expression Re-evaluation&lt;&#x2F;h3&gt;
&lt;p&gt;bevy_hanabi&#x27;s &lt;code&gt;ExprWriter&lt;&#x2F;code&gt; re-evaluates &lt;code&gt;rand()&lt;&#x2F;code&gt; calls on every expression use. Cloning an expression doesn&#x27;t preserve computed values:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; dir&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;vec3&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(ry, rz)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;normalized&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  &#x2F;&#x2F; Uses rand()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; pos&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; dir&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; radius;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;          &#x2F;&#x2F; NEW random values!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; vel&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; dir&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; speed;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;           &#x2F;&#x2F; DIFFERENT random values!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&#x2F;&#x2F; Result: pos and vel point in completely different directions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Fixed by reading the POSITION attribute after it&#x27;s set:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; fb_pos&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;vec3&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(ry, rz)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;normalized&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; radius;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; init_pos&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; SetAttributeModifier&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Attribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;POSITION&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, fb_pos&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;expr&lt;&#x2F;span&gt;&lt;span&gt;());&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; pos_read&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; writer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;attr&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Attribute&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;POSITION&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  &#x2F;&#x2F; Read back!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; velocity&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; pos_read&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;normalized&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; speed;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;the-cleanup&quot;&gt;The Cleanup&lt;&#x2F;h2&gt;
&lt;p&gt;After that ordeal, the remaining emitters (dust ring, smoke cloud, wisp puffs) were refreshingly straightforward. A few notes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;code&gt;FaceCameraPosition&lt;&#x2F;code&gt; orientation, use &lt;code&gt;.with_rotation()&lt;&#x2F;code&gt; not &lt;code&gt;.with_axis_rotation()&lt;&#x2F;code&gt; for random sprite rotation&lt;&#x2F;li&gt;
&lt;li&gt;Flipbook animation requires updating &lt;code&gt;SPRITE_INDEX&lt;&#x2F;code&gt; in the update phase, not just init&lt;&#x2F;li&gt;
&lt;li&gt;Non-uniform X&#x2F;Y scaling works with &lt;code&gt;SizeOverLifetimeModifier&lt;&#x2F;code&gt; using &lt;code&gt;Gradient&amp;lt;Vec3&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;results&quot;&gt;Results&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Emitter&lt;&#x2F;th&gt;&lt;th&gt;CPU Entities&lt;&#x2F;th&gt;&lt;th&gt;GPU Entities&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Main Blast&lt;&#x2F;td&gt;&lt;td&gt;9-17&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Secondary Blast&lt;&#x2F;td&gt;&lt;td&gt;7-13&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Sparks&lt;&#x2F;td&gt;&lt;td&gt;30-60&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Flash Sparks&lt;&#x2F;td&gt;&lt;td&gt;20-50&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Parts Debris&lt;&#x2F;td&gt;&lt;td&gt;50-75&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Dirt Debris&lt;&#x2F;td&gt;&lt;td&gt;~35&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Velocity Dirt&lt;&#x2F;td&gt;&lt;td&gt;10-15&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Dust Ring&lt;&#x2F;td&gt;&lt;td&gt;2-3&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Smoke Cloud&lt;&#x2F;td&gt;&lt;td&gt;10-15&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Wisp Puffs&lt;&#x2F;td&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;strong&gt;Total reduction&lt;&#x2F;strong&gt;: ~170-290 entities → ~10 entities per explosion (95% reduction)&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Performance&lt;&#x2F;strong&gt;: 8-shell barrages now maintain 80+ FPS instead of dropping to 40.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debug with simple visuals first.&lt;&#x2F;strong&gt; Small colored quads revealed correct velocity when complex effects created illusions.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UV zoom can overwhelm particle motion.&lt;&#x2F;strong&gt; 500x zoom creates ~13 m&#x2F;s apparent motion that drowns out 3-5 m&#x2F;s actual velocity.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UV zoom math is counterintuitive.&lt;&#x2F;strong&gt; Divide to zoom in, multiply to zoom out.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Expression cloning re-evaluates.&lt;&#x2F;strong&gt; Use &lt;code&gt;attr()&lt;&#x2F;code&gt; to read back computed values in bevy_hanabi.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SimulationSpace affects scale.&lt;&#x2F;strong&gt; Global ignores transform scale, Local applies it.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sometimes you need to fork.&lt;&#x2F;strong&gt; Complex VFX may require engine modifications.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The whole migration took about 9 days, with ~60% spent on the main emitters orientation&#x2F;velocity issues that turned out to be mostly a visual illusion. Was it worth it? The 95% entity reduction and stable 80+ FPS during barrages say yes. But I learned a lesson to actually think with my brain during debugging rather than cursing at Claude.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>AMD GPU Stability</title>
		<published>2025-12-14T00:00:00+00:00</published>
		<updated>2025-12-14T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/amdgpu-stability/" type="text/html"/>
		<id>https://ggando.com/til/amdgpu-stability/</id>
		<content type="html">&lt;p&gt;I&#x27;ve been encountering more freezes and crashes with my AMD GPU lately. This has been the case since I started using it on Ubuntu 22.04, but it&#x27;s happening more often now as I run longer RL training sessions. Crashes occur after &lt;strong&gt;1-2 hours&lt;&#x2F;strong&gt; of compute workload (sometimes up to 26 hours)&lt;&#x2F;p&gt;
&lt;p&gt;I see logs like this with &lt;code&gt;sudo dmesg -w | grep -i amdgpu&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;details&gt;
&lt;summary&gt;log&lt;&#x2F;summary&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649450] amdgpu 0000:2f:00.0: amdgpu: GCVM_L2_PROTECTION_FAULT_STATUS:0x00000000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649454] amdgpu 0000:2f:00.0: amdgpu:      Faulty UTCL2 client ID: CB&#x2F;DB (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;0x0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649458] amdgpu 0000:2f:00.0: amdgpu:      MORE_FAULTS: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649462] amdgpu 0000:2f:00.0: amdgpu:      WALKER_ERROR: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649465] amdgpu 0000:2f:00.0: amdgpu:      PERMISSION_FAULTS: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649469] amdgpu 0000:2f:00.0: amdgpu:      MAPPING_ERROR: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649473] amdgpu 0000:2f:00.0: amdgpu:      RW: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649480] amdgpu 0000:2f:00.0: amdgpu: [gfxhub] page fault (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;src_id:0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ring:169 vmid:0 pasid:0, for process  pid&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; thread  pid&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649486] amdgpu 0000:2f:00.0: amdgpu:   in page starting at address 0x0000000000000000 from client 10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649491] amdgpu 0000:2f:00.0: amdgpu: GCVM_L2_PROTECTION_FAULT_STATUS:0x00000000&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649494] amdgpu 0000:2f:00.0: amdgpu:      Faulty UTCL2 client ID: CB&#x2F;DB (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;0x0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649499] amdgpu 0000:2f:00.0: amdgpu:      MORE_FAULTS: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649502] amdgpu 0000:2f:00.0: amdgpu:      WALKER_ERROR: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649506] amdgpu 0000:2f:00.0: amdgpu:      PERMISSION_FAULTS: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649510] amdgpu 0000:2f:00.0: amdgpu:      MAPPING_ERROR: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45766.649513] amdgpu 0000:2f:00.0: amdgpu:      RW: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.021585] amdgpu 0000:2f:00.0: amdgpu: soft reset failed, will fallback to full reset&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.270210] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; *ERROR*&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; MES failed to response msg=&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.270475] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.393294] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.393507] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.516446] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.516671] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.639817] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.640034] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.762951] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.763170] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.886107] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45767.886325] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.009310] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.009532] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.132480] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.132719] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.255533] [drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; MES failed to response msg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.255760] [drm:amdgpu_mes_unmap_legacy_queue [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.522784] [drm:gfx_v11_0_cp_gfx_enable.isra.0 [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt;ERROR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; failed to halt cp gfx&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.561065] amdgpu 0000:2f:00.0: amdgpu: MODE1 reset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.561070] amdgpu 0000:2f:00.0: amdgpu: GPU mode1 reset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45768.561151] amdgpu 0000:2f:00.0: amdgpu: GPU smu mode1 reset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.077273] amdgpu 0000:2f:00.0: amdgpu: GPU reset succeeded, trying to resume&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.289571] amdgpu 0000:2f:00.0: amdgpu: RAP: optional rap ta ucode is not available&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.289576] amdgpu 0000:2f:00.0: amdgpu: SECUREDISPLAY: securedisplay ta ucode is not available&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.289581] amdgpu 0000:2f:00.0: amdgpu: SMU is resuming...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.289586] amdgpu 0000:2f:00.0: amdgpu: smu driver &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; version&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; = 0x0000003d, smu fw if version = 0x0000003f, smu fw program = 0, smu fw version =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0x004e7300&lt;&#x2F;span&gt;&lt;span&gt; (78.115.0)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.289590] amdgpu 0000:2f:00.0: amdgpu: SMU driver &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;if&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; version&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; not matched&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.428583] amdgpu 0000:2f:00.0: amdgpu: SMU is resumed successfully&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513522]&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; amdgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 0000:2f:00.0:&lt;&#x2F;span&gt;&lt;span&gt; [drm:jpeg_v4_0_hw_init [amdgpu]] JPEG decode initialized successfully.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513951] amdgpu 0000:2f:00.0: amdgpu: ring gfx_0.0.0 uses VM inv eng 0 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513953] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.0.0 uses VM inv eng 1 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513955] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.1.0 uses VM inv eng 4 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513957] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.2.0 uses VM inv eng 6 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513959] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.3.0 uses VM inv eng 7 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513961] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.0.1 uses VM inv eng 8 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513963] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.1.1 uses VM inv eng 9 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513964] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.2.1 uses VM inv eng 10 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513966] amdgpu 0000:2f:00.0: amdgpu: ring comp_1.3.1 uses VM inv eng 11 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513968] amdgpu 0000:2f:00.0: amdgpu: ring sdma0 uses VM inv eng 12 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513970] amdgpu 0000:2f:00.0: amdgpu: ring sdma1 uses VM inv eng 13 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513972] amdgpu 0000:2f:00.0: amdgpu: ring vcn_unified_0 uses VM inv eng 0 on hub 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513974] amdgpu 0000:2f:00.0: amdgpu: ring vcn_unified_1 uses VM inv eng 1 on hub 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513975] amdgpu 0000:2f:00.0: amdgpu: ring jpeg_dec uses VM inv eng 4 on hub 8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.513977] amdgpu 0000:2f:00.0: amdgpu: ring mes_kiq_3.1.0 uses VM inv eng 14 on hub 0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.516831] amdgpu 0000:2f:00.0: amdgpu: recover vram bo from shadow start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.533125] amdgpu 0000:2f:00.0: amdgpu: recover vram bo from shadow &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.536136] amdgpu 0000:2f:00.0: amdgpu: GPU reset(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;) succeeded&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[45769.572609] [drm:amdgpu_cs_ioctl [amdgpu]]&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; *ERROR*&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; Failed to initialize parser&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -125!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;&#x2F;details&gt;
&lt;p&gt;When this happens, the GUI session becomes completely unresponsive and training processes die. The GPU resets and I can still SSH into the machine from my laptop, but I need to reboot every time since GNOME Mutter is bad at handling it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;crash-pattern-analyzed-by-claude&quot;&gt;Crash Pattern (analyzed by Claude)&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Page Fault&lt;&#x2F;strong&gt; → CPC (Command Processor Compute) fails at address 0x0, page table corruption&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MES Timeout&lt;&#x2F;strong&gt; → GPU&#x27;s job scheduler stops responding (&lt;code&gt;MES failed to response msg=3&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Soft Reset Fails&lt;&#x2F;strong&gt; → Can&#x27;t unmap queues, recovery path broken&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;MODE1 Reset&lt;&#x2F;strong&gt; → Full hardware reset succeeds, but desktop session already dead&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Also seeing &lt;code&gt;SMU driver if version not matched&lt;&#x2F;code&gt; (driver expects 0x3d, GPU reports 0x3f) — firmware&#x2F;driver compatibility issue.&lt;&#x2F;p&gt;
&lt;details&gt;
&lt;summary&gt;Diagnosis by Claude&lt;&#x2F;summary&gt;
&lt;h2 id=&quot;diagnostic-analysis&quot;&gt;Diagnostic Analysis&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;kernel-log-analysis-dmesg&quot;&gt;Kernel Log Analysis (dmesg)&lt;&#x2F;h3&gt;
&lt;p&gt;The crash follows a consistent pattern visible in kernel logs:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;stage-1-page-fault&quot;&gt;Stage 1: Page Fault&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: [gfxhub] page fault (src_id:0 ring:xxx vmid:x pasid:xxxxx)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: in page starting at address 0x0000000000000000 from client 10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: GCVM_L2_PROTECTION_FAULT_STATUS:0x00000B3A&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: Faulty UTCL2 client ID: CPC (0x5)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: MORE_FAULTS: 0x0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: WALKER_ERROR: 0x5&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: PERMISSION_FAULTS: 0x3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: MAPPING_ERROR: 0x1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Interpretation:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPC (Command Processor Compute)&lt;&#x2F;strong&gt; is the faulty client - indicates compute workload trigger&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Address 0x0000000000000000&lt;&#x2F;strong&gt; suggests null pointer dereference or page table corruption&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;WALKER_ERROR + MAPPING_ERROR&lt;&#x2F;strong&gt; = Page table walk failure during address translation&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;stage-2-mes-timeout&quot;&gt;Stage 2: MES Timeout&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[drm:mes_v11_0_submit_pkt_and_poll_completion.constprop.0 [amdgpu]] *ERROR* MES failed to response msg=3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Interpretation:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MES (Micro Engine Scheduler)&lt;&#x2F;strong&gt; is the GPU&#x27;s internal job scheduler&lt;&#x2F;li&gt;
&lt;li&gt;MES fails to respond to driver commands within timeout period&lt;&#x2F;li&gt;
&lt;li&gt;This is the core bug - MES firmware cannot handle the error condition&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;stage-3-soft-reset-fails&quot;&gt;Stage 3: Soft Reset Fails&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[drm:amdgpu_mes_unmap_legacy_queue [amdgpu]] *ERROR* failed to unmap legacy queue&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: [drm:amdgpu_job_timedout [amdgpu]] *ERROR* ring gfx_0.0.0 timeout&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Interpretation:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;GPU scheduler tries to recover by unmapping queues&lt;&#x2F;li&gt;
&lt;li&gt;Timeout occurs waiting for GPU to respond&lt;&#x2F;li&gt;
&lt;li&gt;Soft recovery path fails&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;stage-4-mode1-reset&quot;&gt;Stage 4: MODE1 Reset&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: GPU reset begin!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu 0000:2f:00.0: amdgpu: GPU reset succeeded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Interpretation:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Driver falls back to full hardware reset&lt;&#x2F;li&gt;
&lt;li&gt;GPU recovers at hardware level&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;BUT&lt;&#x2F;strong&gt; - Desktop session and applications are already dead&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;smu-firmware-mismatch&quot;&gt;SMU Firmware Mismatch&lt;&#x2F;h3&gt;
&lt;p&gt;Additional warning observed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;amdgpu: SMU driver if version not matched&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The driver expects SMU firmware version 0x3d but GPU reports 0x3f - indicates potential driver&#x2F;firmware compatibility issue.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;crash-triggers-observed&quot;&gt;Crash Triggers Observed&lt;&#x2F;h3&gt;
&lt;p&gt;Different processes triggered crashes in different sessions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Xorg (display server)&lt;&#x2F;li&gt;
&lt;li&gt;OBS (video encoding)&lt;&#x2F;li&gt;
&lt;li&gt;Brave browser (hardware-accelerated compositing)&lt;&#x2F;li&gt;
&lt;li&gt;Python&#x2F;PyTorch processes&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All crashes followed the identical MES failure pattern, suggesting the trigger is multi-context GPU scheduling, not any specific application.&lt;&#x2F;p&gt;
&lt;&#x2F;details&gt;
&lt;h3 id=&quot;fix-attempts&quot;&gt;Fix attempts&lt;&#x2F;h3&gt;
&lt;p&gt;To alleviate this, I added two options &lt;code&gt;iommu=pt&lt;&#x2F;code&gt; and &lt;code&gt;amdgpu.gfxoff=0&lt;&#x2F;code&gt; to &lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_DEFAULT=0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_TIMEOUT_STYLE=menu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_TIMEOUT=10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_DISTRIBUTOR=`lsb_release -i -s 2&amp;gt; &#x2F;dev&#x2F;null || echo Debian`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_CMDLINE_LINUX_DEFAULT=&amp;quot;quiet splash acpi_enforce_resources=lax iommu=pt amdgpu.gfxoff=0&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;GRUB_CMDLINE_LINUX=&amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;iommu=pt&lt;&#x2F;code&gt;: IOMMU (a translation layer between devices and RAM) passthrough mode&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;amdgpu.gfxoff=0&lt;&#x2F;code&gt;: Disable GFXOFF (aggressive power saving that&#x27;s buggy on RDNA3)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I set the passthrough mode because I was getting IOMMU translation failures. After editing, run &lt;code&gt;update-grub&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; vim &#x2F;etc&#x2F;default&#x2F;grub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; update-grub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I also upgraded the kernel to a newer, possibly more stable version.&lt;&#x2F;p&gt;
&lt;p&gt;Original:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; uname&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -r&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;6.2.0-39-generic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Update&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Install the HWE kernel (should give you 6.8.x)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt install linux-generic-hwe-22.04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Then reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Post-update&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; uname&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -r&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;6.8.0-90-generic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After enabling &lt;code&gt;amdgpu.gfxoff=0&lt;&#x2F;code&gt;, I&#x27;m not getting freezes when I&#x27;m afk during training, but I still sometimes get random crashes when I actively use the desktop GUI. It seems that the multi-context contention between processes trying to access GPU like the browser doing hardware acceleration for video decoding is the source of issues.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll dig deeper when I have more time, but this is the reality of using AMD GPUs at the moment.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;update&quot;&gt;UPDATE&lt;&#x2F;h3&gt;
&lt;p&gt;I disabled the hardware acceleration of browsers (Brave and Chrome) and disabled more power features by adding the &lt;code&gt;amdgpu.ppfeaturemask=0xfffd7fff&lt;&#x2F;code&gt; flag, it survived for about 9 days before it crashes caused by AnyType (note taking app that presumably used GPU). My apps that use GPUs like the bevy game or image viewer sometimes experience lag spikes though. But this is good enough for now. I plan to upgrade to Ubuntu 24.04 and see if it improves the situation.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;For the record, here&#x27;s my system info and some resources I found:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;system-configuration&quot;&gt;System Configuration&lt;&#x2F;h3&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Component&lt;&#x2F;th&gt;&lt;th&gt;Details&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;GPU&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;AMD Radeon RX 7900 XTX (24GB VRAM) - Navi 31, gfx1100&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;OS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Ubuntu 22.04 LTS&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;ROCm Version&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;6.4.3&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Original Kernel&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;6.2.0-39-generic&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;DKMS Driver&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;amdgpu&#x2F;6.3.6-1697589.22.04&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CPU&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;AMD Ryzen (with Raphael integrated graphics)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;&#x2F;h3&gt;
&lt;p&gt;This is a &lt;strong&gt;widely reported, ongoing issue&lt;&#x2F;strong&gt; with extensive documentation:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Source&lt;&#x2F;th&gt;&lt;th&gt;Issue Numbers&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;ROCm GitHub&lt;&#x2F;td&gt;&lt;td&gt;#3265, #3166, #3452, #2689, #1977&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;freedesktop.org GitLab&lt;&#x2F;td&gt;&lt;td&gt;#2378, multiple others&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;danielrosehill&#x2F;6a531b079906f160911a87dea50e1507&quot;&gt;A Dummy&#x27;s Guide to AMD GPU Issues on Linux&lt;&#x2F;a&gt; - Comprehensive kernel parameter guide&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Porting UE5 explosion to Bevy</title>
		<published>2025-12-13T00:00:00+00:00</published>
		<updated>2025-12-13T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/bevy-explosion1/" type="text/html"/>
		<id>https://ggando.com/blog/bevy-explosion1/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;bevy_explosion1_1280.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;ground-explosion&quot;&gt;Ground Explosion&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ground-explosion&quot; 
    aria-label=&quot;Anchor link for: ground-explosion&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The War FX explosion in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;ggando.com&#x2F;blog&#x2F;bevy-explosion0&#x2F;&quot;&gt;the previous post&lt;&#x2F;a&gt; worked well for tower destruction, but I wanted to make the battlefield more alive by implementing the artillery explosions.&lt;&#x2F;p&gt;
&lt;p&gt;I found this paid CC-BY explosion effect on fab.com while looking for an artillery impact explosion looking like the one from CoH1 when you use the 105mm howitzer. I was unable to find ground explosions that don&#x27;t have much fireballs except this one. I was assuming that it was a Unity prefab, but realized that this is an UE5 asset. I had to install a newer UE 5.4.4 just to access it, because I bought it on fab.com and you&#x27;d need a Fab plugin for your UE but they don&#x27;t support versions &amp;lt; 5.3. I was on 5.1, so I downloaded a pre-built binary from the official website.&lt;&#x2F;p&gt;
&lt;p&gt;I was considering just using my eyes to reproduce the effect, but luckily there&#x27;s a way: you can convert uassets into a much more LLM-friendly Unreal Text 3D (.T3D) format by right-clicking the asset in the content drawer → Asset Actions → select Export.
After importing the asset to a temporary UE project via the fab plugin, I exported the Niagara particle asset into .T3D. It&#x27;s just nested key-value pairs with object hierarchy and LLM-friendly. The one I exported had 33,000+ lines so you&#x27;d need to write a script to analyze it.&lt;&#x2F;p&gt;
&lt;p&gt;I also had to do the same export for material assets to extract material graph details.
Then, just like the last time I had the AI agent extract the particle emitter details like particle attributes and alpha curves emitter by emitter. This time I used Claude Opus 4.5, since the last time I ported the Unity asset Sonnet 4.5 was kind of struggling. I thought this one will be easy as it&#x27;s just a bunch of animated billboards and no weird blending details. But it turns out to be pretty time-consuming as well.
After feeding the gamedev agent with the emitter details and implementing them it somewhat looked decent, but other than the simple spark emitters they looked pretty different from the original effect on UE5 and I had to rework them one by one. Here are some of the issues I solved:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;bug-1-textures-are-wrong&quot;&gt;Bug 1: textures are wrong&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bug-1-textures-are-wrong&quot; 
    aria-label=&quot;Anchor link for: bug-1-textures-are-wrong&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;A super obvious point but it happened to me twice since I let Claude do everything at the beginning including the texture copying part. Likely the texture asset name in the extracted details was wrong initially. If you&#x27;re seeing a fundamentally wrong effect, then you should doubt this first.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;bug-2-texture-animating-when-it-shouldn-t&quot;&gt;Bug 2: texture animating when it shouldn&#x27;t&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bug-2-texture-animating-when-it-shouldn-t&quot; 
    aria-label=&quot;Anchor link for: bug-2-texture-animating-when-it-shouldn-t&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I had a problem with the dust emitter; initially it was using a wrong texture but even after correcting that it still looked completely wrong: particles were &quot;flashing&quot; and the effect was sparse and jittery. So, I asked the analyzer agent to go through the emitter details again, and it turns out that we shouldn&#x27;t be animating texture but rather use a random sprite in the spritesheet.
UE5&#x27;s Niagara has a &lt;code&gt;SubUV Animation Mode&lt;&#x2F;code&gt; parameter with multiple options:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Linear  (default): Sequential frame animation 0→1→2→3…&lt;&#x2F;li&gt;
&lt;li&gt;Random  (NewEnumerator2): Each particle picks ONE random frame at spawn, stays fixed
The dust emitter uses Random  mode - each particle shows a different static frame for visual variety, NOT an animation. The T3D export showed SubUV Animation Mode = NewEnumerator2  which means Random.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;bug-3-wrong-plane&quot;&gt;Bug 3: wrong plane&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bug-3-wrong-plane&quot; 
    aria-label=&quot;Anchor link for: bug-3-wrong-plane&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;There was a smoke emitter where particles spread in a plane, and in the T3D file it was specified as bLocalSpace=True . We first interpreted it as the XY plane, but it turns out that it was the screen plane, not the world ground plane.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;bug-4-alpha-1-0-as-brightness-multiplier&quot;&gt;Bug  4: Alpha &amp;gt; 1.0 as Brightness Multiplier&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bug-4-alpha-1-0-as-brightness-multiplier&quot; 
    aria-label=&quot;Anchor link for: bug-4-alpha-1-0-as-brightness-multiplier&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;My implementation of dust and wisp emitters looked too dim and faint compared to the original effect. This was because UE5&#x27;s Niagara uses alpha values &amp;gt; 1.0 as brightness multipliers. For example, an alpha of 3.0 means &quot;3× brighter RGB, but still fully opaque.&quot; Here&#x27;s the shader fix to handle alpha-as-brightness:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; alpha_multiplier&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; color_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;a;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; tinted_color&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; sprite_sample&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;rgb &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; color_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;rgb &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; alpha_multiplier;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; final_alpha&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; sprite_sample&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;a &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; min&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(alpha_multiplier,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;end-result&quot;&gt;End result&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#end-result&quot; 
    aria-label=&quot;Anchor link for: end-result&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;When it comes to VFX you&#x27;d still need to eyeball it to check by yourself as LLMs can sometimes make wrong assumptions. After going through these fixes I&#x27;m satisfied with the result, considering we&#x27;re not using the UE5&#x27;s sophisticated Niagara particle system. Here&#x27;s the resulting explosion effect:&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;879b6dca-4393-467f-a8dc-760851b034b3?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;p&gt;For more details on the GPU particle setup, see &lt;a href=&quot;https:&#x2F;&#x2F;ggando.com&#x2F;til&#x2F;bevy-gpu-particles&#x2F;&quot;&gt;my TIL post on Bevy GPU particles&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;br&#x2F;&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Porting Unity Particle Effects to Bevy: Billboard Explosions</title>
		<published>2025-12-12T00:00:00+00:00</published>
		<updated>2025-12-12T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/bevy-explosion0/" type="text/html"/>
		<id>https://ggando.com/blog/bevy-explosion0/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;bevy_explosion0_1280.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h4 id=&quot;motivation&quot;&gt;Motivation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#motivation&quot; 
    aria-label=&quot;Anchor link for: motivation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Rewarding outcome of actions is what makes games more engaging to play, and I think visual feedback is one of the most important factors. When you destroy the enemy for example, you want to feel that impact with a nice explosion VFX.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been building a 3D RTS game as a hobby recently, and wanted to implement a decent billboard-based explosion effect since it&#x27;s essential for making the gameplay more satisfying.
However, I realized that there seem to be no open-source examples of this as far as I researched online. Perhaps it&#x27;s also because there&#x27;s asset stores you can sell and people are not encouraged to release assets for free.&lt;&#x2F;p&gt;
&lt;p&gt;So, I decided to port an existing asset into Bevy. I found a free Unity Store asset called &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;assetstore.unity.com&#x2F;packages&#x2F;vfx&#x2F;particles&#x2F;war-fx-5669&quot;&gt;WarFX&lt;&#x2F;a&gt; which I thought would fit perfectly. It has billboard-based particle style aesthetic seen in early 2000s RTS games like Empire at War or Company of Heroes 1, which is exactly what I&#x27;m going for in terms of game design.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;yaml-conversion-and-implementation&quot;&gt;YAML conversion and implementation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#yaml-conversion-and-implementation&quot; 
    aria-label=&quot;Anchor link for: yaml-conversion-and-implementation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Unity stores prefabs in a binary format by default, which makes them impossible for AI tools (or humans) to analyze programmatically. Fortunately, Unity can serialize them as YAML instead by enabling the &quot;Force Text&quot; mode on the unity prefab file. This way we can utilize LLMs to go through the details without having to use your eyes to reproduce them from scratch.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Edit → Project Settings → Editor → Asset Serialization → Mode&lt;&#x2F;li&gt;
&lt;li&gt;Enable Force Text mode&lt;&#x2F;li&gt;
&lt;li&gt;Unity re-serializes those assets as YAML when saving&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I usually use Claude Code, and my workflow was to have Claude Sonnet 4.5 extract the detailed attributes of particle emitters one by one, and save them into structured markdown files. Then I implemented each emitter type one by one as a custom billboard particle system with another Sonnet 4.5 in my gamedev project. When things were unclear, I had the gamedev agent write a prompt to the analyzer agent and communicate with each other.&lt;&#x2F;p&gt;
&lt;p&gt;For the implementation, I used bevy_hanabi for sparks and debris, but others were implemented as a group of dynamically spawned quad meshes with custom Material types using custom blend modes and WGSL shaders.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;challenge-1-missing-initial-colors&quot;&gt;Challenge 1: Missing Initial Colors&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#challenge-1-missing-initial-colors&quot; 
    aria-label=&quot;Anchor link for: challenge-1-missing-initial-colors&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Initially I had trouble with invisible billboards for the core flame particles, but it turns out that the initial extracted attributes were not complete. The prefab yaml file has 25,000 lines, so it&#x27;s easy to miss the details. In my case, the initial extraction python script only focused on ColorModule  (Color Over Lifetime) but missed the InitialModule.startColor  field, which was buried deep in the prefab YAML and used a non-obvious minMaxState  enum.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;challenge-2-uv-scrolling&quot;&gt;Challenge 2: UV Scrolling&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#challenge-2-uv-scrolling&quot; 
    aria-label=&quot;Anchor link for: challenge-2-uv-scrolling&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I disabled UV scrolling completely as the texture content would get cut off at the top edge of the quad mesh. I might try to reintroduce this with a proper wrap sampler mode in the future.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;challenge-3-dark-cores-bright-rims&quot;&gt;Challenge 3: Dark Cores, Bright Rims&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#challenge-3-dark-cores-bright-rims&quot; 
    aria-label=&quot;Anchor link for: challenge-3-dark-cores-bright-rims&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;The most subtle but critical bug was that flame billboards had dark cores and bright rims, which is the opposite of what they should be. The explosion flame particle uses this lerp operation that works as a smooth gradient between bright &#x2F; dark areas, and the bright part makes it look like particles have flames at the core. Here&#x27;s some visualization in ascii:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Without lerp:                 With lerp:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;┌─────────────────┐           ┌─────────────────┐&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│           │                 │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│           │   ░░░░░░░░░     │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│▓▓▓▓▓SMOKE▓▓▓▓▓▓▓│           │  ░▒▒▒▒▒▒▒▒▒░    │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│           │  ░▒▓SMOKE▓▒░    │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│           │  ░▒▒▒▒▒▒▒▒▒░    │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└─────────────────┘           │   ░░░░░░░░░     │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  visible quad edge           │                 │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                              └─────────────────┘&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                                soft falloff, no visible edge&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;lerp(0.5, color, mask)&lt;&#x2F;code&gt; formula I used was correct for Unity, but Unity and Bevy implement the multiply blend differently:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Unity : &lt;code&gt;dst * (src.rgb + src.a)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Bevy AlphaMode::Multiply : &lt;code&gt;dst * (src.rgb + 1 - src.a)&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;They apparently use different blend equations. This difference was breaking the math, and I had to override Bevy&#x27;s blend state via &lt;code&gt;Material::specialize()&lt;&#x2F;code&gt; to match Unity&#x27;s equation. I learned that common operations like &quot;multiply blend&quot; can have different implementations between engines.
Here&#x27;s the resulting explosion effect I implemented. It&#x27;s not perfect, but this is so much better than a single-billboard explosion effect I had before:&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;211f6468-6466-4490-97d5-fab1db52745d?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;p&gt;Then, I incorporated this into this tower explosion effect with more bevy_hanabi particles. Here, I spawned 500 spherical billboard particles that fill a 80-unit sphere, transitioning from bright yellow-orange to dark red over their lifetime. They have a nice drag down arc due to the gravity.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;f7730d99-6e49-4019-8959-46bb10d54b20?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;p&gt;Code available at &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;bevy-rts-sim&#x2F;blob&#x2F;main&#x2F;src&#x2F;wfx_spawn.rs&quot;&gt;this repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;br&#x2F;&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Upgrading ROCm from 5.7.1 to 6.4.3</title>
		<published>2025-09-17T00:00:00+00:00</published>
		<updated>2025-09-17T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/rocm1/" type="text/html"/>
		<id>https://ggando.com/blog/rocm1/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;rocm_upgrade2_640px.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#motivation&quot; 
    aria-label=&quot;Anchor link for: motivation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The other day, I was trying to run RT-DETR on my AMD GPU (7900XTX) but hit this error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AttributeError:&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-punctuation z-definition z-string&quot;&gt; module &amp;#39;triton&amp;#39; has no attribute &amp;#39;language&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Newer PyTorch versions use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;docs.pytorch.org&#x2F;docs&#x2F;stable&#x2F;torch.compiler_dynamo_overview.html&quot;&gt;TorchDynamo&lt;&#x2F;a&gt;, which expects modern Triton APIs. Triton is already integrated into PyTorch for CUDA GPUs, but you need to install a separate ROCm implementation (pytorch-triton-rocm). I initially used PyTorch 2.4.1+rocm6.0 and pytorch-triton-rocm 3.0.0, but encountered the above error. After some research with Claude Code, I realized this version was a bit old (July 2024) and the current main branch does have the &lt;code&gt;triton.language&lt;&#x2F;code&gt; module.&lt;&#x2F;p&gt;
&lt;p&gt;PyTorch ROCm builds are locked to their specific pytorch-triton-rocm versions, so to upgrade triton I needed to upgrade PyTorch versions, and I decided to upgrade ROCm too.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;checking-the-current-versions&quot;&gt;Checking the current versions&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#checking-the-current-versions&quot; 
    aria-label=&quot;Anchor link for: checking-the-current-versions&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;cat &#x2F;opt&#x2F;rocm&#x2F;.info&#x2F;version&lt;&#x2F;code&gt; or &lt;code&gt;ls &#x2F;opt&#x2F;rocm*&lt;&#x2F;code&gt; should give you the ROCm version you installed. You can also check at the package level with &lt;code&gt;dpkg -l | grep rocm&lt;&#x2F;code&gt; or &lt;code&gt;apt list --installed | grep rocm&lt;&#x2F;code&gt; if you installed it via package manager. Example output:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dpkg&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -l&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; rocm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ii&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  rocm-cmake5.7.1                                             0.10.0.50701-98~22.04                               amd64        rocm-cmake built using CMake&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ii&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  rocm-core                                                   6.0.0.60000-91~22.04                                amd64        Radeon Open Compute&lt;&#x2F;span&gt;&lt;span&gt; (ROCm) Runtime software stack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ii&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  rocm-core5.7.1                                              5.7.1.50701-98~22.04                                amd64        Radeon Open Compute&lt;&#x2F;span&gt;&lt;span&gt; (ROCm) Runtime software stack&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ii&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;  rocm-device-libs5.7.1                                       1.0.0.50701-98~22.04                                amd64&lt;&#x2F;span&gt;&lt;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can see that ROCm 5.7.1 packages are installed here.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;uninstalling-rocm-5-7-1&quot;&gt;Uninstalling ROCm 5.7.1&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#uninstalling-rocm-5-7-1&quot; 
    aria-label=&quot;Anchor link for: uninstalling-rocm-5-7-1&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Looking at &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;en&#x2F;docs-5.7.1&#x2F;deploy&#x2F;linux&#x2F;os-native&#x2F;uninstall.html#&quot;&gt;the official uninstall instructions&lt;&#x2F;a&gt;, I noticed that the installation package for ROCm 5.7.1 is &lt;code&gt;rocm-hip-sdk&lt;&#x2F;code&gt;, but for ROCm 6.4.3 it&#x27;s just &lt;code&gt;rocm&lt;&#x2F;code&gt;, so I uninstalled the old ROCm to be safe. The uninstallation step is the same for 6.0.0 (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;docs-6.0.0&#x2F;how-to&#x2F;native-install&#x2F;ubuntu.html#uninstalling&quot;&gt;ref&lt;&#x2F;a&gt;). I recommend running both &lt;code&gt;sudo apt autoremove &amp;lt;package-name&amp;gt;&lt;&#x2F;code&gt; and &lt;code&gt;sudo apt autoremove &amp;lt;package-name with release version&amp;gt;&lt;&#x2F;code&gt; to make sure you uninstall old packages completely:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-hip-sdk5.7.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-core5.7.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-hip-sdk&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-core&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My system only had these two packages though:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-hip-sdk5.7.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt autoremove rocm-core&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After uninstallation, verify that no ROCm packages are installed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt list&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --installed&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; rocm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ls &#x2F;opt&#x2F;rocm&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, remove the apt ROCm repository:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo rm &#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;rocm.list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the kernel driver, 6.4.3 still uses the same package &lt;code&gt;amdgpu-dkms&lt;&#x2F;code&gt;, so &lt;u&gt;I didn&#x27;t have to uninstall it&lt;&#x2F;u&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;installing-rocm-6-4-3&quot;&gt;Installing ROCm 6.4.3&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#installing-rocm-6-4-3&quot; 
    aria-label=&quot;Anchor link for: installing-rocm-6-4-3&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Now we can just follow &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;docs-6.4.3&#x2F;install&#x2F;install-methods&#x2F;package-manager&#x2F;package-manager-ubuntu.html#registering-rocm-repositories&quot;&gt;the ROCm 6.4.3 documentation&lt;&#x2F;a&gt; to install it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#prerequisites&quot; 
    aria-label=&quot;Anchor link for: prerequisites&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Read the official doc &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;docs-6.4.3&#x2F;install&#x2F;prerequisites.html#using-udev-rules&quot;&gt;here&lt;&#x2F;a&gt;. I don&#x27;t use Secure Boot on this computer, and I just configured GPU access permissions by adding myself to the &lt;code&gt;render&lt;&#x2F;code&gt; and &lt;code&gt;video&lt;&#x2F;code&gt; groups:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; usermod&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -a -G&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; render,video&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; $LOGNAME&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;installation&quot;&gt;Installation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#installation&quot; 
    aria-label=&quot;Anchor link for: installation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Note that the new ROCm package is 23+GB.&lt;&#x2F;p&gt;
&lt;p&gt;Download and convert the package signing key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt update&lt;&#x2F;span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt upgrade&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; mkdir&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --parents --mode=0755&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;etc&#x2F;apt&#x2F;keyrings&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;wget&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; https:&#x2F;&#x2F;repo.radeon.com&#x2F;rocm&#x2F;rocm.gpg.key&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -O&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    gpg&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --dearmor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; tee &#x2F;etc&#x2F;apt&#x2F;keyrings&#x2F;rocm.gpg&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Register packages:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;deb [arch=amd64 signed-by=&#x2F;etc&#x2F;apt&#x2F;keyrings&#x2F;rocm.gpg] https:&#x2F;&#x2F;repo.radeon.com&#x2F;rocm&#x2F;apt&#x2F;6.4.3 jammy main&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; tee &#x2F;etc&#x2F;apt&#x2F;sources.list.d&#x2F;rocm.list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-constant&quot;&gt;echo -e&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; tee &#x2F;etc&#x2F;apt&#x2F;preferences.d&#x2F;rocm-pin-600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Install the ROCm package:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt install rocm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Reading&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; package lists... Done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Building&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dependency tree... Done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Reading&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; state information... Done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;The&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; following additional packages will be installed:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  amd-smi-lib&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; comgr composablekernel-dev gdal-data half hip-dev hip-doc hip-runtime-amd hip-samples hipblas hipblas-common-dev hipblas-dev hipblaslt&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;rocthrust-dev&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; roctracer roctracer-dev rocwmma-dev rpp rpp-dev unixodbc-common valgrind&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; upgraded,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 183&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; newly installed,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; to remove and&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; not upgraded.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Need&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; to get 3,992 MB of archives.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;After&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; this operation,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 23.8&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; GB of additional disk space will be used.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Do&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; you want to continue?&lt;&#x2F;span&gt;&lt;span&gt; [Y&#x2F;n]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;post-installation-setup&quot;&gt;Post-installation setup&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#post-installation-setup&quot; 
    aria-label=&quot;Anchor link for: post-installation-setup&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Make sure to follow &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;docs-6.4.3&#x2F;install&#x2F;post-install.html&quot;&gt;the post-installation instructions&lt;&#x2F;a&gt;, especially the system linker step:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; tee&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --append&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;etc&#x2F;ld.so.conf.d&#x2F;rocm.conf&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;EOF&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;opt&#x2F;rocm&#x2F;lib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;opt&#x2F;rocm&#x2F;lib64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;EOF&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ldconfig&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After that, you also need to configure paths:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; update-alternatives&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --display&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; rocm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;rocm&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; - auto mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  link&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; best version is &#x2F;opt&#x2F;rocm-6.4.3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  link&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; currently points to &#x2F;opt&#x2F;rocm-6.4.3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;  link&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; rocm is &#x2F;opt&#x2F;rocm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&#x2F;opt&#x2F;rocm-6.4.3&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; - priority&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 649626295&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# -&amp;gt; shows update-alternatives is already working and has automatically configured ROCm 6.4.3 as the default version.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Claude suggested this rather than the `export LD_LIBRARY_PATH=&#x2F;opt&#x2F;rocm-6.4.3&#x2F;lib`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;echo&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;export LD_LIBRARY_PATH=&#x2F;opt&#x2F;rocm&#x2F;lib&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ~&#x2F;.bashrc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, confirm that the new ROCm has been installed:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; cat &#x2F;opt&#x2F;rocm&#x2F;.info&#x2F;version&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;6.4.3-128&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Verify you can run &lt;code&gt;rocminfo&lt;&#x2F;code&gt; and &lt;code&gt;rocm-smi&lt;&#x2F;code&gt; commands. After a reboot, you should be able to install newer PyTorch and triton versions!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-installed-for-rt-detr&quot;&gt;What I installed for RT-DETR&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#what-i-installed-for-rt-detr&quot; 
    aria-label=&quot;Anchor link for: what-i-installed-for-rt-detr&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;For the record, after this upgrade I was able to install these newer torch packages for running RT-DETR GPU inference and resolve the previous &lt;code&gt;AttributeError&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Working setup&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;torch =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;2.6.0+rocm6.4.3&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;torchvision =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;0.21.0+rocm6.4.3&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;torchaudio =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;2.6.0+rocm6.4.3&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;pytorch-triton-rocm =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;3.2.0+rocm6.4.3&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # &amp;lt;- Now has triton.language&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;transformers =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;4.55.4&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here&#x27;s an example detection result with the RT-DETR Large model:&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;rtdetr_inference_4k.jpg&quot; alt=&quot;img0&quot; width=&quot;640&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>How to sell your Rust app on macOS app store</title>
		<published>2025-05-08T00:00:00+00:00</published>
		<updated>2025-05-08T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/appstore/" type="text/html"/>
		<id>https://ggando.com/blog/appstore/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;rust_appstore_moz.jpg&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#introduction&quot; 
    aria-label=&quot;Anchor link for: introduction&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Last week I released my Rust app (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;viewskater&quot;&gt;an image viewer&lt;&#x2F;a&gt;) on macOS app store. The whole submission process is tedious, and I had to repeat the upload steps multiple times to fix issues with my binaries. I’m writing this post to document the entire process step by step.&lt;&#x2F;p&gt;
&lt;p&gt;Initially, I referred to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;rsms&#x2F;929c9c2fec231f0cf843a1a746a416f5&quot;&gt;this gist&lt;&#x2F;a&gt;, but it’s aimed at distributing apps outside the App Store. If you&#x27;re targeting the App Store, some of the steps are different, which I’ll cover here.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-0-patch-winit-to-remove-private-api-usage&quot;&gt;Step 0: Patch winit to Remove Private API Usage&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-0-patch-winit-to-remove-private-api-usage&quot; 
    aria-label=&quot;Anchor link for: step-0-patch-winit-to-remove-private-api-usage&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Before starting the App Store release process, you need to patch winit if your app depends on it.
As of May 2025, winit (0.30.x) still includes a reference to a private macOS API: &lt;code&gt;_CGSSetWindowBackgroundBlurRadius&lt;&#x2F;code&gt;.
Even if your app doesn&#x27;t use any blur functionality, the App Store will reject your binary just for containing the symbol.
To fix this, you need to replace the deprecated call with a safe alternative using &lt;code&gt;NSVisualEffectView&lt;&#x2F;code&gt;. Refer to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-windowing&#x2F;winit&#x2F;issues&#x2F;4205&quot;&gt;this github issue&lt;&#x2F;a&gt; for details.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;how-to-patch&quot;&gt;How to Patch&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#how-to-patch&quot; 
    aria-label=&quot;Anchor link for: how-to-patch&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Fork winit or make a local copy.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Replace the &lt;code&gt;set_blur&lt;&#x2F;code&gt; function in &lt;code&gt;winit&#x2F;src&#x2F;platform_impl&#x2F;apple&#x2F;appkit&#x2F;window_delegate.rs&lt;&#x2F;code&gt;
with the following version (based on @Areopagitics&#x27; fix):&lt;&#x2F;p&gt;
 &lt;details&gt;
 &lt;summary&gt;Click to see the code&lt;&#x2F;summary&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; set_blur&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, blur&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;window&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; blur {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; effect_view&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; alloc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;class!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;NSVisualEffectView&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;), alloc];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;            msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[alloc, init]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, setMaterial&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sel!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;NSVisualEffectMaterialAppearanceBased&lt;&#x2F;span&gt;&lt;span&gt;)];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, setState&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sel!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;NSVisualEffectStateActive&lt;&#x2F;span&gt;&lt;span&gt;)];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, setBlendingMode&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sel!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;NSVisualEffectBlendingModeBehindWindow&lt;&#x2F;span&gt;&lt;span&gt;)];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, setTranslatesAutoresizingMaskIntoConstraints&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;span&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_view&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[window, contentView] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, addSubview&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; effect_view];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; leading_anchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, leadingAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; trailing_anchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, trailingAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; top_anchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, topAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; bottom_anchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[effect_view, bottomAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_leading&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, leadingAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_trailing&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, trailingAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_top&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, topAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_bottom&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, bottomAnchor];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[leading_anchor, constraintEqualToAnchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_leading];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[trailing_anchor, constraintEqualToAnchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_trailing];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[top_anchor, constraintEqualToAnchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_top];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[bottom_anchor, constraintEqualToAnchor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_bottom];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; content_view&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[window, contentView] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; subviews&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[content_view, subviews] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; count&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[subviews, count] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; in&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;count {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; subview&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;objc2&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;AnyObject&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[subviews, objectAtIndex&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; i] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; is_visual_effect_view&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; bool&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[subview, isKindOfClass&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; class!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;NSVisualEffectView&lt;&#x2F;span&gt;&lt;span&gt;)] };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; is_visual_effect_view {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;                unsafe&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; ()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; msg_send!&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;[subview, removeFromSuperview]; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt; &lt;&#x2F;details&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Point your Cargo.toml to your patched winit:&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;winit = { git =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;https:&#x2F;&#x2F;github.com&#x2F;yourname&#x2F;winit&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, branch =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;your-patched-branch&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;step-1-enroll-in-apple-developer-program&quot;&gt;Step 1: Enroll in Apple Developer Program&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-1-enroll-in-apple-developer-program&quot; 
    aria-label=&quot;Anchor link for: step-1-enroll-in-apple-developer-program&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Unfortunately, a paid Apple Developer Program membership ($99&#x2F;year) is required to distribute apps on the App Store. Hopefully, this post will help you understand how the submission process would look like and whether it&#x27;s worth the effort.&lt;&#x2F;p&gt;
&lt;p&gt;You need to agree to the Apple Developer Program License Agreement which basically says you&#x27;re liable to all the issues caused by your app while you keep the ownership.&lt;&#x2F;p&gt;
&lt;p&gt;This account will be used to create certificates, code-sign app binaries, and define your app on App Store Connect in later steps.&lt;&#x2F;p&gt;
&lt;p&gt;Relevant links:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;developer.apple.com&#x2F;account&quot;&gt;Apple Developer account page&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;appstoreconnect.apple.com&quot;&gt;App Store Connect&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;1-1-create-bundle-id-for-your-app&quot;&gt;1-1. Create Bundle ID for your app&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#1-1-create-bundle-id-for-your-app&quot; 
    aria-label=&quot;Anchor link for: 1-1-create-bundle-id-for-your-app&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;Go to Apple Developer account page -&amp;gt; &lt;strong&gt;&quot;Identifiers&quot;&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Click the &quot;+&quot; icon&lt;&#x2F;li&gt;
&lt;li&gt;Select &quot;App IDs&quot; -&amp;gt; Continue&lt;&#x2F;li&gt;
&lt;li&gt;Select &quot;App&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Fill out the &quot;Bundle ID&quot;: e.g. &quot;com.ggando.viewskater&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Fill out &quot;Description&quot;: e.g. &quot;A fast image viewer&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Enable any of Capabilities required by your app -&amp;gt; &quot;Register&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h4 id=&quot;1-2-create-your-app&quot;&gt;1-2. Create your app&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#1-2-create-your-app&quot; 
    aria-label=&quot;Anchor link for: 1-2-create-your-app&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ol&gt;
&lt;li&gt;Go to App Store Connect &#x2F; Apps&lt;&#x2F;li&gt;
&lt;li&gt;Click the &quot;+&quot; icon -&amp;gt; &quot;New App&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Fill out the New App form:&lt;br &#x2F;&gt;
Platforms: &quot;macOS&quot;&lt;br &#x2F;&gt;
Name: your app name&lt;br &#x2F;&gt;
Primary Language: your language&lt;br &#x2F;&gt;
Bundle ID: your app identifier created in &lt;strong&gt;1-1&lt;&#x2F;strong&gt;&lt;br &#x2F;&gt;
SKU: some ID you&#x27;d like to use, it can be anything. e.g. &quot;viewskater&quot;&lt;br &#x2F;&gt;
User Access: &quot;Full Access&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;After this you&#x27;ll see a page like this for configuring screenshots, description, app pricing, etc.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;app_store_connect0.png&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;step-2-bundle-your-app&quot;&gt;Step 2: Bundle your app&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-2-bundle-your-app&quot; 
    aria-label=&quot;Anchor link for: step-2-bundle-your-app&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I use the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;burtonageo&#x2F;cargo-bundle&quot;&gt;cargo-bundle&lt;&#x2F;a&gt; crate to build a &lt;strong&gt;.app bundle&lt;&#x2F;strong&gt; for macOS. After specifying app metadata in your &lt;code&gt;Cargo.toml&lt;&#x2F;code&gt;, you can run &lt;code&gt;cargo bundle --release&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Used on macOS for generating .app bundles via `cargo bundle`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;target&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&amp;#39;cfg(target_os = &amp;quot;macos&amp;quot;)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;dev-dependencies&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;cargo-bundle =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;0.6.0&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;package&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;metadata&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;bundle&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;name =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;ViewSkater&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;identifier =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;com.ggando.viewskater&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;icon = [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;assets&#x2F;ViewSkater.icns&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;short_description =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;A fast image viewer for browsing large collections of images.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;prepare-app-icon&quot;&gt;Prepare app icon&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#prepare-app-icon&quot; 
    aria-label=&quot;Anchor link for: prepare-app-icon&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;App Store Connect requires you to include a &lt;strong&gt;.icns&lt;&#x2F;strong&gt; file with specific resolutions. If it’s missing, you’ll get an email like this:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;ITMS-90236: Missing required icon - The application bundle does not contain an icon in ICNS format, containing both a 512x512 and a 512x512@2x image.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;@2x means &quot;Retina&quot; versions (2× resolution). So in this case, 512×512px and 1024x1024px PNGs are missing.
It looks like you could include @2x images with cargo-bundle if you explicitly name the icon files like &quot;128x128@2x.png&quot;, but I had better luck with using a manually generated .icns. Here&#x27;s how:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Create an icon.iconset folder with files named exactly:&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_16x16.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_16x16@2x.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_32x32.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_32x32@2x.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_128x128.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_128x128@2x.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_256x256.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_256x256@2x.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_512x512.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon_512x512@2x.png&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;where the resolution of &lt;code&gt;icon_16x16.png&lt;&#x2F;code&gt; is 16x16px, &lt;code&gt;icon_16x16@2x.png&lt;&#x2F;code&gt; is 32x32px, and so on.&lt;&#x2F;p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Use macOS built-in tool:&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;iconutil -c icns -o ViewSkater.icns icon.iconset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Put the resulting .icns in your project, then specify in Cargo.toml&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[package.metadata.bundle]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name = &amp;quot;ViewSkater&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;identifier = &amp;quot;com.ggando.viewskater&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;icon = [&amp;quot;assets&#x2F;ViewSkater.icns&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;short_description = &amp;quot;A fast image viewer for browsing large collections of images.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;cargo bundle —release  will include the .icns icon&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;step-3-set-up-info-plist-entitlements-and-provisioning-profile&quot;&gt;Step 3: Set Up Info.plist, Entitlements, and Provisioning Profile&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-3-set-up-info-plist-entitlements-and-provisioning-profile&quot; 
    aria-label=&quot;Anchor link for: step-3-set-up-info-plist-entitlements-and-provisioning-profile&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Before signing and packaging your Rust app, you need to prepare a few required files inside the .app bundle:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;1-info-plist&quot;&gt;1. Info.plist&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#1-info-plist&quot; 
    aria-label=&quot;Anchor link for: 1-info-plist&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;You need an Info.plist file at &lt;code&gt;YourApp.app&#x2F;Contents&#x2F;Info.plist&lt;&#x2F;code&gt;.
This tells macOS basic metadata about your app.
At minimum, your Info.plist should include:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;xml&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; encoding&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;?&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;DOCTYPE&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; plist&lt;&#x2F;span&gt;&lt;span&gt; PUBLIC &amp;quot;-&#x2F;&#x2F;Apple&#x2F;&#x2F;DTD PLIST 1.0&#x2F;&#x2F;EN&amp;quot; &amp;quot;http:&#x2F;&#x2F;www.apple.com&#x2F;DTDs&#x2F;PropertyList-1.0.dtd&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;CFBundleIdentifier&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;com.yourcompany.yourapp&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;CFBundleName&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;YourApp&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;CFBundleVersion&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;1.0.0&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;CFBundleShortVersionString&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;1.0.0&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;LSMinimumSystemVersion&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;11.0&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &amp;lt;!-- or whatever minimum macOS you want to support --&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;CFBundleIdentifier must match the Bundle ID you created on App Store Connect earlier.&lt;&#x2F;li&gt;
&lt;li&gt;LSMinimumSystemVersion is required. Without it, your build will be rejected:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;ITMS-90983: Missing LSMinimumSystemVersion - The LSMinimumSystemVersion key must be present in the Info.plist file when submitting a macOS app.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;2-entitlements-plist&quot;&gt;2. Entitlements.plist&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#2-entitlements-plist&quot; 
    aria-label=&quot;Anchor link for: 2-entitlements-plist&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Entitlements define permissions your app needs.
Basic Entitlements.plist for a simple Rust app (no special permissions) looks like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;xml&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; encoding&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;?&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;DOCTYPE&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; plist&lt;&#x2F;span&gt;&lt;span&gt; PUBLIC &amp;quot;-&#x2F;&#x2F;Apple&#x2F;&#x2F;DTD PLIST 1.0&#x2F;&#x2F;EN&amp;quot; &amp;quot;http:&#x2F;&#x2F;www.apple.com&#x2F;DTDs&#x2F;PropertyList-1.0.dtd&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;com.apple.security.app-sandbox&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;App Sandbox is required for App Store apps.&lt;&#x2F;li&gt;
&lt;li&gt;If your app needs more capabilities (like network access, file access, etc.), you need to add more keys.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Missing this file will trigger a rejection after upload:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;TMS-90296: App sandbox not enabled - The following executables must include the &#x27;com.apple.security.app-sandbox&#x27; entitlement with a Boolean value of true in the entitlements property list: [[com.ggando.viewskater.pkg&#x2F;Payload&#x2F;ViewSkater.app&#x2F;Contents&#x2F;MacOS&#x2F;viewskater]]&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;3-provisioning-profile&quot;&gt;3. Provisioning Profile&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#3-provisioning-profile&quot; 
    aria-label=&quot;Anchor link for: 3-provisioning-profile&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;You need a &lt;code&gt;.provisionprofile&lt;&#x2F;code&gt; (also called a provisioning profile) tied to your App ID.
Steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Go to Apple Developer account page -&amp;gt; &quot;Certificates&quot; -&amp;gt; in the &quot;Certificates, Identifiers &amp;amp; Profiles&quot; page, select the &lt;strong&gt;&quot;Profiles&quot;&lt;&#x2F;strong&gt; tab -&amp;gt; click the &quot;+&quot; icon&lt;&#x2F;li&gt;
&lt;li&gt;Select the &quot;App Store Connect&quot; option under the &quot;Distribution&quot; section -&amp;gt; &quot;Continue&quot; -&amp;gt; Select an App ID from the pulldown menu -&amp;gt; &quot;Continue&quot; -&amp;gt; generate a provision profile&lt;&#x2F;li&gt;
&lt;li&gt;Download the .provisionprofile file.&lt;&#x2F;li&gt;
&lt;li&gt;Copy it into your .app bundle at:
&lt;code&gt;YourApp.app&#x2F;Contents&#x2F;embedded.provisionprofile&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I&#x27;m not 100% sure, but I believe this file is required. In my case, including only the Entitlements.plist resulted in a rejection with the following error, suggesting the provisioning profile must also be present:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;ITMS-90287: Invalid Code Signing Entitlements - The entitlements in your app bundle signature do not match the ones that are contained in the provisioning profile. The bundle contains a key that is not included in the provisioning profile: &#x27;com.apple.application-identifier&#x27; in &#x27;&amp;lt;path&amp;gt;&#x2F;ViewSkater.app&#x2F;Contents&#x2F;MacOS&#x2F;viewskater&#x27;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;4-remove-the-quarantine-attribute&quot;&gt;4. Remove the quarantine attribute&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#4-remove-the-quarantine-attribute&quot; 
    aria-label=&quot;Anchor link for: 4-remove-the-quarantine-attribute&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Finally, run this to clean up any macOS quarantine flag&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;xattr&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -cr&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;your ap&lt;&#x2F;span&gt;&lt;span&gt;p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;.app`&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you skip this step, App Store Connect may reject the upload:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;ITMS-91109: Invalid package contents - The package contains one or more files with the com.apple.quarantine extended file attribute, such as :“&amp;lt;path&amp;gt;&#x2F;ViewSkater.app&#x2F;Contents&#x2F;Resources&#x2F;ViewSkater.icns”. This attribute isn’t permitted in macOS apps distributed on TestFlight or the App Store. Please remove the attribute from all files within your app and upload again.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h3 id=&quot;step-4-code-sign-the-app&quot;&gt;Step 4. Code-sign the .app&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-4-code-sign-the-app&quot; 
    aria-label=&quot;Anchor link for: step-4-code-sign-the-app&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Code signing proves that your app comes from a verified Apple developer and is required for App Store submission.&lt;&#x2F;p&gt;
&lt;p&gt;Before you can sign your app, you’ll need to generate a certificate request and download the necessary certificates from the Apple Developer site.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;4-1-create-and-install-a-distribution-certificate&quot;&gt;4-1. Create and install a Distribution Certificate&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#4-1-create-and-install-a-distribution-certificate&quot; 
    aria-label=&quot;Anchor link for: 4-1-create-and-install-a-distribution-certificate&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;You’ll need a &lt;strong&gt;“3rd Party Mac Developer Application”&lt;&#x2F;strong&gt; certificate to code-sign your .app.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Open Keychain Access → Certificate Assistant → Request a Certificate from a Certificate Authority....&lt;&#x2F;li&gt;
&lt;li&gt;Fill in your info, select “Saved to disk”, and generate a &lt;code&gt;.certSigningRequest&lt;&#x2F;code&gt; (&lt;strong&gt;CSR&lt;&#x2F;strong&gt;) file.&lt;&#x2F;li&gt;
&lt;li&gt;Go to the Apple Developer Certificates page.&lt;&#x2F;li&gt;
&lt;li&gt;Click +, and under Production, choose &lt;strong&gt;“Mac App Distribution”&lt;&#x2F;strong&gt; (this is officially called 3rd Party Mac Developer Application).&lt;&#x2F;li&gt;
&lt;li&gt;Upload your CSR (certificate signing request) file.&lt;&#x2F;li&gt;
&lt;li&gt;Download the generated .cer file and double-click it to install into Keychain Access.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Confirm the installation by running &lt;code&gt;security find-identity -v&lt;&#x2F;code&gt; in your terminal.
You should see an entry like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ security find-identity -v&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1) &amp;lt;ID&amp;gt; &amp;quot;3rd Party Mac Developer Application: Your Name (Team ID)&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1 valid identities found&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Copy this exact string: &quot;3rd Party Mac Developer Application: Your Name (Team ID)&quot;. You’ll use it in the codesign command in the next step.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;4-2-run-code-signing-command&quot;&gt;4-2. Run code-signing command&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#4-2-run-code-signing-command&quot; 
    aria-label=&quot;Anchor link for: 4-2-run-code-signing-command&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Before submitting your app, you must code-sign the .app itself using your 3rd Party Mac Developer Application certificate.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;codesign&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --deep --force --options&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; runtime&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  --sign&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;3rd Party Mac Developer Application: Your Name (Team ID)&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &#x2F;path&#x2F;to&#x2F;YourApp.app&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Explanation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;--deep ensures that all nested contents (binaries, libraries) are also signed.&lt;&#x2F;li&gt;
&lt;li&gt;--force replaces any existing signatures if needed.&lt;&#x2F;li&gt;
&lt;li&gt;--options runtime enables hardened runtime, which is required for App Store submission.&lt;&#x2F;li&gt;
&lt;li&gt;--sign specifies the application signing certificate.
Important:&lt;&#x2F;li&gt;
&lt;li&gt;The .appmust be signed correctly before you package it into a .pkg.&lt;&#x2F;li&gt;
&lt;li&gt;If you package an unsigned .app, Apple will reject your submission.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;step-5-create-a-pkg-installer-for-app-store-submission&quot;&gt;Step 5. Create a .pkg Installer for App Store Submission&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-5-create-a-pkg-installer-for-app-store-submission&quot; 
    aria-label=&quot;Anchor link for: step-5-create-a-pkg-installer-for-app-store-submission&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;5-1-get-certificates-for-installers&quot;&gt;5-1. Get certificates for installers&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#5-1-get-certificates-for-installers&quot; 
    aria-label=&quot;Anchor link for: 5-1-get-certificates-for-installers&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;First, you need a &lt;strong&gt;“3rd Party Mac Developer Installer”&lt;&#x2F;strong&gt; certificate (this is different from the one we used for code-signing).
The process the same as the previous &quot;3rd Party Mac Developer Application&quot; certificate we used for code-signing. You can re-use the same CSR file you generated before.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Go to Apple Developer Certificates.&lt;&#x2F;li&gt;
&lt;li&gt;Click +, and under Production, choose &lt;strong&gt;“Mac Installer Distribution”&lt;&#x2F;strong&gt; (this is officially called 3rd Party Mac Developer Installer).&lt;&#x2F;li&gt;
&lt;li&gt;Upload your CSR (certificate signing request) just like before.&lt;&#x2F;li&gt;
&lt;li&gt;Download and install the .cer file into Keychain Access.
This certificate will be used to sign the installer package (.pkg), not the app itself.
Confirm the installation by running &lt;code&gt;security find-identity -v&lt;&#x2F;code&gt; again. You should see two entries including &quot;3rd Party Mac Developer Installer&quot;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Initially I used a wrong certificate for this (&quot;Developer ID Installer&quot;) and got rejected after the upload:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;ITMS-90237: The product archive package&#x27;s signature is invalid. Ensure that it is signed with your &#x27;3rd Party Mac Developer Installer&#x27; certificate.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h4 id=&quot;5-2-run-packagebuild-command&quot;&gt;5-2. Run packagebuild command&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#5-2-run-packagebuild-command&quot; 
    aria-label=&quot;Anchor link for: 5-2-run-packagebuild-command&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Once you have the app (.app) already signed and ready, you can create the .pkg using productbuild:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;productbuild&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  --component&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;path&#x2F;to&#x2F;YourApp.app &#x2F;Applications&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  --sign&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;3rd Party Mac Developer Installer: Your Name (Team ID)&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;  &#x2F;path&#x2F;to&#x2F;output&#x2F;YourApp.pkg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Explanation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;--component tells where your .app is and where it should be installed (usually &#x2F;Applications).&lt;&#x2F;li&gt;
&lt;li&gt;--sign uses the installer certificate to sign the package.&lt;&#x2F;li&gt;
&lt;li&gt;The last part is the output .pkg file.
This .pkg is what you upload to App Store Connect when submitting your app for review.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;step-6-upload-the-pkg-with-transporter&quot;&gt;Step 6. Upload the .pkg with Transporter&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-6-upload-the-pkg-with-transporter&quot; 
    aria-label=&quot;Anchor link for: step-6-upload-the-pkg-with-transporter&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Transporter is Apple’s official tool for uploading app builds to App Store Connect. It&#x27;s a way to deliver signed binaries to the App Store.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;6-1-wrap-the-pkg-in-an-itmsp-directory&quot;&gt;6-1. Wrap the &lt;code&gt;.pkg&lt;&#x2F;code&gt; in an &lt;code&gt;.itmsp&lt;&#x2F;code&gt; directory&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#6-1-wrap-the-pkg-in-an-itmsp-directory&quot; 
    aria-label=&quot;Anchor link for: 6-1-wrap-the-pkg-in-an-itmsp-directory&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Transporter doesn’t accept raw &lt;code&gt;.pkg&lt;&#x2F;code&gt; files. You need to wrap your package in an .itmsp directory, which contains both the .pkg and a metadata.xml descriptor.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Create a directory, e.g. ViewSkater.itmsp&lt;&#x2F;li&gt;
&lt;li&gt;Copy the .pkg file into it&lt;&#x2F;li&gt;
&lt;li&gt;Create a metadata.xml file like this:&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;xml&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; encoding&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;?&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;package&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; xmlns&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;http:&#x2F;&#x2F;apple.com&#x2F;itunes&#x2F;importer&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;software5.10&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;software_assets&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; apple_id&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;&amp;lt;your app id&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; app_platform&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;osx&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;asset&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;product-archive&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;data_file&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;file_name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;ViewSkater.pkg&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;file_name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;file&lt;&#x2F;span&gt;&lt;span&gt; size&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;checksum&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; type&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;md5&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;md5&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;checksum&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;data_file&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;asset&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;software_assets&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;package&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As for the file size and checksum values, use these commands:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-constant&quot;&gt;stat -f%z&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ViewSkater.pkg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;md5&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; ViewSkater.pkg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Reflect the actual values in &lt;code&gt;metadata.xml&lt;&#x2F;code&gt; and place it under the .itmsp directory:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ViewSkater.itmsp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|--ViewSkater.pkg&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;|--metadata.xml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h4 id=&quot;6-2-create-an-app-specific-password&quot;&gt;6-2. Create an app-specific password&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#6-2-create-an-app-specific-password&quot; 
    aria-label=&quot;Anchor link for: 6-2-create-an-app-specific-password&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Another annoying step but you need to set up an &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;support.apple.com&#x2F;en-us&#x2F;102654&quot;&gt;app-specific password&lt;&#x2F;a&gt; in macOS Keychain. Go to https:&#x2F;&#x2F;appleid.apple.com&#x2F;account&#x2F;manage and setup an app-specific password.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Go to https:&#x2F;&#x2F;appleid.apple.com.&lt;&#x2F;li&gt;
&lt;li&gt;Sign into your apple account from the &quot;Sign in&quot; menu at the top right corner&lt;&#x2F;li&gt;
&lt;li&gt;In the Sign-In and Security section, select &lt;strong&gt;App-Specific Passwords&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Select Generate an app-specific password or select the Add button , then follow the steps on your screen.&lt;&#x2F;li&gt;
&lt;li&gt;Enter or paste the app-specific password into the password field of the app.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;discussions.apple.com&#x2F;thread&#x2F;254805086?sortBy=rank&quot;&gt;Reference&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;6-3-run-the-transporter-tool-to-upload-your-app&quot;&gt;6-3. Run the transporter tool to upload your app&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#6-3-run-the-transporter-tool-to-upload-your-app&quot; 
    aria-label=&quot;Anchor link for: 6-3-run-the-transporter-tool-to-upload-your-app&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;We can now upload the prepared app package to App Store Connect. Install the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;apps.apple.com&#x2F;us&#x2F;app&#x2F;transporter&#x2F;id1450874784?mt=12&quot;&gt;Transporter app&lt;&#x2F;a&gt;. In my case the GUI app didn&#x27;t work and only gave me vague errors so I used the command line tool:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;&#x2F;Applications&#x2F;Transporter.app&#x2F;Contents&#x2F;itms&#x2F;bin&#x2F;iTMSTransporter&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -m&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; upload&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -f&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;Users&#x2F;gota&#x2F;ggando&#x2F;viewskater&#x2F;target&#x2F;release&#x2F;bundle&#x2F;osx&#x2F;ViewSkater.itmsp&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -u&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;my_emai&lt;&#x2F;span&gt;&lt;span&gt;l&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;  -p&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;app_specific_passwor&lt;&#x2F;span&gt;&lt;span&gt;d&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If successful, you’ll see a confirmation in the terminal, and App Store Connect will also send you an email within a few minutes.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-7-fix-any-issues-from-app-store-connect&quot;&gt;Step 7. Fix any issues from App Store Connect&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-7-fix-any-issues-from-app-store-connect&quot; 
    aria-label=&quot;Anchor link for: step-7-fix-any-issues-from-app-store-connect&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;If there are problems with your upload (missing icons, metadata, signatures, etc.), you’ll get an email titled something like: &quot;Action needed: The uploaded build for &lt;app name&gt; has one or more issues&quot;. You’ll need to fix all the critical ones before your app can move to review.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;example-email&quot;&gt;Example email&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#example-email&quot; 
    aria-label=&quot;Anchor link for: example-email&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Hello,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;We noticed one or more issues with a recent delivery for the following app:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; • VIewSkater&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; • App Apple &amp;lt;ID&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; • Version 0.2.3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; • Build 20250424.45313&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Please correct the following issues and upload a new binary to App Store Connect.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90242: The product archive is invalid - The Info.plist must contain a LSApplicationCategoryType key, whose value is the UTI for a valid category. For more details, see &amp;#39;Submitting your Mac apps to the App Store&amp;#39;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90869: Invalid bundle - The “ViewSkater.app” bundle supports arm64 but not Intel-based Mac computers. Your build must include the x86_64 architecture to support Intel-based Mac computers. To support arm64 only, your macOS deployment target must be 12.0 or higher. For details, view: https:&#x2F;&#x2F;developer.apple.com&#x2F;documentation&#x2F;xcode&#x2F;building_a_universal_macos_binary. &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90296: App sandbox not enabled - The following executables must include the &amp;#39;com.apple.security.app-sandbox&amp;#39; entitlement with a Boolean value of true in the entitlements property list: [[com.ggando.viewskater.pkg&#x2F;Payload&#x2F;ViewSkater.app&#x2F;Contents&#x2F;MacOS&#x2F;viewskater]] Refer to App Sandbox page at https:&#x2F;&#x2F;developer.apple.com&#x2F;documentation&#x2F;security&#x2F;app_sandbox for more information on sandboxing your app. &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90237: The product archive package&amp;#39;s signature is invalid. Ensure that it is signed with your &amp;#39;3rd Party Mac Developer Installer&amp;#39; certificate. &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90983: Missing LSMinimumSystemVersion - The LSMinimumSystemVersion key must be present in the Info.plist file when submitting a macOS app. For details, visit: https:&#x2F;&#x2F;developer.apple.com&#x2F;documentation&#x2F;bundleresources&#x2F;information_property_list&#x2F;lsminimumsystemversion. &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90236: Missing required icon - The application bundle does not contain an icon in ICNS format, containing both a 512x512 and a 512x512@2x image. For further assistance, see the Human Interface Guidelines at https:&#x2F;&#x2F;developer.apple.com&#x2F;design&#x2F;human-interface-guidelines&#x2F;foundations&#x2F;app-icons&#x2F;. &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Though you are not required to fix the following issues, we wanted to make you aware of them:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ITMS-90889: &amp;#39;Cannot be used with TestFlight because the bundle at “ViewSkater.app” is missing a provisioning profile. Main bundles are expected to have provisioning profiles in order to be eligible for TestFlight.&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Apple Developer Relations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If your .app bundle includes a valid provisioning profile, you can also test it via TestFlight. This is useful because Apple enforces a stricter sandbox in TestFlight than on your own machine. For instance, my app includes a CPU memory monitor, but it didn’t work in TestFlight because it couldn’t access process-level system info.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-8-prepare-your-app-info&quot;&gt;Step 8. Prepare your app info&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-8-prepare-your-app-info&quot; 
    aria-label=&quot;Anchor link for: step-8-prepare-your-app-info&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Prepare preview video, screenshots, description and configure pricing.
A few things to keep in mind:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The preview video requirements: 1920x1080px or 1080x1920px, 15~30 seconds duration, 30 FPS&lt;&#x2F;li&gt;
&lt;li&gt;Screenshots resolution: Either of 1280x800px, 1440x900px, 2560x1600px, or 2880x1800px (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;help&#x2F;app-store-connect&#x2F;reference&#x2F;screenshot-specifications&#x2F;&quot;&gt;reference&lt;&#x2F;a&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;Most assets(preview video, screenshots, description, etc.) &lt;strong&gt;can’t be edited&lt;&#x2F;strong&gt; after release unless you submit a new build&lt;&#x2F;li&gt;
&lt;li&gt;You &lt;strong&gt;can&lt;&#x2F;strong&gt; update promotional text, copyright info, and pricing after release&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;step-9-submit-your-app-for-app-review&quot;&gt;Step 9. Submit your app for App Review&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-9-submit-your-app-for-app-review&quot; 
    aria-label=&quot;Anchor link for: step-9-submit-your-app-for-app-review&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;If everything looks good, go ahead and hit the &lt;strong&gt;&quot;Add for Review&quot;&lt;&#x2F;strong&gt; button at the top right corner of your app page on App Store Connect. If something goes wrong like an obvious crash, you&#x27;ll get an email like: &quot;We noticed an issue with your submission&quot;. Just fix the issue and re-upload a new binary.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;step-10-confirm-the-release&quot;&gt;Step 10. Confirm the release&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#step-10-confirm-the-release&quot; 
    aria-label=&quot;Anchor link for: step-10-confirm-the-release&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Once your app is approved, it should appear on the App Store within 24 hours. Cheers!&lt;&#x2F;p&gt;
&lt;p&gt;For reference, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;apps.apple.com&#x2F;us&#x2F;app&#x2F;viewskater&#x2F;id6745068907&quot;&gt;here&#x27;s the link to my app&lt;&#x2F;a&gt; if you want to check it out.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h3 id=&quot;bonus-automating-the-tedious-parts&quot;&gt;Bonus: Automating the Tedious Parts&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#bonus-automating-the-tedious-parts&quot; 
    aria-label=&quot;Anchor link for: bonus-automating-the-tedious-parts&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;After repeating this process a few times, I generated a shell script with GPT to automate the bundling, signing, packaging, and uploading steps.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;ggand0&#x2F;0f6c266a999cf9b03f9ca560352c2bb0&quot;&gt;Here’s the script as a GitHub Gist&lt;&#x2F;a&gt;. Edit the constants at the top before running.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>I built an image viewer in Rust</title>
		<published>2025-03-31T00:00:00+00:00</published>
		<updated>2025-03-31T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/imageviewer0/" type="text/html"/>
		<id>https://ggando.com/blog/imageviewer0/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;viewskater0_1280.jpg&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#motivation&quot; 
    aria-label=&quot;Anchor link for: motivation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;As a deep learning and computer vision engineer, I often find it frustrating to explore unfamiliar, large image datasets. Initially, I hadn’t given much thought to the performance of image viewers because I mostly worked with pre-cleaned benchmark datasets. But as I started working in the industry and began prototyping new models with real-world data, I started feeling the pain of not being able to view large datasets &lt;strong&gt;quickly&lt;&#x2F;strong&gt; and &lt;strong&gt;seamlessly&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Built-in OS image viewers exist, but they&#x27;re not designed for browsing ML datasets. For example, &lt;strong&gt;eog&lt;&#x2F;strong&gt; on Linux has clean UI and works fine, but it skips rendering images when I hold the arrow key to navigate through a directory. I guess it&#x27;s because the rendering part is synchronous. Nautilus also feels sluggish if I try to browse through a directory of lots of images. On macOS, the &lt;strong&gt;Preview&lt;&#x2F;strong&gt; app makes you select all images in a directory just to open them at once, and navigation is still slow. When the dataset is large, I also sometimes sample images and plot them with libraries like &lt;strong&gt;matplotlib&lt;&#x2F;strong&gt;, but honestly no one wants to wrtie code just to view images. You can also create a basic UI with &lt;strong&gt;Streamlit&lt;&#x2F;strong&gt;, but that ends up being a web app running on JavaScript, which is &lt;strong&gt;way slower&lt;&#x2F;strong&gt; than native applications.&lt;&#x2F;p&gt;
&lt;p&gt;So, I decided to develop my own image viewer in &lt;strong&gt;Rust&lt;&#x2F;strong&gt;. The choice was straightforward: I wanted to avoid the slow performance that comes with Python or JavaScript, and didn&#x27;t want to deal with the complexities of memory management in C++. Rust turned out to be quite intuitive for GUI application development.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;rust-gui-frameworks&quot;&gt;Rust GUI frameworks&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#rust-gui-frameworks&quot; 
    aria-label=&quot;Anchor link for: rust-gui-frameworks&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;There are several GUI frameworks available in Rust for building desktop applications. For beginners like me, I think it&#x27;s better to go with a major, more stable library like &lt;strong&gt;Iced&lt;&#x2F;strong&gt;, &lt;strong&gt;egui&lt;&#x2F;strong&gt;, or &lt;strong&gt;Tauri&lt;&#x2F;strong&gt;. These frameworks are more likely to have more resources and ongoing maintenance.&lt;&#x2F;p&gt;
&lt;p&gt;I chose &lt;strong&gt;Iced&lt;&#x2F;strong&gt; because it was the first framework where I was able to quickly add initial features like displaying a single image and starting prototyping. I also didn&#x27;t want to handle &lt;strong&gt;layout calculations myself&lt;&#x2F;strong&gt;; I was planning to implement multiple panes for this image viewer. I found Iced to be developer-friendly. Its APIs are intuitive, and the online community is very active on both GitHub and Discord. &lt;strong&gt;Hector&lt;&#x2F;strong&gt;, the author of Iced, is especially responsive and frequently answers questions on Discord, which really helps a lot. I&#x27;m not familiar with egui at all, but it could definitely be a solid choice depending on what you want to build.&lt;&#x2F;p&gt;
&lt;p&gt;Iced employs &lt;strong&gt;Elm Architecture&lt;&#x2F;strong&gt;, which is an architectural pattern for building UIs. You define the state of UI components, and only updates it when there are events. Events are often emitted by user interactions such as mouse clicks.
In your code, you declare the layout of your app; in Iced these are often widgets wrapped in grid containers in the &lt;code&gt;view()&lt;&#x2F;code&gt; method. You also define state changing events in a &lt;code&gt;Message&lt;&#x2F;code&gt; enum and &lt;code&gt;update()&lt;&#x2F;code&gt;. Then you can already run the app! The framework will handle the rendering based on your states and events.
Initially I wondered if there’s any critical feature missing in Iced to build my app, but I thought I could switch to egui and start over. Interestingly, that moment never came; whenever I got stuck I was able to find the necessary information on &lt;strong&gt;GitHub&lt;&#x2F;strong&gt; or in the &lt;strong&gt;Discord community&lt;&#x2F;strong&gt;. I also referred to how other people are building their codebase (e.g &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;squidowl&#x2F;halloy&quot;&gt;Halloy&lt;&#x2F;a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;GyulyVGC&#x2F;sniffnet&quot;&gt;Sniffnet&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dynamic-caching&quot;&gt;Dynamic Caching&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#dynamic-caching&quot; 
    aria-label=&quot;Anchor link for: dynamic-caching&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;vs_cache0.gif&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;Loading images from disk before every rendering (&lt;strong&gt;synchronous image loading&lt;&#x2F;strong&gt;) is slow, so I implemented a &lt;strong&gt;dynamic image caching&lt;&#x2F;strong&gt; mechanism. Since I was looking to make a slider UI similar to emulsion where the user navigates through a list of images left and right, I adopted the &lt;strong&gt;sliding window&lt;&#x2F;strong&gt; type cache (a.k.a. ring cache) that slides along with the currently displayed image.
Structure-wise this is just an array of cached images; the current displayed image is stored at the center element, and you have &lt;strong&gt;N pre-loaded images&lt;&#x2F;strong&gt; towards the left and right end of the cache. When the user presses a navigation key (left or right), the app renders the prev&#x2F;next cached image from it and then loads a new image asynchronously, inserting it at the beginning or end of the cache to prepare for the next rendering.&lt;&#x2F;p&gt;
&lt;p&gt;This resulted in much faster rendering overall. Initially, I noticed some stuttering and lags on Ubuntu with larger images (e.g. 4K), while my MacBook Pro with Apple M1 chip didn’t show the same issue. This was likely due to the M1 chip’s &lt;strong&gt;unified memory architecture&lt;&#x2F;strong&gt;, whereas my Ubuntu desktop has a discrete GPU, introducing overhead when uploading images to GPU memory. Since Iced uses the &lt;strong&gt;wgpu&lt;&#x2F;strong&gt; backend by default, even with image caching on CPU memory, images still had to be uploaded to the GPU on each render.&lt;&#x2F;p&gt;
&lt;p&gt;This had been a major bottleneck, but I recently resolved it by implementing a &lt;strong&gt;custom event loop&lt;&#x2F;strong&gt; and &lt;strong&gt;GPU-side image caching&lt;&#x2F;strong&gt;. The app now stores a set of &lt;code&gt;wgpu::Texture&lt;&#x2F;code&gt; objects in GPU memory, allowing images to be rendered instantly. It currently achieves around &lt;strong&gt;8-10 FPS&lt;&#x2F;strong&gt; when navigating through a directory of &lt;strong&gt;10MB 4K images&lt;&#x2F;strong&gt;. However, this approach introduced a new challenge: high memory usage when handling large images. To address this, I plan to explore using compressed texture formats such as BC7 or ASTC.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#what-i-learned&quot; 
    aria-label=&quot;Anchor link for: what-i-learned&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This is also my first solid &lt;strong&gt;open-source project&lt;&#x2F;strong&gt;, and I&#x27;m learning day by day. Here are some points I learned by working on the project so far.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-don-t-be-afraid-to-fork&quot;&gt;1. Don&#x27;t Be Afraid to Fork&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#1-don-t-be-afraid-to-fork&quot; 
    aria-label=&quot;Anchor link for: 1-don-t-be-afraid-to-fork&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Many open-source Rust projects are still &lt;strong&gt;young and experimental&lt;&#x2F;strong&gt; (including Iced), and you might encounter issues more frequently compared to frameworks written in other languages. To address them sometimes it&#x27;s just easier to &lt;strong&gt;fork their repo&lt;&#x2F;strong&gt; and modify the code by yourself. For example, when I was implementing a feature that detects a file drop (via drag-and-drop) from user, I realized that obtaining the cursor position upon file drop wasn’t supported by Iced because winit (the windowing library Iced uses) didn’t support this. There was an ongoing PR that is yet to be merged, but I really needed to implement this feature so I forked winit to achieve this.&lt;&#x2F;p&gt;
&lt;p&gt;This turned out to be a good decision, because this PR was never merged until a year later. Open-source is a gift and sometimes you can’t really expect the maintainers to add the exact feature you want. You could &lt;strong&gt;save a lot of time&lt;&#x2F;strong&gt; by a changing a few lines in the framework you use to achieve the thing you want.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-refactoring-in-rust-easy-and-hard&quot;&gt;2. Refactoring in Rust: Easy and Hard&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#2-refactoring-in-rust-easy-and-hard&quot; 
    aria-label=&quot;Anchor link for: 2-refactoring-in-rust-easy-and-hard&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;As a &lt;strong&gt;Rust noob&lt;&#x2F;strong&gt; coming from Python, I feel two things;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It can be &lt;strong&gt;tedious to please the compiler&lt;&#x2F;strong&gt; because Rust is a pretty strict language; it’s type-driven and sometimes you need to fix 30 errors just to change the type of a field in your Struct, making it more  time consuming for prototyping. I also find it painful to fix borrow checking errors during refactoring because Im still not used to it.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;but once it compiles, the code almost always works.&lt;&#x2F;strong&gt; For example, there was a time when I struggled a lot to refactor a set of functions while avoiding &lt;strong&gt;borrow checker errors&lt;&#x2F;strong&gt; in my codebase. I just threw them all to ChatGPT and it returned really weird looking blocks, but it did compile and work. This is a great experience as a Python user, as I usually encounter a bunch of runtime errors every time I do a big refactoring in Python. I’m still figuring this out, but I think you can be a lot more adventurous during refactoring, and keep your code clean.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;3-utilize-sandbox-projects&quot;&gt;3. Utilize Sandbox Projects&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#3-utilize-sandbox-projects&quot; 
    aria-label=&quot;Anchor link for: 3-utilize-sandbox-projects&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;As the codebase grows, it can become &lt;strong&gt;increasingly time-consuming&lt;&#x2F;strong&gt; to change the base structure of your app. You decided to make a breaking change, change lots of lines and by the time you have fixed all of your compiler errors you realize that your new design doesn’t really work. This happened a lot in my case, and I learned to create a &lt;strong&gt;small sandbox project&lt;&#x2F;strong&gt; that has the same fundamental code structure but with a lot of dummy data. You can &lt;strong&gt;focus on refactoring&lt;&#x2F;strong&gt; the core design of your skeleton project this way. Working with a smaller project to test your ideas is also good when you work with AI agents, because their performance tends to deteriorate if the length of your prompt is too long.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;takeaways&quot;&gt;Takeaways&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#takeaways&quot; 
    aria-label=&quot;Anchor link for: takeaways&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Building things in &lt;strong&gt;Rust&lt;&#x2F;strong&gt; comes with its challenges, but overall this has been a &lt;strong&gt;very enjoyable project&lt;&#x2F;strong&gt; and I’m having a lot of fun working on it. Here’s my impression of Rust and Iced so far:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rust is not scary&lt;&#x2F;strong&gt;, even if you’re from Python&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Iced is developer-friendly&lt;&#x2F;strong&gt; and great for prototyping&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Iced is a solid choice&lt;&#x2F;strong&gt; for high-performance image rendering and complex layouts&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I plan to keep improving the app, and add features like &lt;strong&gt;visualizing object detection datasets&lt;&#x2F;strong&gt;. Thank you for reading this post! If you’re curious about the resulting app, here’s a link to the repo: &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;viewskater&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;viewskater&lt;&#x2F;a&gt;. Also, here&#x27;s the demo video:&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;
    &lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;eSkMOStVaTs?si=cVtjPZs7MWtSXzts&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; 
    style=&quot;border:0;position:absolute;top:0;left:0;height:100%;width:100%;&quot; 
    allow=&quot;accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;br &#x2F;&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Decouple layout calculations from rendering</title>
		<published>2025-03-08T00:00:00+00:00</published>
		<updated>2025-03-08T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/20250308/" type="text/html"/>
		<id>https://ggando.com/til/20250308/</id>
		<content type="html">&lt;p&gt;So, in the new version of my app, I use iced&#x27;s custom shader widget to render images. I&#x27;ve been using a rendering pipeline and shader that directly calculate vertices of images since I&#x27;d like to keep the aspect ratio of images when user resizes them. I had a function updating vertices and screen rect buffer like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; update_vertices&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-storage&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, device&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Device&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, bounds_relative&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;)) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (x, y, width, height)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; bounds_relative;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; left&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; right&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; width)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; top&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; y;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; bottom&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; height);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; vertices&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 16&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        left, bottom,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Bottom-left&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        right, bottom,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Bottom-right&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        right, top,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Top-right&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        left, top,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Top-left&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;    self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;vertex_buffer &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; device&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create_buffer_init&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;util&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BufferInitDescriptor&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        label&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Some&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Quad Vertex Buffer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        contents&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; bytemuck&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cast_slice&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;vertices),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        usage&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BufferUsages&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;VERTEX&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;BufferUsages&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;COPY_DST&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    });&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;pub fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; update_screen_uniforms&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    queue&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Queue&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    image_dimensions&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    shader_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    bounds_relative&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; debug&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; false&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_size&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_dimensions&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_dimensions&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; as&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; f32&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; vertices&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;vertices;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (_left, bottom, _right, _top)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (vertices[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;], vertices[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;], vertices[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;2&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;], vertices[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Compute aspect ratios&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_aspect&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_height;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_aspect&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_height;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Calculate scale factors - the key is to use the SMALLER dimension to maintain aspect ratio&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (scale_x, scale_y, fit_mode)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; = if&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_aspect&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_aspect {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Image is wider than container - fit width&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scale&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_width;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        (scale, scale,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;FIT_WIDTH&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; else&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Image is taller than container - fit height&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scale&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_height;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        (scale, scale,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;FIT_HEIGHT&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Apply scaling to get final dimensions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scaled_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scale_x;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scaled_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; image_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scale_y;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Calculate the scale factors relative to the container size&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; final_scale_x&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scaled_width&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_width;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; final_scale_y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scaled_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_height;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Calculate the vertical gap that needs to be distributed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; gap_y&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; shader_height&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; scaled_height;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Calculate offset to center the scaled image vertically&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Fine-tune the vertical offset with a correction factor to match Image widget&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; The bottom + 1.0 term accounts for asymmetric NDC space&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; offset_correction&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.001&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Fine-tuning parameter (may need adjustment)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; offset_y_ndc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (bottom&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 1.0&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; -&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; final_scale_y)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 2.0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; +&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; offset_correction;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; screen_rect_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        final_scale_x,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;      &#x2F;&#x2F; Scale X &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        final_scale_y,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;      &#x2F;&#x2F; Scale Y&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;        0.0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;                &#x2F;&#x2F; Offset X (centered horizontally)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        offset_y_ndc,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;       &#x2F;&#x2F; Offset Y to center vertically&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Update screen rect buffer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    queue&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;write_buffer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;screen_rect_buffer,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;        0&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;        bytemuck&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;cast_slice&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;screen_rect_data),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    );&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;However, I noticed that the rendered image would &quot;jiggle&quot; slightly when resizing the window. At first, I assumed the layout math was off. But it turned out to be a deeper issue with how the layout and rendering were coupled.&lt;&#x2F;p&gt;
&lt;p&gt;Calculating the screen rect buffer at the shader level can be fragile. For example, I was using NDC-space vertex coordinates to calculate uniforms like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;let vertices = self.vertices;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;let (_left, bottom, _right, _top) = (vertices[0], vertices[1], vertices[2], vertices[3]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;let offset_y_ndc = (bottom + 1.0) * (1.0 - final_scale_y) &#x2F; 2.0 + offset_correction;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I did need this to make the screen uniforms work correctly, but notice that NDC coordinates are in the range [-1.0, 1.0]. This means that even a tiny floating-point error (like 0.001) can shift the image by several pixels, especially on high-resolution screens.&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;941ad326-20cf-4d34-a259-337f8594ccdc?autoplay=false&amp;loop=false&amp;muted=false&amp;preload=false&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;
&lt;p&gt;Therefore, I decided to decouple layout calculations from rendering calculations like iced does in their widgets. I just pre-calculate the layout bounds in layout(), and then just render the shader full screen within the layout bounds. This way, the shader side doesn&#x27;t have to worry about the layout calculations, and the layout calculations are only done once in layout(). It became much more smooth as you can see in the video below!&lt;&#x2F;p&gt;
&lt;div style=&quot;position:relative;padding-top:56.25%;&quot;&gt;&lt;iframe src=&quot;https:&#x2F;&#x2F;iframe.mediadelivery.net&#x2F;embed&#x2F;399279&#x2F;1664ce36-45b6-4619-ac27-0efca09c886f?autoplay=true&amp;loop=false&amp;muted=false&amp;preload=true&amp;responsive=true&quot; loading=&quot;lazy&quot; style=&quot;border:0;position:absolute;top:0;height:100%;width:100%;&quot; allow=&quot;accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;&#x2F;iframe&gt;&lt;&#x2F;div&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>iced&#x27;s event loop is faster</title>
		<published>2025-03-01T00:00:00+00:00</published>
		<updated>2025-03-01T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/20250301/" type="text/html"/>
		<id>https://ggando.com/til/20250301/</id>
		<content type="html">&lt;p&gt;I spent the last week debugging the performance bottlneck of the wgpu integration version of my app. At first, I thought it was rendfering or state updates, but those parts only took 20~30ms, which should have given me at least 30FPS, but when I measured FPS it was less than 15 and very slow.&lt;&#x2F;p&gt;
&lt;p&gt;I burned through 500 API calls on Cursor debugging this (lol), but it turns out that &lt;strong&gt;the bottleneck was the event loop itself&lt;&#x2F;strong&gt;. I noticed weird gaps between the end of &lt;code&gt;window_event()&lt;&#x2F;code&gt; and the start of next event. What had really been bothering me was that the previous version of my app ran way faster on macOS (MBP with M1 chip) using iced&#x27;s event loop, but the wgpu-integrated version doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;So I ended up hypothesizing that iced&#x27;s event loop (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&#x2F;blob&#x2F;0.13.1&#x2F;winit&#x2F;src&#x2F;program.rs&quot;&gt;iced_winit::program&lt;&#x2F;a&gt;) must be faster than winit&#x27;s default one. I tested this theory by generating a new event loop with Claude 3.7, that adapts iced&#x27;s event loop. It can be roughly summarized like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; std&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;mpsc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Receiver&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Sender&lt;&#x2F;span&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; winit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; WindowEvent&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    event_loop&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ControlFlow&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; EventLoop&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; EventLoopProxy&lt;&#x2F;span&gt;&lt;span&gt;},&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;WindowBuilder&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;enum&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Control&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    ChangeFlow&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ControlFlow&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    Exit&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event_loop&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; EventLoop&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;with_user_event&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; proxy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; EventLoopProxy&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;()&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event_loop&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create_proxy&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Create communication channels for event handling&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (event_sender, event_receiver)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Sender&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;, ()&amp;gt;&amp;gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Receiver&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;, ()&amp;gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mpsc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (control_sender, control_receiver)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Sender&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Control&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Receiver&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Control&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mpsc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; WindowBuilder&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;build&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;event_loop)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Failed to create window&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    event_loop&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;move |&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;event, _, control_flow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;control_flow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; ControlFlow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Poll&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Send events to the queue instead of handling them immediately&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type&quot;&gt;        if let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(_)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event_sender&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;send&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;to_static&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            return&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Exit if sender is dropped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Process control messages asynchronously&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type&quot;&gt;        while let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(control)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; control_receiver&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;try_recv&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; control {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                Control&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ChangeFlow&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(flow)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;control_flow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; flow,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                Control&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Exit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt; *&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;control_flow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; ControlFlow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Exit&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Handle events from queue (non-blocking)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type&quot;&gt;        while let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(event)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event_receiver&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;try_recv&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;            match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                Event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;WindowEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; { event,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ..&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt; match&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; event {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                    WindowEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;CloseRequested&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;                        control_sender&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Control&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Exit&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;ok&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                    WindowEvent&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Resized&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(size)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                        println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Window resized to: {:?}&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, size);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;                    _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                Event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;RedrawRequested&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(_)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;                    println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Redraw triggered&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;                _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; Request a redraw when needed (avoids redundant frames)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        window&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;request_redraw&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    });&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I was able to roughly reproduce the performance of the previous version on my MBP, which is &lt;strong&gt;40~50 FPS&lt;&#x2F;strong&gt; when rendering relevatively smaller images (~1080p). I uploaded a short comparison video on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;gtgando&#x2F;status&#x2F;1896092935743291609&quot;&gt;X&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I haven&#x27;t fully understood how this works yet, but the key difference seems to be how this loop handles events asynchronously, while the winit&#x27;s loop handles them synchronously. You can observe this in this part:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (event_sender, event_receiver)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Sender&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;, ()&amp;gt;&amp;gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Receiver&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;_&lt;&#x2F;span&gt;&lt;span&gt;, ()&amp;gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mpsc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; (control_sender, control_receiver)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Sender&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Control&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Receiver&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Control&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; mpsc&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;channel&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This setup queues events in a channel (&lt;code&gt;mpsc::channel()&lt;&#x2F;code&gt;), allowing them to be handled separately from the main event loop. The &lt;code&gt;event_sender&lt;&#x2F;code&gt; pushes incoming events into a queue, while the &lt;code&gt;event_receiver&lt;&#x2F;code&gt; pulls them for processing without blocking the loop.&lt;&#x2F;p&gt;
&lt;p&gt;While debugging this, I also noticed that sometimes winit produces a flood of &lt;code&gt;CursorMoved&lt;&#x2F;code&gt; events, and every time this happens, the app processes them one by one, triggering a re-render for each event. I think this setup keeps the app responsive by throwing those spammy events into an async channel instead of blocking the loop. If you&#x27;re building a wgpu + winit app, this might help you speed things up too. Here&#x27;s &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;viewskater&#x2F;blob&#x2F;atlas&#x2F;src&#x2F;main.rs&quot;&gt;the link to the full event loop&lt;&#x2F;a&gt; I used in my main.rs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&#x2F;blob&#x2F;81ca3d2a223d62fbb48b93dcea5409f6212605fa&#x2F;winit&#x2F;src&#x2F;program.rs&quot;&gt;iced&#x2F;winit&#x2F;src
&#x2F;program.rs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;viewskater&#x2F;blob&#x2F;atlas&#x2F;src&#x2F;main.rs&quot;&gt;my main.rs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>How to load fonts in wgpu integration with iced</title>
		<published>2025-02-21T00:00:00+00:00</published>
		<updated>2025-02-21T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/wgpu-font/" type="text/html"/>
		<id>https://ggando.com/til/wgpu-font/</id>
		<content type="html">&lt;p&gt;I currently use the wgpu integration setup for my image viewer app and needed to load custom fonts (including an icon font) without using a Compositor. In iced 0.13.1, you usually set your fonts via &lt;code&gt;Settings&lt;&#x2F;code&gt; and pass it like &lt;code&gt;application(...).settings(your_settings)&lt;&#x2F;code&gt;. I found this part in &lt;code&gt;iced_winit::program:run_action()&lt;&#x2F;code&gt;, and it seems like it is the &lt;code&gt;Compositor&lt;&#x2F;code&gt; that loads fonts in the regular iced setup:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Action&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;LoadFont&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; { bytes, channel }&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-storage z-type&quot;&gt;    if let&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Some&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(compositor)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; compositor {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;        &#x2F;&#x2F; TODO: Error handling (?)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;        compositor&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;load_font&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(bytes&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;());&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; channel&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;send&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span&gt;(()));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;iced&#x2F;graphics&#x2F;src&#x2F;compositor.rs&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&#x2F;&#x2F;&#x2F; Loads a font from its bytes.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; load_font&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-storage&quot;&gt;&amp;amp;mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt; self&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;, font&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Cow&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;static&lt;&#x2F;span&gt;&lt;span&gt;, [&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;]&amp;gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    crate::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;text&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;font_system&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;write&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Write to font system&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;load_font&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(font);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Compositor is not accessible in the wgpu integration example because we directly use &lt;code&gt;Engine&lt;&#x2F;code&gt; to render things, but it turns out you can directly access the FontSystem like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; std&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;borrow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Cow&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; iced_wgpu&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;graphics&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;text&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;font_system;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; register_font_manually&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(font_data&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;static&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;]) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    use&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; std&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;sync&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;RwLockWriteGuard&lt;&#x2F;span&gt;&lt;span&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Get a mutable reference to the font system&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;    let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; font_system&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; font_system&lt;&#x2F;span&gt;&lt;span&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type z-storage&quot;&gt;    let mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; font_system_guard&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; RwLockWriteGuard&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;&amp;lt;_&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; font_system&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;write&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;        .&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Failed to acquire font system lock&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;    &#x2F;&#x2F; Load the font into the global font system&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;    font_system_guard&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;load_font&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Cow&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;Borrowed&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;(font_data));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and call it after the Engine creation in Self::Loading() block:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;let&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt; engine&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; Engine&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;::&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;new&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;    &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;adapter,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;device,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;queue, format,&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; None&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;engine&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;create_image_cache&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;device);&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Manually create image cache&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&#x2F;&#x2F; Manually register fonts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;register_font_manually&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;include_bytes!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;..&#x2F;assets&#x2F;fonts&#x2F;viewskater-fonts.ttf&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;register_font_manually&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;include_bytes!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;..&#x2F;assets&#x2F;fonts&#x2F;Iosevka-Regular-ascii.ttf&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;register_font_manually&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;include_bytes!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;..&#x2F;assets&#x2F;fonts&#x2F;Roboto-Regular.ttf&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now you can use icon fonts like before!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;links&quot;&gt;Links&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&#x2F;blob&#x2F;81ca3d2a223d62fbb48b93dcea5409f6212605fa&#x2F;winit&#x2F;src&#x2F;program.rs#L1536&quot;&gt;iced&#x2F;winit&#x2F;src
&#x2F;program.rs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&#x2F;blob&#x2F;ffc412d6b7f8009c783715c021fc36780f26db36&#x2F;runtime&#x2F;src&#x2F;font.rs#L11&quot;&gt;runtime&#x2F;src&#x2F;font.rs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&#x2F;blob&#x2F;81ca3d2a223d62fbb48b93dcea5409f6212605fa&#x2F;graphics&#x2F;src&#x2F;compositor.rs#L66&quot;&gt;graphics&#x2F;src&#x2F;compositor.rs&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Second TIL</title>
		<published>2025-02-20T00:00:00+00:00</published>
		<updated>2025-02-20T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/til-second/" type="text/html"/>
		<id>https://ggando.com/til/til-second/</id>
		<content type="html">&lt;p&gt;Placeholdder second note to debug html.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&#x2F;&#x2F; some code here...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;println!&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Hello World&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Trying the TIL section</title>
		<published>2025-02-19T00:00:00+00:00</published>
		<updated>2025-02-19T00:00:00+00:00</updated>
		<link href="https://ggando.com/til/til-first/" type="text/html"/>
		<id>https://ggando.com/til/til-first/</id>
		<content type="html">&lt;p&gt;I usually do tech journaling on my local note taking app, but I realized that sometimes those notes are worth sharing. It feels too technical to publish on platforms like X and the volume tends to be too small for blog posts, so I made a designated section just to dump them. If this doesn&#x27;t work I&#x27;ll go back to just making blogs, but we&#x27;ll see.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Why AP Confused Me in IR (And How I Finally Understood It)</title>
		<published>2025-01-30T00:00:00+00:00</published>
		<updated>2025-01-30T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/map/" type="text/html"/>
		<id>https://ggando.com/blog/map/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;map_datauni1_1280.jpg&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h2 id=&quot;context&quot;&gt;Context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#context&quot; 
    aria-label=&quot;Anchor link for: context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ve been working on an image search system for a client, which retrieves most similar images from a database given a user&#x27;s query image. I usually use &lt;strong&gt;top-$K$ accuracy metrics&lt;&#x2F;strong&gt; when prototyping image similarity apps, where I check if any relevant image appears in the top-$K$ recommendations or whether the query&#x27;s class label is included in the unique top-$K$ categories of the retrieved result.&lt;&#x2F;p&gt;
&lt;p&gt;These metrics have been useful, but as the model’s performance improved on the current datasets, we found that top-$K$ accuracy had become a bit too lenient. So, we decided to include &lt;strong&gt;mean average precision (mAP)&lt;&#x2F;strong&gt; in the evaluation to gain a more nuanced understanding of the model’s retrieval performance.&lt;&#x2F;p&gt;
&lt;p&gt;I knew that the AP in object detection (OD) is defined as the area under the precision-recall curve (AUC-PR), but the definition of AP seemed a bit different in the context of information retrieval (IR). After doing some research, I realized that there’s a key difference: &lt;strong&gt;AP in IR approximates AUC-PR without interpolated precision&lt;&#x2F;strong&gt;, whereas &lt;strong&gt;AP in OD is explicitly defined as AUC-PR with interpolated precision&lt;&#x2F;strong&gt;. Additionally, retrieval and recommender systems are often evaluated with AP@K that has multiple variants, and it was painful to figure them all out.&lt;&#x2F;p&gt;
&lt;p&gt;The mathematical terms used in AP and AP@K definitions were also confusing because many online resources gloss over whether these terms are calculated based on the entire dataset or just the retrieved sequence. I found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.evidentlyai.com&#x2F;ranking-metrics&#x2F;mean-average-precision-map&quot;&gt;Evidently AI&#x27;s article&lt;&#x2F;a&gt; to be quite comprehensive, but &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;builtin.com&#x2F;articles&#x2F;mean-average-precision&quot;&gt;this article on builtin.com&lt;&#x2F;a&gt; is possibly the best resource as it clearly describes how AP is defined in both IR and OD contexts. While these posts already cover the topic well, I&#x27;d like to add a few nuances that really helped me understand these metrics better.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;situation&quot;&gt;Situation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#situation&quot; 
    aria-label=&quot;Anchor link for: situation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;We have an IR or some kind of search system (items can be anything like documents or images) that retrives $N$ most relevant items, given a user&#x27;s query $q$ (e.g., a single image, a set of search keywords, etc.). We want to evaluate how good the returned results are for this particular query, and also know how well it performed for $M$ queries on average. The system is not perfect and items within this retrieved list can actually be &quot;relevant&quot; or &quot;not relevant&quot; for the user.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a quick visualization to clarify the key symbols used throughout this post:
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;notation2.jpg&quot; alt=&quot;img0&quot; width=&quot;600&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Notation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$N$: Total number of retrieved items&lt;&#x2F;li&gt;
&lt;li&gt;$K$: Cut-off rank for top-$K$ evaluation (user-specified)&lt;&#x2F;li&gt;
&lt;li&gt;$R$: Total number of relevant items in the dataset (for a given query)&lt;&#x2F;li&gt;
&lt;li&gt;$R_K$: Number of relevant items within the top-$K$ retrieved results&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;In the above visualization, $K = 5$.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;precision-and-recall-in-ir&quot;&gt;Precision and Recall in IR&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#precision-and-recall-in-ir&quot; 
    aria-label=&quot;Anchor link for: precision-and-recall-in-ir&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;p-vs-p-k&quot;&gt;P vs P@K&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#p-vs-p-k&quot; 
    aria-label=&quot;Anchor link for: p-vs-p-k&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;First, we need to understand AP because mAP is just an averaged value of APs. To do so, we start by understanding precision in IR.
Precision in IR is similar to OD and the denominator is the total number of retrieved (predicted) items. $Precision@K$ or $\text{P@}k$ is just a precision at a fixed cutoff point ($K$). $K$ is user-specified (something you decide).&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x: auto; white-space: nowrap;&quot;&gt;
$$
\text{Precision} = \frac{\text{Number of relevant items retrieved}}{N}
$$
&lt;p&gt;$$
\begin{aligned}
\text{Precision@K} &amp;amp;= \frac{\text{Number of relevant items in top } K}{K} \\
&amp;amp;= \frac{R_K}{K}
\end{aligned}
$$&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
In this post, I&#x27;ll just use $\text{P@}k$ from here as it&#x27;s shorter and convienient.
&lt;h3 id=&quot;recall-vs-recall-k&quot;&gt;Recall vs recall@K&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#recall-vs-recall-k&quot; 
    aria-label=&quot;Anchor link for: recall-vs-recall-k&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;d also like to mention these before explaining AP. They are basically just regular recalls but $Recall@K$ has a cutoff point (K) for calculating the numerator term. Note that the denominator is both the total number of relevant items in the entire dataset for a particular query.&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x: auto; white-space: nowrap;&quot;&gt;
$$
\text{Recall} = \frac{\text{Number of relevant items retrieved}}{R}
$$
&lt;p&gt;$$
\begin{aligned}
\text{Recall@K} &amp;amp;= \frac{\text{Number of relevant items in top } K}{R} \\
&amp;amp;= \frac{ R_K }{R}
\end{aligned}
$$&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;h2 id=&quot;ap-vs-ap-k-in-ir&quot;&gt;AP vs AP@K in IR&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ap-vs-ap-k-in-ir&quot; 
    aria-label=&quot;Anchor link for: ap-vs-ap-k-in-ir&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This was the most confusing part for me. I&#x27;d like to describe two types of AP here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;📊 &lt;strong&gt;$AP$&lt;&#x2F;strong&gt;: Average Precision calculated over the &lt;strong&gt;retrieved ranked list&lt;&#x2F;strong&gt; for query $q$. The result is &lt;strong&gt;normalized over the total number of relevant items in the dataset&lt;&#x2F;strong&gt; ($R$) . It is alsmot equal to the AUC-PR without interpolated precisions.&lt;&#x2F;li&gt;
&lt;li&gt;🎯 &lt;strong&gt;$AP@K$&lt;&#x2F;strong&gt;: Average Precision that only considers &lt;strong&gt;top-$K$ recommendations&lt;&#x2F;strong&gt; for query $q$ in the retrieved sequence. &lt;strong&gt;The normalization factor depends on specific definitions&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I&#x27;ll explain the standard AP first, and then AP@K.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;average-precision-ap&quot;&gt;Average Precision (AP)&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#average-precision-ap&quot; 
    aria-label=&quot;Anchor link for: average-precision-ap&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;AP is sometimes denoted as $AP(q)$, but people just call it as &quot;AP&quot; because it&#x27;s usually obvious that we compute AP for a single query in IR. Considering this, here is the definition that I find intuitive:&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x: auto; white-space: nowrap;&quot;&gt;
$$
AP = \frac{1}{R} \sum_{k=1}^{N} \text{P@}k \times rel(k)
$$
&lt;&#x2F;div&gt;
&lt;p&gt;Where:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$N$: Total number of items in the retrieved ranked list.
&lt;ul&gt;
&lt;li&gt;In large-scale systems, this usually refers to the number of &lt;strong&gt;retrieved items&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;In small-scale systems (e.g., when computing a full similarity matrix), $N$ can be the size of &lt;strong&gt;entire dataset&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;$R$: Total number of relevant items in the dataset (for the current query)&lt;&#x2F;li&gt;
&lt;li&gt;$\text{P@}k$: Precision at rank $k$&lt;&#x2F;li&gt;
&lt;li&gt;$rel(k)$: Indicator function (1 if the item at rank $k$ is relevant, 0 otherwise)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Unlike precision, the denominator of AP is the &lt;strong&gt;actual total number of relevant items in the entire dataset&lt;&#x2F;strong&gt; $R$, not just the number of relevant items retrieved. For example, if there are &lt;strong&gt;10,000 relevant items&lt;&#x2F;strong&gt; in your dataset for the query and your retrieval system returns &lt;strong&gt;1,000 items&lt;&#x2F;strong&gt;, you iterate over those 1,000 items to compute AP. However, you still use &lt;strong&gt;10,000 as $R$&lt;&#x2F;strong&gt; in the denominator to reflect the fact that there are many relevant items the system might have missed.&lt;&#x2F;p&gt;
&lt;p&gt;Both precision and AP measure how accurate the predictions are in identifying relevant items, but AP goes a step further by considering &lt;strong&gt;the order of relevant items in the retrieved sequence&lt;&#x2F;strong&gt;, rewarding relevant items placed towards the top. This focus on ranking is crucial in IR, since users expect the most relevant results to appear at the top of the list. Precision only calculates the proportion of relevant items retrieved, and it does not account for their positions in the ranking. As a result, &lt;strong&gt;precision cannot evaluate how well a system prioritizes relevant items in higher ranks&lt;&#x2F;strong&gt;, which is often key to a good user experience.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;ap_img.jpg&quot; alt=&quot;img0&quot; width=&quot;600&quot; style=&quot;display: block; margin: auto;&quot;&gt;
&lt;h3 id=&quot;average-precision-at-k-ap-k&quot;&gt;Average Precision at K (AP@K)&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#average-precision-at-k-ap-k&quot; 
    aria-label=&quot;Anchor link for: average-precision-at-k-ap-k&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;In practice, we often care more about the retrieval performance in the &lt;strong&gt;top-$K$&lt;&#x2F;strong&gt; results rather than across the entire retrieved list. This is where &lt;strong&gt;AP@K&lt;&#x2F;strong&gt; comes in—it &lt;strong&gt;only evaluates the top-$K$ retrieved items&lt;&#x2F;strong&gt;, focusing on how well the system ranks relevant items at the top Interestingly, there are multiple variants for AP@K definitions. Here, I list two of the most common ones:&lt;&#x2F;p&gt;
&lt;p&gt;$$
AP_1@K = \frac{1}{R_K} \sum_{k=1}^{K} \text{P@}k \times rel(k)
$$&lt;&#x2F;p&gt;
&lt;p&gt;$$
AP_2@K = \frac{1}{\min(K, R)} \sum_{k=1}^{K} \text{P@}k \times rel(k)
$$&lt;&#x2F;p&gt;
&lt;p&gt;Where:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$K$: Cut-off rank for top-$K$ evaluation (user-specified; i.e., top-10)&lt;&#x2F;li&gt;
&lt;li&gt;$R_K$: Total number of relevant items within the top-$K$ retrieved results&lt;&#x2F;li&gt;
&lt;li&gt;$R$: Total number of relevant items in the dataset (for the current query)&lt;&#x2F;li&gt;
&lt;li&gt;$\text{P@}k$: Precision at rank $k$ (same as AP)&lt;&#x2F;li&gt;
&lt;li&gt;$rel(k)$: Indicator function (1 if the item at rank $k$ is relevant, 0 otherwise) (same as AP)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For clarity, I&#x27;ll refer to the first definition as &lt;strong&gt;$AP_1@K$&lt;&#x2F;strong&gt; and the second one as &lt;strong&gt;$AP_2@K$&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Based on my research, $AP_2@K$ seems to be more commonly used, especially in research papers. I have seen a moderate number of online articles using $AP_1@K$, but in academic research, $AP_2@K$ appears to be more commonly used. For example, I found the following papers defining AP@K using the $AP_2@K$ formula:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;arindam.cs.illinois.edu&#x2F;papers&#x2F;15&#x2F;collab-ranking.pdf&quot;&gt;Deep Learning Based Dense Retrieval: A Comparative Study&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;pdf&#x2F;2410.20315v1&quot;&gt;Collaborative Ranking with a Push at the Top&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;html&#x2F;2411.02537v2&quot;&gt;Inquire: A Natural World Text-to-Image Retrieval Benchmark&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The &quot;INQUIRE&quot; paper explains in more detail how different research works have defined AP@K. If you&#x27;re curious, I recommend checking out Appendix G of that paper. Their explanation suggests that $AP_2@K$ originates from the TREC book (2005), but unfortunately I don’t have access to it.&lt;&#x2F;p&gt;
&lt;p&gt;Another example is the implementation of &lt;code&gt;RetrievalMAP()&lt;&#x2F;code&gt; in torchmetrics (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Lightning-AI&#x2F;torchmetrics&#x2F;blob&#x2F;master&#x2F;src&#x2F;torchmetrics&#x2F;functional&#x2F;retrieval&#x2F;average_precision.py#L22&quot;&gt;source&lt;&#x2F;a&gt;) using this definition. That said, there are also a number of sources that only mention the $AP_1@K$ definition, such as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;towards-data-science&#x2F;mean-average-precision-at-k-map-k-clearly-explained-538d8e032d2&quot;&gt;this TDS article&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.evidentlyai.com&#x2F;ranking-metrics&#x2F;mean-average-precision-map&quot;&gt;the Evidently AI&#x27;s article&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Ultimately, which AP@K definition to use is a user choice, and I&#x27;ll describe the intuitions behind these below to help you decide.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ap-1-k&quot;&gt;$AP_1@K$&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ap-1-k&quot; 
    aria-label=&quot;Anchor link for: ap-1-k&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;This definition of &lt;strong&gt;AP@K&lt;&#x2F;strong&gt; normalizes the sum of precision scores by $R_K$, which is the number of relevant items within the top-$K$ results. Intuitively, this version measures &lt;strong&gt;how precise the top-$K$ retrieval result was&lt;&#x2F;strong&gt;, as it’s just an average of Precision@K. If no relevant items are found in the top-$K$, $R_K = 0$, and AP@K is undefined (though often treated as zero in implementations).&lt;&#x2F;p&gt;
&lt;p&gt;However, the issue with this definition is that it’s a purely precision-oriented metric, and &lt;strong&gt;the system can cheat by placing only a few relevant items within the top-$K$&lt;&#x2F;strong&gt;. For example, if the system retrieves just a single relevant item at rank 1 and misses all other relevant items, AP@K is still 1.0. This makes it less strict compared to $AP_2@K$, which explicitly penalizes for missing relevant items outside the top-$K$.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ap-2-k&quot;&gt;$AP_2@K$&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ap-2-k&quot; 
    aria-label=&quot;Anchor link for: ap-2-k&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Unlike $AP_1@K$, this version &lt;strong&gt;penalizes missing relevant items if they were not retrieved within the top-$K$&lt;&#x2F;strong&gt;, since the denominator is $\min(K, R)$.&lt;&#x2F;p&gt;
&lt;p&gt;This means that it rewards the system for ranking more relevant items in the top-$K$, even if they&#x27;re placed towards the bottom in that range. For example, consider a case where $R=2$ and the system retrieves this sequence:
$$[1, 0, 0, 0, 0]$$&lt;&#x2F;p&gt;
&lt;p&gt;$AP_1@K = 1.0$, but $AP_2@K = 0.5$ since one relevant item is still missing.&lt;&#x2F;p&gt;
&lt;p&gt;Now, if the system promotes another relevant item into the top-5 like this:
$$[1, 0, 0, 0, 1]$$&lt;&#x2F;p&gt;
&lt;p&gt;$AP_1@K = AP_2@K = 0.7$.&lt;&#x2F;p&gt;
&lt;p&gt;This ranking is more desirable than the previous one, but we can see that $AP_1@K$ decreases by doing so, while $AP_2@K$ properly rewards the improved ranking.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ap-k-examples&quot;&gt;AP@K Examples&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ap-k-examples&quot; 
    aria-label=&quot;Anchor link for: ap-k-examples&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;To understand how these AP@K definitions behave more, let&#x27;s consider these two examples:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ex1-ap-1-k-and-ap-2-k-are-the-same&quot;&gt;Ex1: $AP_1@K$ and $AP_2@K$ are the same&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ex1-ap-1-k-and-ap-2-k-are-the-same&quot; 
    aria-label=&quot;Anchor link for: ex1-ap-1-k-and-ap-2-k-are-the-same&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;A system returns a sequence: $$[0, 0, 1, 0, 1, 0, 0, 1, 0, 0]$$ for your query (1 = relevant, 0 = not relevant) where the actual total number of relevant item is 3 (all relevant items retrieved). To build some intuition I made a simple animated example here:&lt;&#x2F;p&gt;
&lt;!--&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;map0.gif&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p class=&quot;break-words overflow-hidden&quot;&gt;
This visualization was generated with a Python library `manim`. Source code: [Gist Link](https:&#x2F;&#x2F;gist.github.com&#x2F;ggand0&#x2F;9f5230ae384796244136ea089da8d5e4)
&lt;&#x2F;p&gt;
--&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;apk_ex2.gif&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;As for the summation part, we simply compute a precision at each relevant item and take the average of those:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;precision = TP &#x2F; (TP + FP)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;precision@3 = 1 &#x2F; (1 + 2) = 0.333&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;precision@5 = 2 &#x2F; (2 + 3)  = 0.4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;precision@8 = 3 &#x2F; (3 + 5) = 0.375&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this particular example, the relevant items were scattered across ranks and two of the items were ranked at 5 and 8 even though they&#x27;re relevant, resulting in a rather lower AP@10 of 0.369.&lt;&#x2F;p&gt;
&lt;p&gt;This example shows that &lt;strong&gt;when all relevant items are retrieved within the top-$K$, $AP_1@K$ and $AP_2@K$ are identical.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ex2-ap-1-k-is-1-0-ap-2-k-is-low&quot;&gt;Ex2: $AP_1@K$ is 1.0, $AP_2@K$ is low&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#ex2-ap-1-k-is-1-0-ap-2-k-is-low&quot; 
    aria-label=&quot;Anchor link for: ex2-ap-1-k-is-1-0-ap-2-k-is-low&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;A system returns a sequence: $$[1, 1, 0, 0, 0, 0, 0, 0, 0, 0]$$ where the actual total number of relevant item is &lt;strong&gt;6&lt;&#x2F;strong&gt; (&lt;strong&gt;4 relevant items were missed&lt;&#x2F;strong&gt;).&lt;&#x2F;p&gt;
&lt;!--&lt;img src=&quot;&#x2F;vid&#x2F;apk_ex1.gif&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;--&gt;
&lt;p&gt;$$
\begin{aligned}
AP_1@K &amp;amp;= \frac{\frac{1}{1} + \frac{2}{2}}{2} = 1.0 \\
AP_2@K &amp;amp;= \frac{\frac{1}{1} + \frac{2}{2}}{6} = 0.333
\end{aligned}
$$&lt;&#x2F;p&gt;
&lt;p&gt;Here, $AP_1@K$ is 1.0 because it rewards placing just a few relevant items toward the top, even though 4 relevant items were completely missed. If you are only interested in evaluating the precision-like aspect of system performance, this may be fine. However, $AP_2@K$ penalizes the system for failing to retrieve all relevant items and results in a much lower value.&lt;&#x2F;p&gt;
&lt;p&gt;In most recommender systems, &lt;strong&gt;we ideally want to fill the top ranks with as many relevant items as possible.&lt;&#x2F;strong&gt; For this example, an ideal ranking would look like this:
$$[1, 1, 1, 1, 1, 1, 0, 0, 0, 0]$$&lt;&#x2F;p&gt;
&lt;p&gt;In this case, $AP_1@K$ does not distinguish between these two cases and remains 1.0, while $AP_2@K$ properly rewards this by achieving a perfect 1.0 score.&lt;&#x2F;p&gt;
&lt;p&gt;This highlights why &lt;strong&gt;$AP_2@K$ is often preferred in practice&lt;&#x2F;strong&gt;, as it better reflects real-world scenarios where we care about &lt;strong&gt;not just ranking precision, but also about missing relevant items (recall-like aspect)&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;map-in-ir&quot;&gt;mAP in IR&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#map-in-ir&quot; 
    aria-label=&quot;Anchor link for: map-in-ir&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The mean Average Precision (mAP) is simply the AP values averaged over multiple queries. If we have $M$ queries, each with its own AP, the formulae for mAP and mAP@K are:&lt;&#x2F;p&gt;
&lt;div style=&quot;overflow-x: auto; white-space: nowrap;&quot;&gt;
$$
\text{mAP} = \frac{1}{M} \sum_{j=1}^{M} \text{AP}_j
$$
&lt;p&gt;$$
\text{mAP@K} = \frac{1}{M} \sum_{j=1}^{M} \text{AP@K}_j
$$&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;Where:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;$M$ is the total number of queries,&lt;&#x2F;li&gt;
&lt;li&gt;$\text{AP}_j$ is the Average Precision for the $j$-th query,&lt;&#x2F;li&gt;
&lt;li&gt;$\text{AP@K}_j$ is the Average Precision at cutoff $K$ for the $j$-th query.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;$M$ is just a user-specified parameter, so for example you can compute mAP for all the query items in your test set or per-category mAPs depending on your dataset.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;relation-to-pr-curve&quot;&gt;Relation to PR Curve&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#relation-to-pr-curve&quot; 
    aria-label=&quot;Anchor link for: relation-to-pr-curve&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;precision-recall-curve&quot;&gt;Precision-Recall Curve&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#precision-recall-curve&quot; 
    aria-label=&quot;Anchor link for: precision-recall-curve&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;AP (not AP@K) in IR is almost equal to AUC-PR without interpolated precision&lt;&#x2F;strong&gt; because it’s calculated as a sum of precisions at every relevant item. The actual AUC-PR may differ slightly since it&#x27;s defined as the continuous integration over the entire precision-recall curve.&lt;&#x2F;p&gt;
&lt;p&gt;In contrast, &lt;strong&gt;AP in OD is explicitly defined as the AUC-PR with interpolated precision&lt;&#x2F;strong&gt;, where the PR curve is smoothed to be non-decreasing. This interpolation stabilizes the evaluation, making AP in OD exactly equal to the area under the interpolated PR curve.&lt;&#x2F;p&gt;
&lt;p&gt;Here, I provide an example of how you can compute the points to plot a PR curve using the same example I used earlier. You need pairs of (Precision@K, Recall) points at every rank:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Rank&lt;&#x2F;th&gt;&lt;th&gt;Rel&lt;&#x2F;th&gt;&lt;th&gt;Precision@K&lt;&#x2F;th&gt;&lt;th&gt;Recall&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;0&#x2F;3 (0.00)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;0&#x2F;3 (0.00)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;1&#x2F;3 = 0.33&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;1&#x2F;3 (0.333)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;1&#x2F;3 (0.333)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;2&#x2F;5 = 0.40&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;2&#x2F;3 (0.667)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;2&#x2F;3 (0.667)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;2&#x2F;3 (0.667)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;3&#x2F;8 = 0.375&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;3&#x2F;3 (1.0)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;3&#x2F;3 (1.0)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;&#x2F;td&gt;&lt;td&gt;0&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;td&gt;3&#x2F;3 (1.0)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;NOTE: &lt;u&gt;This assumes an oversimplified situation where the total number of relevant items in the database is 3.&lt;&#x2F;u&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;AP = 0.369&lt;&#x2F;strong&gt;, and PR curve can be plotted like this:&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr1.png&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;The PR curve is defined by (precision, recall) points, with recall increasing at each relevant item in the retrieved sequence. The PR curve can include additional (precision, recall) points even after recall reaches 1.0, reflecting further retrieved items. However, recall remains constant while precision declines as more non-relevant items are included. This point is discussed in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;scikit-learn&#x2F;scikit-learn&#x2F;issues&#x2F;23213&quot;&gt;this scikit-learn github issue&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;starting-point-of-pr-curve&quot;&gt;Starting point of PR curve&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#starting-point-of-pr-curve&quot; 
    aria-label=&quot;Anchor link for: starting-point-of-pr-curve&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;In IR textbooks and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=yjCMEjoc_ZI&quot;&gt;Victor&#x27;s lecture&lt;&#x2F;a&gt;, the starting point of PR curve is either (0, 0) or (1&#x2F;R, 1). However, people often seem to add (0, 1) as the initial point for convention. For example, sklearn does this and &lt;code&gt;precision_recall_curve&lt;&#x2F;code&gt; returns a PR curve including this point (related discussion &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;scikit-learn&#x2F;scikit-learn&#x2F;issues&#x2F;4223&quot;&gt;here&lt;&#x2F;a&gt;). This appears to be another &quot;user-specified&quot; point.&lt;&#x2F;p&gt;
&lt;p&gt;I think it&#x27;s fine to include this anchor point on Y axis for visualization, as it makes plots look nicer and it&#x27;s easier to compare different curves this way, but if you need to be mathematically rigorous in IR context I&#x27;d avoid including this point. I also found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;classeval.wordpress.com&#x2F;introduction&#x2F;introduction-to-the-precision-recall-plot&#x2F;&quot;&gt;this post&lt;&#x2F;a&gt; mentioning how to plot the first point in the classification context.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;interpolated-precision&quot;&gt;Interpolated precision&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#interpolated-precision&quot; 
    aria-label=&quot;Anchor link for: interpolated-precision&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;This is also commonly known, but in IR evaluation, we often interpolate precision values to smooth out fluctuations (the sawtooth shape) in standard PR curves. This allows for a clearer comparison of PR curves across different systems. However, note that AP does not approximate the area under the PR curve with interpolated precision.
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr2.png&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;realistic-example&quot;&gt;Realistic example&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#realistic-example&quot; 
    aria-label=&quot;Anchor link for: realistic-example&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;In the first example, the system was able to retrieve all the relevant items. What if it fails to retrieve all relevant items? Let&#x27;s imagine another example when the system returns the sequence:
$$[1,1,0,1,0,1,0,0,0,0,0,1,0,0]$$ where 1 = relevant, 0 = not, and the number of relevant items is 8. In this case, the recall of PR curve will &lt;strong&gt;not reach 1.0&lt;&#x2F;strong&gt; since the system only retrieved 5 relevant items. &lt;strong&gt;AP = 0.479&lt;&#x2F;strong&gt; and the PR curve will look like this:
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr3.png&quot; alt=&quot;img0&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;It is &lt;strong&gt;normal&lt;&#x2F;strong&gt; for the recall of PR curve to not reach 1.0 in the context of retrieval or detection. For classification tasks, the recall of PR curve always reachs 1.0  because the model’s purpose is to classify all given test samples. Most resources don&#x27;t even mention this, but I found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.slideshare.net&#x2F;slideshow&#x2F;performance-evaluation-of-ir-models&#x2F;229729988#10&quot;&gt;a great Slideshare presentation&lt;&#x2F;a&gt; that clearly explains this in IR context (refer to page 10 and 13). I also found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;ridgerunai.medium.com&#x2F;machine-learning-mean-average-precision-map-and-other-object-detection-metrics-45267507a904&quot;&gt;this medium post&lt;&#x2F;a&gt; explaining PR curve in OD context with a practical example where the curve doesn&#x27;t reach recall=1.0.&lt;&#x2F;p&gt;
&lt;p&gt;Also, notice that we only iterate over the retrieved ranked list to plot the PR curve just like the calculation of AP, rather than going through all the relevant items in the dataset.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sensitivity-to-precision&quot;&gt;Sensitivity to precision&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#sensitivity-to-precision&quot; 
    aria-label=&quot;Anchor link for: sensitivity-to-precision&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;So far, I&#x27;ve used simple examples where the number of total relevant items is a single digit. To better visualize the PR curve dynamics, let’s scale things up with a case where the sequence length is 1,000 and the number of relevant items is 500 to understand this curve more. We assume that all 500 relevant items were retrieved within this sequence. This is an unrealistic situation in retrieval, but it helps illustrate the intuition behind PR curves when more data points are available.&lt;&#x2F;p&gt;
&lt;p&gt;To see how the distribution of relevant items affects the PR curve, we generate a set of sequences with varying early precisions. Specifically, we incrementally add more relevant items within the first 250 positions across different sequences. To ensure all the curves start from the same point, I fixed the first 10 retrieved items as all &quot;relevant&quot; in every sequence. Here’s the result:&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr4_fixed10_early_auc.png&quot; alt=&quot;img1&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;I also highlighted the AUC of curves with a pale color. Notice that the last point of each curve is at (1.0, 0.5) since all sequences have retrieved 250 out of 500 relevant items by the 1,000th item, meaning Precision@1000 = 0.5 at that point. We can observe that the PR curve is &lt;strong&gt;very sensitive to the precision of early retrieved items&lt;&#x2F;strong&gt;. The more relevant items you miss early on, the sharper the drop in the AP curve corresponding to them.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s another example where we fix the first 50 items instead of 10. The part of the curve that drops to 0.6 remains the same, but it looks like you can still achieve a good AP if you start regaining high precision relatively early.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr4_fixed50_early_auc.png&quot; alt=&quot;img1&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;What if you retrieve more the relevant items towards the end? Here&#x27;s another plot where we vary the number of relevant items retrieved from 0 to 250 in the last 250 items. The remaining relevant items are placed randomly in the rest of the sequence (0~750). In this case, the fewer relevant items you retrieve at the end, the higher the AUC, because that means more relevant items were retrieved earlier.&lt;&#x2F;p&gt;
&lt;p&gt;Relevant items retrieved later in the sequence still contribute to the AUC, but their impact is significantly less compared to relevant items retrieved in higher ranks.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr4_fixed10_late_auc.png&quot; alt=&quot;img1&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;h3 id=&quot;sensitivity-to-recall&quot;&gt;Sensitivity to recall&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#sensitivity-to-recall&quot; 
    aria-label=&quot;Anchor link for: sensitivity-to-recall&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Lastly, let’s see how the PR curves change when we vary the number of relevant items retrieved out of 500 actual relevant items. I plotted 5 curves with the number of relevant items in each sequence set to:
&lt;code&gt;[100, 200, ..., 500]&lt;&#x2F;code&gt;.
Here&#x27;s the result:&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;pr4_recall_new.png&quot; alt=&quot;img2&quot; width=&quot;500&quot; style=&quot;display: block; margin: auto;&quot;&#x2F;&gt;
&lt;p&gt;We can see that &lt;strong&gt;a higher recall&lt;&#x2F;strong&gt; of the retrieved sequence &lt;strong&gt;pushes the curves to the right&lt;&#x2F;strong&gt;. This makes sense; without relevant items in the retrieved sequence, there are fewer precision values to contribute to the AUC. The more relevant items you retrieve, the more the curve fills out, increasing both recall and the area under the curve.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;historical-context&quot;&gt;Historical context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#historical-context&quot; 
    aria-label=&quot;Anchor link for: historical-context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I also did a bit of research on how this metric became popular among IR researchers, to learn more on why we use mAPs in the first place. I found &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;downloads.webis.de&#x2F;lecturenotes&#x2F;information-retrieval&#x2F;unit-en-ir-organization.pdf&quot;&gt;this lecturenote&lt;&#x2F;a&gt; providing free PDF links for well-known IR books. I explored old IR books with GPT a little bit, and I think it comes down to these 3 reasons:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Comparing PR curves visually is difficult.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Single-value overall metric is easier to compare.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Standardization&lt;&#x2F;strong&gt;: AP became a standard metric for evaluating IR systems, especially in early benchmarks like the TREC competitions.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;It seems that PR curves were already known and used in evaluation of IR systems in 60s-70s, before the introduction of mAP@K and other averaging metrics. They were pioneered by the works of researchers such as Gerard Salton and C. J. van Rijsbergen.&lt;&#x2F;p&gt;
&lt;p&gt;Salton seems to already recognize issues of PR curve in his earlier works. For example, I found this paragraph in &quot;Introduction to Modern Information Retrieval&quot; third edition, which based on the original Salton&#x27;s book, which seems to touch the point 1:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Recall-precision graphs, such as that of Fig. 5-2b, have been criticized because a number of parameters are obscured. … Another problem arises when a number of curves such as the one of Fig. 5-2b, each valid for a single query, must be processed to obtain average performance characteristics for many user queries.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Page 159 of the manning&#x27;s book also mentions point 2:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Examining the entire precision-recall curve is very informative, but there is often a desire to boil this information down to a few numbers, or perhaps even a single number.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;From here, evaluation metrics seemed to shift towards &lt;strong&gt;&quot;averaging techniques&quot;&lt;&#x2F;strong&gt; such as AP@K and &quot;Precision at 11 Recall Levels&quot; (another way to average precisions at fixed recall levels) in 70s-80s. However, it was &lt;strong&gt;the TREC (Text REtrieval Conference)&lt;&#x2F;strong&gt; in the 90s that particulary accelerated the adoption of this metric.&lt;&#x2F;p&gt;
&lt;p&gt;Before TREC, researchers used different datasets and metrics, making it hard to make fair comparision between IR systems. TREC introduced shared datasets and standard evaluation protocols, similar to what we see in many ML benchmarks today. I think that&#x27;s why the TREC and metrics used in it became popular from that point on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-initial-misunderstandings&quot;&gt;My initial misunderstandings&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#my-initial-misunderstandings&quot; 
    aria-label=&quot;Anchor link for: my-initial-misunderstandings&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;As someone who is still a noob in IR, &lt;strong&gt;whether we&#x27;re considering the entire database or just the retrieved sequence&lt;&#x2F;strong&gt; was the source of my confusion. For example, I wondered why we divide by 3, not the actual total number of relevant items in the earlier example at first, since the definitions of AP available online all say the denominator of AP is &quot;the total number of relevant items&quot;. It turns out that I was just looking at the visualization of AP@K while refering to the definition of AP.&lt;&#x2F;p&gt;
&lt;p&gt;At some point, I also misunderstood $N$ in the AP formula as the size of the entire dataset, not the length of the retrieved sequence. but this also turned out to be incorrect as per the definition. &lt;strong&gt;AP is defined only for a ranked list of retrieved items.&lt;&#x2F;strong&gt; As for the relevant items that weren’t retrieved in the database, we can’t define precisions for those items because they weren’t assigned a rank. This is analogous to the fact that we can&#x27;t compute precision for non-existent bounding boxes that weren&#x27;t predicted (it’s obvious when we think of it this way!). However, $N$ &lt;strong&gt;can be&lt;&#x2F;strong&gt; the size of full dataset &lt;strong&gt;if&lt;&#x2F;strong&gt; you retrieve the entire dataset by computing a full similarity matrix, for example. Very confusing!&lt;&#x2F;p&gt;
&lt;p&gt;It was also confusing to see how the Precision-Recall (PR) curve in most resources always reaches a recall of 1.0, even though in practice, it&#x27;s common for the recall of a retrieved sequence to &lt;strong&gt;not&lt;&#x2F;strong&gt; reach 1.0. After observing PR curves that always reach a recall of 1.0, I mistakenly thought that the definition of AP was normalized &lt;em&gt;locally&lt;&#x2F;em&gt; within the retrieved sequence, using $R_K$ as the denominator.&lt;&#x2F;p&gt;
&lt;p&gt;While this is the case for $AP@K$  in some definitions, it&#x27;s incorrect for &lt;strong&gt;AP&lt;&#x2F;strong&gt; because recall is defined based on the total number of relevant items in the dataset $R$, not just the retrieved items. Both AP and the Precision-Recall curve are calculated using the points within the retrieved sequence, but they are &lt;strong&gt;normalized by $R$&lt;&#x2F;strong&gt; to account for all relevant items, including those that were not retrieved.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;u&gt;Depending on your prompts, even GPT agents seem to mix these things up. So, I recommend reviewing the official definitions of these metrics in IR textbooks to confirm the standard explanations.&lt;&#x2F;u&gt; For example, page 166 of the Manning&#x27;s book &quot;An Introduction to Information Retrieval&quot; (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;nlp.stanford.edu&#x2F;IR-book&#x2F;pdf&#x2F;irbookprint.pdf&quot;&gt;PDF link&lt;&#x2F;a&gt;) defines mAP without &lt;code&gt;@K&lt;&#x2F;code&gt;. Another good definition of AP is page 70-71 of the Büttcher et al.&#x27;s book (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;mitmecsept.wordpress.com&#x2F;wp-content&#x2F;uploads&#x2F;2018&#x2F;05&#x2F;stefan-bc3bcttcher-charles-l-a-clarke-gordon-v-cormack-information-retrieval-implementing-and-evaluating-search-engines-2010-mit.pdf&quot;&gt;PDF link&lt;&#x2F;a&gt;).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#conclusion&quot; 
    aria-label=&quot;Anchor link for: conclusion&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AP&lt;&#x2F;strong&gt; sums precisions over at all recall levels.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AP@K&lt;&#x2F;strong&gt; only considers the top-$K$ results and has multiple variants depending on the normalization factor.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;AP in IR&lt;&#x2F;strong&gt; approximates the AUC of the precision-recall curve.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;mAP in IR&lt;&#x2F;strong&gt; is just the average of APs for multiple queries.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Now that I understand mAP, I feel this is a must-have metric for evaluating search systems. It provides a single-value representation of retrieval quality that balances both precision &amp;amp; recall aspects, making it easier to compare different models.&lt;&#x2F;p&gt;
&lt;p&gt;Personally, I’ve decided to adopt $AP@K$ definition with the $\min(K, R)$ term ($AP_2@K$) for evaluation in my projects, as it better accounts for missing relevant items. Hopefully this post gave you a bit more clarity on common IR metrics. Thanks for reading!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#references&quot; 
    aria-label=&quot;Anchor link for: references&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h4 id=&quot;books-and-papers&quot;&gt;&lt;strong&gt;Books and papers:&lt;&#x2F;strong&gt;&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#books-and-papers&quot; 
    aria-label=&quot;Anchor link for: books-and-papers&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Evaluation in Information Retrieval&lt;&#x2F;em&gt; — C.D. Manning, P. Raghavan, H. Schütze (the Manning&#x27;s book) [&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www-nlp.stanford.edu&#x2F;IR-book&#x2F;&quot;&gt;PDF chapterlinks&lt;&#x2F;a&gt;]&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Learning to Rank for Information Retrieval&lt;&#x2F;em&gt; — T.-Y. Liu [&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;didawiki.di.unipi.it&#x2F;lib&#x2F;exe&#x2F;fetch.php&#x2F;magistraleinformatica&#x2F;ir&#x2F;ir13&#x2F;1_-_learning_to_rank.pdf&quot;&gt;PDF link&lt;&#x2F;a&gt;]&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Information Retrieval: Implementing and Evaluating Search Engines&lt;&#x2F;em&gt; — C.L.A. Clarke, G. Cormack, S. Büttcher [&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;mitmecsept.wordpress.com&#x2F;wp-content&#x2F;uploads&#x2F;2018&#x2F;05&#x2F;stefan-bc3bcttcher-charles-l-a-clarke-gordon-v-cormack-information-retrieval-implementing-and-evaluating-search-engines-2010-mit.pdf&quot;&gt;PDF link&lt;&#x2F;a&gt;]&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Information Retrieval&lt;&#x2F;em&gt; — C.J. van Rijsbergen [&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;openlib.org&#x2F;home&#x2F;krichel&#x2F;courses&#x2F;lis618&#x2F;readings&#x2F;rijsbergen79_infor_retriev.pdf&quot;&gt;PDF link&lt;&#x2F;a&gt;]&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;Introduction to Modern Information Retrieval&lt;&#x2F;em&gt; — G. Salton, M.J. McGill, &lt;em&gt;Computer Science Series&lt;&#x2F;em&gt;, 1983 [&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;sigir.org&#x2F;resources&#x2F;museum&#x2F;#:~:text=Introduction%20to%20Modern%20Information%20Retrieval&quot;&gt;PDF chapter links&lt;&#x2F;a&gt;]
(You need to text search within the page)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;em&gt;INFORMATION STORAGE AND RETRIEVAL&lt;&#x2F;em&gt; — G. Salton, 1974 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;files.eric.ed.gov&#x2F;fulltext&#x2F;ED101718.pdf&quot;&gt;[PDF link]&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;html&#x2F;2411.02537v2&quot;&gt;Inquire: A Natural World Text-to-Image Retrieval Benchmark&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h4 id=&quot;articles-online-resources&quot;&gt;&lt;strong&gt;Articles &amp;amp; Online Resources:&lt;&#x2F;strong&gt;&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#articles-online-resources&quot; 
    aria-label=&quot;Anchor link for: articles-online-resources&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;The Smart environment for retrieval system evaluation&lt;&#x2F;em&gt; — G. Salton, 2008 &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;sigir.org&#x2F;files&#x2F;museum&#x2F;Information_Retrieval_Experiment&#x2F;pdfs&#x2F;p316-salton.pdf&quot;&gt;[PDF link]&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;ciir-publications.cs.umass.edu&#x2F;getpdf.php?id=1066&quot;&gt;The History of Information Retrieval Research&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;www.slideshare.net&#x2F;slideshow&#x2F;performance-evaluation-of-ir-models&#x2F;229729988#13&quot;&gt;Performance Evaluation of Information Retrieval Systems&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;towards-data-science&#x2F;mean-average-precision-at-k-map-k-clearly-explained-538d8e032d2&quot;&gt;Mean Average Precision at K (MAP@K) Clearly Explained&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;jonathan-hui.medium.com&#x2F;map-mean-average-precision-for-object-detection-45c121a31173&quot;&gt;mAP (Mean Average Precision) for Object Detection&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;trec.nist.gov&#x2F;pubs&#x2F;trec16&#x2F;appendices&#x2F;measures.pdf&quot;&gt;TREC-16 evaluation measuer appendix&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;downloads.webis.de&#x2F;lecturenotes&#x2F;information-retrieval&#x2F;unit-en-ir-organization.pdf&quot;&gt;Information Retrieval lecture notes&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;web.stanford.edu&#x2F;class&#x2F;cs276&#x2F;&quot;&gt;Stanford CS 276 &#x2F; LING 286 Syllabus&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;sanchom.wordpress.com&#x2F;2011&#x2F;09&#x2F;01&#x2F;precision-recall&#x2F;&quot;&gt;It’s a bird… it’s a plane… it… depends on your classifier’s threshold&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;ridgerunai.medium.com&#x2F;machine-learning-mean-average-precision-map-and-other-object-detection-metrics-45267507a904&quot;&gt;Mean Average Precision (mAP) and other Object Detection Metrics&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;sdsawtelle.github.io&#x2F;blog&#x2F;output&#x2F;mean-average-precision-MAP-for-recommender-systems.html#Common-Variations-on-AP-Formula&quot;&gt;Mean Average Precision (MAP) For Recommender Systems&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Switching to Zola from Nuxt.js</title>
		<published>2025-01-23T00:00:00+00:00</published>
		<updated>2025-01-23T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/zola/" type="text/html"/>
		<id>https://ggando.com/blog/zola/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;zola.webp&quot; alt=&quot;img0&quot; width=&quot;500&quot;&#x2F;&gt;
&lt;h2 id=&quot;context&quot;&gt;Context&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#context&quot; 
    aria-label=&quot;Anchor link for: context&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I initially created my previous website sometime with Nuxt.js in 2023. It served its purpose showing my bio and past projects, but I found it somewhat cumbersome for a static site like this. I also felt a mental barrier when it came to updating my website or writing new blog posts. In fact, I wrote my only blog post in February 2024 and never wrote anything since then. lol! As a result, I decided to migrate to a Rust-based static site generator called Zola, more compact framework that lets you focus on what I&#x27;d like to achieve with this website; documenting and sharing what I’ve learned on the internet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#how-it-works&quot; 
    aria-label=&quot;Anchor link for: how-it-works&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;set-up-the-project&quot;&gt;Set up the project&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#set-up-the-project&quot; 
    aria-label=&quot;Anchor link for: set-up-the-project&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I opted to fork the design from the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;isunjn&#x2F;serene&quot;&gt;serene&lt;&#x2F;a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;TeaDrinkingProgrammer&#x2F;tranquil&quot;&gt;tranquil&lt;&#x2F;a&gt; themes. Once you chose a theme, setting up a Zola project was fairly easy; the serene theme provides a comprehensive &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;isunjn&#x2F;serene&#x2F;blob&#x2F;latest&#x2F;USAGE.md&quot;&gt;usage doc&lt;&#x2F;a&gt; and I just needed to follow their instructions. Here&#x27;s a summary of the steps::&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;zola init &amp;lt;proj_name&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cd  &amp;lt;proj_name&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;git submodule add -b latest https:&#x2F;&#x2F;github.com&#x2F;isunjn&#x2F;serene.git themes&#x2F;serene&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Copy &lt;code&gt;themes&#x2F;serene&#x2F;config.example.toml&lt;&#x2F;code&gt; to the top level of your project, then rename it to &lt;code&gt;config.toml&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Edit config.toml to suit your needs&lt;&#x2F;li&gt;
&lt;li&gt;Set up subdirectories under content and create the required files:&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;content&#x2F;posts&#x2F;_index.md&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;content&#x2F;projects&#x2F;_index.md&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;content&#x2F;projects&#x2F;data.toml&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;After running a local server by &lt;code&gt;zola serve&lt;&#x2F;code&gt;, you should be able to see the skelton website in your browser. A neat thing about Zola is that you can override the templates or asset files (js &#x2F; css) used in the theme with your custom files. For example, if you put your custom home.html under .&#x2F;templates , Zola will prioritize that file over the corresponding one in the theme.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;style-with-tailwind-css&quot;&gt;Style with tailwind css&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#style-with-tailwind-css&quot; 
    aria-label=&quot;Anchor link for: style-with-tailwind-css&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;From here, I wanted to improve the styles of serene using Tailwind CSS. Incorporating tailwind was straightforward:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm install &amp;lt;packages&amp;gt;&lt;&#x2F;code&gt; or create this &lt;code&gt;package.json&lt;&#x2F;code&gt; and then &lt;code&gt;npm install&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;dependencies&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;@tailwindcss&#x2F;typography&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^0.5.16&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;autoprefixer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^10.4.20&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;parcel&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^2.13.3&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;postcss&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^8.5.1&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;postcss-cli&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^11.0.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;tailwindcss&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^3.4.17&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;  &amp;quot;scripts&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;    &amp;quot;build:css&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;npx tailwindcss -i .&#x2F;static&#x2F;css&#x2F;tailwind.css -o .&#x2F;static&#x2F;css&#x2F;main.css --minify&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The build:css  command is for building the css in deployment phase later.&lt;&#x2F;p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Generate tailwind.config.js
Run the command npx tailwindcss init . Here&#x27;s my tailwind.config.js I generated with ChatGPT:&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;javascript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt;&#x2F;**&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type&quot;&gt; @type&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; {import(&amp;#39;tailwindcss&amp;#39;).Config}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment&quot;&gt; *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-storage z-type&quot;&gt;const&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant&quot;&gt; defaultTheme&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;tailwindcss&#x2F;defaultTheme&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;module&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;exports&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  content: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;.&#x2F;templates&#x2F;**&#x2F;*.html&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Zola HTML templates&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;    &amp;quot;.&#x2F;content&#x2F;**&#x2F;*.md&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;     &#x2F;&#x2F; Markdown files in Zola&amp;#39;s content directory&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  darkMode:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;class&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; &#x2F;&#x2F; Enable class-based dark mode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  theme: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    extend: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      colors: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        bg:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--bg-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        text:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--text-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;primary&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--primary-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;primary-pale&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--primary-pale-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;blockquote&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--blockquote-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;text-pale&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--text-pale-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;inline-code-bg&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--inline-code-bg-color)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      backgroundColor: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;        &amp;#39;dark-mode&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;var(--dark-mode-img-brightness)&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fontFamily: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      sans: [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Inter Variable&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; ...&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;defaultTheme.fontFamily.sans],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  },&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  plugins: [&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;tailwindcss&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;@tailwindcss&#x2F;typography&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;    require&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;autoprefixer&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Include Tailwind CSS in your project
I placed &lt;code&gt;tailwind.css&lt;&#x2F;code&gt; at &lt;code&gt;.&#x2F;static&#x2F;css&#x2F;tailwind.css&lt;&#x2F;code&gt;  and compiled it to &lt;code&gt;.&#x2F;static&#x2F;css&#x2F;main.css&lt;&#x2F;code&gt;  with this command: &lt;code&gt;npx tailwindcss -i .&#x2F;static&#x2F;css&#x2F;tailwind.css -o .&#x2F;static&#x2F;css&#x2F;main.css —watch&lt;&#x2F;code&gt;. This automatically rebuilds the css whenever changes are detected.&lt;&#x2F;li&gt;
&lt;li&gt;Override _base.html and include your css
As mentioned earlier, you can override theme templates with your own files. To include my custom css file, I created a new &lt;code&gt;_base.html&lt;&#x2F;code&gt; under &lt;code&gt;.&#x2F;templates&lt;&#x2F;code&gt; directory and added the following line:
&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;&#x2F;css&#x2F;main.css&quot;&amp;gt;&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Style custom templates with Tailwind CSS
Referring to another tailwind based theme tranquil, I just threw all the relevant templates to ChatGPT 4o to apply tailwind styles in my custom templates..and it worked. It took about 4 days to generate and refine custom templates. As I usually don&#x27;t do front-end things, it took a fair amount of effort to achieve the satisfying results, but it&#x27;s amazing how we can use AI to speed up the process these days.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;things-i-got-stuck-on&quot;&gt;Things I got stuck on&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#things-i-got-stuck-on&quot; 
    aria-label=&quot;Anchor link for: things-i-got-stuck-on&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;While the process was mostly straightforward, here are some challenges I faced:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A long URL breaking the mobile layout
In my only existing post, a long URL wasn’t wrapping correctly within the parent element, making it look like there was a weird horizontal gap on the right side. I mistakenly thought it was a tailwind issue so I ended up wasting a few hours troubleshooting with ChatGPT.&lt;&#x2F;li&gt;
&lt;li&gt;Tera not ignoring the commented-out blocks of html:
Zola uses a templating engine called Tera, and we can use its templating syntax to interact with the data in config.toml. I often comment out old code blocks when I make breaking changes. For example, in _base.html, I had this:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;&amp;lt;!--&amp;lt;body class=&amp;quot;{% block page %}{% endblock page%}{% if config.extra.force_theme == &amp;quot;dark&amp;quot; %}dark{% endif %}&amp;quot;&amp;gt;--&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-tag&quot;&gt;body&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity&quot;&gt; class&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;{% block page %}{% endblock page %}bg-bg text-text dark:bg-dark-mode dark:text-white {% if config.extra.force_theme == &amp;#39;dark&amp;#39; %}dark{% endif %}&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Interestingly this results in an error saying &lt;code&gt;Block page is duplicated&lt;&#x2F;code&gt;, because Tera doesn’t ignore commented-out HTML. I suspect this also contributed to another styling issue I had.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deployment&quot;&gt;Deployment&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#deployment&quot; 
    aria-label=&quot;Anchor link for: deployment&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I use Vercel for deploying my personal website and I was able to deploy my Zola site without much trouble. After connecting your github repo, make sure to match the Zola version on Vercel with your local version. In my case it was 0.19.2. Addtionally, I needed to use &lt;code&gt;Node.js 20.x&lt;&#x2F;code&gt; to avoid the error &lt;code&gt;zola: &#x2F;lib64&#x2F;libm.so.6: version  GLIBC_2.29&#x27; not found (required by zola)&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;takeaway&quot;&gt;Takeaway&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#takeaway&quot; 
    aria-label=&quot;Anchor link for: takeaway&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Zola is easy, Zola works&lt;&#x2F;li&gt;
&lt;li&gt;Compatible with Tailwind CSS&lt;&#x2F;li&gt;
&lt;li&gt;Mind the Tera blocks&lt;&#x2F;li&gt;
&lt;li&gt;Utilize ChatGPT 4o&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All in all, I feel pretty good about the new website setup with Zola including how the website looks now. Highly recommended if you&#x27;re hoping to do sometiing similar! If you&#x27;re curious about the code I used for this website, you can check out the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggand0&#x2F;ggando-website&quot;&gt;repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>How to install ROCm 5.7.1 for 7900 XTX</title>
		<published>2024-02-03T00:00:00+00:00</published>
		<updated>2024-02-03T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/rocm0/" type="text/html"/>
		<id>https://ggando.com/blog/rocm0/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;7900xtx.jpg&quot; alt=&quot;img0&quot; width=&quot;500&quot;&#x2F;&gt;
In this post, I&#x27;ll share how I installed ROCm 5.7.1 for the AMD RX 7900 XTX on my machine. I did struggle to make it work, but once you figure out the supported combination of OS&#x2F;kernel&#x2F;ROCm versions, it is a straightforward process. Please note that this is just an example, and you may need to tweak the steps to install ROCm in your environment. The goal of this post is to create a working environment for running PyTorch 2.1.0 built for ROCm 5.6. As far as I tested in this post, the PyTorch built for ROCm 5.6 does work with 5.7.1.
&lt;h2 id=&quot;preparation&quot;&gt;Preparation&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#preparation&quot; 
    aria-label=&quot;Anchor link for: preparation&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;uninstall-the-nvidia-driver&quot;&gt;Uninstall the Nvidia driver&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#uninstall-the-nvidia-driver&quot; 
    aria-label=&quot;Anchor link for: uninstall-the-nvidia-driver&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;If you&#x27;re like me and you&#x27;re switching from an Nvidia card to AMD, you may want to uninstall the current graphics driver first using the command: &lt;code&gt;sudo apt purge nvidia*&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;swap-the-gpu&quot;&gt;Swap the GPU&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#swap-the-gpu&quot; 
    aria-label=&quot;Anchor link for: swap-the-gpu&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Turn off the computer, swap the GPU, and then boot the computer. The easiest way to check if the GPU is detected by the operating system is to view the &#x27;About&#x27; section in Settings:
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;rocm0_0.png&quot; alt=&quot;img1&quot; width=&quot;500&quot;&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Alternatively, you can run the following command:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ lspci -nnk | grep -i vga -A3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2f:00.0 VGA compatible controller [0300]: Advanced Micro Devices, Inc. [AMD&#x2F;ATI] Device [1002:744c] (rev c8)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Subsystem: Sapphire Technology Limited Device [1da2:471e]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Kernel driver in use: amdgpu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Kernel modules: amdgpu&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;upgrade-to-ubuntu-22-04&quot;&gt;Upgrade to Ubuntu 22.04&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#upgrade-to-ubuntu-22-04&quot; 
    aria-label=&quot;Anchor link for: upgrade-to-ubuntu-22-04&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;I had been using Ubuntu 20.04 for quite a while. Initially, I tried installing the AMD driver on 20.04 with a kernel 6.2, but it didn&#x27;t work for me. After some Google searches, I found that some people had success with Ubuntu 22.04, so I decided to upgrade. If you plan to do the same, make sure to fix any GPG key errors on &#x27;apt-get&#x27; first; otherwise, it won&#x27;t let you upgrade. This process took about an hour, if I remember correctly. Don&#x27;t forget to back up your data as well.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo do-release-upgrade&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;After the upgrade your 7900 XTX should work right out of the box without manually installing a new graphics driver, as the OS is already shipped with a built-in AMD driver.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install-the-supported-kernel-kernel-6-2&quot;&gt;Install the supported kernel (kernel 6.2)&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#install-the-supported-kernel-kernel-6-2&quot; 
    aria-label=&quot;Anchor link for: install-the-supported-kernel-kernel-6-2&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This was a tricky step for me. There are two ways to install DKMS &amp;amp;ROCm; using the installation script or the package manager. You can opt for either approach, but I chose the package manager for my preference. Make sure that your OS and kernel versions are supported; you can check the list &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;latest&#x2F;reference&#x2F;system-requirements.html#supported-distributions&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If your kernel version is not supported, you&#x27;ll need to install a compatible version.
In my case, I opted for kernel version 6.2 as it is supported for 7900 XTX. However, I encountered the error &quot;(dkms apport) kernel version not supported&quot; after manually installing the mainline kernel 6.2: &lt;code&gt;6.2.0-060200-generic&lt;&#x2F;code&gt; manually from .deb files. It seems that mainline kernels are not compatible with the AMD kernel driver. To address this, I had to upgrade to the kernel version &lt;code&gt;6.2.0.39-generic&lt;&#x2F;code&gt;. using apt and uninstall the previously installed 6.2.0.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt update&lt;&#x2F;span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; apt search linux-&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;image-&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-language&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;linux-image-unsigned-6.2.0-39-generic&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; # &amp;lt;= 6.2 is in the list&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt install linux-image-unsigned-6.2.0-39-generic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you encounter the same error, you&#x27;ll also want to uninstall the incompatible kernel version, as the installation script or package manager will attempt to install the driver for all the kernels with the version 6.2.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dpkg&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --list&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; linux-image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt purge linux-modules-6.2.0-060200-generic linux-image-unsigned-6.2.0-060200-generic linux-headers-6.2.0-060200-generic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo update-grub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;install-the-amd-dkms-and-rocm-5-7-1&quot;&gt;Install the AMD DKMS and ROCm 5.7.1&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#install-the-amd-dkms-and-rocm-5-7-1&quot; 
    aria-label=&quot;Anchor link for: install-the-amd-dkms-and-rocm-5-7-1&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;First, navigate to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;latest&#x2F;how-to&#x2F;prerequisites.html&quot;&gt;the prerequisites page&lt;&#x2F;a&gt;; Make sure you&#x27;ve installed the kernel headers and development packages, and set up permissions for user groups if you haven&#x27;t already.&lt;&#x2F;p&gt;
&lt;p&gt;If you intend to use the nightly version of PyTorch that supports ROCm 6.0, follow the official instructions from the latest ROCm doc (6.0 as of 01&#x2F;04&#x2F;24). For my use case I needed a stable version so I opted to install ROCm 5.7.1.
Follow the instructions provided in the doc for the desired version of ROCm you want to install. Here&#x27;s the link to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;en&#x2F;docs-5.7.1&#x2F;deploy&#x2F;linux&#x2F;quick_start.html&quot;&gt;the 5.7.1 doc&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Add repositories&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Install DKMS &amp;amp; ROCm&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt install amdgpu-dkms&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sudo apt install rocm-hip-sdk5.7.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For the DKMS installation part, you can follow the steps in either the 5.7 or 6.0 documentation. Initially, I followed the steps from the 6.0 docs and installed the DKMS &amp;amp; ROCm 6.0 only to realize that it&#x27;s not compatible with the latest stable version of PyTorch. Then, I uninstalled the ROCm 6.0, installed 5.7.1 and had no issues afterward. This suggests that the DKMS installed with the latest doc&#x27;s steps was compatible with 5.7.1.&lt;&#x2F;p&gt;
&lt;p&gt;After completing the installation, make sure to follow the steps in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;en&#x2F;docs-5.7.1&#x2F;deploy&#x2F;linux&#x2F;os-native&#x2F;install.html#post-install-actions-and-verification-process&quot;&gt;the post installation section&lt;&#x2F;a&gt;. To confirm correct installation, run the following commands:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; dkms status&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;opt&#x2F;rocm&#x2F;bin&#x2F;rocminfo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; &#x2F;opt&#x2F;rocm&#x2F;bin&#x2F;rocm-smi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that the path to binaries in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;rocm.docs.amd.com&#x2F;projects&#x2F;install-on-linux&#x2F;en&#x2F;latest&#x2F;how-to&#x2F;native-install&#x2F;post-install.html&quot;&gt;the latest doc page&lt;&#x2F;a&gt; is wrong and it&#x27;s &lt;code&gt;&#x2F;opt&#x2F;rocm&#x2F;...&lt;&#x2F;code&gt; or &lt;code&gt;&#x2F;opt&#x2F;rocm-6.0.0&#x2F;...&lt;&#x2F;code&gt;. I think clinfo will only be installed when you installed rocm with opencl option.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install-pytorch-2-1-0&quot;&gt;Install PyTorch 2.1.0&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#install-pytorch-2-1-0&quot; 
    aria-label=&quot;Anchor link for: install-pytorch-2-1-0&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I had success with torch 2.1.0 and torchvision 0.16.0. I usually use both poetry and docker envs for my ML projects and the below is an example setup.&lt;&#x2F;p&gt;
&lt;p&gt;poetry &lt;code&gt;pyproject.toml&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;tool&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;poetry&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;name =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;rocm_test&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;version =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;0.1.0&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;description =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;authors = [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;John Doe&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;readme =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;README.md&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;tool&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;poetry&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;dependencies&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;python =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;gt;=3.9&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;torch = { url =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;https:&#x2F;&#x2F;download.pytorch.org&#x2F;whl&#x2F;rocm5.6&#x2F;torch-2.1.0%2Brocm5.6-cp39-cp39-linux_x86_64.whl&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;torchvision = { url =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;https:&#x2F;&#x2F;download.pytorch.org&#x2F;whl&#x2F;rocm5.6&#x2F;torchvision-0.16.0%2Brocm5.6-cp39-cp39-linux_x86_64.whl&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt; }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;tool&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;poetry&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;group&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;dependencies&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;pytest =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;^7.3.1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;build-system&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;requires = [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;poetry-core&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;build-backend =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;poetry.core.masonry.api&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Dockerfile:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;docker&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;FROM&lt;&#x2F;span&gt;&lt;span&gt; rocm&#x2F;pytorch:latest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;WORKDIR&lt;&#x2F;span&gt;&lt;span&gt; &#x2F;app&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;COPY&lt;&#x2F;span&gt;&lt;span&gt; . .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;RUN&lt;&#x2F;span&gt;&lt;span&gt; pip3 install --upgrade pip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;RUN&lt;&#x2F;span&gt;&lt;span&gt; pip3 install -r requirements.txt # If you have other dependencies&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;RUN&lt;&#x2F;span&gt;&lt;span&gt; pip3 install https:&#x2F;&#x2F;download.pytorch.org&#x2F;whl&#x2F;rocm5.6&#x2F;torch-2.1.0%2Brocm5.6-cp39-cp39-linux_x86_64.whl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;RUN&lt;&#x2F;span&gt;&lt;span&gt; pip3 install https:&#x2F;&#x2F;download.pytorch.org&#x2F;whl&#x2F;rocm5.6&#x2F;torchvision-0.16.0%2Brocm5.6-cp39-cp39-linux_x86_64.whl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To confirm that PyTorch detects your AMD GPU, you can run these in the interpreter. As you can see I&#x27;m getting an NVML warning here but I didn&#x27;t encounter any issues running models on GPU.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Python&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3.9&lt;&#x2F;span&gt;&lt;span&gt;.6 (default, Jun&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 15 2022&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 10&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;7&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;40&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;GCC 9.4&lt;&#x2F;span&gt;&lt;span&gt;.0] on linux&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;help&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;copyright&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;credits&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; or&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;license&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt; for&lt;&#x2F;span&gt;&lt;span&gt; more information.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt; import&lt;&#x2F;span&gt;&lt;span&gt; torch&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-variable&quot;&gt;__version__&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;2.1.0+rocm5.6&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.cuda.is_available()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.cuda.device_count()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;home&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;gota&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;.cache&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;pypoetry&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;virtualenvs&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;rocm&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;test&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-invalid z-illegal&quot;&gt;7WKIdKFi&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;py3.9&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;lib&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;python3.9&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;site&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;packages&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;torch&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span&gt;cuda&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;__init__&lt;&#x2F;span&gt;&lt;span&gt;.py:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;611&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt; UserWarning&lt;&#x2F;span&gt;&lt;span&gt;: Can&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;t initialize NVML&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  warnings.warn(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;Can&amp;#39;t initialize NVML&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.cuda.current_device()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-constant&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.cuda.get_device_name(torch.cuda.current_device())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;Radeon RX 7900 XTX&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span&gt; torch.rand(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span&gt;).to(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;cuda:0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;tensor([[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.6327&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.1133&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.1328&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.8192&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.8875&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.6650&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        [&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;0.6430&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.2727&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; 0.1466&lt;&#x2F;span&gt;&lt;span&gt;]],&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable&quot;&gt; device&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;cuda:0&amp;#39;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Alternatively, you can run this nice script by @josedamico:&lt;&#x2F;p&gt;
&lt;p class=&quot;break-words overflow-hidden&quot;&gt;
  https:&#x2F;&#x2F;gist.github.com&#x2F;damico&#x2F;484f7b0a148a0c5f707054cf9c0a0533
&lt;&#x2F;p&gt;
&lt;h2 id=&quot;run-the-stable-diffusion-webui&quot;&gt;Run the Stable Diffusion WebUI&lt;span class=&quot;not-prose&quot;&gt;
    &lt;a class=&quot;tl-link text-2xl&quot; 
    href=&quot;#run-the-stable-diffusion-webui&quot; 
    aria-label=&quot;Anchor link for: run-the-stable-diffusion-webui&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;span&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;To further confirm that this PyTorch environment works, let&#x27;s run the Stable Diffusion WebUI locally and perform GPU inference.
For instance, you can place the Dockerfile in the project directory and run the env like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; git clone https:&#x2F;&#x2F;github.com&#x2F;AUTOMATIC1111&#x2F;stable-diffusion-webui.git&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; cp Dockerfile stable-diffusion-webui&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; cd stable-diffusion-webui&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; docker build&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -t&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sd-ui:1.0 .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; docker run&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; -it --rm -p&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 7860:7860&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --shm-size&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; 8G&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;--device=&#x2F;dev&#x2F;kfd&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --device=&#x2F;dev&#x2F;dri --group-add=video \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;--ipc=host&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --cap-add=SYS_PTRACE --security-opt&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; seccomp=unconfined&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --volume=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;`&lt;&#x2F;span&gt;&lt;span class=&quot;z-support&quot;&gt;pwd&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-punctuation z-definition z-string&quot;&gt;`:&#x2F;app:rw&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --volume=&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other&quot;&gt;$HOME&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt;&#x2F;dockerx:&#x2F;dockerx&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; sd-ui:1.0 bash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To load models and run the server, I needed to add specific options described in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;AUTOMATIC1111&#x2F;stable-diffusion-webui&#x2F;wiki&#x2F;Install-and-Run-on-AMD-GPUs#running-inside-docker&quot;&gt;their Wiki page&lt;&#x2F;a&gt;. Within the docker container, use launch.py to run the server. Include the --listen option to host it on 0.0.0.0 so that you can access the web UI from the host machine&#x27;s browser.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name&quot;&gt;$&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; python launch.py&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --precision&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; full&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant&quot;&gt; --no-half --listen&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, confirm that you can generate images in the browser. If you&#x27;re curious you may also want to try other cool models to see if they work with this setup.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;rocm0_1.png&quot; alt=&quot;img2&quot; width=&quot;640&quot;&#x2F;&gt;
&lt;p&gt;Now you can leverage this hefty GPU for training your awesome deep learning models. Happy training!&lt;&#x2F;p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Placeholder</title>
		<published>2024-01-01T00:00:00+00:00</published>
		<updated>2024-01-01T00:00:00+00:00</updated>
		<link href="https://ggando.com/blog/first/" type="text/html"/>
		<id>https://ggando.com/blog/first/</id>
		<content type="html">&lt;img src=&quot;https:&#x2F;&#x2F;ggando.b-cdn.net&#x2F;mitvision_640px.jpg&quot; alt=&quot;img0&quot; width=&quot;500&quot;&#x2F;&gt;
&lt;p&gt;Welcome to my blog! This is a placeholder post for debugging.&lt;&#x2F;p&gt;
&lt;p&gt;July 1, 1966 was a historical date when the MIT Summer Vision Project was proposed by Seymour Papert. This project marked a significant moment in the history of computer science and artificial intelligence. It aimed to explore computer vision by developing algorithms capable of understanding visual scenes.&lt;&#x2F;p&gt;
&lt;p&gt;The idea seemed deceptively simple: get a computer to recognize objects in an image. Little did the researchers know, this &quot;summer project&quot; would evolve into a decades-long challenge, laying the groundwork for modern advancements in computer vision and machine learning.&lt;&#x2F;p&gt;
&lt;p&gt;Today, computer vision powers applications from facial recognition and self-driving cars to medical imaging and augmented reality. The vision of those early pioneers continues to inspire researchers and developers worldwide.&lt;&#x2F;p&gt;
&lt;p&gt;Stay tuned for more updates as I build this blog to explore topics like this and beyond!&lt;&#x2F;p&gt;
</content>
	</entry>
</feed>