<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rahul Kumar</title>
    <description>The latest articles on DEV Community by Rahul Kumar (@pctablet505).</description>
    <link>https://dev.to/pctablet505</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4139875%2F9dbf49a4-6a98-413c-9991-cd61063c057c.jpg</url>
      <title>DEV Community: Rahul Kumar</title>
      <link>https://dev.to/pctablet505</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pctablet505"/>
    <language>en</language>
    <item>
      <title>In most open source projects we measured, one person gives most of the first answers</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:35:12 +0000</pubDate>
      <link>https://dev.to/pctablet505/in-most-open-source-projects-we-measured-one-person-gives-most-of-the-first-answers-4h38</link>
      <guid>https://dev.to/pctablet505/in-most-open-source-projects-we-measured-one-person-gives-most-of-the-first-answers-4h38</guid>
      <description>&lt;p&gt;Porchlamp followed 95,078 first pull requests, each from someone who hadn't sent that project a pull request in about six months or more. Projects that lean on one person answer them within a day at least as often as projects that share the job.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One person gives over half of first answers: 79%&lt;/strong&gt; of 2,843 projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merged where one person gives 90% or more: 56%&lt;/strong&gt; of first pull requests in the typical project, against 43% where nobody gives over half&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent at two weeks, then asked: 28%&lt;/strong&gt; got an answer in the next 30 days, against 14% with no comment in the same projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merged, counting every pull request: 32%&lt;/strong&gt; of first pull requests, against 47% for people who had sent one before&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In 1,175 of 2,843 projects, one person gives 90% or more of the first answers
&lt;/h2&gt;

&lt;p&gt;Count who gives the first answer to each outside pull request, and the median project has 3 people giving first answers at all. In 79% of these projects, one person gives more than half of them. Fame barely changes it: among 266 projects with 50,000 stars or more, it's 73%, with a median of 4 people.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlui2jezk7tb6dlcwifi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnlui2jezk7tb6dlcwifi.png" alt="Histogram of projects by the busiest person's share of first answers. The tallest bar by far is 90 to 100%, with 1,175 projects." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only projects with at least 20 first answers we could attribute to a person. Known bots are left out; a few automation accounts that look like people remain.&lt;/p&gt;

&lt;h2&gt;
  
  
  More first pull requests get merged where one person does the answering
&lt;/h2&gt;

&lt;p&gt;In the typical project where one person gives 90% or more of the first answers, 56% of first pull requests get merged. Where nobody gives more than half, it's 43%. Answers within two weeks lean the same way, 75% against 64%, but that gap is smaller and less sure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jyjmzcrf2phr3qc8t9t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9jyjmzcrf2phr3qc8t9t.png" alt="Answered within two weeks and merged, by the busiest person's share of first answers: both rise from the shared band to the 90 to 100% band." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stars and volume don't explain the merges. Comparing only projects with similar stars and a similar number of first pull requests, the gap is 13 percentage points (95% range 8 to 18). Leaving out projects owned by the busiest person's own account, and projects where only one person answers at all, leaves 140 and 92 projects and a gap of 13 percentage points (95% range 7 to 19). The same two comparisons in answers give 6 points (1 to 12) and 4 points (−3 to 11).&lt;/p&gt;

&lt;p&gt;The typical project is the median of 1,199 projects with at least 20 first answers we could attribute to a person and at least 10 first pull requests at least 14 days old: 702 with one person at 90% or more, 93 with nobody over half. Each project counts once, so a busy one weighs no more than a small one, and a project has to answer often to clear that floor. That's why these shares run higher than the page's counts by pull request: 47% answered within two weeks, 32% merged. Another 961 projects have at least 10 first pull requests and fewer than 20 such answers: the typical one answers 23% of its first pull requests within two weeks. In 572 of them one person gave nearly all of the few answers, and the typical one answers 30%.&lt;/p&gt;

&lt;p&gt;One possible reason is the bystander effect: a job that's clearly one person's gets done, and a job that's everyone's can wait. This compares projects, so it can't prove that. Shared projects may also get harder pull requests or have stricter rules for merging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authors who asked got about 9 percentage points more answers. A comment with no ask and no tag got none.
&lt;/h2&gt;

&lt;p&gt;Of first pull requests silent at two weeks, 28% got an answer in the next 30 days (by day 44) if the author had asked in the past week, with words like "friendly ping" or "could you take a look". In the same projects, with no comment after the first hour, it was 14%. Part of that gap comes from which projects get asked. Inside one project, about 9 percentage points remain. A comment with no ask and no tag shows nothing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnervxsr6dcm2mynhs4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnervxsr6dcm2mynhs4n.png" alt="Three arms against no comment. Asked in the past week: +9.3 points. Tagged someone with no request words: +6.2. Commented with no ask and no tag: −0.2." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First pull requests silent at two weeks, 286 with a recent ask in 236 projects. Lines show the 95% range. Across all outside pull requests the same comparison gives 8 points (6 to 10) for asking, from 1,821 pull requests in 850 projects, and 0.3 points (−0.8 to 1.3) for a comment with no ask and no tag.&lt;/p&gt;

&lt;p&gt;Merges follow: by the end of the data, 19% merged against 9%, or 5.5 points inside one project (1.4 to 9.7). An ask in the first week shows less: 2.3 points (−0.9 to 5.6), measured at one week.&lt;/p&gt;

&lt;p&gt;Authors who ask may also care more, or have a better pull request. A comment with no ask and no tag shows no gap, which argues against plain engagement as the whole explanation. This still compares what happened and doesn't prove that asking causes the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  After two weeks of silence, the odds of a merge have more than halved
&lt;/h2&gt;

&lt;p&gt;More than half of first pull requests, 53%, hear nothing from the project's people for two weeks, and the longer the silence, the lower the odds of a merge. Each point counts pull requests at least four months old that had no answer by that day, and how many of them were merged in the end: 17% after a week of silence, 13% after two weeks, 9% after a month, 5% after two months. By then the two lines nearly meet, at 5% and 6%.&lt;/p&gt;

&lt;p&gt;A late answer is still possible. Of first pull requests silent at two weeks, 11% hear something by day 30, 21% by day 60 and 27% by day 90.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijjr85ku9pdpcgqk6in3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijjr85ku9pdpcgqk6in3.png" alt="Share merged in the end by days with no answer: 32% at the start, 17% at a week, 13% at two weeks, 9% at a month, 5% at two months." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Share merged by the end of the data, at least four months after opening, by days with no answer. 24,917 first pull requests are old enough to count. An answer means a reply or review from the project's people, a merge, or a close by them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first pull request waits longest
&lt;/h2&gt;

&lt;p&gt;Within a day, 25% of first pull requests have an answer, against 33% for people the project has seen before. After two weeks the gap is still there: 47% against 60%.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa302kyuohpdf62lwn66p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa302kyuohpdf62lwn66p.png" alt="Share answered over time. First pull requests stay well below returning contributors from the first hour to three months." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The time scale is logarithmic, and the shares allow for pull requests too young to tell or closed by their author. 95,078 first and 83,226 returning pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Weekend pull requests wait longer
&lt;/h2&gt;

&lt;p&gt;Inside the same project, weekday pull requests beat weekend ones in 69% of 2,261 projects, by a median of 6 percentage points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx75q1ue4lctxsorxnvg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx75q1ue4lctxsorxnvg6.png" alt="Share answered within 24 hours by the day the pull request opened. Monday 39%, Wednesday 39%, Friday 34%, Saturday 29%, Sunday 33%." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All outside pull requests, days in UTC, including the busiest projects that the first and returning figures leave out, so don't compare these shares with those. The inside-a-project figure uses projects with at least 30 weekday and 10 weekend pull requests. Weekend senders may differ from weekday ones, which that comparison only partly removes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten projects that answer first-timers quickly and merge their work
&lt;/h2&gt;

&lt;p&gt;Picked by hand from the 800 projects with at least 25 first pull requests old enough to judge, so this isn't a ranking. All ten answered at least 78% of them within a day and merged at least 52%. Most have under 60 first pull requests, so read them as examples. These count first pull requests only, so each differs from the number on the project's Porchlamp page, which counts every outside pull request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Typical first answer&lt;/th&gt;
&lt;th&gt;Within a day&lt;/th&gt;
&lt;th&gt;Merged&lt;/th&gt;
&lt;th&gt;First PRs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/servo/rust-smallvec/" rel="noopener noreferrer"&gt;smallvec&lt;/a&gt; (small vectors for Rust)&lt;/td&gt;
&lt;td&gt;58 minutes&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;td&gt;72%&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/postcss/postcss/" rel="noopener noreferrer"&gt;PostCSS&lt;/a&gt; (tool for transforming CSS)&lt;/td&gt;
&lt;td&gt;1.2 hours&lt;/td&gt;
&lt;td&gt;88%&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/faker-js/faker/" rel="noopener noreferrer"&gt;Faker&lt;/a&gt; (fake data for tests)&lt;/td&gt;
&lt;td&gt;1.6 hours&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/freecodecamp/freecodecamp/" rel="noopener noreferrer"&gt;freeCodeCamp&lt;/a&gt; (free coding curriculum)&lt;/td&gt;
&lt;td&gt;1.8 hours&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;52%&lt;/td&gt;
&lt;td&gt;238&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/bkaradzic/bgfx/" rel="noopener noreferrer"&gt;bgfx&lt;/a&gt; (cross-platform graphics library)&lt;/td&gt;
&lt;td&gt;2.4 hours&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/pcottle/learngitbranching/" rel="noopener noreferrer"&gt;Learn Git Branching&lt;/a&gt; (interactive Git tutorial)&lt;/td&gt;
&lt;td&gt;3 hours&lt;/td&gt;
&lt;td&gt;93%&lt;/td&gt;
&lt;td&gt;96%&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/wolfpld/tracy/" rel="noopener noreferrer"&gt;Tracy&lt;/a&gt; (frame profiler)&lt;/td&gt;
&lt;td&gt;3.2 hours&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/schollz/croc/" rel="noopener noreferrer"&gt;croc&lt;/a&gt; (send files between computers)&lt;/td&gt;
&lt;td&gt;3.5 hours&lt;/td&gt;
&lt;td&gt;78%&lt;/td&gt;
&lt;td&gt;78%&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/quinn-rs/quinn/" rel="noopener noreferrer"&gt;Quinn&lt;/a&gt; (QUIC in Rust)&lt;/td&gt;
&lt;td&gt;4 hours&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;td&gt;56%&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://porchlamp.org/projects/kovidgoyal/calibre/" rel="noopener noreferrer"&gt;calibre&lt;/a&gt; (e-book manager)&lt;/td&gt;
&lt;td&gt;4.8 hours&lt;/td&gt;
&lt;td&gt;92%&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data.&lt;/strong&gt; Porchlamp's 2026-11-05 snapshot holds 1,726,945 outside pull requests opened from 27 April 2026 to 24 October 2026, across 10,998 projects with a record. These results read 592,986 of them, those with recorded replies after the rules below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Left out.&lt;/strong&gt; Bots, drafts never marked ready, accounts under 90 days old, and project members, including anyone the record shows merging or closing someone else's pull request, whatever GitHub's label says. In very busy projects, replies are read from the snapshot's even random sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First pull request.&lt;/strong&gt; The author has no earlier pull request to that project in its record of the past 365 days or in the window. Every pull request here opened in the last six months, so the look-back runs from about six months to a year. Only projects whose record isn't cut off at its cap of about 500 can tell, so most of the busiest projects are missing. The split covers 178,304 of the 592,986 pull requests read, 30%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which results use what.&lt;/strong&gt; The waiting curve, the answered-by curve and the listed projects use first pull requests only. So do the asking figures unless a line says all outside pull requests, and the answered and merged shares beside who answers. The weekday results and who gives the first answers use all outside pull requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who answers.&lt;/strong&gt; The person behind the first answer to each outside pull request: who wrote the reply or review, or who merged it. Known bots are left out. A project counts once, whatever its size, so the shares beside who answers are the median project's. The comparison of similar projects uses bands of stars (under 3,000, 3,000 to 10,000, 10,000 to 30,000, 30,000 or more) and of first pull requests (under 15, 15 to 30, 30 to 60, 60 or more), and the 95% range comes from resampling projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An answer.&lt;/strong&gt; The earliest reply or review from the project's people, a merge, or a close by them. A reply needs at least 40 characters, and automated greetings and replies from bots don't count. The clock starts when a draft is marked ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asking.&lt;/strong&gt; A comment from the author after the first hour, matched against a word list: ping, bump, gentle nudge, could you review, take a look, any update and similar. A hand check of 60 matches found 57 real asks. The list also misses requests: of 60 comments that only tagged someone, 25 were asking. Tool commands and CLA signatures aren't asks, but they do count as commenting. The baseline is no comment after the first hour. The inside-one-project gap compares the groups project by project, weighted by how many each has (Mantel-Haenszel), and the 95% range comes from resampling projects. We tightened the word list after a first look at the data, so this is a result to test again on the next snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Waiting curve.&lt;/strong&gt; Pull requests at least four months old. One an author closed unanswered drops out from that day. The 53% in the waiting section uses all 95,078 and treats an author closing their own pull request as leaving the count, so it doesn't divide out from the curve's table.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Not covered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why a project didn't answer.&lt;/strong&gt; Silence may mean a closed list, a mailing-list workflow, or a pull request nobody wanted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not seen.&lt;/strong&gt; Merges after the record's date, and any pull request's quality or size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause.&lt;/strong&gt; All of this describes what happened, including who answers and the asking and weekday comparisons. Authors who ask may also care more, or have a better pull request, and projects with shared answering may get harder ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When that person stops.&lt;/strong&gt; We didn't measure what happens to a project when its one answerer leaves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six months is one season.&lt;/strong&gt; Volume and answer rates may move with the time of year.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://porchlamp.org/first-pull-requests/" rel="noopener noreferrer"&gt;original page&lt;/a&gt; has the waiting-curve slider, every point in a table, the numbers as JSON, and a lookup for your own pull requests.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>30 open source projects to contribute to that actually answer your pull request</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Tue, 03 Nov 2026 20:29:10 +0000</pubDate>
      <link>https://dev.to/pctablet505/30-open-source-projects-to-contribute-to-that-actually-answer-your-pull-request-44n0</link>
      <guid>https://dev.to/pctablet505/30-open-source-projects-to-contribute-to-that-actually-answer-your-pull-request-44n0</guid>
      <description>&lt;p&gt;You can spend an evening on a fix, send the pull request, and hear nothing for weeks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagjcuriu4qzytyg19qt7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagjcuriu4qzytyg19qt7.gif" alt="A night street where ten houses light their porch lamps one by one, in order of how fast each project replies, with its reply time above the house" width="600" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I track this in &lt;a href="https://porchlamp.org/" rel="noopener noreferrer"&gt;Porchlamp&lt;/a&gt;, a ranking I built of open source projects by what happens to pull requests from outsiders. At the typical ranked project, the first human reply takes about 19.5 hours.&lt;/p&gt;

