<?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: GeneLab_999</title>
    <description>The latest articles on DEV Community by GeneLab_999 (@genelab_999).</description>
    <link>https://dev.to/genelab_999</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%2F3743545%2Fe831cbea-2547-4969-b084-e435a8e2fc05.png</url>
      <title>DEV Community: GeneLab_999</title>
      <link>https://dev.to/genelab_999</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/genelab_999"/>
    <language>en</language>
    <item>
      <title>Proving what a URL allowlist decides, in Lean 4</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Wed, 28 Oct 2026 17:46:41 +0000</pubDate>
      <link>https://dev.to/genelab_999/proving-what-a-url-allowlist-decides-in-lean-4-3803</link>
      <guid>https://dev.to/genelab_999/proving-what-a-url-allowlist-decides-in-lean-4-3803</guid>
      <description>&lt;p&gt;&lt;em&gt;This article describes URLFence as of commit &lt;a href="https://github.com/hiroki-abe-58/urlfence/tree/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3" rel="noopener noreferrer"&gt;&lt;code&gt;ea3c061&lt;/code&gt;&lt;/a&gt; (CI: &lt;a href="https://github.com/hiroki-abe-58/urlfence/actions/runs/40618042270" rel="noopener noreferrer"&gt;run 40618042270&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AI agents fetch web pages for you, and their tools are supposed to stay on an allowlist of hosts.&lt;br&gt;
That kind of check ("is this URL's host allowed?") keeps going wrong in the same few ways, in agent&lt;br&gt;
tools and elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A string prefix or suffix instead of a hostname comparison.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;CVE-2026-24052 (Claude Code WebFetch before 1.0.111) and CVE-2026-46341 (Apify MCP server before
0.9.21) used &lt;code&gt;startsWith&lt;/code&gt;, so &lt;code&gt;modelcontextprotocol.io.example.com&lt;/code&gt; and &lt;code&gt;docs.apify.com.evil.com&lt;/code&gt;
passed.&lt;/li&gt;
&lt;li&gt;CVE-2026-33393 (Discourse's spam-host allowlist) used &lt;code&gt;end_with?&lt;/code&gt; without a dot boundary, so
&lt;code&gt;attacker-example.com&lt;/code&gt; passed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Userinfo taken for the host:&lt;/strong&gt; CVE-2025-47241 (browser-use before 0.1.45).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalising before checking:&lt;/strong&gt; CVE-2026-6322 (fast-uri before 3.1.2) decoded a percent-encoded
&lt;code&gt;@&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;URLFence&lt;/strong&gt; (&lt;a href="https://github.com/hiroki-abe-58/urlfence" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence&lt;/a&gt;) is a small Lean 4 library and CLI that makes&lt;br&gt;
this decision, with theorems about what it decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;.lake/build/bin/urlfence check &lt;span class="nt"&gt;--allow&lt;/span&gt; docs.python.org &lt;span class="nt"&gt;--allow&lt;/span&gt; &lt;span class="s1"&gt;'*.example.com'&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; https://docs.python.org/3/ https://docs.python.org.evil.com/ https://docs.python.org@evil.com/
&lt;span class="go"&gt;{"input":"https://docs.python.org/3/","decision":"allow","scheme":"https","host":"docs.python.org","port":null}
{"input":"https://docs.python.org.evil.com/","decision":"deny","scheme":"https","host":"docs.python.org.evil.com","port":null}
{"input":"https://docs.python.org@evil.com/","decision":"reject","reason":"userinfo"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It &lt;strong&gt;fails closed&lt;/strong&gt;. It accepts only a strict, canonical form of &lt;code&gt;http(s)&lt;/code&gt; URLs and refuses&lt;br&gt;
everything else with a reason:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;whitespace, backslashes and non-ASCII characters;&lt;/li&gt;
&lt;li&gt;uppercase letters in the scheme or host;&lt;/li&gt;
&lt;li&gt;userinfo and percent signs in the authority;&lt;/li&gt;
&lt;li&gt;IPv6 literals and IDNA &lt;code&gt;xn--&lt;/code&gt; labels;&lt;/li&gt;
&lt;li&gt;browser-style shortcuts like &lt;code&gt;https:/x&lt;/code&gt;, &lt;code&gt;127.1&lt;/code&gt; or &lt;code&gt;0x7f.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That costs some convenience. In exchange, an accepted URL leaves little room for a second reading:&lt;br&gt;
the proofs pin how URLFence reads it, and a test checks that Node's WHATWG parser agrees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The theorems
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parse_authority&lt;/code&gt;: in an accepted URL, the text from &lt;code&gt;//&lt;/code&gt; up to the first &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt; or the end
is exactly the host followed by an optional &lt;code&gt;:port&lt;/code&gt;. &lt;code&gt;isDelim_iff&lt;/code&gt; pins those three characters.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parseHost_chars&lt;/code&gt;: the host contains only &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;.&lt;/code&gt;, so it has no &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;:&lt;/code&gt; or
brackets to confuse anyone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub_matches_iff_render&lt;/code&gt;: a &lt;code&gt;*.example.com&lt;/code&gt; pattern matches exactly when the host ends with &lt;code&gt;.&lt;/code&gt;
followed by &lt;code&gt;example.com&lt;/code&gt;, which is the dot boundary the suffix bugs missed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_allow_iff&lt;/code&gt;: a URL is allowed iff it parses, its scheme is allowed, it matches an allowed
pattern, and it matches no denied pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The audit script requires 55 published theorems by name (22 for URLs, 33 for the address check&lt;br&gt;
below). All of them depend only on Lean's standard&lt;br&gt;
axioms, and each one is pinned together with the library definitions it depends on. The audit also&lt;br&gt;
replays every module through Lean's kernel checker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tested against real parsers
&lt;/h2&gt;

&lt;p&gt;The proofs are about URLFence's own parser. Whether a browser-grade parser reads the URL the same way&lt;br&gt;
is a &lt;strong&gt;tested&lt;/strong&gt; claim, not a proven one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A differential test runs about 10,000 random and adversarial URLs, including printable ASCII
characters inserted into hosts at random positions.&lt;/li&gt;
&lt;li&gt;For every URL URLFence accepts, Node's WHATWG &lt;code&gt;URL&lt;/code&gt; must report the same scheme, host and port, and
no userinfo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/compat.py&lt;/code&gt; goes further, on a fixed corpus of 41,647 boundary-heavy URLs (17,044 accepted).
It compares how Python's &lt;code&gt;urllib&lt;/code&gt;, &lt;code&gt;urllib3&lt;/code&gt;, &lt;code&gt;requests&lt;/code&gt;, curl and Node parse each accepted URL,
and what they actually send: the Python clients and curl through a local proxy, and all five on
their direct path (Node through &lt;code&gt;http.request&lt;/code&gt;/&lt;code&gt;https.request&lt;/code&gt;; &lt;code&gt;fetch&lt;/code&gt; is not covered). There
the harness redirects each connection the client opens to a fixture on 127.0.0.1 that records
the host and port the client chose, the &lt;code&gt;Host&lt;/code&gt; header (http) and the TLS server name from the
ClientHello (https), then answers 403 or a fatal TLS alert; every connection goes to 127.0.0.1, so nothing leaves the machine. On
the full corpus (Python 3.12.3, urllib3 2.0.7, requests 2.31.0, curl 8.5.0, Node 18.19.1) no
client disagrees on scheme, host or port in any of the 15 views, and none
reports userinfo in its parse or in an http request. One URL, &lt;code&gt;https://b//yc%80a:;&lt;/code&gt;, is refused
by urllib3 and requests when sending, so they send nothing. Not observed: DNS (nothing is
resolved), whether the certificate is verified, and anything inside TLS; the harness hooks the
clients, so it shows what each client chose, not what an unmodified client would do on a real
network after DNS.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests/mutants.py&lt;/code&gt; plants 26 plausible bugs one at a time: 15 in the URL check, 8 in the address
check and 3 in the Node fetch example. They include the prefix and suffix mistakes, a wrong
authority boundary and an IPv4 address read at the wrong offset. 22 break the build (18 in proof
files, 4 in build-time examples; some only because a proof script no longer fits the changed
code), one is caught by &lt;code&gt;tests/test_addr.py&lt;/code&gt;, and the three in the Node example by
&lt;code&gt;tests/test_guarded_fetch_node.py&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub Actions runs the audit, the registry check, the tests (with the runner's Node for the WHATWG comparison), a&lt;br&gt;
fixed 1/16 subset of the client comparison and the mutation check on every push to &lt;code&gt;main&lt;/code&gt; and on&lt;br&gt;
every pull request. The full client comparison is a local run (&lt;code&gt;python3 tests/compat.py&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the name resolves to
&lt;/h2&gt;

&lt;p&gt;The URL check cannot know what an allowed name resolves to. A second command, &lt;code&gt;urlfence addr&lt;/code&gt;,&lt;br&gt;
checks resolved addresses against the IANA IPv4 and IPv6 special-purpose registries (as last updated&lt;br&gt;
on 2025-10-09; that the Lean tables match IANA's files is checked by a script, not proven). It decodes&lt;br&gt;
IPv4-mapped addresses, NAT64 addresses in a /96 prefix given with &lt;code&gt;--nat64&lt;/code&gt;, and ISATAP&lt;br&gt;
identifiers, and denies 6to4, Teredo and, unless configured, all of &lt;code&gt;64:ff9b::/96&lt;/code&gt;. 2026&lt;br&gt;
advisories in MCP tooling and agent frameworks, among them auth-fetch-mcp (CVE-2026-49857), the MCP&lt;br&gt;
Registry (CVE-2026-44430) and pydantic-ai (CVE-2026-48782), found address checks that missed&lt;br&gt;
internal addresses written in an IPv6 form. The address theorems say that&lt;br&gt;
every address in a registry row that is not globally reachable is denied (IPv4-mapped addresses&lt;br&gt;
instead get the decision of the IPv4 address they carry), and that a denied IPv4 address stays&lt;br&gt;
denied in each of those forms. The address parser is compared with &lt;code&gt;inet_pton&lt;/code&gt; on 50,000 strings (for IPv4 its&lt;br&gt;
reading is also proven).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;examples/guarded_fetch.py&lt;/code&gt; pins by default: on every hop it resolves the host once, refuses if&lt;br&gt;
any answer is denied, and connects only to a checked address, keeping the name for &lt;code&gt;Host&lt;/code&gt; and TLS.&lt;br&gt;
A proxy would resolve the name again, so while pinning the example refuses to use one.&lt;br&gt;
&lt;code&gt;examples/guarded_fetch.mjs&lt;/code&gt; does the same for Node's &lt;code&gt;http&lt;/code&gt;/&lt;code&gt;https.request&lt;/code&gt; (not &lt;code&gt;fetch()&lt;/code&gt;): it&lt;br&gt;
connects to the checked address on a socket it opens itself, so Node does no lookup of its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unpinned clients:&lt;/strong&gt; a client that resolves the name itself (a browser, curl, requests) or
goes through a proxy is not covered by &lt;code&gt;urlfence addr&lt;/code&gt;; pin the connection, or filter egress.
The examples pin TCP connections only, not UDP or QUIC, and do not restrict ports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some IPv4-in-IPv6 schemes:&lt;/strong&gt; addresses in unconfigured network-specific NAT64 prefixes, the
other RFC 6052 prefix lengths, 6rd, MAP and SIIT-EAM are not decoded, so they are allowed as plain
global addresses; deny such prefixes with &lt;code&gt;--deny&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own networks:&lt;/strong&gt; internal services on global addresses must be denied by you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirects:&lt;/strong&gt; &lt;code&gt;urlfence check&lt;/code&gt; decides one URL; re-check every hop, as the examples do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What the server does:&lt;/strong&gt; open redirects and proxies on an allowed host are out of scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IPv6 and IDNA in URLs:&lt;/strong&gt; both are refused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pair it with egress controls for the connection itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hiroki-abe-58/urlfence &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;urlfence
lake build              &lt;span class="c"&gt;# builds the CLI here: .lake/build/bin/urlfence&lt;/span&gt;
./scripts/reproduce.sh   &lt;span class="c"&gt;# fresh clone of HEAD: build, audit, registry check, tests, client comparison, mutants&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs git, elan, Python, Node, curl and openssl. To call it from your&lt;br&gt;
own code, &lt;code&gt;examples/guarded_fetch.py&lt;/code&gt; (or &lt;code&gt;guarded_fetch.mjs&lt;/code&gt; for Node) shows the pattern: check the URL, request exactly the checked&lt;br&gt;
string, check every resolved address and connect only to a checked one, and check every redirect&lt;br&gt;
target before following it.&lt;/p&gt;

&lt;p&gt;It is Apache-2.0. I started and lead the project; the code and proofs were written with&lt;br&gt;
Claude Code (Claude Opus 5.5), and so far they have been reviewed only by AI, in separate Claude&lt;br&gt;
sessions. No human has reviewed them yet, so reviews, counterexamples and issues are&lt;br&gt;
welcome. The only guarantees are the theorems; the tests are evidence on the inputs they cover.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;URLFence repository: &lt;a href="https://github.com/hiroki-abe-58/urlfence" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Commit described here: &lt;a href="https://github.com/hiroki-abe-58/urlfence/tree/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence/tree/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3&lt;/a&gt;, with its CI run: &lt;a href="https://github.com/hiroki-abe-58/urlfence/actions/runs/40618042270" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence/actions/runs/40618042270&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;README, including the limits: &lt;a href="https://github.com/hiroki-abe-58/urlfence/blob/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3/README.md" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence/blob/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Published theorem statements: &lt;a href="https://github.com/hiroki-abe-58/urlfence/blob/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3/scripts/statements.expected" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/urlfence/blob/ea3c0615aafe2ec9b8360b0d3b14250462e45ae3/scripts/statements.expected&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IANA special-purpose address registries: &lt;a href="https://www.iana.org/assignments/iana-ipv4-special-registry/" rel="noopener noreferrer"&gt;https://www.iana.org/assignments/iana-ipv4-special-registry/&lt;/a&gt; and
&lt;a href="https://www.iana.org/assignments/iana-ipv6-special-registry/" rel="noopener noreferrer"&gt;https://www.iana.org/assignments/iana-ipv6-special-registry/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Advisories cited:
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-24052" rel="noopener noreferrer"&gt;CVE-2026-24052&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-46341" rel="noopener noreferrer"&gt;CVE-2026-46341&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-33393" rel="noopener noreferrer"&gt;CVE-2026-33393&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2025-47241" rel="noopener noreferrer"&gt;CVE-2025-47241&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-6322" rel="noopener noreferrer"&gt;CVE-2026-6322&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-49857" rel="noopener noreferrer"&gt;CVE-2026-49857&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-44430" rel="noopener noreferrer"&gt;CVE-2026-44430&lt;/a&gt;,
&lt;a href="https://www.cve.org/CVERecord?id=CVE-2026-48782" rel="noopener noreferrer"&gt;CVE-2026-48782&lt;/a&gt;
(&lt;a href="https://github.com/advisories/GHSA-cg7w-rg45-pc59" rel="noopener noreferrer"&gt;GHSA-cg7w-rg45-pc59&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Writing disclosure: this article was drafted and fact-checked with AI assistance under my direction.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>formalmethods</category>
    </item>
    <item>
      <title>PathFence: A Lean 4 Path Checker—and Where Its Guarantees Stop</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Tue, 27 Oct 2026 15:02:31 +0000</pubDate>
      <link>https://dev.to/genelab_999/eliminating-toctou-path-traversal-in-python-with-kernel-enforced-containment-and-lean-4-formal-l64</link>
      <guid>https://dev.to/genelab_999/eliminating-toctou-path-traversal-in-python-with-kernel-enforced-containment-and-lean-4-formal-l64</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Correction (2026-10-28).&lt;/strong&gt; The earlier version of this article described a &lt;code&gt;Workspace&lt;/code&gt; API, Windows support, a macOS/BSD fallback, a pure-Python package for Python 3.9+, four explicit &lt;code&gt;openat2&lt;/code&gt; resolve flags, a &lt;code&gt;formal/&lt;/code&gt; directory, a count of 15 theorems and five theorem names, none of which exist, and unsupported microsecond benchmarks. Those claims did not match the repository. This article replaces them with a description checked against code commit &lt;code&gt;e697b4a&lt;/code&gt;, with the README packaging clarification in &lt;code&gt;06753e0&lt;/code&gt;. Thanks to the reporter of &lt;a href="https://github.com/hiroki-abe-58/pathfence/issues/2" rel="noopener noreferrer"&gt;issue #2&lt;/a&gt;. I am responsible for correcting the article published on my account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;PathFence is a Lean 4 library and command-line tool for lexical path-policy decisions. It is not a sandbox or a complete verified file-system implementation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/hiroki-abe-58/pathfence" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/pathfence&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the checker does
&lt;/h2&gt;

&lt;p&gt;A policy has a base directory, allowed roots and denied roots. Roots must already be canonical; &lt;code&gt;/w/..&lt;/code&gt; is refused rather than silently normalized. Requests return &lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;deny&lt;/code&gt;, or &lt;code&gt;reject&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pathfence check &lt;span class="nt"&gt;--allow&lt;/span&gt; /tmp/allowed_dir &lt;span class="nt"&gt;--&lt;/span&gt; /tmp/allowed_dir_malicious/secret ../x a/../b
&lt;span class="go"&gt;{"input":"/tmp/allowed_dir_malicious/secret","decision":"deny","path":"/tmp/allowed_dir_malicious/secret"}
{"input":"../x","decision":"reject","reason":"escapes"}
{"input":"a/../b","decision":"allow","path":"/tmp/allowed_dir/b","root":"/tmp/allowed_dir","rel":"b"}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty input and input containing NUL are rejected. Absolute paths are normalized lexically. Relative paths are walked beneath the base: a &lt;code&gt;..&lt;/code&gt; that would leave it is rejected even if later segments would return inside. Containment compares complete segments, so &lt;code&gt;/tmp/ab&lt;/code&gt; is not inside &lt;code&gt;/tmp/a&lt;/code&gt;. An allowed result names both the allowed &lt;code&gt;root&lt;/code&gt; and the relative remainder &lt;code&gt;rel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These are POSIX lexical rules, not native Windows path rules. Symlinks, mounts, hard links, case folding and concurrent file-system changes are outside the checker’s view.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Lean proves
&lt;/h2&gt;

&lt;p&gt;The main theorem, &lt;code&gt;check_allow_iff&lt;/code&gt;, characterizes exactly when the modeled checker allows a resolved path: it resolves successfully, lies within an allowed root, and lies within no denied root. Other published statements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;resolve_rel_spec&lt;/code&gt;: successful relative resolution agrees with the specified beneath walk.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resolve_rel_within&lt;/code&gt;: successful relative resolution remains within the base.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowedUnder_spec&lt;/code&gt;: the reported root is allowed and its remainder reconstructs the resolved path.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parseRoot_render&lt;/code&gt;: a root string that parses successfully renders back to exactly that string.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;within_iff_render&lt;/code&gt;: for a root other than &lt;code&gt;/&lt;/code&gt;, segment containment corresponds to equality or a prefix followed by &lt;code&gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository pins &lt;strong&gt;25 published statements&lt;/strong&gt;. The audit reports &lt;strong&gt;147 theorem declarations&lt;/strong&gt;, including helpers and generated equation lemmas, at the code revision above. This is not a claim of 147 separate security guarantees. The audit checks dependencies on Lean’s standard axioms, replays library modules through the kernel checker, and pins the published statements and their transitive library definitions by SHA-256. The library imports only Lean’s &lt;code&gt;Init&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The audit itself remains a trust boundary: someone who changes its inputs or implementation can change what it checks. The CLI’s argument parsing, UTF-8 handling and JSON output, the compiler/runtime, Python examples and operating system are not covered by a complete end-to-end proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests complement the proof
&lt;/h2&gt;

&lt;p&gt;The suite compares the CLI against an independent Python implementation of the documented rules and uses &lt;code&gt;posixpath.normpath&lt;/code&gt; for absolute inputs. On Linux it also compares 1,500 generated relative paths against real &lt;code&gt;openat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS)&lt;/code&gt; calls on a directory tree without symlinks. Agreement on that tested domain is evidence, not a proof of equivalence to the kernel.&lt;/p&gt;

&lt;p&gt;The mutation check plants 11 bugs in the CLI and requires the suite to detect them; three further mutations target the extraction example. GitHub Actions runs build, audit, tests and mutation checks. Tests and review in separate Claude sessions have found issues both around the proven function and in the intended specification, including invalid UTF-8 handling, option-like path arguments, empty inputs and noncanonical roots.&lt;/p&gt;

&lt;h2&gt;
  
  
  File access still needs kernel enforcement
&lt;/h2&gt;

&lt;p&gt;Do not take an allowed path and assume an ordinary &lt;code&gt;open(path)&lt;/code&gt; is safe. A path can be lexically allowed while traversing a symlink into a denied directory.&lt;/p&gt;

&lt;p&gt;The repository’s &lt;strong&gt;example&lt;/strong&gt; &lt;code&gt;examples/safe_open.py&lt;/code&gt; combines the checker with Linux &lt;code&gt;openat2&lt;/code&gt;, opening &lt;code&gt;rel&lt;/code&gt; beneath a handle for &lt;code&gt;root&lt;/code&gt;. Its signature is:&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;safe_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mo"&gt;0o644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a &lt;code&gt;pathfence.Workspace&lt;/code&gt; API. The example explicitly passes &lt;code&gt;RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS&lt;/code&gt;. Linux’s &lt;code&gt;RESOLVE_NO_SYMLINKS&lt;/code&gt; also implies no magic-link traversal; that does not mean the code explicitly passes four flags. The example requires Linux 5.6+ and has no macOS/BSD fallback or native Windows implementation. Trusted root selection and file-system assumptions still matter.&lt;/p&gt;

&lt;p&gt;The newer &lt;code&gt;examples/safe_extract.py&lt;/code&gt; handles tar/zip archives, accepts only regular files and directories, checks member names, and creates members beneath a new private destination with exclusive creation. It additionally uses &lt;code&gt;RESOLVE_NO_XDEV&lt;/code&gt;. Archive links are rejected. Its runtime checks and kernel behavior are separate from the Lean proofs about names; it is not a formally verified archive parser.&lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;Workspace&lt;/code&gt;, &lt;code&gt;subworkspace&lt;/code&gt;, or &lt;code&gt;atomic_write&lt;/code&gt; lifecycle API in this revision. The former article’s microsecond &lt;code&gt;openat2&lt;/code&gt; and fallback benchmarks were unsupported and have been removed. The repository’s actual benchmark concerns the lexical CLI, not those fictional APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The published &lt;code&gt;pathfence&lt;/code&gt; 0.1.0 package is on PyPI and requires &lt;strong&gt;Python 3.10+&lt;/strong&gt;. The 0.1.0 wheels were built from tag v0.1.0 (&lt;code&gt;a959fdc&lt;/code&gt;), not from the newer code revision discussed above. They bundle the Lean executable; this is not a pure-Python implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-binary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:all: pathfence
pathfence check &lt;span class="nt"&gt;--allow&lt;/span&gt; /tmp/allowed_dir &lt;span class="nt"&gt;--&lt;/span&gt; ../x a/../b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the source and verification workflow:&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/hiroki-abe-58/pathfence
&lt;span class="nb"&gt;cd &lt;/span&gt;pathfence
./scripts/reproduce.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reproduction workflow needs git, elan, Python, sha256sum and Linux 5.6+ for the kernel tests. Its openat2 calls must not be blocked by seccomp; the workflow fails if any test is skipped. Consult the README for current platform support and prerequisites. Package installation alone does not make an agent’s file tools confined.&lt;/p&gt;

&lt;p&gt;PathFence is Apache-2.0 licensed and experimental. I started and lead the project; code and proofs were developed with Claude Code (Claude Opus 5.5) and reviewed only by AI, in separate Claude sessions; no human has reviewed them yet. Human review, counterexamples and reproducible issues are welcome.&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A Small Attention Fallback Fix Cut Peak Mask-Pass VRAM by 87%</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Sat, 24 Oct 2026 08:27:32 +0000</pubDate>
      <link>https://dev.to/genelab_999/a-small-attention-fallback-fix-cut-peak-mask-pass-vram-by-87-15mp</link>
      <guid>https://dev.to/genelab_999/a-small-attention-fallback-fix-cut-peak-mask-pass-vram-by-87-15mp</guid>
      <description>&lt;p&gt;&lt;em&gt;A merged fix in Xiaomi-Lens-0, and why fallback paths deserve their own benchmarks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When an optional acceleration library is unavailable, what happens next?&lt;/p&gt;

&lt;p&gt;In Xiaomi-Lens-0, the pixel mask decoder fell back from &lt;code&gt;flash-attn&lt;/code&gt; to &lt;code&gt;nn.MultiheadAttention&lt;/code&gt;. In the configuration I tested, that fallback made large images consume enough GPU memory to fail.&lt;/p&gt;

&lt;p&gt;An efficient alternative was already implemented. The fix was to make it the fallback.&lt;/p&gt;

&lt;p&gt;At a 3-megapixel input size, the additional peak GPU memory used by the mask pass dropped from &lt;strong&gt;18.02 GB to 2.28 GB&lt;/strong&gt;—about &lt;strong&gt;87%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SeerRay-Lab/Xiaomi-Lens-0/pull/3" rel="noopener noreferrer"&gt;The fix has been merged upstream.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The expensive path was the fallback
&lt;/h2&gt;

&lt;p&gt;Xiaomi-Lens-0 can locate and segment objects in images. Its pixel mask decoder includes a &lt;code&gt;PatchSelfAttention&lt;/code&gt; module with multiple attention implementations.&lt;/p&gt;

&lt;p&gt;Before this change, when the &lt;code&gt;flash-attn&lt;/code&gt; path was unavailable, the decoder used its MultiheadAttention implementation.&lt;/p&gt;

&lt;p&gt;In the tested execution path, the model’s padding mask caused attention weights to be materialized across all 16 heads. With (P) image patches, the attention matrices scale with (P^2).&lt;/p&gt;

&lt;p&gt;That gets expensive quickly as image resolution increases.&lt;/p&gt;

&lt;p&gt;The decoder already had a &lt;code&gt;scaled_dot_product_attention&lt;/code&gt; implementation, selectable through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;XIAOMI_LENS_DECODER_ATTN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sdpa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It used the same model weights and avoided that memory bottleneck in my test environment.&lt;/p&gt;

&lt;p&gt;The opportunity was to make that existing path work automatically for users without &lt;code&gt;flash-attn&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The change
&lt;/h2&gt;

&lt;p&gt;The routing previously looked like 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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;configured_backend&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sdpa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sdpa_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;configured_backend&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;try_flash_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mha_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the change:&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;configured_backend&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;mha_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;configured_backend&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;try_flash_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sdpa_attention&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These snippets simplify the actual implementation, but capture the change.&lt;/p&gt;

&lt;p&gt;Explicit MHA selection remains available. The warning message now reports the correct fallback.&lt;/p&gt;

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

&lt;p&gt;The benchmark environment was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NVIDIA RTX 5090, 32 GB VRAM&lt;/li&gt;
&lt;li&gt;Windows 11&lt;/li&gt;
&lt;li&gt;PyTorch 2.14.1+cu130&lt;/li&gt;
&lt;li&gt;Transformers 5.4.0&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;flash-attn&lt;/code&gt; installed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used scikit-image’s coffee photograph, resized to different input sizes, and the README prompt targeting “the coffee cup.”&lt;/p&gt;

&lt;p&gt;The benchmark followed the inference stages used by &lt;code&gt;infer.py&lt;/code&gt;. The table measures &lt;strong&gt;peak GPU memory added by &lt;code&gt;run_forward&lt;/code&gt;, the mask pass, above the loaded-model baseline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; measure total application VRAM.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input size&lt;/th&gt;
&lt;th&gt;Patches&lt;/th&gt;
&lt;th&gt;Previous MHA fallback&lt;/th&gt;
&lt;th&gt;SDPA fallback&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.5 MP&lt;/td&gt;
&lt;td&gt;1,944&lt;/td&gt;
&lt;td&gt;0.86 GB&lt;/td&gt;
&lt;td&gt;0.46 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 MP&lt;/td&gt;
&lt;td&gt;3,952&lt;/td&gt;
&lt;td&gt;2.58 GB&lt;/td&gt;
&lt;td&gt;0.83 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 MP&lt;/td&gt;
&lt;td&gt;7,776&lt;/td&gt;
&lt;td&gt;8.53 GB&lt;/td&gt;
&lt;td&gt;1.55 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 MP&lt;/td&gt;
&lt;td&gt;11,616&lt;/td&gt;
&lt;td&gt;18.02 GB&lt;/td&gt;
&lt;td&gt;2.28 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 3 MP, that is &lt;strong&gt;15.74 GB less additional peak memory&lt;/strong&gt; for this stage.&lt;/p&gt;

&lt;p&gt;In a separate large-image test through a ComfyUI integration, the MHA path ran out of memory while attempting a single 30.75 GiB allocation. The SDPA path completed, with a whole-request peak of 15.05 GB and a runtime of approximately six seconds.&lt;/p&gt;

&lt;p&gt;That separate test used a byte-identical copy of &lt;code&gt;PatchSelfAttention&lt;/code&gt;, rather than running &lt;code&gt;infer.py&lt;/code&gt; itself. Its whole-request measurement should not be directly compared with the mask-pass numbers above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did the outputs change?
&lt;/h2&gt;

&lt;p&gt;I compared six image-and-prompt cases, including object segmentation and a reasoning request, checking token IDs, boxes, and every mask logit.&lt;/p&gt;

&lt;p&gt;Two checks verified that the routing change behaved as intended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The new default matched the previous explicit SDPA path &lt;strong&gt;bit for bit in all six cases&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Explicitly selecting MHA still matched the previous default &lt;strong&gt;bit for bit in all six cases&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing SDPA against MHA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated token IDs and boxes were unchanged.&lt;/li&gt;
&lt;li&gt;Masks thresholded at 0.5 had an IoU of &lt;strong&gt;0.9976–0.9998&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were small differences at mask boundaries. This was a limited six-case comparison, not a comprehensive quality evaluation, but it made the numerical differences visible alongside the memory improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a universal MHA-to-SDPA rule
&lt;/h2&gt;

&lt;p&gt;The result depends on the execution path.&lt;/p&gt;

&lt;p&gt;PyTorch’s MultiheadAttention can itself use optimized SDPA, and its documentation recommends &lt;code&gt;need_weights=False&lt;/code&gt; to enable that path. SDPA also selects implementations based on the inputs and available backends; calling the API does not guarantee the same memory savings on every device.&lt;/p&gt;

&lt;p&gt;Migrating implementations requires care: boolean mask semantics differ between MHA’s key padding mask and SDPA’s attention mask, and SDPA’s dropout must be configured explicitly.&lt;/p&gt;

&lt;p&gt;The relevant documentation is here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.pytorch.org/docs/2.14/generated/torch.nn.MultiheadAttention.html" rel="noopener noreferrer"&gt;PyTorch MultiheadAttention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pytorch.org/docs/2.14/generated/torch.nn.functional.scaled_dot_product_attention.html" rel="noopener noreferrer"&gt;PyTorch scaled_dot_product_attention&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The finding in this PR is narrower: &lt;strong&gt;this decoder already had an effective SDPA path, and selecting it as the fallback substantially reduced memory use in the measured environment.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a small default change matters
&lt;/h2&gt;

&lt;p&gt;An environment variable helps users who know it exists and understand when to set it.&lt;/p&gt;

&lt;p&gt;A better fallback reaches users automatically.&lt;/p&gt;

&lt;p&gt;This fix changed one file. The work behind it was identifying the expensive execution path, measuring its scaling, checking output differences, and giving maintainers evidence they could review.&lt;/p&gt;

&lt;p&gt;For someone whose image previously ran out of memory, that small routing change can determine whether the model is usable at all.&lt;/p&gt;

&lt;p&gt;My next step is to investigate a few other models and see whether similar fallback problems recur. If the diagnosis and verification work proves reusable, it could become a shared toolkit.&lt;/p&gt;

&lt;p&gt;For now, the practical lesson is simple: &lt;strong&gt;benchmark the path your model takes when an optional dependency is missing. That path may deserve more attention than it gets.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>python</category>
      <category>opensource</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>From ImportError to Video: Bringing Looking Ahead to Windows</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Sat, 24 Oct 2026 04:32:00 +0000</pubDate>
      <link>https://dev.to/genelab_999/from-importerror-to-video-bringing-looking-ahead-to-windows-15eb</link>
      <guid>https://dev.to/genelab_999/from-importerror-to-video-bringing-looking-ahead-to-windows-15eb</guid>
      <description>&lt;p&gt;Looking Ahead's 1.3B image-to-video example stopped on my Windows machine before inference started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleNotFoundError: No module named 'fcntl'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The machine had an RTX 5090. FlashAttention 4 was installed. Neither was enough to get past the import.&lt;/p&gt;

&lt;p&gt;My fix, &lt;a href="https://github.com/TencentARC/Looking-Ahead/pull/2" rel="noopener noreferrer"&gt;TencentARC/Looking-Ahead PR #2&lt;/a&gt;, was merged on October 23, 2026 (UTC). With the patch, the tested 1.3B example produced &lt;strong&gt;80 frames at 768×432 on Windows 11&lt;/strong&gt;, using PyTorch's scaled dot-product attention fallback.&lt;/p&gt;

&lt;p&gt;The change spans six files. The interesting part is how three separate assumptions had to be removed before the fallback actually worked end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wanted to run Looking Ahead
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/TencentARC/Looking-Ahead" rel="noopener noreferrer"&gt;Looking Ahead&lt;/a&gt; is a video-generation research project from researchers at Nanjing University, Tencent ARC Lab, and Zhejiang University.&lt;/p&gt;

&lt;p&gt;Its method, &lt;strong&gt;Future Chunk Prediction (FCP)&lt;/strong&gt;, addresses a shortcut in autoregressive video training: neighboring video chunks can be similar enough that a model relies on local continuity without learning much about how a scene evolves.&lt;/p&gt;

&lt;p&gt;FCP adds auxiliary training branches that predict more distant future chunks from the current chunk's intermediate features, without seeing the intervening chunks. Those branches are removed at inference. The authors report improvements in physical consistency; the &lt;a href="https://hellorpg.github.io/looking-ahead/" rel="noopener noreferrer"&gt;project page&lt;/a&gt; explains the method and shows their comparisons.&lt;/p&gt;

&lt;p&gt;The released code includes 1.3B and 5B models. Its documented test environment was Linux with an NVIDIA H200. My contribution makes the tested Windows inference path accessible through the upstream code. It does not change FCP, retrain the model, or establish Windows support for every configuration.&lt;/p&gt;

&lt;p&gt;That is why this merge matters to me: the compatibility fix now lives in the research repository, where the next person trying the model can receive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Installed does not mean importable
&lt;/h2&gt;

&lt;p&gt;The attention modules used &lt;code&gt;is_flash_attn_4_available()&lt;/code&gt; before importing &lt;code&gt;flash_attn.cute&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the tested environment, that availability check reported the installed package. Importing it still failed: its &lt;code&gt;quack&lt;/code&gt; dependency imported &lt;code&gt;fcntl&lt;/code&gt;, a Unix-specific module unavailable on Windows.&lt;/p&gt;

&lt;p&gt;The merged code attempts the import and treats an &lt;code&gt;ImportError&lt;/code&gt; as an unavailable optional backend:&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;FLASH_ATTN_4_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;is_flash_attn_4_available&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;FLASH_ATTN_4_AVAILABLE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;flash_attn.cute&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;FLASH_ATTN_4_AVAILABLE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="nf"&gt;warn_flash_attention_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both attention modules receive this handling. A shared helper logs the selected backend and reports each distinct import failure once.&lt;/p&gt;

&lt;p&gt;The warning explicitly says that performance and numerical results may differ. That matters because a run that silently switches backend is harder to reproduce or compare.&lt;/p&gt;

&lt;p&gt;The helper receives the exception type and message as strings. It does not cache the exception object and its traceback just to deduplicate a warning.&lt;/p&gt;

&lt;p&gt;This check establishes importability, not a guarantee that every subsequent kernel call will work on every device. It also catches &lt;code&gt;ImportError&lt;/code&gt; specifically; it does not convert arbitrary runtime failures into silent fallbacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Cross-attention bypassed the fallback
&lt;/h2&gt;

&lt;p&gt;Getting past the import exposed the next problem.&lt;/p&gt;

&lt;p&gt;The code already had an &lt;code&gt;attention()&lt;/code&gt; function that selected between FlashAttention 4 and PyTorch SDPA. But cross-attention called the FA4-only &lt;code&gt;flash_attention()&lt;/code&gt; directly. Removing FA4 from the environment therefore avoided the import error while leaving an assertion waiting later in the run.&lt;/p&gt;

&lt;p&gt;The relevant change is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- x = flash_attention(q, k, v, k_lens=context_lens)
&lt;/span&gt;&lt;span class="gi"&gt;+ x = attention(q, k, v, k_lens=context_lens)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now cross-attention uses the same backend-selection boundary.&lt;/p&gt;

&lt;p&gt;There is a semantic condition behind that edit. In the call path covered by the PR, &lt;code&gt;context_lens&lt;/code&gt; is &lt;code&gt;None&lt;/code&gt;, so the fallback computes full attention. The SDPA branch does not implement arbitrary variable-length padding masks. Routing a masked attention call through it would need additional work.&lt;/p&gt;

&lt;p&gt;When FA4 is available, the dispatcher forwards the same arguments to the existing FA4 implementation.&lt;/p&gt;

&lt;p&gt;The general lesson is to trace the callers. An optional backend is only optional if the execution paths that need it actually pass through the fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Model loading had its own Unix dependency
&lt;/h2&gt;

&lt;p&gt;The remaining &lt;code&gt;fcntl&lt;/code&gt; dependency was outside attention.&lt;/p&gt;

&lt;p&gt;The project stages base-model files and checkpoints into a local cache, using &lt;code&gt;fcntl&lt;/code&gt; for file locking. The default staging path brought the same platform assumption back into the run.&lt;/p&gt;

&lt;p&gt;The patch makes the import optional in both loading utilities. If &lt;code&gt;fcntl&lt;/code&gt; is unavailable, staging is skipped and the model or checkpoint is loaded from its original path.&lt;/p&gt;

&lt;p&gt;This preserves the underlying operation—reading the weights—without pretending that the Unix locking mechanism exists. It also avoids adding a new cross-platform lock implementation for a cache optimization.&lt;/p&gt;

&lt;p&gt;On systems where &lt;code&gt;fcntl&lt;/code&gt; exists, the existing staging path remains available. On systems where FA4 imports successfully, that attention path remains available too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Windows run established
&lt;/h2&gt;

&lt;p&gt;The PR records the following setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows 11 and RTX 5090.&lt;/li&gt;
&lt;li&gt;Python 3.12 and PyTorch 2.10.0+cu128.&lt;/li&gt;
&lt;li&gt;Other package versions from the project's &lt;code&gt;uv.lock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;FCP 1.3B EMA checkpoint, sailboat example, seed 42, and a five-second duration request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before the patch, the run stopped at import. With the patch and SDPA, it produced &lt;strong&gt;80 frames at 768×432&lt;/strong&gt;, with &lt;strong&gt;209 seconds of sampling&lt;/strong&gt;, &lt;strong&gt;231 seconds for the whole run&lt;/strong&gt;, and &lt;strong&gt;16.4 GiB peak allocated GPU memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One detail belongs beside those results: all reported runs used &lt;code&gt;load_diffusion_pretrained=false&lt;/code&gt;. The PR explains that the EMA checkpoint strictly replaces every backbone weight, so the base DiT initialization does not determine the final output. This was not a claim that every model file or loading step could be omitted.&lt;/p&gt;

&lt;p&gt;These numbers describe one tested example and environment. They are not an end-to-end validation of training, every checkpoint, or every GPU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserving FA4 and comparing SDPA are different checks
&lt;/h2&gt;

&lt;p&gt;The fallback needed to work, but the original fast path also needed a regression check.&lt;/p&gt;

&lt;p&gt;For a controlled experiment, I made FA4 importable using &lt;code&gt;triton-windows&lt;/code&gt; and a test-only no-op &lt;code&gt;fcntl&lt;/code&gt; module. &lt;strong&gt;That shim is not in the PR and is not an installation recommendation&lt;/strong&gt;—it does not provide real file locking.&lt;/p&gt;

&lt;p&gt;With FA4 available, the original branch and the patched branch produced &lt;strong&gt;bit-identical decoded frames&lt;/strong&gt;. That comparison checks whether the patch changed the existing FA4 path.&lt;/p&gt;

&lt;p&gt;Comparing SDPA against FA4 answers a different question. On the same example, the first conditioning frame was identical, while frame PSNR was &lt;strong&gt;33.5 dB on average and 31.6 dB at minimum&lt;/strong&gt;. The generated results were not bit-identical.&lt;/p&gt;

&lt;p&gt;FA4 sampling took &lt;strong&gt;154 seconds&lt;/strong&gt;, versus &lt;strong&gt;209 seconds&lt;/strong&gt; for SDPA. The fallback made the example runnable with a measurable speed tradeoff. It did not reproduce FA4's exact numerical output.&lt;/p&gt;

&lt;p&gt;These are comparisons recorded in the &lt;a href="https://github.com/TencentARC/Looking-Ahead/pull/2" rel="noopener noreferrer"&gt;PR's validation notes&lt;/a&gt;, not new experiments performed for this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5B follow-up found the next boundary
&lt;/h2&gt;

&lt;p&gt;I also checked the 5B model with the paper-fan example and documented the result in a &lt;a href="https://github.com/TencentARC/Looking-Ahead/pull/2#issuecomment-6346484769" rel="noopener noreferrer"&gt;follow-up comment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sampling completed: &lt;strong&gt;30 latent frames in 231 seconds&lt;/strong&gt;, with &lt;strong&gt;24.3 GiB peak allocated GPU memory&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The immediately following VAE decode did not fit in the same process on the tested 32 GB GPU / 64 GB RAM machine. The generator and both KV caches remained on the GPU; Windows commit charge passed 104 GiB, and I stopped the run.&lt;/p&gt;

&lt;p&gt;Decoding those saved latents separately took &lt;strong&gt;8.2 GiB and 11 seconds&lt;/strong&gt;. That result points to a phase-lifetime and memory-management problem, beyond the portability fix in this PR.&lt;/p&gt;

&lt;p&gt;So the precise outcome is: &lt;strong&gt;the 1.3B example completed end to end; the 5B example completed sampling, with decode checked in a separate process&lt;/strong&gt;. The merge does not include a solution for the combined 5B memory peak.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would carry into the next portability fix
&lt;/h2&gt;

&lt;p&gt;This patch connected three things: optional-backend detection, the callers that select that backend, and the platform-specific optimization in model loading.&lt;/p&gt;

&lt;p&gt;For another research implementation, I would follow the same checks: attempt the optional import, trace every caller, verify that the fallback preserves the required attention semantics, log the selected path, and test the complete workload. A successful import is only the beginning.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/TencentARC/Looking-Ahead/pull/2/files" rel="noopener noreferrer"&gt;merged changes&lt;/a&gt; are small enough to read alongside the &lt;a href="https://github.com/TencentARC/Looking-Ahead" rel="noopener noreferrer"&gt;model's setup and inference instructions&lt;/a&gt;. FCP and the released models are the research authors' work; my contribution is getting this tested inference path through Windows without replacing the existing FA4 path.&lt;/p&gt;

&lt;p&gt;Have you encountered an “optional” dependency that turned out to be mandatory through a different call site?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted with AI assistance from the merged diff, project documentation, and recorded PR validation results. Merge commit: &lt;code&gt;15f90becc4d37f4f8c74cb0c868b6d855a7c605d&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>Same Weights, Same Seed, Different Pixels: Porting PixRestore to ComfyUI</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:16:00 +0000</pubDate>
      <link>https://dev.to/genelab_999/same-weights-same-seed-different-pixels-porting-pixrestore-to-comfyui-55pm</link>
      <guid>https://dev.to/genelab_999/same-weights-same-seed-different-pixels-porting-pixrestore-to-comfyui-55pm</guid>
      <description>&lt;p&gt;Same weights. Same input images. Same seeds. Two inference paths that each reproduced their own results.&lt;/p&gt;

&lt;p&gt;Yet &lt;strong&gt;11.3% of the output's 8-bit channel values differed&lt;/strong&gt; between them.&lt;/p&gt;

&lt;p&gt;That was one result from verifying my unofficial &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore" rel="noopener noreferrer"&gt;PixRestore integration for ComfyUI&lt;/a&gt;. The difference was small—at most 5/255—but enough to make “matches the official output” an incomplete claim.&lt;/p&gt;

&lt;p&gt;The useful part of this integration was defining exactly what “matches” meant: which execution mode, which process settings, and which representation of an image.&lt;/p&gt;

&lt;p&gt;Here are the problems documented in the implementation and verification runs, and the checks that made the comparison meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, define which official run you mean
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/csslc/PixRestore" rel="noopener noreferrer"&gt;PixRestore&lt;/a&gt; is an image restoration research project. Its released PixRestore-S model restores 512×512 images with one denoiser call, using DINOv2 features as conditioning. My integration exposes it through ComfyUI's usual image inputs and outputs.&lt;/p&gt;

&lt;p&gt;The reference environment already required a decision. Upstream pinned PyTorch 2.4.0, but that build did not have the kernels needed for the RTX 5090 used in these tests. The recorded reference therefore used PyTorch 2.14.1+cu130, with the other documented upstream dependency pins.&lt;/p&gt;

&lt;p&gt;Then came &lt;code&gt;torch.compile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Several upstream functions had compile decorators. On the tested Windows environment, the unmodified inference run stopped with &lt;code&gt;TritonMissing&lt;/code&gt; at the first compiled call. That left two paths to compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the official script in &lt;strong&gt;eager mode&lt;/strong&gt;, with &lt;code&gt;TORCHDYNAMO_DISABLE=1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;triton-windows&lt;/code&gt; to a copy of the reference environment and run the &lt;strong&gt;compiled path&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both ran. They did not produce exactly the same output.&lt;/p&gt;

&lt;p&gt;Across the 12 demo inputs, the DINOv2 input, all six recorded DINOv2 feature layers, and the initial noise matched. The diffusion transformer's output differed. The &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore/blob/main/docs/VERIFICATION.md" rel="noopener noreferrer"&gt;verification report&lt;/a&gt; attributes the difference to the Inductor-compiled kernels under bf16 autocast.&lt;/p&gt;

&lt;p&gt;The final 8-bit comparison was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;11.3% of channel values differed.&lt;/strong&gt; This is not the percentage of pixels that looked visibly different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum absolute difference:&lt;/strong&gt; 5/255.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mean absolute difference:&lt;/strong&gt; 0.11/255.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two compiled runs matched each other. Three eager runs matched each other.&lt;/p&gt;

&lt;p&gt;So a repeatability test could pass for both implementations while an equivalence test between them failed.&lt;/p&gt;

&lt;p&gt;The ComfyUI node uses eager execution, removing the compile wrappers from the pinned PixRestore modules. The claim in the README is deliberately specific: &lt;strong&gt;it reproduces the official eager path on the tested setup&lt;/strong&gt;. Compiled execution inside ComfyUI is not part of that claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  A node inherits the application's numerical settings
&lt;/h2&gt;

&lt;p&gt;A standalone inference script starts in its own process. A custom node runs inside an application that has already configured PyTorch.&lt;/p&gt;

&lt;p&gt;In this setup, ComfyUI enabled reduced-precision math reductions for scaled dot-product attention. The official reference process used a different setting. Matching the checkpoint and seed would leave that difference in place.&lt;/p&gt;

&lt;p&gt;The integration captures the caller's backend settings, applies the reference settings for its call, and restores the caller's settings afterward. The implementation covers TF32, attention backends, and other precision-related flags.&lt;/p&gt;

&lt;p&gt;This excerpt from &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore/blob/main/pixrestore_comfy/runtime.py" rel="noopener noreferrer"&gt;runtime.py&lt;/a&gt; shows the scope:&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="nd"&gt;@contextlib.contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;official_flags&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;_LOCK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_flag_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;_apply_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OFFICIAL_FLAGS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;_apply_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tests check both sides: the reference settings are active inside the context, and the caller's settings return afterward.&lt;/p&gt;

&lt;p&gt;These are process-wide flags. The local lock serializes this pack's use; it does not coordinate every other extension in the process. This is a scoped compatibility measure, not general isolation for arbitrary concurrent PyTorch work.&lt;/p&gt;

&lt;p&gt;Random-number state needs similar care. The node seeds the relevant generator inside &lt;code&gt;torch.random.fork_rng&lt;/code&gt;, so restoration does not leave ComfyUI's RNG state changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The execution environment is part of the comparison&lt;/strong&gt;, even when it never appears as an argument to the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The last conversion is still part of inference
&lt;/h2&gt;

&lt;p&gt;ComfyUI passes images as floating-point tensors. The reference script reads and writes 8-bit images.&lt;/p&gt;

&lt;p&gt;That creates another boundary to verify: a float tensor that looks correct must still save to the intended integer values.&lt;/p&gt;

&lt;p&gt;The integration follows the reference normalization and output conversion, then returns the resulting 8-bit values to ComfyUI as floats. A small test checks the final round trip for every possible 8-bit value:&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;u8&lt;/span&gt; &lt;span class="o"&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;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&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;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
&lt;span class="n"&gt;saved&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;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;255.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;numpy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&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="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;assert &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;numpy&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a shortened version of the repository's &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore/blob/main/tests/test_images_and_flags.py" rel="noopener noreferrer"&gt;image conversion test&lt;/a&gt;. It checks the specific conversion path rather than assuming that a float-to-integer round trip is harmless.&lt;/p&gt;

&lt;p&gt;For model comparisons, the report also records hashes of the normalized input, noise, DINOv2 features, model output, final float image, and 8-bit result. That makes it possible to locate where a mismatch starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make sure the parity test can fail
&lt;/h2&gt;

&lt;p&gt;The released-weight GPU checks matched the official eager path across 12 demo inputs and four center-crop cases, including execution through ComfyUI's HTTP queue.&lt;/p&gt;

&lt;p&gt;But exact matches alone leave a question: is the test actually sensitive to a wrong computation?&lt;/p&gt;

&lt;p&gt;The CPU parity tests include two deliberate perturbations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the seed.&lt;/li&gt;
&lt;li&gt;Multiply the extracted DINOv2 features by &lt;code&gt;1.001&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both must break equality with the reference. The &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore/blob/main/tests/test_parity_cpu.py" rel="noopener noreferrer"&gt;test code&lt;/a&gt; makes that expectation explicit.&lt;/p&gt;

&lt;p&gt;The real ComfyUI checks also ran an &lt;strong&gt;A → B → A&lt;/strong&gt; sequence with caching disabled and confirmed that the calls executed. An unload-and-reload check verified that the same result survived a fresh model load.&lt;/p&gt;

&lt;p&gt;There is an important boundary around the CPU tests: CI uses random weights with the released architecture and fp32 execution. It checks the implementation path, not restoration quality or released-weight GPU parity. The user-facing node still requires CUDA.&lt;/p&gt;

&lt;p&gt;I would reuse these checks for another integration: record intermediate values, deliberately perturb an input to the computation, and confirm that a repeatability test is actually rerunning the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exact reproduction does not guarantee a good restoration
&lt;/h2&gt;

&lt;p&gt;Once the integration matched its reference, image quality remained a separate question.&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%2Fmj1t3ruun85cd2ocp89s.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%2Fmj1t3ruun85cd2ocp89s.png" alt="Clean synthetic source, degraded input, and PixRestore-S output, shown as 1:1 crops" width="690" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Columns: clean source, degraded input, restoration. Rows: Gaussian noise, blur, JPEG compression. These are 200×200 crops from a synthetic source image, not real-photo restoration results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The demo contains four synthetic source images with three synthetic degradations each. All 12 cases improved in PSNR against their source images. Noise removal also removed fine texture, and sharpened edges could contain newly synthesized detail.&lt;/p&gt;

&lt;p&gt;That is consistent with a generative restoration model: it produces a plausible image, which can differ from the original.&lt;/p&gt;

&lt;p&gt;The wrapper's current scope stays narrow: PixRestore-S, one step, CFG 1.0, and 512×512 input. Other sizes are rejected unless the user explicitly selects the official center-crop path. There is no tiling, and the quality of the crop path on real photographs was not evaluated.&lt;/p&gt;

&lt;p&gt;Those limits keep the tested behavior identifiable. They also make the next useful experiment clear: evaluate suitable real images before making broader quality claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the implementation stands
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-PixRestore" rel="noopener noreferrer"&gt;repository&lt;/a&gt; contains Loader, Restore, and Unload nodes, example workflows, and the setup instructions. The measurements and comparisons above come from the October 5 verification runs on Windows 11, an RTX 5090, and ComfyUI v0.38.0; they are not new runs performed for this article.&lt;/p&gt;

&lt;p&gt;The lesson I would carry into the next port is to define the reference before chasing exact equality. Pin the code and weights, name the execution mode, compare intermediate tensors, and test the image conversion boundary. Then test output quality as its own problem.&lt;/p&gt;

&lt;p&gt;If you have moved a model from a standalone script into a larger application, where did your first numerical mismatch appear: preprocessing, backend settings, or output conversion?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PixRestore research and pretrained models are by the &lt;a href="https://github.com/csslc/PixRestore" rel="noopener noreferrer"&gt;upstream authors&lt;/a&gt;. This ComfyUI integration is unofficial. This article was drafted with AI assistance using the repository's source code and recorded verification results.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>comfyui</category>
      <category>machinelearning</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>🛡️ Patched PHP2Shell but php -v Still Says 8.3.6? Here's How to Actually Check</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:44:58 +0000</pubDate>
      <link>https://dev.to/genelab_999/patched-php2shell-but-php-v-still-says-836-heres-how-to-actually-check-ddj</link>
      <guid>https://dev.to/genelab_999/patched-php2shell-but-php-v-still-says-836-heres-how-to-actually-check-ddj</guid>
      <description>&lt;p&gt;I patched a test box for PHP2Shell, ran &lt;code&gt;php -v&lt;/code&gt;, and got... &lt;code&gt;PHP 8.3.6&lt;/code&gt;. Same as before. 😅&lt;/p&gt;

&lt;p&gt;Turns out that's expected — and if you're judging "are we safe?" by the version number, you can get it wrong in &lt;strong&gt;both&lt;/strong&gt; directions. Here's what I found on Ubuntu 24.04, plus the three checks I now use instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PHP fixed CVE-2026-109618 and CVE-2026-109619 ("PHP2Shell", CVSS v4.0 9.2, unauthenticated RCE) in &lt;strong&gt;8.5.12 / 8.4.27 / 8.3.36 / 8.2.35&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On Ubuntu/Debian, the patched package keeps the old upstream version (e.g. &lt;code&gt;8.3.6&lt;/code&gt;). Check the &lt;strong&gt;package version&lt;/strong&gt; and the &lt;strong&gt;changelog&lt;/strong&gt;, not &lt;code&gt;php -v&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Installing the update isn't enough: &lt;strong&gt;restart Apache / PHP-FPM&lt;/strong&gt;, or the old code keeps running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick context: what is PHP2Shell?
&lt;/h2&gt;

&lt;p&gt;Two memory-safety bugs in the PHP runtime itself, disclosed on Oct 21, 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109618&lt;/strong&gt; – "RCE via OOM corruption": an out-of-memory bailout can leave half-initialized request data that cleanup then treats as valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CVE-2026-109619&lt;/strong&gt; – "RCE via stale global state": request state survives after its memory is freed, and the next request on the same worker uses it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;According to the PHP advisory, every PHP install serving web requests is affected (PHP-FPM, mod_php, php-cgi, FrankenPHP, LiteSpeed), with &lt;strong&gt;mod_php at highest risk&lt;/strong&gt; because an attack can land before your app code even runs. watchTowr reported seeing active exploitation attempts against both.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;WordPress folks:&lt;/strong&gt; this is &lt;em&gt;not&lt;/em&gt; a WordPress bug. WordPress.org says no WordPress update is required or planned. You need to update PHP. Security plugins won't help here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 1: Record the "before" state
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
dpkg-query &lt;span class="nt"&gt;-W&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'${Package} ${Version}\n'&lt;/span&gt; php8.3-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHP 8.3.6 (cli) (built: Oct  7 2026 18:06:38) (NTS)
php8.3-common 8.3.6-0ubuntu0.24.04.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Install the fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-upgrade&lt;/span&gt; &lt;span class="s1"&gt;'php8.3-*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu's announcement lists &lt;code&gt;8.3.6-0ubuntu0.24.04.13&lt;/code&gt; as the fixed version for 24.04 (and &lt;code&gt;8.1.2-1ubuntu2.28&lt;/code&gt; for 22.04).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The gotcha 🪤
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
php8.3-common 8.3.6-0ubuntu0.24.04.13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The version string didn't move. Only the &lt;strong&gt;build date&lt;/strong&gt; changed (Oct 7 → Oct 19).&lt;/p&gt;

&lt;p&gt;Ubuntu backports security fixes without bumping the upstream number. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ "8.3.6 &amp;lt; 8.3.36, we're vulnerable!" → wrong, this box is patched.&lt;/li&gt;
&lt;li&gt;❌ "We ran apt upgrade, we're fine!" → also wrong if nothing was restarted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most convincing evidence is the changelog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zcat /usr/share/doc/php8.3-common/changelog.Debian.gz | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'CVE-2026-109618'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; 2 on my patched box&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The top entry literally references &lt;code&gt;CVE-2026-109618_109619.patch&lt;/code&gt;. That one line is what I'd paste into a status update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Restart, or it didn't happen
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2      &lt;span class="c"&gt;# mod_php&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php8.3-fpm   &lt;span class="c"&gt;# PHP-FPM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu's post calls out restarting apache2 for mod_php, and the PHP advisory says to restart every worker. Even if unattended-upgrades installed the fix overnight, the running processes are still the old ones until you restart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Pro Tip:&lt;/strong&gt; &lt;code&gt;php -i&lt;/code&gt; on the CLI reads &lt;code&gt;/etc/php/8.3/cli/php.ini&lt;/code&gt;, not your web SAPI's config. On my box the CLI showed &lt;code&gt;memory_limit =&amp;gt; -1&lt;/code&gt;. Don't verify web-side mitigations from the CLI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What about mitigations if you can't patch today?
&lt;/h2&gt;

&lt;p&gt;The PHP advisory lists partial measures: &lt;code&gt;memory_limit=-1&lt;/code&gt; (set so apps can't override it), a lower &lt;code&gt;post_max_size&lt;/code&gt;, &lt;code&gt;session.upload_progress.enabled=0&lt;/code&gt; in php.ini or the FPM pool (not via &lt;code&gt;ini_set()&lt;/code&gt; or &lt;code&gt;.user.ini&lt;/code&gt;), and &lt;code&gt;fatal_error_backtraces=0&lt;/code&gt; on 8.5. It's explicit that these reduce specific attack paths but &lt;strong&gt;do not make an installation safe&lt;/strong&gt;. watchTowr goes further and says .ini-based mitigations should be considered ineffective.&lt;/p&gt;

&lt;p&gt;If you're on &lt;strong&gt;PHP 8.1 or older&lt;/strong&gt;, there's no official fixed release. Check your distro or LTS vendor for backports, or upgrade to 8.2+. WordPress.org even suggests taking self-managed old-PHP servers offline until you can update. (And note: 8.2 itself reaches end of life on Dec 31, 2026.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;On my Ubuntu 24.04.5 test box:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;php -v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.3.6 (Oct 7 build)&lt;/td&gt;
&lt;td&gt;8.3.6 (Oct 19 build)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package version&lt;/td&gt;
&lt;td&gt;&lt;code&gt;…24.04.12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;…24.04.13&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changelog mentions CVE&lt;/td&gt;
&lt;td&gt;not checked&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare against upstream 8.3.36&lt;/td&gt;
&lt;td&gt;"vulnerable"&lt;/td&gt;
&lt;td&gt;"vulnerable" ❌ false alarm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If someone asks "are we safe from PHP2Shell?", answer with three things, not a version number:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Package version + changelog line&lt;/strong&gt; (the fix is installed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restart time&lt;/strong&gt; of Apache / PHP-FPM (the fix is running)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs since Oct 20 reviewed&lt;/strong&gt; (nobody got in before you patched)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also wrote a small Python script that compares mixed lists of upstream and Debian/Ubuntu version strings using dpkg's ordering rules (verified against &lt;code&gt;dpkg --compare-versions&lt;/code&gt; on 306 pairs). Grab it here: &lt;a href="https://github.com/hiroki-abe-58" rel="noopener noreferrer"&gt;GitHub link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How are you handling version checks across your fleet? Have you hit this backport confusion before? Let me know in the comments! 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;📝 Japanese versions: &lt;a href="https://qiita.com/GeneLab_999/items/cc3433e9a26503318b45" rel="noopener noreferrer"&gt;Qiita (for non-engineer web managers)&lt;/a&gt; / &lt;a href="https://zenn.dev/genelab_999/articles/737954109df4ff" rel="noopener noreferrer"&gt;Zenn (engineer deep-dive)&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;🐦 Follow me on X: &lt;a href="https://x.com/geneLab_999" rel="noopener noreferrer"&gt;@geneLab_999&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP security advisory (php.net)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/blog/php2shell-vulnerability-fixes-available" rel="noopener noreferrer"&gt;Ubuntu: php2shell fixes available&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://security-tracker.debian.org/tracker/CVE-2026-109618" rel="noopener noreferrer"&gt;Debian Security Tracker: CVE-2026-109618&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wordpress.org/news/2026/10/critical-php-security-release/" rel="noopener noreferrer"&gt;WordPress.org: Critical PHP Security Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://watchtowr.com/intelligence/php-remote-code-execution-php2shell/" rel="noopener noreferrer"&gt;watchTowr: PHP2Shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>security</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Separate Dialogue, Music, and Sound Effects in ComfyUI</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:15:47 +0000</pubDate>
      <link>https://dev.to/genelab_999/separate-dialogue-music-and-sound-effects-in-comfyui-apa</link>
      <guid>https://dev.to/genelab_999/separate-dialogue-music-and-sound-effects-in-comfyui-apa</guid>
      <description>&lt;p&gt;A video arrives with one mixed soundtrack. You want to replace the dialogue, but keep the background music and sound effects. Muting the audio removes all three.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS" rel="noopener noreferrer"&gt;ComfyUI-DBS&lt;/a&gt; to bring that separation into a ComfyUI workflow. It splits a soundtrack into &lt;strong&gt;music, sound effects, and speech&lt;/strong&gt;, then lets you save the parts or combine them again. The repository includes a workflow that writes a new video using the music and effects, with the extracted dialogue saved separately.&lt;/p&gt;

&lt;p&gt;It runs locally on a CPU or NVIDIA CUDA GPU. The two available models total about &lt;strong&gt;23 MB&lt;/strong&gt; to download.&lt;/p&gt;

&lt;p&gt;This is an &lt;strong&gt;unofficial integration&lt;/strong&gt; of &lt;a href="https://github.com/JusperLee/Dynamic-Band-Splitting" rel="noopener noreferrer"&gt;Dynamic Band Splitting (DBS)&lt;/a&gt;, by Bingzhe Zhang, Kai Li, and Xiaolin Hu. The research and trained models are theirs; my contribution is the ComfyUI integration, workflows, and verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do with the three tracks
&lt;/h2&gt;

&lt;p&gt;DBS produces three audio outputs, often called stems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Music:&lt;/strong&gt; the musical part of the soundtrack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFX:&lt;/strong&gt; sound effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speech:&lt;/strong&gt; spoken dialogue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For dialogue removal, the workflow adds &lt;strong&gt;music + SFX&lt;/strong&gt; and uses that as the video's new soundtrack. For dubbing preparation or transcription, you can save the speech output on its own. You can also use the separate tracks to adjust their relative levels in a later editing step.&lt;/p&gt;

&lt;p&gt;These are estimated sources. Some dialogue can remain in the other tracks, and some wanted sounds can be affected. Start with a short excerpt and listen to every output before processing a longer video.&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%2Fgy06nxxozmeaoman98eu.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%2Fgy06nxxozmeaoman98eu.png" alt="Spectrograms of a Tears of Steel soundtrack excerpt and its separated music, sound effects, and speech" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A 12-second excerpt from Tears of Steel and the DBS-TIGER stems produced by this integration on CPU. Source film: Blender Foundation, &lt;a href="https://creativecommons.org/licenses/by/3.0/" rel="noopener noreferrer"&gt;CC BY 3.0&lt;/a&gt;. The excerpt comes from the &lt;a href="https://cslikai.cn/Dynamic-Band-Splitting/" rel="noopener noreferrer"&gt;DBS project page&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The spectrogram shows the outputs, but listening is the useful test. The authors provide &lt;a href="https://cslikai.cn/Dynamic-Band-Splitting/" rel="noopener noreferrer"&gt;audio examples and a listening demo&lt;/a&gt; for exploring the underlying model. Those examples are separate from the ComfyUI integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install ComfyUI-DBS
&lt;/h2&gt;

&lt;p&gt;You need &lt;strong&gt;ComfyUI v0.38.0 or later&lt;/strong&gt;, with the V3 node API, and &lt;strong&gt;Python 3.10 or later&lt;/strong&gt;. A CUDA GPU is optional.&lt;/p&gt;

&lt;p&gt;Run these commands from your &lt;strong&gt;ComfyUI root directory&lt;/strong&gt;, using the Python environment that runs ComfyUI:&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/hiroki-abe-58/ComfyUI-DBS custom_nodes/ComfyUI-DBS
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; custom_nodes/ComfyUI-DBS/requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use a portable installation, replace &lt;code&gt;python&lt;/code&gt; with that installation's Python executable. Installing dependencies into a different Python environment will not make them available to ComfyUI.&lt;/p&gt;

&lt;p&gt;Download the two models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python custom_nodes/ComfyUI-DBS/tools/download_model.py &lt;span class="nt"&gt;--models-dir&lt;/span&gt; models/dbs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The download script uses pinned model revisions and checks the files' SHA-256 hashes. It creates these folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ComfyUI/
  models/
    dbs/
      DBS-TIGER-DnR/
      DBS-BSRNN-DnR/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The requirements are &lt;code&gt;einops&lt;/code&gt; and &lt;code&gt;safetensors&lt;/code&gt;, which ComfyUI usually already includes. After setup, the nodes load local model files; they do not download models during a run.&lt;/p&gt;

&lt;p&gt;Restart ComfyUI to load the new nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the dialogue from a video
&lt;/h2&gt;

&lt;p&gt;Open the supplied &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS/blob/main/workflows/gui/video_remove_dialogue.json" rel="noopener noreferrer"&gt;video_remove_dialogue.json workflow&lt;/a&gt; from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom_nodes/ComfyUI-DBS/workflows/gui/video_remove_dialogue.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose your video in &lt;strong&gt;Load Video&lt;/strong&gt;. The workflow's &lt;code&gt;clip.mp4&lt;/code&gt; filename is a placeholder for your own file.&lt;/p&gt;

&lt;p&gt;The workflow performs these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load Video → Get Video Components&lt;/strong&gt; extracts the frames, frame rate, and audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DBS Loader → DBS Separate&lt;/strong&gt; separates the audio into music, SFX, and speech.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge Audio&lt;/strong&gt;, set to &lt;code&gt;add&lt;/code&gt;, combines the music and SFX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Video → Save Video&lt;/strong&gt; writes a video with that combined soundtrack.&lt;/li&gt;
&lt;li&gt;A separate audio output saves the dialogue as FLAC.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The supplied workflow uses &lt;code&gt;channels = each channel&lt;/code&gt; to process the stereo channels independently, and &lt;code&gt;output_sample_rate = input&lt;/code&gt; to return audio at the input sample rate. The model itself works at 44.1 kHz; the node handles resampling.&lt;/p&gt;

&lt;p&gt;For an audio-only input, the &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS/tree/main/workflows/gui" rel="noopener noreferrer"&gt;GUI workflow folder&lt;/a&gt; also includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;separate_soundtrack.json&lt;/code&gt;: saves all three stems as FLAC.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remove_dialogue.json&lt;/code&gt;: saves music + SFX, plus the separate dialogue track.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can begin with these workflows and connect the audio outputs to your existing graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which model should you choose?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DBS-TIGER-DnR&lt;/strong&gt; is the default. It has 1.0 million parameters and higher reported separation scores than DBS-BSRNN on the authors' DnR v2 evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DBS-BSRNN-DnR&lt;/strong&gt; has 4.7 million parameters, but it was faster in the repository's local measurements. It is the practical starting point for CPU use.&lt;/p&gt;

&lt;p&gt;Here are two examples from the &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS/blob/main/docs/VERIFICATION.md" rel="noopener noreferrer"&gt;recorded verification runs&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On an &lt;strong&gt;RTX 5090&lt;/strong&gt;, a 10-minute input processed as a channel mean took &lt;strong&gt;37.9 seconds with DBS-TIGER&lt;/strong&gt; and &lt;strong&gt;26.9 seconds with DBS-BSRNN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On an &lt;strong&gt;Intel Core Ultra 9 285K using 24 CPU threads&lt;/strong&gt;, a 60-second input processed as a channel mean took &lt;strong&gt;122.5 seconds with DBS-TIGER&lt;/strong&gt; and &lt;strong&gt;32.8 seconds with DBS-BSRNN&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measurements used Windows 11, an already-loaded model, and the pack's runtime in-process. They exclude model loading and video export. They are useful reference points, not speed promises for another machine.&lt;/p&gt;

&lt;p&gt;Stereo takes more work because each channel is processed separately. For the same 10-minute input on the RTX 5090, the recorded stereo times were &lt;strong&gt;74.5 seconds for DBS-TIGER&lt;/strong&gt; and &lt;strong&gt;53.7 seconds for DBS-BSRNN&lt;/strong&gt;. Use those figures when considering the stereo video workflow above.&lt;/p&gt;

&lt;p&gt;With the loader set to &lt;code&gt;auto&lt;/code&gt;, the pack uses ComfyUI's GPU when it is CUDA and otherwise falls back to CPU. Apple and AMD GPU acceleration are not implemented in this pack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I verified—and what still needs listening
&lt;/h2&gt;

&lt;p&gt;A model integration can load successfully while changing the model's behavior. I wanted to check the audio output against the official package as well as make the nodes work.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS/blob/main/docs/VERIFICATION.md" rel="noopener noreferrer"&gt;verification report&lt;/a&gt; records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Six CPU comparison cases:&lt;/strong&gt; the pack's raw output matched the official package byte for byte.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four CUDA comparison cases:&lt;/strong&gt; outputs matched byte for byte when both sides used the documented deterministic PyTorch settings. Default CUDA runs can differ slightly, including between runs of the official code itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real ComfyUI queue checks:&lt;/strong&gt; audio separation, music-plus-SFX output, and video export were exercised through ComfyUI, alongside a clean-install check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those checks establish integration fidelity under the tested conditions. They do not establish perfect separation quality, or byte-identical output for every exported file and configuration. The report documents the export rounding and CUDA differences too.&lt;/p&gt;

&lt;p&gt;The released models were trained on &lt;strong&gt;DnR v2&lt;/strong&gt;, which combines English speech, music, and sound effects into film-like mixtures. Songs with sung vocals, other languages, or unusually loud music need their own evaluation. Singing may be assigned to the music track.&lt;/p&gt;

&lt;p&gt;For a first test, choose a short clip with clearly audible dialogue, music, and effects. Listen to the original, the extracted speech, and the recombined music + SFX. That will tell you much more about your use case than a spectrogram alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS" rel="noopener noreferrer"&gt;Try ComfyUI-DBS and the included workflows on GitHub&lt;/a&gt;.&lt;/strong&gt; If it is useful in your workflow, a star helps other ComfyUI users discover it. Feedback with your model choice, device, and the kind of audio you tried is welcome in the repository's issues.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ComfyUI-DBS is independent of the DBS authors and Comfy Org. The integration code is MIT-licensed; the vendored DBS code and model licensing are documented in the repository's &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-DBS#licenses" rel="noopener noreferrer"&gt;license notes&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>comfyui</category>
      <category>tutorial</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Decision models are English-first. I measured them in Japanese, then built one.</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Thu, 01 Oct 2026 02:21:03 +0000</pubDate>
      <link>https://dev.to/genelab_999/decision-models-are-english-first-i-measured-them-in-japanese-then-built-one-1p12</link>
      <guid>https://dev.to/genelab_999/decision-models-are-english-first-i-measured-them-in-japanese-then-built-one-1p12</guid>
      <description>&lt;p&gt;&lt;em&gt;What happens to System One decision models outside English: a position prior in Laya, an open Japanese model, a translated benchmark, and what transfers back to English.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Decision models had a big September. TypeSafe AI shipped Jev and called it the first "System One" model. Liquid AI followed with d1. Open implementations appeared within days: Laya, AnyJev, kev, Lev.&lt;/p&gt;

&lt;p&gt;The idea is simple and good. Instead of asking an LLM to write JSON and parsing it, you send text plus typed questions and get back probabilities, with zero output tokens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;noul&lt;/strong&gt;: yes / no, returns P(yes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;choice&lt;/strong&gt;: pick one of named options, returns a distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;score&lt;/strong&gt;: a position on an ordered scale, returns an expected level and a distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every public number for these models is in English. I work with Japanese text, so I measured them in Japanese. This post is what I found, what I built, and what I'd tell anyone building one for another language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;laya-multilingual&lt;/code&gt; never picked the first level of an ordinal question in Japanese: &lt;strong&gt;0 of 300&lt;/strong&gt;. Same in English: 0 of 290. A label-free check for this is now in Laya.&lt;/li&gt;
&lt;li&gt;I built &lt;strong&gt;sokudan&lt;/strong&gt;, an open 310M Japanese decision model. On 300 Japanese business messages: choice accuracy 0.880, score RPS 0.075, bool AUROC 0.844. &lt;code&gt;pip install sokudan&lt;/code&gt;, runs on CPU, CUDA or Apple Silicon (MLX).&lt;/li&gt;
&lt;li&gt;Trained only on Japanese, its &lt;strong&gt;choice&lt;/strong&gt; transfers to English almost intact; its &lt;strong&gt;yes / no&lt;/strong&gt; does not.&lt;/li&gt;
&lt;li&gt;I published a Japanese translation of LocalLLaMA's typed-decisions benchmark, with every case back-translation-checked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The first thing I found: a position prior
&lt;/h2&gt;

&lt;p&gt;I wrote 300 synthetic Japanese support messages (&lt;code&gt;bench_ja&lt;/code&gt;) with three unseen schemas: route to one of four departments, rate urgency on three levels, flag churn intent. Then I ran &lt;code&gt;laya-multilingual&lt;/code&gt; on them.&lt;/p&gt;

&lt;p&gt;Choice was fine. Score was not: its RPS was &lt;strong&gt;0.232&lt;/strong&gt;, worse than always predicting the majority class (0.197). Looking at the predictions, it had chosen the first-listed urgency level &lt;strong&gt;0 times out of 300&lt;/strong&gt;, while that level was the correct answer for a real share of the messages.&lt;/p&gt;

&lt;p&gt;The test that made this undeniable is simple and needs no labels. Give every option &lt;strong&gt;the same text&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;level 0: a request
level 1: a request
level 2: a request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A model that reads content, not position, should spread its probability evenly. Any preference is pure presentation. Measure slot 0's log-probability against the mean over slots, and the first-slot rate over all orderings of three real options (1/3 if order doesn't matter).&lt;/p&gt;

&lt;p&gt;I reported it (&lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;laya#131&lt;/a&gt;), and the maintainer documented it in the README and model card. Then I upstreamed the check itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/NandhaKishorM/laya/pull/259" rel="noopener noreferrer"&gt;laya#259&lt;/a&gt; (merged): &lt;code&gt;presentation_checks.py&lt;/code&gt;, the identical-option control, first-slot rate and a parity check, so any retrained checkpoint can be tested for the prior.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/NandhaKishorM/laya/pull/650" rel="noopener noreferrer"&gt;laya#650&lt;/a&gt; (merged, ships in 0.3.22): fixed states in Japanese, Korean, Hindi and Turkish. The first-slot check fails in all four.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/NandhaKishorM/laya/pull/753" rel="noopener noreferrer"&gt;laya#753&lt;/a&gt; (open): the same control for choice. Here &lt;code&gt;laya-multilingual&lt;/code&gt; &lt;em&gt;favours&lt;/em&gt; slot 0 in all five languages. So the avoidance isn't a general dislike of the first option; it's specific to ordinal questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ran the same control on &lt;a href="https://github.com/jaredpalmer/kev/issues/161" rel="noopener noreferrer"&gt;kev&lt;/a&gt; and &lt;a href="https://github.com/Abhinavexists/lev/issues/1" rel="noopener noreferrer"&gt;Lev&lt;/a&gt;. On score questions, both put less probability on slot 0 than on the other slots when every level carries the same text. For Lev, averaging the score over the presented order and its reverse raises score accuracy by &lt;strong&gt;6.9 points&lt;/strong&gt; on the English bench (8.7 on the Japanese one) and brings the control close to zero (&lt;a href="https://github.com/Abhinavexists/lev/pull/2" rel="noopener noreferrer"&gt;lev#2&lt;/a&gt;, open, off by default).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 1:&lt;/strong&gt; accuracy alone won't show you this. Measure presentation with a label-free control, in every language you ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Building a Japanese one
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/hiroki-abe-58/sokudan" rel="noopener noreferrer"&gt;sokudan&lt;/a&gt; (即断, "snap decision") is my answer to "what if the decision model were built for Japanese from the start".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backbone: &lt;a href="https://huggingface.co/sbintuitions/modernbert-ja-310m" rel="noopener noreferrer"&gt;sbintuitions/modernbert-ja-310m&lt;/a&gt; (MIT). 314.6M parameters.&lt;/li&gt;
&lt;li&gt;State and question are encoded &lt;strong&gt;jointly&lt;/strong&gt; in one sequence, with a marker token per option; a small head reads each marker. Score uses a cumulative-link head, since the number of levels is decided per request.&lt;/li&gt;
&lt;li&gt;Training data: synthetic, 21 domains, 4,833 documents, 31,243 labelled (document, question) pairs. One RTX 5090.&lt;/li&gt;
&lt;li&gt;Apache-2.0. Weights on &lt;a href="https://huggingface.co/GeneLab/sokudan-ja-310m" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt;. &lt;code&gt;pip install sokudan&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the same 300 Japanese messages, with Lev measured on the same machine:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bench_ja (300)&lt;/th&gt;
&lt;th&gt;choice acc&lt;/th&gt;
&lt;th&gt;score RPS ↓&lt;/th&gt;
&lt;th&gt;score acc&lt;/th&gt;
&lt;th&gt;bool acc&lt;/th&gt;
&lt;th&gt;bool AUROC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;sokudan v0.2&lt;/strong&gt; (310M)&lt;/td&gt;
&lt;td&gt;0.880&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.075&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.817&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.780&lt;/td&gt;
&lt;td&gt;0.844&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lev (calibrated)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.887&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.138&lt;/td&gt;
&lt;td&gt;0.643&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.817&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.889&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;laya-multilingual&lt;/td&gt;
&lt;td&gt;0.747&lt;/td&gt;
&lt;td&gt;0.232&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0.543&lt;/td&gt;
&lt;td&gt;0.523&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;majority class&lt;/td&gt;
&lt;td&gt;0.380&lt;/td&gt;
&lt;td&gt;0.197&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0.703&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest reading: Lev, a much larger model (about 9 GB of VRAM in bf16 in my runs) trained for English, is &lt;strong&gt;as good or better&lt;/strong&gt; than my Japanese one on choice and yes / no. Scale buys a lot of transfer. Where the dedicated model wins is the ordinal question, which is exactly where the position prior hurts. sokudan's own cost is small: about 24 ms per bench item on an RTX 5090, 1.3 GB of VRAM, and it runs on a CPU.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sokudan&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sokudan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GeneLab/sokudan-ja-310m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;先月の請求で同じ金額が二回引き落とされています。至急ご確認ください。&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instructions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;この問い合わせはどの部署が担当すべきか&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;criteria&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;請求&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;支払い・返金&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;技術&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;不具合・障害&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;営業&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;料金・新規契約&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;その他&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;上記以外&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;department&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;choice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 請求
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a server that speaks the same &lt;code&gt;/v1/systemone&lt;/code&gt; request and response shape as TypeSafe's public API reference (and Liquid's d1 uses the same shape), so a client written for that format can point at &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What transfers back to English
&lt;/h2&gt;

&lt;p&gt;I trained sokudan on Japanese only, then ran it on the English version of the bench (290 messages):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;English&lt;/th&gt;
&lt;th&gt;Japanese&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;choice acc&lt;/td&gt;
&lt;td&gt;0.872&lt;/td&gt;
&lt;td&gt;0.880&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;score RPS ↓&lt;/td&gt;
&lt;td&gt;0.114&lt;/td&gt;
&lt;td&gt;0.075&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bool acc&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0.690&lt;/strong&gt; (majority 0.683)&lt;/td&gt;
&lt;td&gt;0.780&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choice carries over almost untouched. Score degrades but stays useful. Yes / no collapses to the majority class. My reading: choosing between named options leans on matching the option text against the state, which a mostly-Japanese backbone can still do in English; a calibrated yes / no needs the model to have learned what "yes" looks like in that language.&lt;/p&gt;

&lt;p&gt;So: &lt;strong&gt;sokudan is a Japanese model.&lt;/strong&gt; Please don't use it for English yes / no questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson 2:&lt;/strong&gt; if you're building for a non-English language, report the transfer table both ways. Which question types survive the language shift is not obvious, and it isn't the same for all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. A Japanese typed-decisions benchmark
&lt;/h2&gt;

&lt;p&gt;My bench is one family of synthetic support messages. To test outside it, I translated LocalLLaMA's typed-decisions (Apache-2.0) into Japanese: 1,600 cases across agent traces, customer service, invoices and security incidents.&lt;/p&gt;

&lt;p&gt;Machine translation needs checking, so every case was back-translated and judged against the source by a second model. Overall match: &lt;strong&gt;0.946&lt;/strong&gt;. Every case carries its match flag, so you can filter. Dataset: &lt;a href="https://huggingface.co/datasets/GeneLab/typed-decisions-ja" rel="noopener noreferrer"&gt;GeneLab/typed-decisions-ja&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Zero-shot on the 400 test cases, same scorer for both languages:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Japanese&lt;/th&gt;
&lt;th&gt;English&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lev&lt;/td&gt;
&lt;td&gt;0.628&lt;/td&gt;
&lt;td&gt;0.635&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3-8B via AnyJev&lt;/td&gt;
&lt;td&gt;0.625&lt;/td&gt;
&lt;td&gt;0.648&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;label prior&lt;/td&gt;
&lt;td&gt;0.483&lt;/td&gt;
&lt;td&gt;0.483&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sokudan&lt;/td&gt;
&lt;td&gt;0.424&lt;/td&gt;
&lt;td&gt;0.435&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;laya&lt;/td&gt;
&lt;td&gt;0.348&lt;/td&gt;
&lt;td&gt;0.353&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things. Every model loses at most 0.023 going from English to Japanese, which is decent evidence the translation kept the difficulty. And sokudan is &lt;strong&gt;below the label prior&lt;/strong&gt; here: agent traces and invoices are a different world from the business messages it was trained on. A small dedicated model is dedicated.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Things that bit me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option descriptions matter enormously.&lt;/strong&gt; On the department question, giving each option a short description versus bare labels: choice accuracy &lt;strong&gt;0.880 vs 0.537&lt;/strong&gt;. Short labels like "billing" or "technical" only mean something once described.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Catch-all options are under-chosen.&lt;/strong&gt; When "Other" is the right answer, sokudan picked it for 11 of 38 cases, though P(Other) still ranks those cases well. Threshold P(Other) instead of taking the argmax. I later found part of the cause in my own training data: 904 rows had a "none of these" option, and it was never the correct answer. The model learned exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three seeds lie.&lt;/strong&gt; My first release reported a three-seed mean. Score accuracy across those seeds was 0.663, 0.800 and 0.827; the mean described no actual run, and the shipped weights were the 0.663 one. I moved to 16 seeds per condition, exact permutation tests, and decision rules committed before training. Most ideas I tested afterwards failed that bar. The one that passed was the simplest: v0.2 is a uniform average of eight seeds' weights.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same fix works on one model and not another.&lt;/strong&gt; Order averaging lifts Lev's score accuracy by 6.9 points. On sokudan I tried three versions of it (at inference, as a training penalty, and by shuffling options in the data); each moved some position checks, but none kept accuracy. Measure the fix on your model; don't import it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you're building one for your language
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write a small bench in your language with schemas the model has never seen, and keep it out of training.&lt;/li&gt;
&lt;li&gt;Run the identical-option control and the first-slot rate per question type. Laya's &lt;code&gt;presentation_checks.py&lt;/code&gt; already has fixed states for five languages; adding yours is a few lines.&lt;/li&gt;
&lt;li&gt;Report the transfer table to and from English.&lt;/li&gt;
&lt;li&gt;Use enough seeds to see the spread before you believe a difference.&lt;/li&gt;
&lt;li&gt;Check your training data for options that are always wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;sokudan: &lt;a href="https://github.com/hiroki-abe-58/sokudan" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://huggingface.co/GeneLab/sokudan-ja-310m" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt; · &lt;a href="https://pypi.org/project/sokudan/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; · &lt;a href="https://colab.research.google.com/github/hiroki-abe-58/sokudan/blob/main/notebooks/sokudan_quickstart.ipynb" rel="noopener noreferrer"&gt;Colab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Benchmarks: &lt;code&gt;bench_ja&lt;/code&gt; / &lt;code&gt;bench_en&lt;/code&gt; in the repo (CC BY 4.0, evaluation only) · &lt;a href="https://huggingface.co/datasets/GeneLab/typed-decisions-ja" rel="noopener noreferrer"&gt;typed-decisions-ja&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Listed in the &lt;a href="https://huggingface.co/spaces/multimodalart/jev-decision-index" rel="noopener noreferrer"&gt;Jev Decision Index&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;sokudan is an independent open-source project, not affiliated with TypeSafe AI or Liquid AI. "System One" is TypeSafe AI's name for this category. TypeSafe's terms prohibit using its service or outputs to build similar products, so I have never called Jev, and none of the numbers above are Jev's. I haven't measured d1 either.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>nlp</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>I didn't fix the bug: contributing to a 20k-star ML repo by measuring it</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Thu, 24 Sep 2026 04:41:45 +0000</pubDate>
      <link>https://dev.to/genelab_999/i-didnt-fix-the-bug-contributing-to-a-20k-star-ml-repo-by-measuring-it-1lg7</link>
      <guid>https://dev.to/genelab_999/i-didnt-fix-the-bug-contributing-to-a-20k-star-ml-repo-by-measuring-it-1lg7</guid>
      <description>&lt;p&gt;Last week I opened an issue on &lt;a href="https://github.com/NandhaKishorM/laya" rel="noopener noreferrer"&gt;laya&lt;/a&gt;, an open-source decision model with a little over 20k stars. The issue reported that one of its checkpoints almost never picks the first-listed option on ordinal questions. About 36 hours later, three things had happened. The maintainer had documented the limitation in a release. He had asked me to run the before/after on the retrained checkpoint. And he had merged a regression check I wrote. That was four days after my first measurement.&lt;/p&gt;

&lt;p&gt;Nothing is fixed. The fix is a retrain, and the retrain hasn't happened yet. What I contributed is narrower. I think it is also the kind of thing an outsider is best placed to contribute: a measurement nobody could wave away, and a tool that says whether the fix worked.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fastest way I've found to contribute to an ML repo you don't maintain: measure it, make the measurement impossible to explain away, and then hand the maintainer a tool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The timeline, in UTC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sep 20&lt;/strong&gt; — first Japanese baseline run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 22, 05:56&lt;/strong&gt; — &lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;issue #131&lt;/a&gt; opened&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 22, 06:40&lt;/strong&gt; — condition F posted (position vs. label, explained below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 22, 07:15&lt;/strong&gt; — a third party, AlKor13, traces it to the weights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 22, 10:56&lt;/strong&gt; — my labelled A/B test of his hypothesis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 22, 20:40&lt;/strong&gt; — the maintainer's decision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 23, 07:25&lt;/strong&gt; — v0.3.7 ships with the limitation in the README&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 23, 14:42&lt;/strong&gt; — &lt;a href="https://github.com/NandhaKishorM/laya/pull/259" rel="noopener noreferrer"&gt;PR #259&lt;/a&gt; opened&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sep 23, 17:48&lt;/strong&gt; — PR #259 merged&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What laya is, and why I was measuring it
&lt;/h2&gt;

&lt;p&gt;laya is a non-autoregressive "System 1" decision model. You give it some text and a set of typed questions: &lt;code&gt;choice&lt;/code&gt;, &lt;code&gt;score&lt;/code&gt; for ordinal levels, and &lt;code&gt;noul&lt;/code&gt; for yes/no. It answers all of them in one forward pass, with probabilities and no text generation. It ships several checkpoints, including an English &lt;code&gt;laya&lt;/code&gt; and a &lt;code&gt;laya-multilingual&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm building a Japanese model in the same space (&lt;a href="https://github.com/hiroki-abe-58/sokudan" rel="noopener noreferrer"&gt;sokudan&lt;/a&gt;). Before writing any training code, I wanted a baseline: how well does the existing multilingual checkpoint handle Japanese?&lt;/p&gt;

&lt;p&gt;I built 300 label-conditioned Japanese business emails. The labels were fixed first. Then a local LLM wrote an email to match them, and any email containing the label words themselves was thrown out and regenerated. Each email got three questions: department (4-way &lt;code&gt;choice&lt;/code&gt;), urgency (3-level &lt;code&gt;score&lt;/code&gt;) and whether the customer is hinting at cancelling (&lt;code&gt;bool&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The results split by question type. &lt;code&gt;choice&lt;/code&gt; worked: 0.747 accuracy against a 0.380 majority-class baseline. &lt;code&gt;score&lt;/code&gt; lost to the majority baseline (RPS 0.232 vs 0.197, lower is better). &lt;code&gt;bool&lt;/code&gt; also lost to the majority baseline (0.543 vs 0.703), with an AUROC of 0.523. That is barely better than ranking at random.&lt;/p&gt;

&lt;p&gt;One comparison I did not run is TypeSafe's Jev, the hosted API laya benchmarks itself against. Its customer agreement prohibits using the service or its outputs to develop similar products, and my project is a similar product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that didn't fit
&lt;/h2&gt;

&lt;p&gt;"Worse than majority" is a finding, not an explanation. The confusion matrix for &lt;code&gt;score&lt;/code&gt; was more interesting. The lowest level, "not urgent", was never predicted in all 300 emails, even though 77 emails had it as the gold label.&lt;/p&gt;

&lt;p&gt;At least three explanations fit that. The ordinal head could be degenerate. The model could be reading position rather than content. Or this particular Japanese phrase could be the problem.&lt;/p&gt;

&lt;p&gt;To separate them, I re-ran the same 300 emails under five option schemas: original order, reversed, reworded (low / medium / high), reworded and reversed, and four levels. In all five, the first-listed option was chosen 0 or 1 time out of 300.&lt;/p&gt;

&lt;p&gt;The cleanest pair was the original order against the reversed one. "Not urgent" was chosen 0 times when listed first and 250 times when listed last. The emails were the same, the words were the same, and only the slot had changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making it hard to explain away
&lt;/h2&gt;

&lt;p&gt;Before reporting, I tried to list the replies a busy maintainer could reasonably give, and to answer each one with data before he had to ask.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Your harness is wrong."&lt;/strong&gt; I re-ran the benchmark using only the two lines from the README, &lt;code&gt;laya.load()&lt;/code&gt; and &lt;code&gt;agent.predict()&lt;/code&gt;, with no wrapper of mine in between. Then I compared the results to my saved probabilities. The maximum difference was 0.0 after the probability floor I apply, and 2.2e-16 on the raw values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It's a Japanese problem."&lt;/strong&gt; I built 290 English emails with the same label weights, the same generator and the same rejection rules, and ran the same five conditions. The multilingual checkpoint went 0 for 290 in every one of them. English is one of its training languages, so "it can't read the input" doesn't explain this.&lt;/p&gt;

&lt;p&gt;First-listed option chosen, per condition (original / reversed / reworded / reworded reversed / four levels):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;Japanese (n=300)&lt;/th&gt;
&lt;th&gt;English (n=290)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;laya-multilingual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0, 0, 1, 1, 0&lt;/td&gt;
&lt;td&gt;0, 0, 0, 0, 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;laya&lt;/code&gt; (English)&lt;/td&gt;
&lt;td&gt;13, 56, 8, 1, 110&lt;/td&gt;
&lt;td&gt;65, 74, 0, 5, 4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F4mnxwffr0u7dapzo95xo.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%2F4mnxwffr0u7dapzo95xo.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The English checkpoint picks the first slot in most conditions, on the same data through the same harness. So the task isn't the cause, and neither is the harness. It comes down to one checkpoint. I opened &lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;#131&lt;/a&gt; with a title that said exactly that.&lt;/p&gt;

&lt;p&gt;(A correction to myself: in the issue I summarised the English checkpoint as picking the first option "22–26% of the time". That holds for the two original orderings. It is 0 in one condition and under 2% in two others. The table above is the honest version.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is it the position or the word?"&lt;/strong&gt; Within any single fixed ordering, "the model rejects slot 1" and "the model rejects whichever word sits in slot 1" predict the same table. Someone on X asked whether the effect survives shuffling. Twenty-two minutes later I had condition F: the option order is shuffled per item, with a fixed seed. The results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first slot was chosen 0 times out of 300.&lt;/li&gt;
&lt;li&gt;Choices by slot were [0, 149, 151].&lt;/li&gt;
&lt;li&gt;Choices by label were 75 / 93 / 132.&lt;/li&gt;
&lt;li&gt;Each label had sat in the first slot 90 / 109 / 101 times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So every label lost exactly the picks it had while it was in slot 1. Slots 2 and 3 split evenly, so this isn't a preference for the last slot either. One run was enough to separate position from label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Someone else went deeper
&lt;/h2&gt;

&lt;p&gt;What happened next was the best part, and it wasn't my work. &lt;a href="https://github.com/AlKor13" rel="noopener noreferrer"&gt;AlKor13&lt;/a&gt;, who is not a maintainer, read the raw marker logits straight off the forward pass, before temperature and softmax.&lt;/p&gt;

&lt;p&gt;On the identical code path, swapping only the checkpoint made the hole appear or disappear. Then he ran the control I wish I'd thought of: three identical options, so the inputs differ only by position. The English checkpoint came out essentially flat. The multilingual one came out sharply position-dependent. That settled it: the cause is in the weights, not the code.&lt;/p&gt;

&lt;p&gt;He went one step further. Score options are rendered with a &lt;code&gt;level N:&lt;/code&gt; prefix, and dropping that prefix made the slot-0 suppression vanish from the raw logits. But he stated the limit of his own result. Without the prefix the input is off-distribution, so the recovery might just be the learned prior getting scrambled. Settling that would take labelled data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing someone else's hypothesis, and my own
&lt;/h2&gt;

&lt;p&gt;I had the labelled data, so I ran three renderings on both benchmarks: A is the shipped &lt;code&gt;level N:&lt;/code&gt; rendering, C drops the prefix, and D uses word ordinals. My harness reproduced &lt;code&gt;agent.predict()&lt;/code&gt; to within 4.6e-5 under condition A. I compared the renderings with paired McNemar tests on identical items:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bench&lt;/th&gt;
&lt;th&gt;change&lt;/th&gt;
&lt;th&gt;score accuracy&lt;/th&gt;
&lt;th&gt;items that flipped correctness&lt;/th&gt;
&lt;th&gt;p&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;A → C&lt;/td&gt;
&lt;td&gt;0.447 → 0.513&lt;/td&gt;
&lt;td&gt;56.7%&lt;/td&gt;
&lt;td&gt;0.145&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ja&lt;/td&gt;
&lt;td&gt;A → D&lt;/td&gt;
&lt;td&gt;0.447 → 0.570&lt;/td&gt;
&lt;td&gt;38.3%&lt;/td&gt;
&lt;td&gt;0.0007&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;A → C&lt;/td&gt;
&lt;td&gt;0.266 → 0.428&lt;/td&gt;
&lt;td&gt;56.9%&lt;/td&gt;
&lt;td&gt;0.0003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;en&lt;/td&gt;
&lt;td&gt;A → D&lt;/td&gt;
&lt;td&gt;0.266 → 0.293&lt;/td&gt;
&lt;td&gt;19.3%&lt;/td&gt;
&lt;td&gt;0.350&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fvuhd9qex9uqjvwjp9bbo.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%2Fvuhd9qex9uqjvwjp9bbo.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two of the four comparisons are significant, and they are different conditions in different languages. D wins in Japanese and C wins in English, and each is null in the other language. The claim this supports is "the shipped rendering isn't the best one for this checkpoint". It does not support "drop the prefix and it's fixed".&lt;/p&gt;

&lt;p&gt;I'll admit the accuracy column alone had me halfway to writing the second claim. The paired test is what stopped me, and I said so in the thread.&lt;/p&gt;

&lt;p&gt;The column that matters is the flip rate. Under C, 57% of items change correctness, while headline accuracy moves by 6.7 to 16.2 points. A cosmetic change to the option string nearly re-rolls the prediction. That is an instability, not a mitigation. As a control, removing the prefix makes the English checkpoint &lt;em&gt;worse&lt;/em&gt; on the English set (0.583 → 0.500). So &lt;code&gt;level N:&lt;/code&gt; isn't harmful in general. The multilingual checkpoint has learned something wrong about that specific pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  The maintainer's call
&lt;/h2&gt;

&lt;p&gt;About fifteen hours after I opened the issue, the maintainer, &lt;a href="https://github.com/NandhaKishorM" rel="noopener noreferrer"&gt;NandhaKishorM&lt;/a&gt;, replied to both of us. He called the thread "a model of how to run a bug down" and made three decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The bug is in the weights, not the code.&lt;/li&gt;
&lt;li&gt;He would not ship a new default rendering. He cited the 57% flip rate: changing the rendering would swap one instability for another.&lt;/li&gt;
&lt;li&gt;The real fix is in training. Ordinal options need the same position balancing that &lt;code&gt;choice&lt;/code&gt; options already get.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next morning, v0.3.7 shipped with a line in the README's limits section. It says &lt;code&gt;laya-multilingual&lt;/code&gt; has a position bias on &lt;code&gt;score&lt;/code&gt; questions, that English score questions should go to the English checkpoint, and that users should validate score outputs on their own data for other languages. The issue stays open until a retrained checkpoint lands.&lt;/p&gt;

&lt;p&gt;I offered to run the same A–F and A/C/D before/after on that checkpoint. He said he'd ping me when it's ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handing over a tool
&lt;/h2&gt;

&lt;p&gt;That offer had a weak point: it depended on me. My benchmarks are CC BY 4.0, so anyone can rerun them. But the question the retrain has to answer doesn't need labels at all: did the slot prior go away? So I wrote PR &lt;a href="https://github.com/NandhaKishorM/laya/pull/259" rel="noopener noreferrer"&gt;#259&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python research/eval/presentation_checks.py &lt;span class="nt"&gt;--model&lt;/span&gt; &amp;lt;retrained checkpoint&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--subfolder&lt;/span&gt; multilingual]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses ten short English support messages that are fixed in the file, with no dataset and no labels. It runs two checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;score_slot0_identical&lt;/code&gt; is AlKor13's identical-option control turned into a gate. It measures slot 0's raw logit minus the slot mean, over K = 3, 4 and 5 identical levels, and requires the result to be at least −0.20.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;score_first_slot_permuted&lt;/code&gt; puts the three real levels in all six orders for each message, so every level sits in every slot exactly twice. A model whose answer doesn't depend on order picks the first slot in exactly 1/3 of decisions. The gate is at least 0.15.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results on the checkpoints that ship today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;checkpoint&lt;/th&gt;
&lt;th&gt;slot-0 logit (gate ≥ −0.20)&lt;/th&gt;
&lt;th&gt;first-slot rate (gate ≥ 0.15)&lt;/th&gt;
&lt;th&gt;verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;laya&lt;/code&gt; (English)&lt;/td&gt;
&lt;td&gt;+0.664&lt;/td&gt;
&lt;td&gt;0.217&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;laya-multilingual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;−0.492&lt;/td&gt;
&lt;td&gt;0.017&lt;/td&gt;
&lt;td&gt;FAIL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F1cpvb61088fc92i1c13d.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%2F1cpvb61088fc92i1c13d.png" alt=" " width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I fixed the threshold conditions before the real 10-message run, and I didn't move them afterwards.&lt;/strong&gt; There were two conditions. First, in every leave-one-out subset, multilingual must fail and English must pass. Second, each checkpoint's worst leave-one-out value must clear its gate by at least 0.10 logit (slot 0) or 0.05 (first-slot rate). The tightest margin turned out to be the English first-slot rate, at 0.054.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gate is one-sided on purpose.&lt;/strong&gt; With identical options, the English checkpoint isn't flat either. It leans toward early slots, and the lean grows with K. A two-sided "no position effect" gate would fail the checkpoint everyone agrees is fine. So the gate asks only the narrower question #131 is about: is slot 0 suppressed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The harness checks itself before it gives a verdict.&lt;/strong&gt; It first compares its logit path against &lt;code&gt;Agent.system_one&lt;/code&gt; on every message. On the shipped checkpoints, the maximum |Δp| was 4.98e-5, which is just the API's 4-decimal rounding. If the difference exceeds 1e-3, the script exits with code 2 and reports no verdict. Exit 0 means pass, 1 means a check failed, and 2 means the harness disagrees with the package. There are 69 offline tests using scripted logits. For example, a scripted slot-0 hole fails both checks, and an order-invariant model scores exactly 1/3.&lt;/p&gt;

&lt;p&gt;The PR also lists its own limits. Passing is not accuracy. The check covers English only, &lt;code&gt;score&lt;/code&gt; only and ten short messages. And the thresholds were set on CPU with fp32.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before opening the PR
&lt;/h2&gt;

&lt;p&gt;I spent more time reading the repo than writing the script. Tests there are plain scripts, not pytest. CI doesn't load model checkpoints. Research code lives under &lt;code&gt;research/&lt;/code&gt;. A new test has to be registered in both &lt;code&gt;ci.yml&lt;/code&gt; and &lt;code&gt;release.yml&lt;/code&gt;, and an open PR, &lt;a href="https://github.com/NandhaKishorM/laya/pull/184" rel="noopener noreferrer"&gt;#184&lt;/a&gt;, hadn't yet settled how research tests get wired in. So I didn't wire mine in. I said so in the PR and offered to do it once #184 lands. Nothing under &lt;code&gt;laya/&lt;/code&gt; changed, and I added no new dependencies.&lt;/p&gt;

&lt;p&gt;The same afternoon, another contributor, Rukafuu, had proposed a general metamorphic robustness framework in &lt;a href="https://github.com/NandhaKishorM/laya/issues/244" rel="noopener noreferrer"&gt;#244&lt;/a&gt;, and the maintainer had invited them to start with option permutation. That was their work to do. I kept my check specific to #131 and noted that it could move into their framework once it exists. Their PR, #269, has since been merged.&lt;/p&gt;

&lt;h1&gt;
  
  
  259 was merged about three hours after I opened it. The maintainer said a label-free check answering one question was "exactly what #131 needs". He also said that separating a failed check from a harness disagreement in the exit codes made it "easy to trust". That second point is the one I'm proudest of, because it's the part nobody asked for.
&lt;/h1&gt;

&lt;h2&gt;
  
  
  On using Claude Code
&lt;/h2&gt;

&lt;p&gt;I used Claude Code throughout: for the harness, the offline tests and drafting the PR text. The commit carries a &lt;code&gt;Co-Authored-By&lt;/code&gt; line. The decisions about what to measure, what to claim, what to take back and where to stop were mine, and so are any mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself next time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Measure before you report, and check your harness against the official package before you measure anything that matters.&lt;/strong&gt; "Your harness is wrong" is the cheapest possible dismissal. Rule it out first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer the obvious objections before anyone raises them.&lt;/strong&gt; Is it the language? Run it in English. The harness? Check parity. The word? Shuffle per item. Each of those came back as one line in the thread instead of a round-trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take back your own overclaims, out loud.&lt;/strong&gt; McNemar turned my "the prefix is the problem" into "the rendering is unstable". The weaker claim is the one the maintainer could act on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit precisely.&lt;/strong&gt; The most important control in this story came from AlKor13. My PR is his idea turned into a gate, and the PR says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't take work someone else was just handed.&lt;/strong&gt; #244 was Rukafuu's. Keeping my check narrow cost nothing and avoided stepping on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't claim the fix.&lt;/strong&gt; I found it, reported it, and merged the tool that will say whether the retrain worked. That's the whole claim.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;h1&gt;
  
  
  131 is open, and the retrain is pending. When it lands, &lt;code&gt;presentation_checks.py&lt;/code&gt; gives the first answer, and I'll run the labelled A–F and A/C/D before/after on top of it.
&lt;/h1&gt;

&lt;p&gt;What I posted the day it merged:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-2102969840386687112-803" src="https://platform.twitter.com/embed/Tweet.html?id=2102969840386687112"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2102969840386687112-803');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2102969840386687112&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/NandhaKishorM/laya" rel="noopener noreferrer"&gt;https://github.com/NandhaKishorM/laya&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issue #131: &lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;https://github.com/NandhaKishorM/laya/issues/131&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PR #259: &lt;a href="https://github.com/NandhaKishorM/laya/pull/259" rel="noopener noreferrer"&gt;https://github.com/NandhaKishorM/laya/pull/259&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Related: &lt;a href="https://github.com/NandhaKishorM/laya/issues/171" rel="noopener noreferrer"&gt;#171&lt;/a&gt;, &lt;a href="https://github.com/NandhaKishorM/laya/issues/244" rel="noopener noreferrer"&gt;#244&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;My data and repro scripts: &lt;a href="https://github.com/hiroki-abe-58/sokudan/blob/main/docs/baseline_ja.md" rel="noopener noreferrer"&gt;baseline_ja.md&lt;/a&gt;, &lt;a href="https://github.com/hiroki-abe-58/sokudan/blob/main/docs/baseline_en.md" rel="noopener noreferrer"&gt;baseline_en.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>python</category>
    </item>
    <item>
      <title>Claude Opus 5.5 vs GPT-6 Astra vs GPT-6 Sol: the effort knob matters more than the model</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Wed, 23 Sep 2026 11:27:37 +0000</pubDate>
      <link>https://dev.to/genelab_999/claude-opus-55-vs-gpt-6-astra-vs-gpt-6-sol-the-effort-knob-matters-more-than-the-model-55gm</link>
      <guid>https://dev.to/genelab_999/claude-opus-55-vs-gpt-6-astra-vs-gpt-6-sol-the-effort-knob-matters-more-than-the-model-55gm</guid>
      <description>&lt;p&gt;On September 22, 2026, Anthropic shipped &lt;strong&gt;Claude Opus 5.5&lt;/strong&gt; and OpenAI shipped &lt;strong&gt;GPT-6 Sol&lt;/strong&gt; on the same day. OpenAI's flagship, &lt;strong&gt;GPT-6 Astra&lt;/strong&gt;, had landed three weeks earlier.&lt;/p&gt;

&lt;p&gt;If you tried to pick one from the launch posts, you probably noticed the problem: &lt;strong&gt;nobody compared against the right opponent.&lt;/strong&gt; Anthropic's table compares Opus 5.5 with GPT-6 Astra and the &lt;em&gt;previous&lt;/em&gt; GPT-5.6 Sol. OpenAI's Sol post compares with Claude Opus 5 and Fable 5.1, not Opus 5.5. Every chart is technically true and none of them answers "which one should I call from my code today?"&lt;/p&gt;

&lt;p&gt;So I went to &lt;a href="https://artificialanalysis.ai/" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt; (AA), which ran all three through the same ten evaluations at every reasoning-effort level, and did the cost math.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raw intelligence:&lt;/strong&gt; Opus 5.5 (max) scores &lt;strong&gt;58&lt;/strong&gt; on AA's Intelligence Index. Astra (max) scores &lt;strong&gt;53&lt;/strong&gt;, Sol (max) &lt;strong&gt;48&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List price:&lt;/strong&gt; Sol is cheapest per token, Astra is 2.5x Opus 5.5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-task cost flips the ranking:&lt;/strong&gt; at max effort Opus 5.5 emits ~4x more output tokens than Astra, so Opus max ($5.98/task) costs more than Astra max ($3.26/task).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The useful finding:&lt;/strong&gt; below ~$1.30 per task, Sol and Astra trade the lead. Above ~$1.30, Opus 5.5 is on top at every budget. &lt;strong&gt;Opus 5.5 at &lt;code&gt;high&lt;/code&gt; ($1.82) outscores Astra at &lt;code&gt;max&lt;/code&gt; ($3.26).&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The effort setting moves your bill more than the model choice does.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. The price sheet (and its fine print)
&lt;/h2&gt;

&lt;p&gt;Per 1M tokens, standard tier, as of September 23, 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GPT-6 Sol&lt;/th&gt;
&lt;th&gt;GPT-6 Astra&lt;/th&gt;
&lt;th&gt;Claude Opus 5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;$4.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;$50.00&lt;/td&gt;
&lt;td&gt;$20.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache read&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache write&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$12.50&lt;/td&gt;
&lt;td&gt;$5.00 (5 min) / $8.00 (1 h)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt &amp;gt; 272K input tokens&lt;/td&gt;
&lt;td&gt;2x input &amp;amp; cache, 1.5x output, &lt;strong&gt;whole request&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;same as Sol&lt;/td&gt;
&lt;td&gt;no surcharge up to 1M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things developers tend to miss:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sol and Opus 5.5 have the same cache-read price.&lt;/strong&gt; Anthropic says cache reads make up the majority of agentic and coding costs. The more your agent re-reads a cached prefix, the less the 2x headline gap matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI's long-context surcharge applies to the entire request&lt;/strong&gt;, not just the tokens past 272K. For a cache-heavy, long-context agent, Opus 5.5 can end up &lt;em&gt;cheaper&lt;/em&gt; than Sol per request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also: tokenizers differ between vendors (Anthropic itself notes its newer tokenizer produces roughly 30% more tokens for the same text than its older one). Per-token prices are not a common currency. Compare cost per task instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tokens per task is the hidden multiplier
&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%2Fatu9cum6kw0bnhm9l2d5.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%2Fatu9cum6kw0bnhm9l2d5.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At max effort on AA's suite, Opus 5.5 emits ~119k output tokens per task. Astra emits ~27k, Sol ~31k. That's why Astra, at 2.5x Opus's per-token price, still comes out cheaper per task at the top settings.&lt;/p&gt;

&lt;p&gt;Here's the full grid from AA (Intelligence Index score / estimated USD per task):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;effort&lt;/th&gt;
&lt;th&gt;GPT-6 Sol&lt;/th&gt;
&lt;th&gt;GPT-6 Astra&lt;/th&gt;
&lt;th&gt;Claude Opus 5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;34 / $0.13&lt;/td&gt;
&lt;td&gt;46 / $0.82&lt;/td&gt;
&lt;td&gt;42 / $0.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium&lt;/td&gt;
&lt;td&gt;40 / $0.25&lt;/td&gt;
&lt;td&gt;50 / $1.54&lt;/td&gt;
&lt;td&gt;51 / $1.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;td&gt;43 / $0.37&lt;/td&gt;
&lt;td&gt;51 / $1.73&lt;/td&gt;
&lt;td&gt;54 / $1.82&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;xhigh&lt;/td&gt;
&lt;td&gt;44 / $0.53&lt;/td&gt;
&lt;td&gt;52 / $2.31&lt;/td&gt;
&lt;td&gt;56 / $3.46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max&lt;/td&gt;
&lt;td&gt;48 / $1.06&lt;/td&gt;
&lt;td&gt;53 / $3.26&lt;/td&gt;
&lt;td&gt;58 / $5.98&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that Opus 5.5 is &lt;em&gt;cheaper&lt;/em&gt; than Astra at low and medium, roughly equal at high, and more expensive at xhigh and max.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Buy intelligence by the dollar
&lt;/h2&gt;

&lt;p&gt;The question I actually care about: &lt;strong&gt;for a given per-task budget, which (model, effort) pair gives the highest score?&lt;/strong&gt; That's a tiny script:&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;# Artificial Analysis Intelligence Index v4.3.2 (Sept 22, 2026): (score, est. USD per task)
&lt;/span&gt;&lt;span class="n"&gt;AA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-sol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.13&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-sol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&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="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-sol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&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="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.37&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-sol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xhigh&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="mi"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.53&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-sol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&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="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.06&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-astra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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="mi"&gt;46&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-astra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&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="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.54&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-astra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&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="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.73&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-astra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xhigh&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="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.31&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-6-astra&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&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="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.26&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&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="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.34&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&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="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.82&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xhigh&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="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.46&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max&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="mi"&gt;58&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.98&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;best_under&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget_usd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Highest-scoring (model, effort) whose estimated cost fits the per-task budget.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;affordable&lt;/span&gt; &lt;span class="o"&gt;=&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&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;cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;AA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;budget_usd&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;affordable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&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;neg_cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&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;affordable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ties go to the cheaper config
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cfg&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="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;neg_cost&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.00&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;best_under&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$0.25 -&amp;gt; (('gpt-6-sol', 'medium'), 40, 0.25)
$0.60 -&amp;gt; (('gpt-6-sol', 'xhigh'), 44, 0.53)
$0.90 -&amp;gt; (('gpt-6-astra', 'low'), 46, 0.82)
$1.10 -&amp;gt; (('gpt-6-sol', 'max'), 48, 1.06)
$1.40 -&amp;gt; (('claude-opus-5-5', 'medium'), 51, 1.34)
$2.00 -&amp;gt; (('claude-opus-5-5', 'high'), 54, 1.82)
$4.00 -&amp;gt; (('claude-opus-5-5', 'xhigh'), 56, 3.46)
$6.00 -&amp;gt; (('claude-opus-5-5', 'max'), 58, 5.98)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plotted as "best score you can buy at each budget":&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%2Fadp0adgyo6jcwex9fta1.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%2Fadp0adgyo6jcwex9fta1.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under ~$1.30/task:&lt;/strong&gt; Sol owns most of the range. Astra &lt;code&gt;low&lt;/code&gt; wins a narrow band around $0.82–$1.05.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Over ~$1.30/task:&lt;/strong&gt; Opus 5.5 leads everywhere (it ties Astra once, briefly, at score 51).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sol tops out at 48 and Astra at 53.&lt;/strong&gt; Past those points, paying more buys nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 10,000 tasks a month, this stops being academic:&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%2Fqaelx3b3mdgmw0eczdy2.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%2Fqaelx3b3mdgmw0eczdy2.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Opus 5.5 &lt;code&gt;high&lt;/code&gt; scores one point above Astra &lt;code&gt;max&lt;/code&gt; and costs &lt;strong&gt;$14,400/month less&lt;/strong&gt; in this illustration. Also note the gap between Opus 5.5 &lt;code&gt;medium&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;: about 4.5x the cost for 7 more points.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Benchmarks have referees
&lt;/h2&gt;

&lt;p&gt;Anthropic's launch table shows Opus 5.5 at 66.4% on Terminal-Bench 4.0 vs Astra at 57.9%. The footnotes matter: Opus ran at &lt;code&gt;xhigh&lt;/code&gt; in Anthropic's setup, and Astra's number is OpenAI's own figure at &lt;code&gt;high&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When AA ran both in the same harness:&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%2Fju3ny3j7da2ba7vnfabk.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%2Fju3ny3j7da2ba7vnfabk.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;59.6% vs 59.6%.&lt;/strong&gt; A tie. This is the same lesson as Astra's ARC-AGI-3 score, which ARC Prize reported as 99.9% with a provider-specific harness and 62.7% with the standard one. Vendor tables are drawn under each vendor's best conditions. Read the footnotes, and prefer third-party runs that hold the harness constant.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Where each model actually wins
&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%2Fge5bgn1zwzeucbdtr82u.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%2Fge5bgn1zwzeucbdtr82u.png" alt=" " width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From AA at max effort:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GPT-6 Sol&lt;/th&gt;
&lt;th&gt;GPT-6 Astra&lt;/th&gt;
&lt;th&gt;Claude Opus 5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal-Bench 4.0&lt;/td&gt;
&lt;td&gt;43.9%&lt;/td&gt;
&lt;td&gt;59.6%&lt;/td&gt;
&lt;td&gt;59.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AutomationBench-AA&lt;/td&gt;
&lt;td&gt;61.6%&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;td&gt;69.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDP.pdf (professional documents)&lt;/td&gt;
&lt;td&gt;24.8%&lt;/td&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;td&gt;26.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hallucination rate (lower is better)&lt;/td&gt;
&lt;td&gt;60.1%&lt;/td&gt;
&lt;td&gt;51%&lt;/td&gt;
&lt;td&gt;58.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GDPval-AA (44 occupations, Elo)&lt;/td&gt;
&lt;td&gt;1487&lt;/td&gt;
&lt;td&gt;1542&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1846&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opus 5.5&lt;/strong&gt; is far ahead on knowledge work (GDPval-AA is a ~300 Elo lead over Astra). AA's industry sub-indexes agree: finance, legal, and economics all favor Opus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astra&lt;/strong&gt; leads on document reasoning and hallucinates least. In the vendors' own tables it also leads on Terminal-Bench Science (64.6% vs 58.7%, per Anthropic's table).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astra has several claims Opus 5.5 simply hasn't been measured on&lt;/strong&gt;: FrontierMath Tier 4 (97.6%), ScreenSpot-Pro (92.7%), MRCR v2 at 512K–1M (96.3%), ExploitBench (100%). No number means no verdict either way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sol&lt;/strong&gt; is the fast, cheap one. It isn't built to win this table.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Latency
&lt;/h2&gt;

&lt;p&gt;AA, all at &lt;code&gt;medium&lt;/code&gt; effort:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;GPT-6 Sol&lt;/th&gt;
&lt;th&gt;GPT-6 Astra&lt;/th&gt;
&lt;th&gt;Claude Opus 5.5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first answer token&lt;/td&gt;
&lt;td&gt;~2.0 s&lt;/td&gt;
&lt;td&gt;~6.2 s&lt;/td&gt;
&lt;td&gt;~24.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output speed&lt;/td&gt;
&lt;td&gt;~114 tok/s&lt;/td&gt;
&lt;td&gt;~48 tok/s&lt;/td&gt;
&lt;td&gt;~75 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time per task&lt;/td&gt;
&lt;td&gt;~60 s&lt;/td&gt;
&lt;td&gt;~198 s&lt;/td&gt;
&lt;td&gt;~206 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a user-facing chat UI, Sol at &lt;code&gt;medium&lt;/code&gt; is in a different league. Crank Astra to &lt;code&gt;max&lt;/code&gt; and you're waiting over five minutes for the first answer token.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Integration gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opus 5.5 can't run with thinking disabled.&lt;/strong&gt; Effort is your only depth control. Sol offers a &lt;code&gt;none&lt;/code&gt; reasoning setting (though AA found Sol's non-reasoning mode scored lower &lt;em&gt;and&lt;/em&gt; cost more per task than &lt;code&gt;low&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opus 5.5 routes some requests to other models.&lt;/strong&gt; When its safeguards trigger, most cybersecurity tasks are handled by Claude Opus 4.8. AA's numbers were measured with this default fallback on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Astra is rated "Critical" for cyber capability&lt;/strong&gt; under OpenAI's Preparedness Framework. It currently refuses advanced tasks like proof-of-concept exploits, and OpenAI's misalignment monitoring can stop a task outright in the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mind the 272K line on OpenAI models.&lt;/strong&gt; A prompt at 280K tokens is billed very differently from one at 270K.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. A routing recipe you can steal
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default to Opus 5.5 &lt;code&gt;medium&lt;/code&gt;&lt;/strong&gt; for knowledge work and hard agent tasks. It beats Sol's best score and matches Astra &lt;code&gt;high&lt;/code&gt;, for less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalate to Opus 5.5 &lt;code&gt;high&lt;/code&gt;&lt;/strong&gt; only when a task fails your acceptance check, rather than starting at &lt;code&gt;max&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send high-volume, well-specified work to Sol&lt;/strong&gt; (classification, extraction, first-line support), especially if latency matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Astra in the pool&lt;/strong&gt; for document-heavy reasoning, low-hallucination requirements, science and GUI-heavy work, and verify on your own tasks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then measure the only metric that matters, &lt;strong&gt;cost per shipped output&lt;/strong&gt;, including human fix time:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;cost_per_attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;  &lt;span class="c1"&gt;# USD, measured on YOUR tasks
&lt;/span&gt;    &lt;span class="n"&gt;accept_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;       &lt;span class="c1"&gt;# share of outputs you actually ship
&lt;/span&gt;    &lt;span class="n"&gt;fix_minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;       &lt;span class="c1"&gt;# human repair time per shipped output
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cost_per_accepted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hourly_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;60.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;model_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_per_attempt&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accept_rate&lt;/span&gt;
    &lt;span class="n"&gt;human_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fix_minutes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;hourly_rate&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;model_cost&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;human_cost&lt;/span&gt;

&lt;span class="c1"&gt;# Hypothetical numbers, just to show the shape of the math
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cheap config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost_per_attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pricier config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost_per_attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accept_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fix_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;cost_per_accepted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per shipped output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   cheap config: $12.42 per shipped output
 pricier config: $5.58 per shipped output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With any realistic hourly rate, human review time dominates the model bill. That is exactly why the "cheapest model" and the "cheapest workflow" are often different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AA's tasks are not your tasks. Treat the scores as a map, not a forecast.&lt;/li&gt;
&lt;li&gt;Costs are AA's estimates, including typical cache-hit rates.&lt;/li&gt;
&lt;li&gt;A few per-benchmark values above are rounded integers as AA published them.&lt;/li&gt;
&lt;li&gt;AA re-based its index since Astra launched (v4.1.1 → v4.3.2). Scores from different versions aren't comparable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, &lt;a href="https://www.anthropic.com/claude-opus-5-5" rel="noopener noreferrer"&gt;Introducing Claude Opus 5.5&lt;/a&gt; and &lt;a href="https://platform.claude.com/docs/en/about-claude/pricing" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://openai.com/index/introducing-gpt-6-sol-and-luna/" rel="noopener noreferrer"&gt;Introducing GPT-6 Sol and Luna&lt;/a&gt;, &lt;a href="https://openai.com/index/gpt-6-astra/" rel="noopener noreferrer"&gt;GPT-6 Astra&lt;/a&gt;, &lt;a href="https://developers.openai.com/api/docs/models/gpt-6-sol" rel="noopener noreferrer"&gt;GPT-6 Sol model page&lt;/a&gt;, &lt;a href="https://developers.openai.com/api/docs/models/gpt-6-astra" rel="noopener noreferrer"&gt;GPT-6 Astra model page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Artificial Analysis, &lt;a href="https://artificialanalysis.ai/models/releases/comparisons/claude-opus-5-5-vs-gpt-6-astra" rel="noopener noreferrer"&gt;Opus 5.5 vs Astra&lt;/a&gt;, &lt;a href="https://artificialanalysis.ai/models/releases/comparisons/gpt-6-sol-vs-claude-opus-5-5" rel="noopener noreferrer"&gt;Sol vs Opus 5.5&lt;/a&gt;, &lt;a href="https://artificialanalysis.ai/articles/benchmarking-gpt-6-astra" rel="noopener noreferrer"&gt;Benchmarking GPT-6 Astra&lt;/a&gt;, &lt;a href="https://artificialanalysis.ai/articles/claude-opus-5-5" rel="noopener noreferrer"&gt;Opus 5.5 article&lt;/a&gt;, &lt;a href="https://artificialanalysis.ai/articles/gpt-6-sol-and-luna-push-the-cost-efficiency-frontier" rel="noopener noreferrer"&gt;Sol and Luna article&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I researched and drafted this with help from Claude, which makes Anthropic a party to this comparison. Every figure comes from Artificial Analysis or the vendors' own pages, linked above.&lt;/em&gt;&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;Qiita (Japanese): &lt;a href="https://qiita.com/GeneLab_999" rel="noopener noreferrer"&gt;https://qiita.com/GeneLab_999&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zenn (Japanese): &lt;a href="https://zenn.dev/genelab_999" rel="noopener noreferrer"&gt;https://zenn.dev/genelab_999&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href="https://www.youtube.com/@geneLab_999" rel="noopener noreferrer"&gt;https://www.youtube.com/@geneLab_999&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;X: &lt;a href="https://x.com/geneLab_999" rel="noopener noreferrer"&gt;https://x.com/geneLab_999&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>openai</category>
      <category>claude</category>
    </item>
    <item>
      <title>I built a Jev-style decision model for Japanese in two days. Claude Code A/B-tested my architecture and killed it.</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Tue, 22 Sep 2026 06:35:24 +0000</pubDate>
      <link>https://dev.to/genelab_999/i-built-a-jev-style-decision-model-for-japanese-in-two-days-claude-code-ab-tested-my-architecture-4bi5</link>
      <guid>https://dev.to/genelab_999/i-built-a-jev-style-decision-model-for-japanese-in-two-days-claude-code-ab-tested-my-architecture-4bi5</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TypeSafe's Jev introduced "System One" models: no text generation, you send a state and typed questions, you get typed answers with calibrated probabilities in one forward pass. Open reproductions followed within weeks. The most complete one is Laya (ModernBERT-large, 421M).&lt;/li&gt;
&lt;li&gt;I benchmarked &lt;code&gt;laya-multilingual&lt;/code&gt; on 300 Japanese business emails. &lt;code&gt;choice&lt;/code&gt; works. &lt;code&gt;score&lt;/code&gt; (ordinal) and &lt;code&gt;bool&lt;/code&gt; are below the majority-class baseline.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;score&lt;/code&gt; failure is a positional bias: &lt;strong&gt;the first-listed option is chosen 0 to 1 times out of 300, under five different orderings and wordings.&lt;/strong&gt; It reproduces in English (0/290). The English &lt;code&gt;laya&lt;/code&gt; checkpoint does not have it. Filed as &lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;NandhaKishorM/laya#131&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I built a Japanese one, &lt;code&gt;sokudan-ja-310m&lt;/code&gt;, on ModernBERT-Ja. On the same 300 items with unseen schemas it beats laya-multilingual on all three primitives, three seeds. Model, code, benchmark and training-data recipe are public.&lt;/li&gt;
&lt;li&gt;Day 1 produced a model that ignored the input text. Day 2 fixed the data and it still failed. The actual cause was my architecture. Claude Code, which I had handed the spec to, ran the A/B that proved it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Model: &lt;a href="https://huggingface.co/GeneLab/sokudan-ja-310m" rel="noopener noreferrer"&gt;https://huggingface.co/GeneLab/sokudan-ja-310m&lt;/a&gt;&lt;br&gt;
Code: &lt;a href="https://github.com/hiroki-abe-58/sokudan" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/sokudan&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a System One model is
&lt;/h2&gt;

&lt;p&gt;If you have ever asked an LLM "which department should handle this ticket, answer with one word" and then written a regex to parse the reply, you know the problem. You are using a text generator as a classifier and paying for it in latency, cost and parse failures.&lt;/p&gt;

&lt;p&gt;A System One model skips the generation. Three question types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;type&lt;/th&gt;
&lt;th&gt;asks&lt;/th&gt;
&lt;th&gt;returns&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;choice&lt;/td&gt;
&lt;td&gt;pick one of N options&lt;/td&gt;
&lt;td&gt;choice, per-option probabilities, confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;score&lt;/td&gt;
&lt;td&gt;rate on an ordinal rubric&lt;/td&gt;
&lt;td&gt;score, distribution, confidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bool (Jev calls it noul)&lt;/td&gt;
&lt;td&gt;is this statement true&lt;/td&gt;
&lt;td&gt;P(true)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Multiple questions go in one request and are evaluated against the same state independently. Laya implements this as an encoder with one &lt;code&gt;[MASK]&lt;/code&gt; token per option; the hidden state at each mask is scored and softmaxed within the question. Because the answer space is defined at request time, new schemas need no retraining. On an RTX 5090 it runs at 22 ms per item.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark
&lt;/h2&gt;

&lt;p&gt;I generated 300 Japanese business emails with a local LLM (Qwen3 30B-A3B), &lt;strong&gt;label-conditioned&lt;/strong&gt;: the prompt says "write a slightly irritated billing inquiry that hints at cancellation", so the generation conditions are the gold labels. No annotation. Three questions per document: 4-way department routing (&lt;code&gt;choice&lt;/code&gt;), 3-level urgency (&lt;code&gt;score&lt;/code&gt;), "hints at churn" (&lt;code&gt;bool&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Baselines, all on the same 300 items with the same scoring code: &lt;code&gt;laya-multilingual&lt;/code&gt;, &lt;code&gt;laya&lt;/code&gt; (English checkpoint fed Japanese), majority class, random, and the generating LLM itself as a classifier (an upper bound, not a fair comparison, since it wrote the data).&lt;/p&gt;

&lt;p&gt;I did not benchmark Jev itself. TypeSafe's Master Customer Agreement (2.3(b)) prohibits using the service or its output to develop a similar product, and this is one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results for laya-multilingual
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;choice acc&lt;/th&gt;
&lt;th&gt;score RPS (lower is better)&lt;/th&gt;
&lt;th&gt;score acc&lt;/th&gt;
&lt;th&gt;bool acc&lt;/th&gt;
&lt;th&gt;bool AUROC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;laya-multilingual&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.747&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.232&lt;/td&gt;
&lt;td&gt;0.443&lt;/td&gt;
&lt;td&gt;0.543&lt;/td&gt;
&lt;td&gt;0.523&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;majority class&lt;/td&gt;
&lt;td&gt;0.380&lt;/td&gt;
&lt;td&gt;0.197&lt;/td&gt;
&lt;td&gt;0.460&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.703&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;random&lt;/td&gt;
&lt;td&gt;0.253&lt;/td&gt;
&lt;td&gt;0.201&lt;/td&gt;
&lt;td&gt;0.403&lt;/td&gt;
&lt;td&gt;0.513&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;code&gt;choice&lt;/code&gt; is fine: twice the majority baseline, three times random. If you need multi-class routing in Japanese, this checkpoint works today.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;score&lt;/code&gt; is worse than always predicting the majority level. The confusion matrix explains why:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;gold \ predicted&lt;/th&gt;
&lt;th&gt;not urgent&lt;/th&gt;
&lt;th&gt;soon&lt;/th&gt;
&lt;th&gt;work is blocked&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;not urgent (77)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;soon (138)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;work is blocked (85)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The lowest level is never predicted. My first guesses were "weak on Japanese negation" or "ordinal training is broken". Five more conditions ruled both out:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;condition&lt;/th&gt;
&lt;th&gt;options (in listed order)&lt;/th&gt;
&lt;th&gt;first option chosen&lt;/th&gt;
&lt;th&gt;argmax counts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A original&lt;/td&gt;
&lt;td&gt;not urgent / soon / blocked&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt; / 300&lt;/td&gt;
&lt;td&gt;[0, 167, 133]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B reversed&lt;/td&gt;
&lt;td&gt;blocked / soon / not urgent&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt; / 300&lt;/td&gt;
&lt;td&gt;[0, 50, 250]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C relabeled&lt;/td&gt;
&lt;td&gt;low / mid / high&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1&lt;/strong&gt; / 300&lt;/td&gt;
&lt;td&gt;[1, 291, 8]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D relabeled, reversed&lt;/td&gt;
&lt;td&gt;high / mid / low&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1&lt;/strong&gt; / 300&lt;/td&gt;
&lt;td&gt;[1, 0, 299]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E four levels&lt;/td&gt;
&lt;td&gt;not at all / not urgent / soon / blocked&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt; / 300&lt;/td&gt;
&lt;td&gt;[0, 78, 37, 185]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compare B and D. The last-listed option means "not urgent" in one and "high" in the other. Both attract most of the mass (250 and 299). The model is answering by slot, not by label.&lt;/p&gt;

&lt;p&gt;Then I ran the same five conditions on 290 English emails, because English is a training language for this checkpoint and "it can't read the input" should not apply:&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;Japanese (n=300)&lt;/th&gt;
&lt;th&gt;English (n=290)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;laya-multilingual&lt;/td&gt;
&lt;td&gt;0, 0, 1, 1, 0&lt;/td&gt;
&lt;td&gt;0, 0, 0, 0, 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;laya (English checkpoint)&lt;/td&gt;
&lt;td&gt;13, 56, 8, 1, 110&lt;/td&gt;
&lt;td&gt;65, 74, 0, 5, 4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Still zero. In English, laya-multilingual also gets &lt;code&gt;score&lt;/code&gt; RPS 0.340 (random is 0.197) and &lt;code&gt;bool&lt;/code&gt; AUROC 0.355, which is below 0.5, meaning the ranking is inverted. The English checkpoint picks the first option 22 to 26 percent of the time under the same conditions. This is not a language gap; it is something specific to the multilingual checkpoint.&lt;/p&gt;

&lt;p&gt;Practical consequence: ordinal rubrics almost always list the lowest level first. On this checkpoint, &lt;code&gt;score&lt;/code&gt; will almost never return the lowest level regardless of input.&lt;/p&gt;

&lt;p&gt;I verified my harness against the official &lt;code&gt;laya&lt;/code&gt; package with &lt;code&gt;laya.load()&lt;/code&gt; / &lt;code&gt;agent.predict()&lt;/code&gt; straight from the README; probabilities match to float precision. Repro: &lt;a href="https://github.com/hiroki-abe-58/sokudan/blob/main/docs/baseline_en.md" rel="noopener noreferrer"&gt;docs/baseline_en.md&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Temperature scaling, for the record, fixes ECE (0.148 to 0.087) and changes no argmax at all. As expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built one
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sokudan-ja-310m&lt;/code&gt;: &lt;code&gt;sbintuitions/modernbert-ja-310m&lt;/code&gt; backbone (MIT), full fine-tune, one &lt;code&gt;&amp;lt;mask&amp;gt;&lt;/code&gt; per option, softmax within the question. Two design choices worth mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;score&lt;/code&gt; is not a multi-class softmax. It uses a cumulative-link parameterization that keeps the CDF monotone for any number of levels K, decided at request time: each level's marker contributes a non-negative increment via softplus, and P(y &amp;gt; k) is a sigmoid over the running sum. K=2 and K=7 share the same head.&lt;/li&gt;
&lt;li&gt;All training data is gold-labeled synthetic data from the same label-conditioned generation. No Laya or Jev outputs anywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results on the same 300 items, three seeds, mean ± SD. The benchmark's three schemas were never in training:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;choice acc&lt;/th&gt;
&lt;th&gt;score RPS&lt;/th&gt;
&lt;th&gt;score acc&lt;/th&gt;
&lt;th&gt;bool acc&lt;/th&gt;
&lt;th&gt;bool AUROC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;sokudan-ja-310m&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.847 ± 0.009&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.090 ± 0.023&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.763 ± 0.088&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.788 ± 0.010&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.789 ± 0.043&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;laya-multilingual&lt;/td&gt;
&lt;td&gt;0.747&lt;/td&gt;
&lt;td&gt;0.232&lt;/td&gt;
&lt;td&gt;0.443&lt;/td&gt;
&lt;td&gt;0.543&lt;/td&gt;
&lt;td&gt;0.523&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;majority class&lt;/td&gt;
&lt;td&gt;0.380&lt;/td&gt;
&lt;td&gt;0.197&lt;/td&gt;
&lt;td&gt;0.460&lt;/td&gt;
&lt;td&gt;0.703&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seed variance on &lt;code&gt;score&lt;/code&gt; accuracy is large (0.663 / 0.800 / 0.827). The distributed weights are seed 0, which is the worst of the three on that metric and the best on bool AUROC. Seed 0 was designated before any of the three finished training. Seeds 1 and 2 are revisions in the same repo.&lt;/p&gt;

&lt;p&gt;Under the five position-bias conditions, sokudan picks the first option 45 to 98 times out of 300 (i.e. normally), across all seeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I actually want to write about
&lt;/h2&gt;

&lt;p&gt;Day 1 ended with a model whose &lt;code&gt;bool&lt;/code&gt; AUROC on the benchmark was 0.513. Diagnostics showed something worse than "weak": &lt;strong&gt;replacing the input text with an empty string improved bool accuracy&lt;/strong&gt; (0.623 to 0.703). The model was reading the question and emitting a prior. My training data's bool questions were all mechanical derivations ("does this document belong to category X", "is urgency at least level k") and none required reading for intent. "Hints at churn" is an intent question. You cannot transfer a skill you never trained.&lt;/p&gt;

&lt;p&gt;Day 2 morning: Claude Code and I redesigned the data. Twenty-four attributes baked into the generation prompt as conditions, split into three tiers: surface (mentions a deadline), explicit attitude (states dissatisfaction), implicit intent (implies it is about to decline). Five attributes held out entirely so we had an unseen-schema validation set that was not the benchmark. Claude Code caught two things in the verification pass that I would not have: implicit-intent attributes imply each other, so independent 50/50 sampling creates false labels that are actually true; and discarding verifier disagreements removes hard cases asymmetrically, teaching "always yes". Both fixed.&lt;/p&gt;

&lt;p&gt;Day 2 afternoon: trained on the new data. Held-out bool AUROC 0.509. Every held-out attribute at chance, including "ends with a question mark". And &lt;code&gt;choice&lt;/code&gt; and &lt;code&gt;score&lt;/code&gt; were worse than Day 1 despite 2.6 times the data.&lt;/p&gt;

&lt;p&gt;Here is the diagnostic that mattered. On attributes the model &lt;em&gt;had&lt;/em&gt; trained on, evaluated on unseen documents, only 4 of 14 were above 0.7, and the top two were "contains numbers" and "mentions a deadline". Lexical. Every attitude and intent attribute sat at 0.5 even though the model had seen thousands of examples of each. Before asking why it did not transfer, we had to ask why it had not learned.&lt;/p&gt;

&lt;p&gt;My architecture encoded the state and the question separately and merged them in a two-layer cross-attention head. The selling point was that the state is encoded once per request and broadcast to all questions, so latency does not grow with question count. I had rejected Laya's joint layout (question and state in one sequence) on the grounds that ModernBERT has global attention only every third layer and a 128-token local window, so a question block placed far from the state could not see it.&lt;/p&gt;

&lt;p&gt;Claude Code proposed the A/B and ran it: same data, same mix, same epochs, same learning rate, the only change being joint encoding with no cross-attention head.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;cross-attention (mine)&lt;/th&gt;
&lt;th&gt;joint (Laya's layout)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;held-out bool AUROC, unseen schema&lt;/td&gt;
&lt;td&gt;0.506&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.872&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;implicit-intent tier&lt;/td&gt;
&lt;td&gt;0.486&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.786&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;explicit-attitude tier&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.995&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;trained attributes on unseen docs&lt;/td&gt;
&lt;td&gt;4 of 14 above 0.7&lt;/td&gt;
&lt;td&gt;14 of 14 between 0.96 and 1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seconds per epoch&lt;/td&gt;
&lt;td&gt;464&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;330&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ablating the state (empty or shuffled) drops joint to 0.485 / 0.504. It is reading the text.&lt;/p&gt;

&lt;p&gt;My local-attention argument was correct and irrelevant. The mean sequence length in this data is 160 tokens. The "question block at position 3000" I was worried about never occurs. Meanwhile, the only held-out attribute joint failed was "ends with a question mark" (0.688), which is a positional question and exactly where a 128-token window hurts. The concern was real; I had aimed it at the wrong place.&lt;/p&gt;

&lt;p&gt;I retracted the design, the latency claim, and the paragraph in the architecture doc that explained why joint would not work. v0.1 ships joint. Claude Code wrote the retraction section with the A/B numbers and a note on where the original argument's premise failed.&lt;/p&gt;

&lt;p&gt;Two things I put in the spec that earned their place: &lt;strong&gt;never fabricate a number, write TBD&lt;/strong&gt; (no estimate ever leaked into a doc), and &lt;strong&gt;stop at the gate&lt;/strong&gt; (three sub-majority checkpoints were stopped before publication). One thing I did not put in the spec: permission to refute the spec's author. It did that on its own.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;On the benchmark, bool under-predicts true (mean P(true) 0.125 vs a gold rate of 0.297). AUROC 0.789, so the ranking works; set your threshold to your prior. Temperature does not shift this.&lt;/li&gt;
&lt;li&gt;Calibration helps choice and bool ECE and hurts score RPS (0.090 to 0.149). Default is uncalibrated; &lt;code&gt;temperatures.json&lt;/code&gt; is included; refit on your own validation set.&lt;/li&gt;
&lt;li&gt;Long inputs: held-out bool AUROC drops from 0.904 on short documents to 0.730 at 400 to 800 tokens. Local attention.&lt;/li&gt;
&lt;li&gt;Latency scales with question count under joint encoding. &lt;code&gt;usage.backbone_passes&lt;/code&gt; in the response tells you how many.&lt;/li&gt;
&lt;li&gt;Single benchmark, 300 items, all synthetic from one LLM. Real-data validation is next.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Model: &lt;a href="https://huggingface.co/GeneLab/sokudan-ja-310m" rel="noopener noreferrer"&gt;https://huggingface.co/GeneLab/sokudan-ja-310m&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Code, bench_ja, bench_en, ablations, retracted design: &lt;a href="https://github.com/hiroki-abe-58/sokudan" rel="noopener noreferrer"&gt;https://github.com/hiroki-abe-58/sokudan&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Laya issue: &lt;a href="https://github.com/NandhaKishorM/laya/issues/131" rel="noopener noreferrer"&gt;https://github.com/NandhaKishorM/laya/issues/131&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;TypeSafe docs: &lt;a href="https://docs.typesafe.ai" rel="noopener noreferrer"&gt;https://docs.typesafe.ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Laya: &lt;a href="https://huggingface.co/convaiinnovations/laya" rel="noopener noreferrer"&gt;https://huggingface.co/convaiinnovations/laya&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ModernBERT-Ja: &lt;a href="https://huggingface.co/sbintuitions/modernbert-ja-310m" rel="noopener noreferrer"&gt;https://huggingface.co/sbintuitions/modernbert-ja-310m&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>nlp</category>
      <category>opensource</category>
    </item>
    <item>
      <title>🛠️ I Built a One-Click ComfyUI Setup for RTX 5090 on Windows — No WSL2, No Docker</title>
      <dc:creator>GeneLab_999</dc:creator>
      <pubDate>Mon, 02 Mar 2026 15:18:28 +0000</pubDate>
      <link>https://dev.to/genelab_999/i-built-a-one-click-comfyui-setup-for-rtx-5090-on-windows-no-wsl2-no-docker-4n1i</link>
      <guid>https://dev.to/genelab_999/i-built-a-one-click-comfyui-setup-for-rtx-5090-on-windows-no-wsl2-no-docker-4n1i</guid>
      <description>&lt;p&gt;I bought an RTX 5090. 32GB VRAM. The most powerful consumer GPU on the planet.&lt;/p&gt;

&lt;p&gt;Then I tried to run ComfyUI on Windows. It broke immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: sm_120 is not compatible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three days later, I had a fully working solution. I packaged it and open-sourced it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell" rel="noopener noreferrer"&gt;ComfyUI-Win-Blackwell&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the whole story.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why RTX 50-series Breaks Everything
&lt;/h2&gt;

&lt;p&gt;NVIDIA's Blackwell architecture (RTX 5090/5080/5070) uses a new compute capability code called &lt;code&gt;sm_120&lt;/code&gt;. The problem? PyTorch's stable release doesn't include kernels for it.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install torch&lt;/code&gt; → &lt;strong&gt;doesn't work on Blackwell&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You need PyTorch nightly with CUDA 13.0 (cu130)&lt;/li&gt;
&lt;li&gt;But then &lt;strong&gt;xformers&lt;/strong&gt; (the standard ComfyUI speed boost) forces PyTorch back to stable&lt;/li&gt;
&lt;li&gt;And custom nodes silently pull stable PyTorch through their dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a dependency trap. Every fix creates a new problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  The 5 Rules I Discovered
&lt;/h2&gt;

&lt;p&gt;After 3 days of trial and error, I distilled everything into 5 rules. Break any one of them and your environment dies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 1: Use PyTorch nightly cu130&lt;/strong&gt; — stable doesn't have sm_120 kernels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 2: Never install xformers&lt;/strong&gt; — it force-downgrades PyTorch to stable. This is the trap that got me twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 3: Strip &lt;code&gt;torch&lt;/code&gt; from every requirements.txt&lt;/strong&gt; — custom nodes list torch as a dependency, and pip will happily replace your nightly build with stable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 4: Verify PyTorch after every custom node install&lt;/strong&gt; — run &lt;code&gt;python -c "import torch; print(torch.__version__)"&lt;/code&gt; and check that it still says &lt;code&gt;cu130&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule 5: Clear proxy environment variables&lt;/strong&gt; — system proxies block pip and git silently on Windows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Pro Tip:&lt;/strong&gt; Rule 2 was the hardest to figure out. xformers installs &lt;em&gt;successfully&lt;/em&gt;, ComfyUI &lt;em&gt;starts&lt;/em&gt; fine, and then crashes mid-inference with &lt;code&gt;sm_120 is not compatible&lt;/code&gt;. You don't even realize PyTorch was downgraded until you check the version.&lt;/p&gt;
&lt;/blockquote&gt;


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

&lt;p&gt;I automated all 5 rules into a one-click setup:&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/hiroki-abe-58/ComfyUI-Win-Blackwell.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ComfyUI-Win-Blackwell
&lt;span class="c"&gt;# Double-click setup.bat — done in ~20 minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;What &lt;code&gt;setup.bat&lt;/code&gt; handles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.13 environment&lt;/li&gt;
&lt;li&gt;PyTorch nightly cu130 (not stable, not cu128)&lt;/li&gt;
&lt;li&gt;triton-windows + torch.compile (replaces xformers)&lt;/li&gt;
&lt;li&gt;ComfyUI core + custom dependencies (with torch stripped out)&lt;/li&gt;
&lt;li&gt;28 verified custom nodes&lt;/li&gt;
&lt;li&gt;Post-install verification that cu130 is still intact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also built companion tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;verify_env.py&lt;/code&gt; — Blackwell-specific environment checker (sm_120, cu130, Triton, torch.compile)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fix_windows_compat.py&lt;/code&gt; — Converts Linux workflow JSON paths to Windows format, replaces SageAttention with SDPA&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update.bat&lt;/code&gt; — Updates everything while preserving Blackwell compatibility&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;I tested 28 custom nodes one by one. Install → check PyTorch version → run test → record result. That was the most tedious part.&lt;/p&gt;

&lt;p&gt;I also tested 5 Image-to-Video pipelines on 32GB VRAM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HunyuanVideo 1.5 I2V&lt;/strong&gt; (8.3B params, ~16GB) — Smooth. My top recommendation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kandinsky 5.0 Lite I2V&lt;/strong&gt; (2B, ~4GB) — Very smooth. Great for quick tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LTX-2 I2V&lt;/strong&gt; (19B, ~25GB) — Works in FP8. Tight but fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LongCat-Video TI2V&lt;/strong&gt; (13.6B, ~14.5GB) — Works with adjustments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kandinsky 5.0 Pro I2V&lt;/strong&gt; (19B, ~40GB) — Needs CPU offload. Slow.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Why Not Just Use WSL2 or Docker?
&lt;/h2&gt;

&lt;p&gt;The short answer: performance.&lt;/p&gt;

&lt;p&gt;Loading safetensors through WSL2's NTFS translation layer is noticeably slower. Docker has the same issue plus additional setup complexity. For a tool like ComfyUI where you're iterating on workflows and loading large models frequently, native Windows file I/O makes a real difference.&lt;/p&gt;

&lt;p&gt;Also, most AI artists using ComfyUI on Windows aren't Docker experts. A &lt;code&gt;.bat&lt;/code&gt; file they can double-click is the right UX.&lt;/p&gt;


&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;If you have an RTX 5090/5080/5070 and want to run ComfyUI on Windows without WSL2 or Docker, give it a try:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell" rel="noopener noreferrer"&gt;github.com/hiroki-abe-58/ComfyUI-Win-Blackwell&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/hiroki-abe-58" rel="noopener noreferrer"&gt;
        hiroki-abe-58
      &lt;/a&gt; / &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell" rel="noopener noreferrer"&gt;
        ComfyUI-Win-Blackwell
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;ComfyUI for GeForce RTX 50-Series (Blackwell)&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The first fully documented, Windows-native ComfyUI setup for NVIDIA GeForce RTX 5090/5080/5070 (Blackwell architecture, sm_120) with CUDA 13.0.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Other languages:&lt;/strong&gt; &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell/README_ja.md" rel="noopener noreferrer"&gt;日本語&lt;/a&gt; | &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell/README_zh.md" rel="noopener noreferrer"&gt;中文&lt;/a&gt; | &lt;a href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell/README_ko.md" rel="noopener noreferrer"&gt;한국어&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What Makes This Special&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;RTX 50-series GPUs (Blackwell, Compute Capability sm_120) are &lt;strong&gt;not supported by PyTorch stable releases&lt;/strong&gt; as of early 2026. Running ComfyUI on these GPUs requires specific versions and workarounds that are not documented anywhere else in a single, reproducible package.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Technical Highlights&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Feature&lt;/th&gt;

&lt;th&gt;Details&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;GPU Architecture&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;NVIDIA Blackwell (sm_120) -- RTX 5090 / 5080 / 5070&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;CUDA Version&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;13.0 (cu130) -- the latest CUDA runtime&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;PyTorch&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Nightly cu130 build (not stable, not cu128)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;3.13 (latest)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Triton&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;triton-windows fork (official Triton is Linux-only)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;xformers&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Deliberately excluded (causes PyTorch downgrade)&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Custom Nodes&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;28 verified nodes including video &amp;amp; music generation&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;/td&gt;

&lt;td&gt;Windows Native (no WSL2, no Docker required)&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Why This Is Unique&lt;/h3&gt;

&lt;/div&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Blackwell + Windows Native +&lt;/strong&gt;…&lt;/p&gt;


&lt;/li&gt;

&lt;/ol&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/hiroki-abe-58/ComfyUI-Win-Blackwell" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;MIT licensed. Stars and PRs welcome — especially if you verify additional custom nodes on Blackwell hardware.&lt;/p&gt;

&lt;p&gt;Have you tried running AI tools on RTX 50-series? What was your experience? Let me know in the comments! 👇&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you found this helpful, consider following me for more AI + GPU content!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;📝 Japanese version: &lt;a href="https://qiita.com/GeneLab_999" rel="noopener noreferrer"&gt;Qiita&lt;/a&gt; / &lt;a href="https://zenn.dev/rick_lyric" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;🐦 Follow me on X: &lt;a href="https://x.com/geneLab_999" rel="noopener noreferrer"&gt;@geneLab_999&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;💻 GitHub: &lt;a href="https://github.com/hiroki-abe-58" rel="noopener noreferrer"&gt;hiroki-abe-58&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>comfyui</category>
      <category>ai</category>
      <category>showdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