&lt;p&gt;The 30 projects below are much quicker. At every one, half the outside pull requests hear from a person in under four and a half hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use this list
&lt;/h2&gt;

&lt;p&gt;Pick a language, then a project you already use. Add &lt;code&gt;/contribute&lt;/code&gt; to its GitHub address, like &lt;a href="https://github.com/curl/curl/contribute" rel="noopener noreferrer"&gt;github.com/curl/curl/contribute&lt;/a&gt;. That page lists open issues marked for newcomers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqcndr9up8kemhy5yl0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqcndr9up8kemhy5yl0b.png" alt="A night street of five houses with their porch lamps on, one for each Rust project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compiler itself is here. Half the outside pull requests to rust-lang/rust get a human reply in about 70 minutes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/probe-rs/probe-rs/" rel="noopener noreferrer"&gt;probe-rs/probe-rs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Debugging tools for embedded ARM and RISC-V&lt;/td&gt;
&lt;td&gt;58 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/ruffle-rs/ruffle/" rel="noopener noreferrer"&gt;ruffle-rs/ruffle&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A Flash Player emulator&lt;/td&gt;
&lt;td&gt;1.0 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/rust-lang/rust/" rel="noopener noreferrer"&gt;rust-lang/rust&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Rust compiler and standard library&lt;/td&gt;
&lt;td&gt;1.2 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/rust-lang/cargo/" rel="noopener noreferrer"&gt;rust-lang/cargo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The Rust package manager&lt;/td&gt;
&lt;td&gt;1.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/servo/servo/" rel="noopener noreferrer"&gt;servo/servo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A lightweight browser engine&lt;/td&gt;
&lt;td&gt;1.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy86zb4wv3463weqzt3ze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy86zb4wv3463weqzt3ze.png" alt="A night street of five houses with their porch lamps on, one for each Python project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Astronomers answer quickest here: Astropy's median is 1.8 hours.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/astropy/astropy/" rel="noopener noreferrer"&gt;astropy/astropy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Core library for astronomy&lt;/td&gt;
&lt;td&gt;1.8 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/pallets/click/" rel="noopener noreferrer"&gt;pallets/click&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A toolkit for command line interfaces&lt;/td&gt;
&lt;td&gt;2.5 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/numpy/numpy/" rel="noopener noreferrer"&gt;numpy/numpy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The core package for scientific computing&lt;/td&gt;
&lt;td&gt;3.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/inventree/inventree/" rel="noopener noreferrer"&gt;inventree/InvenTree&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Inventory management you host yourself&lt;/td&gt;
&lt;td&gt;3.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/home-assistant/core/" rel="noopener noreferrer"&gt;home-assistant/core&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Home automation with local control&lt;/td&gt;
&lt;td&gt;4.2 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  JavaScript
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqpfxrwgr99lzpfy2e66.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqpfxrwgr99lzpfy2e66.png" alt="A night street of five houses with their porch lamps on, one for each JavaScript project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;freeCodeCamp has more than 450,000 stars and still replies in about two and a half hours.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/ag-grid/ag-grid/" rel="noopener noreferrer"&gt;ag-grid/ag-grid&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A data table for web apps&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/playcanvas/engine/" rel="noopener noreferrer"&gt;playcanvas/engine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A web graphics runtime&lt;/td&gt;
&lt;td&gt;1.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/faker-js/faker/" rel="noopener noreferrer"&gt;faker-js/faker&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Generates fake data for tests&lt;/td&gt;
&lt;td&gt;1.8 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/raspberrypifoundation/blockly/" rel="noopener noreferrer"&gt;RaspberryPiFoundation/blockly&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A visual programming editor&lt;/td&gt;
&lt;td&gt;1.9 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/freecodecamp/freecodecamp/" rel="noopener noreferrer"&gt;freeCodeCamp/freeCodeCamp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The learning platform and its curriculum&lt;/td&gt;
&lt;td&gt;2.5 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Go
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83fw1ehg1de2im2tnsuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F83fw1ehg1de2im2tnsuh.png" alt="A night street of five houses with their porch lamps on, one for each Go project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dolt, a database you can branch and merge like Git, replies in 24 minutes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/dolthub/dolt/" rel="noopener noreferrer"&gt;dolthub/dolt&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Git for data&lt;/td&gt;
&lt;td&gt;24 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/hajimehoshi/ebiten/" rel="noopener noreferrer"&gt;hajimehoshi/ebiten&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A simple 2D game engine&lt;/td&gt;
&lt;td&gt;41 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/k3s-io/k3s/" rel="noopener noreferrer"&gt;k3s-io/k3s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Lightweight Kubernetes&lt;/td&gt;
&lt;td&gt;1.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/go-gitea/gitea/" rel="noopener noreferrer"&gt;go-gitea/gitea&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Self-hosted Git service&lt;/td&gt;
&lt;td&gt;1.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/pulumi/pulumi/" rel="noopener noreferrer"&gt;pulumi/pulumi&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Infrastructure as code&lt;/td&gt;
&lt;td&gt;3.2 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Java
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57d5i02f42t9y74nyzfr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F57d5i02f42t9y74nyzfr.png" alt="A night street of five houses with their porch lamps on, one for each Java project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;JabRef manages BibTeX libraries for people who write papers, and it replies in under two hours.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/vespa-engine/vespa/" rel="noopener noreferrer"&gt;vespa-engine/vespa&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A search and AI platform&lt;/td&gt;
&lt;td&gt;1.3 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/crate/crate/" rel="noopener noreferrer"&gt;crate/crate&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A distributed SQL database&lt;/td&gt;
&lt;td&gt;1.6 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/traccar/traccar/" rel="noopener noreferrer"&gt;traccar/traccar&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A GPS tracking platform&lt;/td&gt;
&lt;td&gt;1.8 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/jabref/jabref/" rel="noopener noreferrer"&gt;JabRef/jabref&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A reference manager&lt;/td&gt;
&lt;td&gt;1.9 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/strimzi/strimzi-kafka-operator/" rel="noopener noreferrer"&gt;strimzi/strimzi-kafka-operator&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Apache Kafka on Kubernetes&lt;/td&gt;
&lt;td&gt;3.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  C and C++
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9t027ak1h9rs1elfjvlz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9t027ak1h9rs1elfjvlz.png" alt="A night street of five houses with their porch lamps on, one for each C and C++ project in the table below" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stockfish, the chess engine, and curl both reply in under three hours.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Median first reply&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/riot-os/riot/" rel="noopener noreferrer"&gt;RIOT-OS/RIOT&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;An operating system for IoT&lt;/td&gt;
&lt;td&gt;1.2 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/rpcs3/rpcs3/" rel="noopener noreferrer"&gt;RPCS3/rpcs3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A PlayStation 3 emulator&lt;/td&gt;
&lt;td&gt;1.4 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/libsdl-org/sdl/" rel="noopener noreferrer"&gt;libsdl-org/SDL&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Cross-platform media library&lt;/td&gt;
&lt;td&gt;2.0 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/official-stockfish/stockfish/" rel="noopener noreferrer"&gt;official-stockfish/Stockfish&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;A strong open chess engine&lt;/td&gt;
&lt;td&gt;2.3 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://porchlamp.org/projects/curl/curl/" rel="noopener noreferrer"&gt;curl/curl&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The tool and library for fetching URLs&lt;/td&gt;
&lt;td&gt;2.7 h&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What normal looks like
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2vqa24s066rc8jtn7ays.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2vqa24s066rc8jtn7ays.png" alt="Bar chart of the median first reply by language: Rust 15.0 hours, C and C++ 15.3, Java 18.5, Go 18.9, JavaScript 21.8, Python 22.7" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More than half of the 3,246 projects I could time give a first reply within a day. Rust and C and C++ are quickest; Python takes longest.&lt;/p&gt;

&lt;p&gt;So if you've waited a week, you're past the median at more than 9 in 10 of them. A short, polite comment is fair at that point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own project
&lt;/h2&gt;

&lt;p&gt;Every ranked project has a page with its reply time and how many outsiders heard nothing. &lt;a href="https://porchlamp.org/" rel="noopener noreferrer"&gt;Search for yours on porchlamp.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Longer lists, ordered by reply time: &lt;a href="https://porchlamp.org/rust/fastest-replies/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, &lt;a href="https://porchlamp.org/python/fastest-replies/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://porchlamp.org/javascript/fastest-replies/" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://porchlamp.org/go/fastest-replies/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, &lt;a href="https://porchlamp.org/java/fastest-replies/" rel="noopener noreferrer"&gt;Java&lt;/a&gt;, &lt;a href="https://porchlamp.org/c-cpp/fastest-replies/" rel="noopener noreferrer"&gt;C and C++&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ranked for first-time contributors: &lt;a href="https://porchlamp.org/rust/first-timers/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;, &lt;a href="https://porchlamp.org/python/first-timers/" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://porchlamp.org/javascript/first-timers/" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt;, &lt;a href="https://porchlamp.org/go/first-timers/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, &lt;a href="https://porchlamp.org/java/first-timers/" rel="noopener noreferrer"&gt;Java&lt;/a&gt;, &lt;a href="https://porchlamp.org/c-cpp/first-timers/" rel="noopener noreferrer"&gt;C and C++&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  How I picked these
  &lt;br&gt;
Numbers come from Porchlamp's snapshot of 2 November 2026, covering pull requests from 27 April to 24 October 2026.

&lt;p&gt;"Outside" leaves out bots, drafts, pull requests their authors closed, brand-new accounts and each project's own members.&lt;/p&gt;

&lt;p&gt;To qualify, a project needed all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;at least 100 timed outside pull requests&lt;/li&gt;
&lt;li&gt;under 10% with no reply within 14 days&lt;/li&gt;
&lt;li&gt;a place in the top 1,500 of the 4,044 ranked projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I dropped medians under 15 minutes, which can be automation rather than a person. From the rest I took five open source projects per language that I thought readers would know.&lt;/p&gt;

&lt;p&gt;A fast first reply is not a merge. It tells you someone is reading.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Which project answered you fastest? I'd like to check it against the numbers.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>beginners</category>
      <category>programming</category>
      <category>github</category>
    </item>
    <item>
      <title>How to tell if an open-source project will answer your pull request</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Sat, 24 Oct 2026 18:06:47 +0000</pubDate>
      <link>https://dev.to/pctablet505/how-to-tell-if-an-open-source-project-will-answer-your-pull-request-3kjm</link>
      <guid>https://dev.to/pctablet505/how-to-tell-if-an-open-source-project-will-answer-your-pull-request-3kjm</guid>
      <description>&lt;p&gt;Before you send a pull request to a project you don't know, ask one question first: will anyone answer it?&lt;/p&gt;

&lt;p&gt;I followed 1.4 million pull requests to 11,000 GitHub projects to see how much the answer varies. A lot.&lt;/p&gt;

&lt;p&gt;At some projects, half the outside pull requests get a human reply within about 30 minutes. At more than 1 in 8 of the projects I could judge, most outsiders hear nothing for two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;On the project's GitHub page, open &lt;strong&gt;Pull requests&lt;/strong&gt; and search for &lt;code&gt;is:pr is:closed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Open five recent ones whose authors are not labelled Member, Owner or Collaborator. Those labels mark insiders.&lt;/li&gt;
&lt;li&gt;For each, note how long the first comment or review from a human took. Skip bots.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If most of the five heard back within a day or two, that's a good sign. If they waited weeks, yours may too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What normal looks like
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc4uiahqe9k2vo8ngi6ap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc4uiahqe9k2vo8ngi6ap.png" alt="Bar chart of 2,300 ranked projects by their median wait for a first human reply: under 1 hour 62, 1 to 3 hours 151, 3 to 6 hours 193, 6 to 12 hours 316, 12 to 24 hours 492, 1 to 3 days 573, 3 to 7 days 274, 1 to 2 weeks 128, over 2 weeks 111. 1,214 projects, more than half, have a median first reply under a day." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At 2,300 projects there were enough replies to time. At more than half of them, the median first reply comes within a day, and one in ten takes longer than a week.&lt;/p&gt;

&lt;p&gt;So if you've waited a week, you're past the median at 9 in 10 of these projects. A short, polite comment asking whether anything is needed from you is fair at that point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fast end
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftoupvnknxe7v8xbzxb5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftoupvnknxe7v8xbzxb5r.png" alt="Bar chart of the median time to a first human reply on outside pull requests: Ebiten 0.5 hours, Nomad 0.6 hours, cargo 1.4 hours, Servo 1.4 hours, k3s 1.7 hours, Stockfish 2.3 hours, Click 2.5 hours, React Hook Form 2.8 hours. The median across 2,300 ranked projects: 22 hours." width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These eight are well known and quick. Ebiten and Nomad have a median first reply of about half an hour; cargo, Servo, k3s, Stockfish, Click and React Hook Form all come in under three hours. If you want a quick answer on a first contribution, they are good places to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  A high star count is no guarantee
&lt;/h2&gt;

&lt;p&gt;12 of the 100 most-starred projects leave most outside pull requests with no human answer for two weeks. Stars measure popularity. Whether anyone reads the queue is a separate question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skip the counting
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://porchlamp.org/" rel="noopener noreferrer"&gt;Porchlamp&lt;/a&gt; does this count for 2,300 projects. Each project's page opens with its median first reply and the share of outside pull requests that got no reply within two weeks. I built it.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  How I counted
  &lt;br&gt;
I followed 1,442,863 pull requests from 227,260 outside contributors to 11,000 GitHub projects, from 25 April to 22 October 2026 (Porchlamp's snapshot of 25 October 2026). Bots, drafts, pull requests their authors closed, brand-new accounts and each project's own members are not counted. A project is judged when it is licensed, maintained software with at least 10 counted outside pull requests; 4,168 were. "Most outsiders hear nothing" means a black hole: with 95% confidence, more than half of those pull requests got no human reply, review, merge or close within 14 days. Reply times come from the 2,300 ranked projects with enough timed replies. For projects with more than 2,000 pull requests, replies are read on an even random sample of 1,000. I picked the eight named projects from the fast group; each had at least 100 timed outside pull requests.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;What's the longest you've waited on a pull request, and did it ever get merged?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Keras to TFLite on Android: wrong normalization and a backwards top-5</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:16:39 +0000</pubDate>
      <link>https://dev.to/pctablet505/keras-to-tflite-on-android-wrong-normalization-and-a-backwards-top-5-28m2</link>
      <guid>https://dev.to/pctablet505/keras-to-tflite-on-android-wrong-normalization-and-a-backwards-top-5-28m2</guid>
      <description>&lt;p&gt;For a photo of a quail, my Android app's top-5 code picks American alligator, sea cucumber, barracouta, reel and sea&lt;br&gt;
urchin. The model is right; the app is not. It keeps the five worst labels, and it scales the pixels with the wrong&lt;br&gt;
numbers.&lt;/p&gt;

&lt;p&gt;I added LiteRT export to Keras (&lt;a href="https://github.com/keras-team/keras/pull/21674" rel="noopener noreferrer"&gt;keras#21674&lt;/a&gt;) and its PyTorch path&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras/pull/22758" rel="noopener noreferrer"&gt;keras#22758&lt;/a&gt;), so the export was the part I trusted. The scores and&lt;br&gt;
labels below come from a run on 22 October 2026 on a computer's CPU, with the app's preprocessing and top-5 code copied into&lt;br&gt;
Python. I did not run the app on a phone for this post.&lt;/p&gt;
&lt;h2&gt;
  
  
  The export: one call
&lt;/h2&gt;

&lt;p&gt;LiteRT is the new name of TensorFlow Lite; the files still end in &lt;code&gt;.tflite&lt;/code&gt;. The&lt;br&gt;
&lt;a href="https://keras.io/guides/litert_export/" rel="noopener noreferrer"&gt;keras.io guide&lt;/a&gt; installs Keras, KerasHub and litert-torch from their main&lt;br&gt;
branches. On 22 October 2026 that broke this export, with&lt;br&gt;
&lt;code&gt;'tfl.batch_matmul' op found invalid output rank, expected 3 but got 4&lt;/code&gt;. The released versions worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;torch &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--index-url&lt;/span&gt; https://download.pytorch.org/whl/cpu
pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;keras&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;3.15.1 keras-hub&lt;span class="o"&gt;==&lt;/span&gt;0.32.0 &lt;span class="se"&gt;\&lt;/span&gt;
    litert-torch&lt;span class="o"&gt;==&lt;/span&gt;0.10.0 ai-edge-litert&lt;span class="o"&gt;==&lt;/span&gt;2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the whole export, from my app's README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Run with KERAS_BACKEND=torch set in the shell.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;keras_hub&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras_hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MobileNetImageClassifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobilenet_v3_small_100_imagenet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/mobilenetv3.tflite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;input_signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;from_preset&lt;/code&gt; loads a KerasHub preset: a pretrained model, fetched by name. &lt;code&gt;input_signature&lt;/code&gt; fixes the input to one&lt;br&gt;
224 by 224 colour image, because the PyTorch export path needs one fixed shape&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras/pull/22997" rel="noopener noreferrer"&gt;keras#22997&lt;/a&gt;). The keras.io guide picks the PyTorch backend because&lt;br&gt;
its files need no flex ops, the TensorFlow ops that the new LiteRT Android runtime cannot run. With the pinned&lt;br&gt;
versions it wrote a 10,255,480-byte file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug 1: the pixel numbers
&lt;/h2&gt;

&lt;p&gt;The exported file starts after preprocessing: calling a KerasHub model directly "does not apply preprocessing"&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/blob/3ba52a2fa10a841633bc8acb06e6fa0195a00077/keras_hub/src/utils/pipeline_model.py#L537-L553" rel="noopener noreferrer"&gt;pipeline_model.py&lt;/a&gt;).&lt;br&gt;
So the app has to resize and normalize every photo itself, with the numbers the model was trained with. My app reads&lt;br&gt;
them from a sidecar, a small JSON file that sits next to the model file.&lt;/p&gt;

&lt;p&gt;The sample sidecar in my app's tests uses 127.5 for every mean and standard deviation. The preset's own&lt;br&gt;
&lt;a href="https://huggingface.co/keras/mobilenet_v3_small_100_imagenet/blob/0c0c8625121a1725a05a0ec7d23f0ed22286b3a2/preprocessor.json" rel="noopener noreferrer"&gt;preprocessor.json&lt;/a&gt;&lt;br&gt;
uses ImageNet's numbers, a different pair for each colour:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flyy0lgjstqqy1nckkjgm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flyy0lgjstqqy1nckkjgm.png" alt="Bar chart. The model expects red from -2.12 to 2.25, green from -2.04 to 2.43 and blue from -1.80 to 2.64. The app's test sidecar sends every channel from -1 to 1." width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing crashes, and on my quail photo the answer did not even change: quail stayed first, but its raw score fell&lt;br&gt;
from 10.11 to 9.72. A wrong number that leaves the top answer alone is easy to ship. The fix is to copy the preset's&lt;br&gt;
numbers instead of guessing them: &lt;code&gt;mean&lt;/code&gt; is &lt;code&gt;-offset / scale&lt;/code&gt; and &lt;code&gt;std&lt;/code&gt; is &lt;code&gt;1 / scale&lt;/code&gt;, on pixels from 0 to 255. As a&lt;br&gt;
sidecar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image_classification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"normalization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mean"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;123.675&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;116.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;103.53&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"std"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;58.395&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;57.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;57.375&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"labels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"imagenet_labels.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"num_classes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bug 2: the top-5 list
&lt;/h2&gt;

&lt;p&gt;These lines pick the five labels the screen shows&lt;br&gt;
(&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/b8d3e2f1baeb5f9d13697e6a0ee5e1f646f6da09/app/src/main/java/com/example/kerasondevice/inference/tasks/ImageClassificationTask.kt#L64-L72" rel="noopener noreferrer"&gt;ImageClassificationTask.kt, lines 64 to 72&lt;/a&gt;,&lt;br&gt;
wrapped to fit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;top5&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PriorityQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;compareByDescending&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Classification&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;probs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEachIndexed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labelList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrElse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"class_$idx"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;top5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Classification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;top5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;TaskResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;top5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sortedByDescending&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java's &lt;code&gt;PriorityQueue&lt;/code&gt; keeps the smallest item, by its comparator, at the head, and &lt;code&gt;compareByDescending&lt;/code&gt; makes that&lt;br&gt;
the highest score. &lt;code&gt;poll()&lt;/code&gt; removes the head. So each time the queue holds six, it throws away the best score, and the&lt;br&gt;
screen ends up with the five least likely of 1,000 classes. For the quail photo that list is classes 50, 329, 389,&lt;br&gt;
758 and 328, while the real top five for the same input are quail, prairie chicken, partridge, black grouse and&lt;br&gt;
ruffed grouse. The&lt;br&gt;
fix is one word: &lt;code&gt;compareBy&lt;/code&gt; instead of&lt;br&gt;
&lt;code&gt;compareByDescending&lt;/code&gt;, so the head is the lowest score and &lt;code&gt;poll()&lt;/code&gt; drops that one. The test for this screen only&lt;br&gt;
checks that one to five labels come back, so it could not catch this.&lt;/p&gt;

&lt;p&gt;Both bugs are still in the repository at commit b8d3e2f (3 July 2026). The fixes are small, and I have left them in&lt;br&gt;
for now: if you want a first pull request, they are yours. To run the app yourself, the&lt;br&gt;
&lt;a href="https://github.com/pctablet505/keras-ondevice-android#try-it" rel="noopener noreferrer"&gt;README&lt;/a&gt; has the three &lt;code&gt;adb push&lt;/code&gt; lines and the install&lt;br&gt;
command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I took from it
&lt;/h2&gt;

&lt;p&gt;Never retype preprocessing numbers. Either the export script writes the sidecar from the preset's own&lt;br&gt;
&lt;code&gt;preprocessor.json&lt;/code&gt;, or the normalization goes inside the exported model, so there is nothing left to keep in sync.&lt;br&gt;
This app keeps the sidecar, because it loads any model that has one next to it.&lt;/p&gt;

&lt;p&gt;Which do you use, a sidecar written by code or normalization inside the model? If inside, how do you export it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/main/docs/02-check-against-the-notebook.md" rel="noopener noreferrer"&gt;Next: the check that would have caught both →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with the help of AI, from my own repository and the public docs it links.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>keras</category>
      <category>tflite</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Two Android ML tests that could never fail, and a TFLite check that can</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:16:22 +0000</pubDate>
      <link>https://dev.to/pctablet505/two-android-ml-tests-that-could-never-fail-and-a-tflite-check-that-can-3bm8</link>
      <guid>https://dev.to/pctablet505/two-android-ml-tests-that-could-never-fail-and-a-tflite-check-that-can-3bm8</guid>
      <description>&lt;p&gt;Two tests in my Android app could never fail. One only counts the labels; the other compares the output with an&lt;br&gt;
answer file that holds no answers. And both of them look at this picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77jch6srvisqy5rj02sk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77jch6srvisqy5rj02sk.png" alt="The test image from the repo: a green square, a yellow circle and a red rectangle on a blue ground. Nothing either model was trained to recognize, so no answer to it is wrong." width="800" height="1040"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app runs a Keras model exported to LiteRT, the new name of TensorFlow Lite. This post is about one rule: a check&lt;br&gt;
proves something only if you have seen it fail. Here is how mine could not, and the check I would build instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two tests that could not fail
&lt;/h2&gt;

&lt;p&gt;The classification test asks only this of the result&lt;br&gt;
(&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/b8d3e2f1baeb5f9d13697e6a0ee5e1f646f6da09/app/src/androidTest/java/com/example/kerasondevice/ClassificationE2ETest.kt#L66" rel="noopener noreferrer"&gt;ClassificationE2ETest.kt, line 66&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Expected top-5 results"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any five labels pass, including the five least likely ones the app shows&lt;br&gt;
(&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/main/docs/01-export-a-vision-model.md#before-you-trust-the-list" rel="noopener noreferrer"&gt;part 1&lt;/a&gt;&lt;br&gt;
explains why it shows those).&lt;/p&gt;

&lt;p&gt;The detection test looks better. It reads a golden file, the saved answer a test compares against, and matches each&lt;br&gt;
expected box by its label and its overlap. Here is the golden file&lt;br&gt;
(&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/b8d3e2f1baeb5f9d13697e6a0ee5e1f646f6da09/app/src/androidTest/assets/golden/detection_golden.json" rel="noopener noreferrer"&gt;detection_golden.json&lt;/a&gt;,&lt;br&gt;
its note cut to fit):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Golden generation skipped: ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ssd_mobilenet_v1_detect.tflite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"images/test_image.jpg"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The note in full reads "Golden generation skipped: No module named 'ai_edge_litert'". No boxes. The match loop runs&lt;br&gt;
once per expected box, so with none it checks nothing, and the size check becomes "at least zero"&lt;br&gt;
(&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/b8d3e2f1baeb5f9d13697e6a0ee5e1f646f6da09/app/src/androidTest/java/com/example/kerasondevice/DetectionE2ETest.kt#L130-L131" rel="noopener noreferrer"&gt;DetectionE2ETest.kt, lines 130 to 131&lt;/a&gt;).&lt;br&gt;
The script that should write the answers wrote this placeholder and carried on.&lt;/p&gt;
&lt;h2&gt;
  
  
  The check that can fail
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9somfv9glwqxgkfdmogf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9somfv9glwqxgkfdmogf.png" alt="The check in six steps: one photo with a known answer, the Keras model's predict(), the .tflite file in Python with ai_edge_litert on the same array, the saved answer golden.json, the phone test matching labels and boxes at IoU 0.5 or more, and a test that fails when the answer file is empty." width="800" height="762"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Get the notebook's answer
&lt;/h3&gt;

&lt;p&gt;Start from a real photo with a known answer. KerasHub's own example uses a California quail, and "quail" is class 85&lt;br&gt;
in ImageNet, the label set these classifiers use&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/blob/3ba52a2fa10a841633bc8acb06e6fa0195a00077/keras_hub/src/utils/imagenet/imagenet_utils.py#L57-L65" rel="noopener noreferrer"&gt;imagenet_utils.py&lt;/a&gt;).&lt;br&gt;
Its example downloads the photo with &lt;code&gt;keras.utils.get_file&lt;/code&gt;, and on 22 October 2026 Wikimedia answered&lt;br&gt;
&lt;code&gt;403 -- Forbidden&lt;/code&gt;. That photo is CC BY-SA 3.0, too, not public domain. So I downloaded a public-domain one from the&lt;br&gt;
&lt;a href="https://commons.wikimedia.org/wiki/File:California_Quail_(9405494428)_(cropped).jpg" rel="noopener noreferrer"&gt;U.S. Fish and Wildlife Service&lt;/a&gt;,&lt;br&gt;
cropped it square, and loaded it from disk. With my exported preset in the example's place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_img&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;california_quail.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras_hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageClassifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobilenet_v3_small_100_imagenet&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;activation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;softmax&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;preds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;top_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras_hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode_imagenet_predictions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;predict()&lt;/code&gt; runs the preset's own preprocessing, so this is the answer the model was built to give. On a computer's&lt;br&gt;
CPU it gave quail 10.30, partridge 3.94, prairie chicken 3.77, black grouse 3.57 and ruffed grouse 3.43. One surprise:&lt;br&gt;
those are raw scores. Despite &lt;code&gt;activation="softmax"&lt;/code&gt;, the 1,000 scores summed to 162.5 (a softmax would have&lt;br&gt;
summed to 1).&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Check the .tflite file in Python first
&lt;/h3&gt;

&lt;p&gt;Run the &lt;code&gt;.tflite&lt;/code&gt; file on the array the Keras model sees after preprocessing (KerasHub's hint is&lt;br&gt;
&lt;code&gt;model(model.preprocessor(x))&lt;/code&gt;), with the interpreter code from the&lt;br&gt;
&lt;a href="https://keras.io/guides/litert_export/" rel="noopener noreferrer"&gt;keras.io guide&lt;/a&gt;. Then compare the two outputs the way KerasHub's own export&lt;br&gt;
tests do&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/blob/3ba52a2fa10a841633bc8acb06e6fa0195a00077/keras_hub/src/tests/test_case.py#L1038-L1058" rel="noopener noreferrer"&gt;test_case.py&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;abs_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keras_val&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;litert_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mean_abs_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs_diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;max_abs_diff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs_diff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the quail photo they agreed: &lt;code&gt;max_abs_diff&lt;/code&gt; 1.1e-05 and &lt;code&gt;mean_abs_diff&lt;/code&gt; 1.9e-06 over the 1,000 scores, with the&lt;br&gt;
same five labels in the same order and quail at 10.31 in both. If the file disagrees with the notebook here, the phone will too, and the bug is&lt;br&gt;
in the export, not the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Hold the phone to the saved answer
&lt;/h3&gt;

&lt;p&gt;Save the five labels and scores as the golden file, or for detection the boxes as &lt;code&gt;label&lt;/code&gt;, &lt;code&gt;score&lt;/code&gt;, &lt;code&gt;x1&lt;/code&gt;, &lt;code&gt;y1&lt;/code&gt;, &lt;code&gt;x2&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;y2&lt;/code&gt;: the keys my detection test already reads. Push the same photo to the phone and keep the test's matching rule:&lt;br&gt;
the same label, and a box overlap of 0.5 or more, measured as IoU (the area two boxes share divided by the area they&lt;br&gt;
cover together). Two guards make it a test that can fail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An empty or missing golden file fails.&lt;/strong&gt; It never counts as a pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same pixels go in.&lt;/strong&gt; The preset crops the photo to a square before it resizes it, and my app stretches it. Use
a square photo, or crop it first.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Here it is failing
&lt;/h2&gt;

&lt;p&gt;I copied my app's preprocessing into Python (resize to 224 by 224, then the preset's numbers) and fed the same file&lt;br&gt;
the same square photo. Quail still came first, at 10.11 instead of 10.31, but prairie chicken and partridge swapped&lt;br&gt;
places. A check that compares the ordered five labels fails right there, with the right numbers in the sidecar: what&lt;br&gt;
is left is the resize step. That is the rule working, on a computer's CPU; I did not run the phone for this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have not solved
&lt;/h2&gt;

&lt;p&gt;How close is close enough? The resize alone produced that gap, before any quantization (weights stored in fewer&lt;br&gt;
bits to make the file smaller), and I do not yet have a rule for the tolerance. KerasHub's thresholds are set by hand,&lt;br&gt;
per model and per output, and I have not found one to copy. If you have a rule, I would like to read it.&lt;/p&gt;

&lt;p&gt;The two tests are still in the repo as they were at commit b8d3e2f (3 July 2026). The scores and differences above come from a run&lt;br&gt;
on 22 October 2026 with keras 3.15.1, keras-hub 0.32.0, litert-torch 0.10.0 and ai-edge-litert 2.3.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/main/docs/03-traps.md" rel="noopener noreferrer"&gt;Next: five ways the export itself breaks, each with its public record →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with the help of AI, from my own repository and the public docs it links.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>testing</category>
      <category>machinelearning</category>
      <category>tflite</category>
    </item>
    <item>
      <title>Why a Keras model fails to convert to TFLite: 11 known cases, 5 traps</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:15:45 +0000</pubDate>
      <link>https://dev.to/pctablet505/why-a-keras-model-fails-to-convert-to-tflite-11-known-cases-5-traps-5bb0</link>
      <guid>https://dev.to/pctablet505/why-a-keras-model-fails-to-convert-to-tflite-11-known-cases-5-traps-5bb0</guid>
      <description>&lt;p&gt;KerasHub, the Keras library of pretrained models, expects 11 of its own models to fail the export to LiteRT (the new&lt;br&gt;
name of TensorFlow Lite), and its tests say why in one line each. Here are all 11, the five traps they fall into, and&lt;br&gt;
what to write instead.&lt;/p&gt;

&lt;p&gt;I contribute to Keras and KerasHub. The change that switched these export tests back on for the PyTorch backend,&lt;br&gt;
&lt;a href="https://github.com/keras-team/keras-hub/pull/2698" rel="noopener noreferrer"&gt;keras-hub#2698&lt;/a&gt;, is mine.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 11, in KerasHub's own words
&lt;/h2&gt;

&lt;p&gt;An expected failure is a test marked to fail until an upstream fix lands; when the fix lands, the test turns loud&lt;br&gt;
instead of silently passing. Each name links to its line in the tests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Trap&lt;/th&gt;
&lt;th&gt;The test's own reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/d_fine/d_fine_object_detector_test.py#L177-L180" rel="noopener noreferrer"&gt;D-Fine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2. One shape&lt;/td&gt;
&lt;td&gt;"D-FINE's multi-scale features hit a torch.export shape guard."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/flux/flux_backbone_test.py#L88-L91" rel="noopener noreferrer"&gt;Flux&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2. One shape&lt;/td&gt;
&lt;td&gt;"torch.export guard from Flux's dynamic num_heads reshape."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/f_net/f_net_text_classifier_test.py#L61-L64" rel="noopener noreferrer"&gt;F-Net&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"litert-torch has no lowering for aten.complex (from ops.fft2)."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/gpt_oss/gpt_oss_causal_lm_test.py#L117-L120" rel="noopener noreferrer"&gt;GPT-OSS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"litert-torch NHWC rewriter has no lowering for aten.amax."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/mixtral/mixtral_causal_lm_test.py#L111-L114" rel="noopener noreferrer"&gt;Mixtral&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"litert-torch cannot lower aten._assert_async from MoE routing."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/qwen3_moe/qwen3_moe_causal_lm_test.py#L128-L131" rel="noopener noreferrer"&gt;Qwen3-MoE&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"litert-torch cannot lower aten._assert_async from MoE routing."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/qwen_moe/qwen_moe_causal_lm_test.py#L146-L149" rel="noopener noreferrer"&gt;Qwen-MoE&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"litert-torch cannot lower aten._assert_async from MoE routing."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/sam3/sam3_pc_image_segmenter_test.py#L169-L172" rel="noopener noreferrer"&gt;SAM3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"torchvision::nms is not registered in the torch.export op set."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/segformer/segformer_image_segmenter_tests.py#L77-L80" rel="noopener noreferrer"&gt;SegFormer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"Unsupported ops during SegFormer's torch export decomposition."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/stable_diffusion_3/stable_diffusion_3_text_to_image_test.py#L204-L207" rel="noopener noreferrer"&gt;StableDiffusion3&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"AdjustablePositionEmbedding cannot be lowered by torch.export."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/vae/vae_backbone_test.py#L38-L41" rel="noopener noreferrer"&gt;VAE&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3. No lowering&lt;/td&gt;
&lt;td&gt;"VAE's pow ops fail TFLite legalization (tfl.pow)."&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At the commit that merged it, KerasHub has 75 export tests in 67 model folders: 63 carry no mark, 11 are expected&lt;br&gt;
failures and 1 is skipped. I counted the marks in the code; I did not run the tests. The pull request names 7 models&lt;br&gt;
that now pass on PyTorch: VGG, Moonshine, SAM, GPTNeoX and BASNet, plus ViTDet and Whisper within a tolerance rather&lt;br&gt;
than an exact match.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomae20uzno3xgfxmzvu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fomae20uzno3xgfxmzvu8.png" alt="The export stages from the keras.io guide's own log, with the five traps: in the model code (Python if, x.shape), at Torch export (one fixed shape), at lowering and the converter passes (ops with no lowering, tfl.pow), in the model.tflite file (inputs named args_0), and under every stage (versions)." width="800" height="884"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. "symbolic tf.Tensor used as a Python bool"
&lt;/h2&gt;

&lt;p&gt;Export turns your Python into a graph. A Python &lt;code&gt;if&lt;/code&gt; on a tensor, or a size read with &lt;code&gt;x.shape&lt;/code&gt;, can break that step&lt;br&gt;
or freeze one case into the graph. KerasHub still skips its DeepLabV3 export test with exactly this message&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/blob/41faf96bf6edcb9398d5807e76c90da0fbd4266a/keras_hub/src/models/deeplab_v3/deeplab_v3_segmenter_test.py#L74-L77" rel="noopener noreferrer"&gt;test&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; keep sizes and branches inside the graph. One merged change, before and after&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/pull/2466" rel="noopener noreferrer"&gt;keras-hub#2466&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before: x.shape is read once, while the graph is built
&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After: "for dynamic shape compatibility with TFLite"
&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a Python &lt;code&gt;if&lt;/code&gt;, &lt;a href="https://github.com/keras-team/keras-hub/pull/2467" rel="noopener noreferrer"&gt;keras-hub#2467&lt;/a&gt; removed the branch instead: it&lt;br&gt;
builds the tensor so that one token gives an empty tensor, "avoiding the need for a Python conditional".&lt;/p&gt;
&lt;h2&gt;
  
  
  2. A &lt;code&gt;torch.export&lt;/code&gt; shape guard
&lt;/h2&gt;

&lt;p&gt;On the PyTorch backend the input shape is fixed when you export. In my words from&lt;br&gt;
&lt;a href="https://github.com/keras-team/keras/issues/22996" rel="noopener noreferrer"&gt;keras#22996&lt;/a&gt;, the issue I filed:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The only viable workaround is to export with a fixed static shape matching the runtime maximum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;D-Fine and Flux are this trap in the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; give the export one shape, the largest you will feed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Fails (keras#22996, shortened): the sequence length is left
# open, traced at 1, and the file breaks on longer input.
&lt;/span&gt;&lt;span class="n"&gt;lm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keras_hub&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Gemma3CausalLM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_preset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma3_270m_it&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;lm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lm.tflite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Works: one fixed shape at export (an image model here).
&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;m.tflite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;litert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;input_signature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The working half ran on 22 October 2026, on a computer's CPU, for my demo app's MobileNetV3.&lt;/p&gt;

&lt;p&gt;To feed a different batch size later, resize the input at run time with &lt;code&gt;resize_tensor_input()&lt;/code&gt;, as the&lt;br&gt;
&lt;a href="https://keras.io/guides/litert_export/" rel="noopener noreferrer"&gt;keras.io guide&lt;/a&gt; shows.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. "no lowering for aten.complex"
&lt;/h2&gt;

&lt;p&gt;The export stops at the name of one operation. Lowering is the step that turns each PyTorch operation into a LiteRT&lt;br&gt;
one, and litert-torch, the converter, has no rule for some of them. Seven of the 11 in the table name such an&lt;br&gt;
operation (aten.complex, aten.amax, aten._assert_async, torchvision::nms, tfl.pow); SegFormer and StableDiffusion3 give&lt;br&gt;
a vaguer reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check, then a fallback with a cost:&lt;/strong&gt; find your model in the table before you start. The keras.io guide's fallback&lt;br&gt;
is the TensorFlow backend, but its flex ops, TensorFlow operations carried into the file, "are not supported by the&lt;br&gt;
new LiteRT Android runtime". The app then needs the legacy runtime, &lt;code&gt;org.tensorflow:tensorflow-lite&lt;/code&gt;, plus&lt;br&gt;
&lt;code&gt;tensorflow-lite-select-tf-ops&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Inputs named &lt;code&gt;args_0&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Your app asks for an input by name, and the file calls it &lt;code&gt;args_0&lt;/code&gt;. litert-torch flattened dict inputs to generic names&lt;br&gt;
(&lt;a href="https://github.com/google-ai-edge/litert-torch/issues/1022" rel="noopener noreferrer"&gt;litert-torch#1022&lt;/a&gt;, closed 4 June 2026).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use litert-torch newer than 0.9.1, which keeps the original keys&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras/pull/23053" rel="noopener noreferrer"&gt;keras#23053&lt;/a&gt;), and print the names before you write app code. For&lt;br&gt;
my MobileNetV3 file, with litert-torch 0.10.0, it printed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signatures:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interpreter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_signature_list&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# {'serving_default': {'inputs': ['args_0'],
#                      'outputs': ['output_0']}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single image input is still called &lt;code&gt;args_0&lt;/code&gt;; the fix keeps the keys of dict inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. &lt;code&gt;tf.lite&lt;/code&gt; is deprecated, and the toolchain moves
&lt;/h2&gt;

&lt;p&gt;KerasHub switched all its export tests off in February 2026, when TensorFlow 2.20 deprecated &lt;code&gt;tf.lite&lt;/code&gt;&lt;br&gt;
(&lt;a href="https://github.com/keras-team/keras-hub/pull/2573" rel="noopener noreferrer"&gt;keras-hub#2573&lt;/a&gt;), and back on in July for PyTorch only, with&lt;br&gt;
Keras 3.15 or newer. Keras 3 with TensorFlow 2.20 and Python 3.13 crashed during export until&lt;br&gt;
&lt;a href="https://github.com/keras-team/keras/pull/22816" rel="noopener noreferrer"&gt;keras#22816&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It moved again on 22 October 2026. Installed from the main branches, the way the keras.io guide does it, the export&lt;br&gt;
above failed in the converter with &lt;code&gt;'tfl.batch_matmul' op found invalid output rank, expected 3 but got 4&lt;/code&gt;. The&lt;br&gt;
released versions of the day before exported it: keras 3.15.1, keras-hub 0.32.0, litert-torch 0.10.0 and&lt;br&gt;
ai-edge-litert 2.3.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check, not a fix:&lt;/strong&gt; record the versions that worked, next to the exported file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip freeze | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"keras|litert"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; export-versions.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The one I cannot close
&lt;/h2&gt;

&lt;p&gt;KerasHub's DeepLabV3 export test is still skipped, while my demo app exports a DeepLabV3 preset on the same PyTorch&lt;br&gt;
backend. One of the two is out of date; my 22 October run covered MobileNetV3 only. If you have run that export recently, which one is it? And which trap did you&lt;br&gt;
hit that is not on this list?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/pctablet505/keras-ondevice-android/blob/main/docs/01-export-a-vision-model.md" rel="noopener noreferrer"&gt;New here? Start with part 1: one model on a phone →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with the help of AI, from my own repository and the public records it links.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>keras</category>
      <category>tflite</category>
      <category>pytorch</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>A hot-expert cache made a 181 GiB model faster on one 96 GiB GPU</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 19:22:03 +0000</pubDate>
      <link>https://dev.to/pctablet505/a-hot-expert-cache-made-a-181-gib-model-faster-on-one-96-gib-gpu-4b03</link>
      <guid>https://dev.to/pctablet505/a-hot-expert-cache-made-a-181-gib-model-faster-on-one-96-gib-gpu-4b03</guid>
      <description>&lt;p&gt;The build of GLM-5.3-Flash I run is 181 GiB, and my GPU has 96 GiB. It still writes up to 16.5 tokens a second on one desktop. Offloading more on its own made it slower; keeping each layer's hot experts on the card made it faster.&lt;/p&gt;

&lt;p&gt;One of the eight bugs on the way gave no error at all. The model just emitted the same token, forever. I will come back to that one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyipvhwd3qm2egrv6p6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftyipvhwd3qm2egrv6p6r.png" alt="Bar chart titled 'Same 125 GiB offload, with and without the hot-expert cache', in decode tokens a second. Baseline, 105 GiB offloaded: default gather 10.77; with the gather kernel and CUDA graphs 13.53. 125 GiB offloaded with the gather kernel: no hot experts on the card 11.34; 66 hot experts per layer 16.50, highlighted. Shipped, 125 GiB with room for long prompts: 54 hot experts per layer 14.99. Whiskers mark ±1 tokens a second, the spread between runs." width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;
Each bar is its own measured setup. Source: the README at commit 865edb6; 16.50 is dated 2026-09-01.



&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A model bigger than the card at about 15 tokens a second, output unchanged.&lt;/li&gt;
&lt;li&gt;Three changes that made writing 1.5x faster and prompt reading 2.9x faster, and the ideas that failed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The numbers, with their conditions
&lt;/h2&gt;

&lt;p&gt;One RTX PRO 6000 Blackwell card, one request at a time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writing (decode), peak:&lt;/strong&gt; 16.50 tokens a second over 8 back-to-back 200-token answers to short prompts, with 66 hot experts per layer on the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing, as shipped:&lt;/strong&gt; 14.99 tokens a second with 54 hot experts per layer, leaving room for long prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading a prompt:&lt;/strong&gt; 1,771 and 1,729 tokens a second on two fresh prompts of about 10,300 tokens (5.82 s and 5.96 s to the first token).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Longest prompt:&lt;/strong&gt; 304,800 tokens in 205 s, 93% of the 327,680 configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; unchanged; on the prompt path, log-probabilities stay within the baseline's own run-to-run spread over 13,519 tokens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prompts are streamed and fresh, so no cache helps. The setups differ in more than one setting (context length, hot experts). The peak ran on 2026-09-01, the rest on 2026-09-02 (prompt runs logged in &lt;code&gt;artifacts/prefill-fresh-2026-09-02.txt&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  How it got faster
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The wall: one PCIe transfer per token
&lt;/h3&gt;

&lt;p&gt;It is an NVFP4 build of GLM-5.3-Flash, so its weights are 4-bit numbers. 90% of it is "experts": each layer has 288, and a router picks 8 per token. In the first setup, 105 GiB of experts lived in host RAM, and the GPU fetched the ones each token needed over PCIe, the link to the rest of the machine. That version wrote 10.77 tokens a second, and nearly all its time went to that one transfer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fycufcm2l0okn006ppgw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fycufcm2l0okn006ppgw2.png" alt="Diagram of the first setup: host RAM, 182 GiB dual-channel, read at 64.6 GB/s, holds 105 GiB of experts. PCIe 5.0 x16 carries 56.4 GB/s to the card; the gather kernel gets 52.1 GB/s, 92% of it. The RTX PRO 6000 has 96 GiB, holds the rest of the model, and has about 1,800 GB/s of its own memory bandwidth (vendor figure)." width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;
Measured on this machine before vLLM PR #53906 merged on 2026-09-03; the card's memory is the vendor figure. Data: the README at commit 865edb6.



&lt;h3&gt;
  
  
  A gather kernel that reads wide
&lt;/h3&gt;

&lt;p&gt;The link was not the problem; the way it was read was. The default gather (&lt;code&gt;index_select&lt;/code&gt;) used narrow, scattered loads. A small Triton kernel that reads wide blocks roughly doubled the rate, to 52.1 GB/s, 92% of the link. Its list of experts stays on the GPU, so the CPU never stops to read it, and CUDA graphs could come back: replaying a recorded step added 13%, for 13.53 tokens a second.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offload more, cache the hot experts
&lt;/h3&gt;

&lt;p&gt;vLLM decides what stays on the card one whole layer at a time, in module order, so the experts left in VRAM were arbitrary. Summed across all layers, my routing counts looked flat and capped any cache at about 1.15x, so I closed caching as a dead end. One groupby by layer reopened it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furij5i8n9ucpox6uvtc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Furij5i8n9ucpox6uvtc5.png" alt="Bar chart: share of expert picks taken by each layer's hottest experts, per layer against summed across layers. Hottest 10%: 38.9% against 13.8%. 25%: 64.1% against 32.5%. 35%: 74.3% against 43.2%. 50%: 85.7% against 58.8%." width="800" height="736"&gt;&lt;/a&gt;&lt;/p&gt;
The same routing data, counted two ways, measured before vLLM PR #53906 merged on 2026-09-03. Data: the repository's assets/routing-skew.svg at commit 865edb6.



&lt;p&gt;In each layer, the hottest 35% of the experts take 74% of the picks. So I offloaded more, 125 GiB instead of 105, and spent the freed VRAM on a cache of each layer's 66 most-used experts: 16.50 tokens a second. The same offload without the cache gave 11.34, slower than the 13.53 before it. The two only work as a pair.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefill in groups of 64 experts
&lt;/h3&gt;

&lt;p&gt;A long prompt (prefill) routes to nearly every expert, which overflowed the 64-expert staging buffer and fell back to slow reads at about 23 GB/s. Walking the experts in groups of 64 took prompt reading from 614 to about 1,750 tokens a second.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcp1sw66ioqdtec838zr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frcp1sw66ioqdtec838zr.png" alt="Bar chart titled 'Prompt reading got 2.9x faster', on prompts of about 10,300 tokens, streamed. Before grouped staging, zero-copy reads: 614 tokens a second, 16.8 s to the first token. Grouped staging, fresh prompt 1: 1,771 tokens a second, 5.82 s to the first token. Grouped staging, fresh prompt 2: 1,729 tokens a second, 5.96 s to the first token. Two grouped runs: about 1,750 tokens a second." width="800" height="611"&gt;&lt;/a&gt;&lt;/p&gt;
Fresh prompts, streamed. Source: the README at commit 865edb6; the grouped runs are logged in artifacts/prefill-fresh-2026-09-02.txt (2026-09-02).



&lt;p&gt;Grouping costs short prompts (a 14-token prompt waited 2.05 s for its first token instead of 0.85 s), so it switches on only at 48 tokens or more.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  What did not work, with the numbers
  &lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A recency (LRU) cache:&lt;/strong&gt; 9.0% hits against 5.6% at random.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Experts on the CPU:&lt;/strong&gt; 0.895x, slower; host RAM is only 1.15x the link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-profiling on sampled traffic:&lt;/strong&gt; 13.02 against 15.77 on prose; sampling flattens the skew.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;6 of the 8 experts per token:&lt;/strong&gt; 20.48 tokens a second, 24% faster, but the output looped ("Jupiter (planet) (planet) (planet)"). It stays off.
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug with no error
&lt;/h2&gt;

&lt;p&gt;Nothing crashed or logged an error; the model emitted the same token forever. vLLM can run the expert math with several kernels, and on this card two that compute in 4-bit directly return wrong numbers silently. Marlin, which unpacks the weights first and warns that the GPU lacks native FP4, is the one that works, and the fastest. Take this one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--kernel-config '{"moe_backend":"marlin"}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try it (it is not turnkey)
&lt;/h2&gt;

&lt;p&gt;You need an RTX PRO 6000 Blackwell card (96 GiB), vLLM with GLM-5.3-Flash support from pull request #53906, the repository's patch, and host RAM for the offloaded experts. Of the 181.3 GiB checkpoint, 163.3 GiB is experts; 125 GiB of them sit in RAM (the test machine has 182 GiB), and the rest stays on the card. The tests ran on a pre-release build, and the setup was not re-run on vLLM 0.30.0 or later. Boot takes 5 to 6 minutes. Without the card, you can still reuse the ideas.&lt;/p&gt;

&lt;p&gt;To start reading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/pctablet505/glm53-flash-single-gpu
&lt;span class="nb"&gt;cd &lt;/span&gt;glm53-flash-single-gpu
less RUNNING-GLM53-ON-ONE-GPU.md   &lt;span class="c"&gt;# the build and the eight bugs, in order&lt;/span&gt;
less config/serve-opt.sh           &lt;span class="c"&gt;# the shipped settings, each with its reason&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;One machine, one checkpoint and one pre-release vLLM build with local patches.&lt;/li&gt;
&lt;li&gt;Most numbers are single runs, with about ±1 token a second of spread.&lt;/li&gt;
&lt;li&gt;Without changing the output, this hardware tops out near 16 to 17 tokens a second.&lt;/li&gt;
&lt;li&gt;If a model that fits in 96 GiB will do, use it: Qwen3.8-Flash-Next (78.5 GiB) runs at 173 tokens a second here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still open: a masked variant of grouped prefill, which skips experts the prompt does not use, reached the first token in 0.36 s on the 14-token prompt, against 0.85 s for what ships. Its correctness was never properly tested. Have you tried masked staging for prefill?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pctablet505/glm53-flash-single-gpu/blob/main/RUNNING-GLM53-ON-ONE-GPU.md" rel="noopener noreferrer"&gt;RUNNING&lt;/a&gt;: start here: the build and all eight bugs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pctablet505/glm53-flash-single-gpu/blob/main/OPTIMIZING-GLM53-ON-ONE-GPU.md" rel="noopener noreferrer"&gt;OPTIMIZING&lt;/a&gt;: the cost model behind these numbers, and the settings that do nothing.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pctablet505/glm53-flash-single-gpu/blob/main/LAB-NOTEBOOK.md" rel="noopener noreferrer"&gt;LAB-NOTEBOOK&lt;/a&gt;: the same work day by day, mistakes included.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pctablet505/servedeck" rel="noopener noreferrer"&gt;servedeck&lt;/a&gt;: the small control plane I use to start, stop and switch the models on this workstation, with an OpenAI-compatible endpoint in front.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am &lt;a href="https://github.com/pctablet505" rel="noopener noreferrer"&gt;pctablet505&lt;/a&gt; on GitHub. I measure what actually happens and leave the failures in; the repositories above are where the rest of it lives.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the help of AI, from my own measurements; the logs are in the repository.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>gpu</category>
      <category>performance</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Keras #22561 speedups: compiled Keras on PyTorch now generates text 11.6x faster</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Fri, 16 Oct 2026 16:29:52 +0000</pubDate>
      <link>https://dev.to/pctablet505/keras-22561-speedups-compiled-keras-on-pytorch-now-generates-text-116x-faster-595m</link>
      <guid>https://dev.to/pctablet505/keras-22561-speedups-compiled-keras-on-pytorch-now-generates-text-116x-faster-595m</guid>
      <description>&lt;p&gt;Between master on Jul 18 and master on Oct 15, 2026, 15 of our PRs landed in Keras. Same GPU, same torch and jax versions, same models; only the Keras source changes. On the issue's test LM, compiled Keras[torch] now generates text 11.6× faster, torch.compile went from slowing Keras down to making it 6.56× faster than eager Keras, and an LLM forward makes 12–23% fewer Python calls on all four backends.&lt;/p&gt;

&lt;p&gt;Here is compiled generation at every merge. Pick a config or a workload, and hover a point to see its runs:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="640" src="https://codepen.io/ljcuhvpr-the-reactor/embed/YPZLPPx?height=640&amp;amp;default-tab=result&amp;amp;height=640&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate-32 throughput, July master vs today
&lt;/h2&gt;

&lt;p&gt;Tokens per second from the 2.1M-parameter test LM, 128 tokens per call (4 sequences × 32 steps); longer bars are faster. In July, compiling a Keras[torch] model made it slower: 1,458 tok/s compiled against 2,139 eager. Today the compiled model runs at 16,975 tok/s: 6.56× faster than eager Keras, within 1.53× of plain compiled PyTorch, down from 17.8×.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77ps08l5pqei7zwmhz65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F77ps08l5pqei7zwmhz65.png" alt="Generate-32 throughput in tokens per second, July versus October master. Keras on PyTorch with torch.compile: 1,458 to 16,975 (11.6 times); eager: 2,139 to 2,588; plain PyTorch 26,003 compiled and 21,870 eager. Keras on JAX under jit: 55,734 and 52,200; eager 437 to 479; plain JAX 57,642 jit and 1,438 eager." width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three merges that made torch.compile work with Keras
&lt;/h2&gt;

&lt;p&gt;Three merges each turned one compiled workload from slow to fast in a single step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj992ju4qavvg34s4hebe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj992ju4qavvg34s4hebe.png" alt="Three cards: #23327, merged Aug 30, compiled LLM forward 2.50 to 0.326 ms, 7.68 times faster; #23201, merged Sep 11, compiled CNN forward 0.945 to 0.156 ms, 6.05 times faster; #23647, merged Sep 30, compiled generate-32 57.2 to 7.82 ms, 7.32 times faster." width="800" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The three PRs: &lt;a href="https://github.com/keras-team/keras/pull/23327" rel="noopener noreferrer"&gt;#23327&lt;/a&gt; (attention), &lt;a href="https://github.com/keras-team/keras/pull/23201" rel="noopener noreferrer"&gt;#23201&lt;/a&gt; (dict ordering in the tree code) and &lt;a href="https://github.com/keras-team/keras/pull/23647" rel="noopener noreferrer"&gt;#23647&lt;/a&gt; (identity checks in the tree code). Graph breaks per forward pass, at every merge:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6d1dmfdlto12h1rl49lg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6d1dmfdlto12h1rl49lg.png" alt="Step chart of torch.compile graph breaks per forward pass at every merge: the LLM drops from 11 to 0 at #23327 and the CNN from 9 to 0 at #23201; both stay at 0 through October 15." width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Less Python in every Keras call, on all four backends
&lt;/h2&gt;

&lt;p&gt;Most of the series changed shared code (Layer.&lt;strong&gt;call&lt;/strong&gt;, op tracebacks, dtype handling, input checks), so the saving reaches every backend, not just torch. Python function calls per forward pass, July to today: PyTorch −23% on the LM and −33% on the CNN; JAX −12% on the LM and −17% on the CNN; TensorFlow −13% on the LM and −19% on the CNN; NumPy −16% on the LM and −18% on the CNN. Adding #23336 takes NumPy to −36% on the LM. These counts are exact and repeat call for call on any machine.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="800" src="https://codepen.io/ljcuhvpr-the-reactor/embed/ByWxyyP?height=800&amp;amp;default-tab=result&amp;amp;height=800&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Keras[jax]
&lt;/h2&gt;

&lt;p&gt;Under jax.jit the Python runs once at trace time, so Keras[jax] was already close to plain JAX and stays there: 2.30 → 2.45 ms per generate-32 against 2.22 ms for plain JAX. Eager Keras[jax] is where the saving shows in the exact counts: −12% Python calls per forward. The timing agrees: the sweep gives 293 → 267 ms (1.10×) and the back-to-back A/B rounds give 1.08×.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every config, every workload
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="720" src="https://codepen.io/ljcuhvpr-the-reactor/embed/wBJjBBE?height=720&amp;amp;default-tab=result&amp;amp;height=720&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Show all 12 configs and workloads (ms per call)
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keras config&lt;/th&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Plain framework (ms)&lt;/th&gt;
&lt;th&gt;Jul 18 (ms)&lt;/th&gt;
&lt;th&gt;Oct 15 (ms)&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;+ #23336 (ms)&lt;/th&gt;
&lt;th&gt;A/B check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · eager&lt;/td&gt;
&lt;td&gt;CNN forward&lt;/td&gt;
&lt;td&gt;0.064&lt;/td&gt;
&lt;td&gt;0.668&lt;/td&gt;
&lt;td&gt;0.477&lt;/td&gt;
&lt;td&gt;1.40×&lt;/td&gt;
&lt;td&gt;0.466&lt;/td&gt;
&lt;td&gt;1.39×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · eager&lt;/td&gt;
&lt;td&gt;LLM forward&lt;/td&gt;
&lt;td&gt;0.189&lt;/td&gt;
&lt;td&gt;1.88&lt;/td&gt;
&lt;td&gt;1.55&lt;/td&gt;
&lt;td&gt;1.22×&lt;/td&gt;
&lt;td&gt;1.48&lt;/td&gt;
&lt;td&gt;1.20×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · eager&lt;/td&gt;
&lt;td&gt;LLM generate-32&lt;/td&gt;
&lt;td&gt;5.85&lt;/td&gt;
&lt;td&gt;59.8&lt;/td&gt;
&lt;td&gt;49.5&lt;/td&gt;
&lt;td&gt;1.21×&lt;/td&gt;
&lt;td&gt;47.2&lt;/td&gt;
&lt;td&gt;1.20×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · torch.compile&lt;/td&gt;
&lt;td&gt;CNN forward&lt;/td&gt;
&lt;td&gt;0.076&lt;/td&gt;
&lt;td&gt;1.28&lt;/td&gt;
&lt;td&gt;0.160&lt;/td&gt;
&lt;td&gt;8.03×&lt;/td&gt;
&lt;td&gt;0.161&lt;/td&gt;
&lt;td&gt;8.41×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · torch.compile&lt;/td&gt;
&lt;td&gt;LLM forward&lt;/td&gt;
&lt;td&gt;0.160&lt;/td&gt;
&lt;td&gt;2.69&lt;/td&gt;
&lt;td&gt;0.302&lt;/td&gt;
&lt;td&gt;8.93×&lt;/td&gt;
&lt;td&gt;0.299&lt;/td&gt;
&lt;td&gt;9.60×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[torch] · torch.compile&lt;/td&gt;
&lt;td&gt;LLM generate-32&lt;/td&gt;
&lt;td&gt;4.92&lt;/td&gt;
&lt;td&gt;87.8&lt;/td&gt;
&lt;td&gt;7.54&lt;/td&gt;
&lt;td&gt;11.6×&lt;/td&gt;
&lt;td&gt;7.66&lt;/td&gt;
&lt;td&gt;12.4×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · eager&lt;/td&gt;
&lt;td&gt;CNN forward&lt;/td&gt;
&lt;td&gt;0.508&lt;/td&gt;
&lt;td&gt;1.34&lt;/td&gt;
&lt;td&gt;1.23&lt;/td&gt;
&lt;td&gt;1.08×&lt;/td&gt;
&lt;td&gt;1.18&lt;/td&gt;
&lt;td&gt;1.15×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · eager&lt;/td&gt;
&lt;td&gt;LLM forward&lt;/td&gt;
&lt;td&gt;2.79&lt;/td&gt;
&lt;td&gt;8.46&lt;/td&gt;
&lt;td&gt;8.38&lt;/td&gt;
&lt;td&gt;1.01×&lt;/td&gt;
&lt;td&gt;7.90&lt;/td&gt;
&lt;td&gt;1.12×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · eager&lt;/td&gt;
&lt;td&gt;LLM generate-32&lt;/td&gt;
&lt;td&gt;89.0&lt;/td&gt;
&lt;td&gt;293&lt;/td&gt;
&lt;td&gt;267&lt;/td&gt;
&lt;td&gt;1.10×&lt;/td&gt;
&lt;td&gt;269&lt;/td&gt;
&lt;td&gt;1.08×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · jax.jit&lt;/td&gt;
&lt;td&gt;CNN forward&lt;/td&gt;
&lt;td&gt;0.077&lt;/td&gt;
&lt;td&gt;0.079&lt;/td&gt;
&lt;td&gt;0.081&lt;/td&gt;
&lt;td&gt;0.97×&lt;/td&gt;
&lt;td&gt;0.080&lt;/td&gt;
&lt;td&gt;1.02×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · jax.jit&lt;/td&gt;
&lt;td&gt;LLM forward&lt;/td&gt;
&lt;td&gt;0.120&lt;/td&gt;
&lt;td&gt;0.131&lt;/td&gt;
&lt;td&gt;0.135&lt;/td&gt;
&lt;td&gt;0.97×&lt;/td&gt;
&lt;td&gt;0.130&lt;/td&gt;
&lt;td&gt;1.05×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keras[jax] · jax.jit&lt;/td&gt;
&lt;td&gt;LLM generate-32&lt;/td&gt;
&lt;td&gt;2.22&lt;/td&gt;
&lt;td&gt;2.30&lt;/td&gt;
&lt;td&gt;2.45&lt;/td&gt;
&lt;td&gt;0.94×&lt;/td&gt;
&lt;td&gt;2.27&lt;/td&gt;
&lt;td&gt;1.05×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A/B check: July ÷ today from 3 separate back-to-back rounds (higher is a bigger speedup).&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Each of our PRs, measured where it landed
&lt;/h2&gt;

&lt;p&gt;Each PR was timed on the commit just before and just after it merged, so every row is that one PR's own effect. Δ is just after the merge minus just before it; negative is better.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Show the per-PR table
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PR&lt;/th&gt;
&lt;th&gt;Merged&lt;/th&gt;
&lt;th&gt;What it changed&lt;/th&gt;
&lt;th&gt;Δ calls/fwd torch&lt;/th&gt;
&lt;th&gt;JAX&lt;/th&gt;
&lt;th&gt;TF&lt;/th&gt;
&lt;th&gt;NumPy&lt;/th&gt;
&lt;th&gt;Δ eager gen-32 (ms)&lt;/th&gt;
&lt;th&gt;Δ compiled gen-32 (ms)&lt;/th&gt;
&lt;th&gt;Graph breaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23183" rel="noopener noreferrer"&gt;#23183&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;07-28&lt;/td&gt;
&lt;td&gt;Traceback details are built only when an op fails, not on every call&lt;/td&gt;
&lt;td&gt;−598&lt;/td&gt;
&lt;td&gt;−586&lt;/td&gt;
&lt;td&gt;−622&lt;/td&gt;
&lt;td&gt;−546&lt;/td&gt;
&lt;td&gt;−2.9&lt;/td&gt;
&lt;td&gt;−5.2&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23327" rel="noopener noreferrer"&gt;#23327&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;08-30&lt;/td&gt;
&lt;td&gt;Removed the torch.compile graph break in the torch backend's dot_product_attention&lt;/td&gt;
&lt;td&gt;+6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.40&lt;/td&gt;
&lt;td&gt;−12.3&lt;/td&gt;
&lt;td&gt;LLM 11 → 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23189" rel="noopener noreferrer"&gt;#23189&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-03&lt;/td&gt;
&lt;td&gt;Fixed an always-true device check that sent every tensor through .to()&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+38&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.28&lt;/td&gt;
&lt;td&gt;−1.1&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23200" rel="noopener noreferrer"&gt;#23200&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-05&lt;/td&gt;
&lt;td&gt;Layer.&lt;strong&gt;call&lt;/strong&gt; stops re-setting built/_called on every call&lt;/td&gt;
&lt;td&gt;−1,848&lt;/td&gt;
&lt;td&gt;−1,098&lt;/td&gt;
&lt;td&gt;−1,188&lt;/td&gt;
&lt;td&gt;−1,100&lt;/td&gt;
&lt;td&gt;−4.9&lt;/td&gt;
&lt;td&gt;−10.5&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23182" rel="noopener noreferrer"&gt;#23182&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-10&lt;/td&gt;
&lt;td&gt;O(1) dtype membership and a fast path for canonical dtype strings&lt;/td&gt;
&lt;td&gt;−300&lt;/td&gt;
&lt;td&gt;−82&lt;/td&gt;
&lt;td&gt;−497&lt;/td&gt;
&lt;td&gt;−116&lt;/td&gt;
&lt;td&gt;−0.89&lt;/td&gt;
&lt;td&gt;−1.5&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23199" rel="noopener noreferrer"&gt;#23199&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-10&lt;/td&gt;
&lt;td&gt;Dropped a redundant channels_last copy on torch conv inputs&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−50&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.40&lt;/td&gt;
&lt;td&gt;−0.29&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23201" rel="noopener noreferrer"&gt;#23201&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-11&lt;/td&gt;
&lt;td&gt;Torch tree code made traceable: canonical dict key order&lt;/td&gt;
&lt;td&gt;−142&lt;/td&gt;
&lt;td&gt;+8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.55&lt;/td&gt;
&lt;td&gt;−5.2&lt;/td&gt;
&lt;td&gt;CNN 9 → 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23296" rel="noopener noreferrer"&gt;#23296&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-11&lt;/td&gt;
&lt;td&gt;Keeps call-context kwargs on the recorded Node (fix)&lt;/td&gt;
&lt;td&gt;−109&lt;/td&gt;
&lt;td&gt;−78&lt;/td&gt;
&lt;td&gt;−14&lt;/td&gt;
&lt;td&gt;−46&lt;/td&gt;
&lt;td&gt;−0.24&lt;/td&gt;
&lt;td&gt;−0.09&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23187" rel="noopener noreferrer"&gt;#23187&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-12&lt;/td&gt;
&lt;td&gt;TorchTrainer skips a full flatten walk to find the batch size&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−26&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.14&lt;/td&gt;
&lt;td&gt;−0.21&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23301" rel="noopener noreferrer"&gt;#23301&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-12&lt;/td&gt;
&lt;td&gt;Layer name scope opened once per call, not twice&lt;/td&gt;
&lt;td&gt;−330&lt;/td&gt;
&lt;td&gt;−568&lt;/td&gt;
&lt;td&gt;−638&lt;/td&gt;
&lt;td&gt;−330&lt;/td&gt;
&lt;td&gt;−0.89&lt;/td&gt;
&lt;td&gt;−0.21&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23190" rel="noopener noreferrer"&gt;#23190&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-15&lt;/td&gt;
&lt;td&gt;torch slice() keeps dynamic dims under torch.export&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−0.23&lt;/td&gt;
&lt;td&gt;−0.16&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23647" rel="noopener noreferrer"&gt;#23647&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;09-30&lt;/td&gt;
&lt;td&gt;Torch tree code stops comparing id()s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−8&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−0.58&lt;/td&gt;
&lt;td&gt;−49.4&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23645" rel="noopener noreferrer"&gt;#23645&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10-07&lt;/td&gt;
&lt;td&gt;torch pooling ops run in channels_last&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.10&lt;/td&gt;
&lt;td&gt;−0.05&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23184" rel="noopener noreferrer"&gt;#23184&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10-13&lt;/td&gt;
&lt;td&gt;Fast path for single-InputSpec input checks&lt;/td&gt;
&lt;td&gt;−102&lt;/td&gt;
&lt;td&gt;−59&lt;/td&gt;
&lt;td&gt;−63&lt;/td&gt;
&lt;td&gt;−63&lt;/td&gt;
&lt;td&gt;+0.06&lt;/td&gt;
&lt;td&gt;+0.05&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23920" rel="noopener noreferrer"&gt;#23920&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10-15&lt;/td&gt;
&lt;td&gt;Functional models reject a mismatched last axis (fix)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;−54&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+0.10&lt;/td&gt;
&lt;td&gt;+0.02&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/keras-team/keras/pull/23336" rel="noopener noreferrer"&gt;#23336&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;not merged yet (approved)&lt;/td&gt;
&lt;td&gt;Memoized dtype promotion in result_type&lt;/td&gt;
&lt;td&gt;−713&lt;/td&gt;
&lt;td&gt;−378&lt;/td&gt;
&lt;td&gt;−1,471&lt;/td&gt;
&lt;td&gt;−3,399&lt;/td&gt;
&lt;td&gt;−2.2&lt;/td&gt;
&lt;td&gt;+0.12&lt;/td&gt;
&lt;td&gt;·&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot functions in one Keras[torch] LLM forward
&lt;/h3&gt;

&lt;p&gt;&lt;/p&gt;
  Show the hot-functions table
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Jul 18&lt;/th&gt;
&lt;th&gt;Oct 15&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;+ #23336&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;isinstance&lt;/td&gt;
&lt;td&gt;2,600&lt;/td&gt;
&lt;td&gt;1,740&lt;/td&gt;
&lt;td&gt;−33%&lt;/td&gt;
&lt;td&gt;1,680&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;traceback error wrappers&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;−35%&lt;/td&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;thread-local config lookups&lt;/td&gt;
&lt;td&gt;624&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;td&gt;−18%&lt;/td&gt;
&lt;td&gt;513&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;standardize_dtype&lt;/td&gt;
&lt;td&gt;177&lt;/td&gt;
&lt;td&gt;244&lt;/td&gt;
&lt;td&gt;+38%&lt;/td&gt;
&lt;td&gt;184&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tree.flatten&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;−28%&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tree.map_structure&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;−39%&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InputSpec checks&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;+0%&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CallSpec objects built&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;+0%&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inspect.Signature.bind&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;+0%&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;convert_to_tensor&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;+0%&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;get_device&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;td&gt;+0%&lt;/td&gt;
&lt;td&gt;138&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How this was measured
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine:&lt;/strong&gt; one NVIDIA RTX PRO 6000 Blackwell, kept idle during the runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Software:&lt;/strong&gt; the same for every run: torch 2.13.0, jax 0.11.2, Python 3.14.7. Only the Keras source changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commits:&lt;/strong&gt; 31 snapshots of master: Jul 18 (&lt;code&gt;a2f8eac8&lt;/code&gt;), just before and just after each of the 15 merges, Oct 15 (&lt;code&gt;cc9de2b31&lt;/code&gt;), and Oct 15 plus #23336.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Models:&lt;/strong&gt; the issue's two test models: a small CNN (114k parameters) and a 2-layer transformer LM (2.1M parameters, 32-token greedy generate).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timing:&lt;/strong&gt; the issue's benchmark script, one process at a time, warm-up first, a GPU sync after every call. Each number is a median, and the whole sweep ran three times for PyTorch and twice for JAX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-check:&lt;/strong&gt; a separate back-to-back run of July against October gives 12.4× compiled and 1.20× eager (the sweep: 11.6× and 1.21×).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counts:&lt;/strong&gt; Python calls from cProfile, graph breaks from &lt;code&gt;torch._dynamo.explain&lt;/code&gt;. These are exact and repeat on any machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not covered:&lt;/strong&gt; training, CPU, larger models, and TensorFlow or NumPy timing (call counts only).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The work is tracked in &lt;a href="https://github.com/keras-team/keras/issues/22561" rel="noopener noreferrer"&gt;issue #22561&lt;/a&gt;. Thank you to the Keras maintainers who reviewed and merged these PRs.&lt;/p&gt;

</description>
      <category>python</category>
      <category>pytorch</category>
      <category>performance</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Why two out of three open-source projects don't get a Porchlamp score</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Fri, 16 Oct 2026 00:15:14 +0000</pubDate>
      <link>https://dev.to/pctablet505/why-7167-of-11000-sampled-projects-get-no-score-gate-by-gate-4l5o</link>
      <guid>https://dev.to/pctablet505/why-7167-of-11000-sampled-projects-get-no-score-gate-by-gate-4l5o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Of 11,000 open-source projects, only about 1 in 3 gets a score. The rest aren't hidden: each one is listed with a plain reason, like "no recent activity" or "most outside pull requests go unanswered".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmef3pnp20qqs5xlke22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhmef3pnp20qqs5xlke22.png" alt="Horizontal bar chart of the 11,000 sampled projects by outcome: ranked 3,833; not ranked no recent activity 2,475, not enough data 1,377, black hole 1,133, not software 1,067, archived 624, unlicensed 491; 7,167 projects get no score in total." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Porchlamp's October 2026 snapshot, 3,833 of 11,000 GitHub projects received a score; the other 7,167 are listed with the reason they were not ranked.&lt;/p&gt;

&lt;h2&gt;
  
  
  How are open-source projects ranked fairly?
&lt;/h2&gt;

&lt;p&gt;A ranking is only useful if every project in it is a real choice for a contributor. A project that's archived, has no license, or hasn't had a commit in months isn't one, however good its numbers look.&lt;/p&gt;

&lt;p&gt;So before scoring, every project goes through a few simple checks. Fail one, and the project is listed with the reason instead of a rank.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks, in plain words
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is it software?&lt;/strong&gt; Docs-only repos, awesome lists and data dumps are left out. (1,067 projects)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it still a real project?&lt;/strong&gt; Archived repos, mirrors and most forks are left out. (624)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it have an open-source license?&lt;/strong&gt; Without one, you can't safely contribute. (491)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is anyone still working on it?&lt;/strong&gt; No human commits in six months means it's stale. This is the biggest group. (2,475)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does anyone answer outside PRs?&lt;/strong&gt; If most pull requests from outsiders get no human response within two weeks, it's a &lt;em&gt;black hole&lt;/em&gt;. (1,133)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then there's a last group: projects that pass every check, but where too little could be measured to score them fairly. They're listed as &lt;em&gt;unrated&lt;/em&gt; (1,377).&lt;/p&gt;

&lt;p&gt;That leaves &lt;strong&gt;3,833 ranked projects.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The black-hole check is careful on purpose
&lt;/h2&gt;

&lt;p&gt;Calling a project a black hole is a strong statement, so the check leans towards giving projects the benefit of the doubt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It needs &lt;strong&gt;at least ten&lt;/strong&gt; outside PRs. With fewer, there's no verdict.&lt;/li&gt;
&lt;li&gt;PRs younger than two weeks don't count yet.&lt;/li&gt;
&lt;li&gt;Bots, drafts, maintainers' own PRs and brand-new accounts are ignored.&lt;/li&gt;
&lt;li&gt;The rule only fires when the data clearly says "most go unanswered", not on a borderline case.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Undecided isn't failed
&lt;/h2&gt;

&lt;p&gt;Some projects do their reviews somewhere else, on Gerrit or a mailing list, and their GitHub PRs only point there. Porchlamp spots those and leaves them undecided rather than calling them silent. Eight projects are handled that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the reasons yourself
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://porchlamp.org/?tab=notranked" rel="noopener noreferrer"&gt;Not ranked list on Porchlamp&lt;/a&gt;&lt;/strong&gt; shows every project that didn't get a score, with its reason. If you think a check treats a project unfairly, &lt;a href="https://github.com/pctablet505/porchlamp" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; and say why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which of these checks would you loosen, and which would you make stricter?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>datascience</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Where to send your first open-source pull request (the projects that actually answer)</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 15 Oct 2026 23:52:12 +0000</pubDate>
      <link>https://dev.to/pctablet505/what-a-first-contribution-ranking-actually-weighs-11ph</link>
      <guid>https://dev.to/pctablet505/what-a-first-contribution-ranking-actually-weighs-11ph</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; For a first contribution, the projects worth your time are the ones where humans answer outside pull requests and first changes are easy to make. Right now the list starts with llama.cpp, Meshery, open-design, stdlib and Ant Design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Porchlamp's October 2026 snapshot of 11,000 GitHub projects, the first-contribution ranking scores projects mostly on two things: how quickly humans respond to outside pull requests (35% of the score) and how easy a first change is (30%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Which open-source projects are good for a first contribution?
&lt;/h2&gt;

&lt;p&gt;The usual advice for a first open-source contribution is to pick something popular. But popular projects are often the slowest to answer a stranger. Your first PR can sit for weeks with no reply, and that's usually where people give up.&lt;/p&gt;

&lt;p&gt;So I built a ranking that asks a different question: &lt;strong&gt;if you're new, where will your pull request actually get seen?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks at
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jj2ngkglmp1aizovrrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jj2ngkglmp1aizovrrp.png" alt="Horizontal bar chart of the weights in the first-contribution ranking: responsiveness 35% (do humans answer outside PRs), accessibility 30% (how easy a first change is), governance 15% (license, CLA and how decisions are made), criticality 10% (how much other software depends on it), rigor 10% (review and CI practices); responsiveness and accessibility are highlighted." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things carry most of the weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Responsiveness (35%):&lt;/strong&gt; how fast a human answers outside PRs, and how many of those PRs get a decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility (30%):&lt;/strong&gt; good first issues that are actually open, and whether first-time contributors get merged and come back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest is governance (15%), how much other software depends on the project (10%), and review and CI practices (10%).&lt;/p&gt;

&lt;h2&gt;
  
  
  The best projects for a first contribution right now
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w89gvr6hm3mhl2u0loh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9w89gvr6hm3mhl2u0loh.png" alt="Close-up screenshot of the Porchlamp leaderboard, Ranked tab, first-timer persona: the table header and the first rows, with Rank, Repository, Score and the change-in-rank column in large type." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;llama.cpp&lt;/li&gt;
&lt;li&gt;Meshery&lt;/li&gt;
&lt;li&gt;open-design&lt;/li&gt;
&lt;li&gt;stdlib&lt;/li&gt;
&lt;li&gt;Ant Design&lt;/li&gt;
&lt;li&gt;Organic Maps&lt;/li&gt;
&lt;li&gt;Apache Maka&lt;/li&gt;
&lt;li&gt;Cluster API&lt;/li&gt;
&lt;li&gt;freeCodeCamp&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few of these jump up a lot compared with the default ranking. open-design climbs 63 places and stdlib 49. That means their strengths are exactly the things a newcomer needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two labels you'll see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"B (capped)":&lt;/strong&gt; not a bad grade. It means only part of the project's data could be measured, so the tier can't go higher yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Δ rank column:&lt;/strong&gt; how far a project moves between the default ranking and this one. It isn't movement over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;strong&gt;&lt;a href="https://porchlamp.org/?persona=first_timer" rel="noopener noreferrer"&gt;Porchlamp first-contribution rankings&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Pick two or three projects in a language you know.&lt;/li&gt;
&lt;li&gt;Open each one's pull requests on GitHub and read a few recent ones from outside contributors. Do they get answers?&lt;/li&gt;
&lt;li&gt;Then find a good first issue and start small.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A high rank isn't a promise about your particular PR, but it's a much better bet than stars.&lt;/p&gt;

&lt;p&gt;If a row looks wrong to you, an issue on the &lt;a href="https://github.com/pctablet505/porchlamp" rel="noopener noreferrer"&gt;repository&lt;/a&gt; with the pull requests that show it is the most useful thing you can send.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which project gave you your first merged PR, and how long did it take?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>beginners</category>
      <category>hacktoberfest</category>
    </item>
    <item>
      <title>Which ecosystems leave your pull request hanging? npm and PyPI more than Rust, Go or Java</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 15 Oct 2026 22:56:51 +0000</pubDate>
      <link>https://dev.to/pctablet505/fewer-cratesio-projects-go-silent-on-outside-pull-requests-than-npm-or-pypi-56am</link>
      <guid>https://dev.to/pctablet505/fewer-cratesio-projects-go-silent-on-outside-pull-requests-than-npm-or-pypi-56am</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you send a pull request to an npm or PyPI project, you're more likely to hear nothing back than with a Rust, Go or Java one. About 3 in 10 active npm and PyPI projects leave most outside PRs unanswered for two weeks, against about 1 in 5 for crates.io.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3ipnese5kpczzth7m8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy3ipnese5kpczzth7m8c.png" alt="Horizontal bar chart of the share of active projects that are black holes, by ecosystem, highest first: PyPI 33.8%, npm 30.0%, Go 22.2%, systems 20.1%, crates.io 20.0%, Maven 19.4%; crates.io is highlighted." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Porchlamp's October 2026 snapshot, 33.8% of active PyPI projects and 30.0% of active npm projects left most outside pull requests unanswered for two weeks, compared with 20.0% of crates.io projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the ecosystem matter?
&lt;/h2&gt;

&lt;p&gt;To find out, I split my data on outside pull requests by package ecosystem.&lt;/p&gt;

&lt;p&gt;A quick definition first. I call a project a &lt;strong&gt;black hole&lt;/strong&gt; when most pull requests from outside contributors get no human reply, review or merge within two weeks. Only active, licensed projects with at least ten outside PRs are judged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which ecosystems answer outside pull requests?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ecosystem&lt;/th&gt;
&lt;th&gt;Projects judged&lt;/th&gt;
&lt;th&gt;Black holes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PyPI&lt;/td&gt;
&lt;td&gt;968&lt;/td&gt;
&lt;td&gt;33.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;1,283&lt;/td&gt;
&lt;td&gt;30.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;609&lt;/td&gt;
&lt;td&gt;22.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Systems (C, C++ and others)&lt;/td&gt;
&lt;td&gt;573&lt;/td&gt;
&lt;td&gt;20.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;crates.io&lt;/td&gt;
&lt;td&gt;586&lt;/td&gt;
&lt;td&gt;20.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maven&lt;/td&gt;
&lt;td&gt;278&lt;/td&gt;
&lt;td&gt;19.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two groups stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm and PyPI:&lt;/strong&gt; roughly 3 in 10 projects go quiet on outsiders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust, Go, Java and systems projects:&lt;/strong&gt; closer to 1 in 5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rust isn't alone at the good end. Maven is a touch better, and systems projects are level with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wouldn't read into it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's not about community character.&lt;/strong&gt; These are averages over very different projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size isn't controlled for.&lt;/strong&gt; Bigger projects go silent more often in general, so an ecosystem with more big projects looks worse for that reason alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-GitHub review isn't counted.&lt;/strong&gt; Projects that review on Gerrit or a mailing list aren't judged, and they may cluster in some ecosystems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you're picking where to send your first PR, this is a mild nudge, not a guarantee about any single project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the project, not just the ecosystem
&lt;/h2&gt;

&lt;p&gt;The full ranking is on &lt;strong&gt;&lt;a href="https://porchlamp.org/" rel="noopener noreferrer"&gt;Porchlamp&lt;/a&gt;&lt;/strong&gt;. Here is &lt;a href="https://porchlamp.org/?eco=crates" rel="noopener noreferrer"&gt;the Porchlamp ranking of crates.io projects&lt;/a&gt;; switch the filter to see any other ecosystem. Every black-hole verdict lists the pull requests behind it. If a project looks misjudged, &lt;a href="https://github.com/pctablet505/porchlamp" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; with the evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Would you expect the Rust number to hold up once project size is taken into account?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>python</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I measured silence: 212,390 outside pull requests across 11,000 GitHub projects</title>
      <dc:creator>Rahul Kumar</dc:creator>
      <pubDate>Thu, 15 Oct 2026 22:03:24 +0000</pubDate>
      <link>https://dev.to/pctablet505/i-measured-silence-212390-outside-pull-requests-across-11000-github-projects-3l60</link>
      <guid>https://dev.to/pctablet505/i-measured-silence-212390-outside-pull-requests-across-11000-github-projects-3l60</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; More than 1 in 4 active open-source projects leave most pull requests from outside contributors with no human response for two weeks. The more stars a project has, the more likely it is to be one of them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In Porchlamp's October 2026 snapshot of 11,000 GitHub projects, 1,133 of the 4,297 active projects (26.4%) left most outside pull requests with no human reply, review or merge within 14 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I looked
&lt;/h2&gt;

&lt;p&gt;One of my pull requests once sat for four months before anyone merged it. Around the same time, a different project merged five of mine in a single day.&lt;/p&gt;

&lt;p&gt;Same person, same kind of code. The only difference was the project. And nothing on a GitHub page tells you which kind you're about to deal with. Stars tell you a project is popular. They don't tell you if anyone will read your PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;I followed six months of pull requests from outside contributors to 11,000 GitHub projects: &lt;strong&gt;212,390 PRs from 110,605 people.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For every PR the question was simple: &lt;strong&gt;did a human respond within two weeks?&lt;/strong&gt; A reply, a review or a merge all count. A bot comment doesn't.&lt;/p&gt;

&lt;p&gt;To keep it fair I left out bots, maintainers' own PRs, drafts, PRs the author closed themselves, and brand-new accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do popular open-source projects answer pull requests?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Silence is common.&lt;/strong&gt; Of the 4,297 active, licensed projects with at least ten outside PRs, 1,133 left most of them unanswered for two weeks. I call these &lt;em&gt;black holes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8w6g6n1ilhmvc7p1ic0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8w6g6n1ilhmvc7p1ic0.png" alt="Card: 1 in 4. 1,133 of 4,297 active projects (26.4%) left most outside pull requests with no human reply, review or merge for two weeks." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. More stars, more silence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jx4j7i0toici0lq0hib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2jx4j7i0toici0lq0hib.png" alt="Bar chart titled More stars, more silence: the share of active projects that are black holes is 21% for projects with 1k to 10k stars, 31% for 10k to 50k and 39% for 50k and more, out of 2,190, 1,862 and 244 projects." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;About 1 in 5 projects with 1k to 10k stars is a black hole. Above 50k stars it's closer to 2 in 5.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Popular isn't the same as welcoming.&lt;/strong&gt; Of the 100 most-starred projects, just 9 reach the top 100 of the default Porchlamp ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't mean
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's not a verdict on maintainers.&lt;/strong&gt; Big projects get far more PRs than anyone can review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It only sees GitHub.&lt;/strong&gt; Projects that review on a mailing list or Gerrit aren't judged at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's a snapshot&lt;/strong&gt; (April to October 2026), so it will drift.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check a project before you give it a weekend
&lt;/h2&gt;

&lt;p&gt;I turned all of this into a free site, the &lt;strong&gt;Porchlamp rankings&lt;/strong&gt;. It ranks open-source projects by what happens to a stranger's pull request, and you can re-rank it for your goal: a first contribution, career value, deep systems work, or AI-assisted work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F55hscjqpe3q6selp3dep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F55hscjqpe3q6selp3dep.png" alt="Close-up screenshot of the Porchlamp leaderboard, Ranked tab, balanced persona: the table header and the first rows, with Rank, Repository, Score, Coverage, Tier and Quadrant columns in large type." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://porchlamp.org/" rel="noopener noreferrer"&gt;See the Porchlamp rankings of 11,000 open-source projects&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Looking for your first project? &lt;a href="https://dev.to/pctablet505/what-a-first-contribution-ranking-actually-weighs-11ph"&gt;Here's where to send your first open-source pull request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every black-hole verdict links to the pull requests behind it, so you can check the evidence yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the longest one of your PRs has ever waited?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>career</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
