<?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: tomek7667</title>
    <description>The latest articles on DEV Community by tomek7667 (@_tomek7667).</description>
    <link>https://dev.to/_tomek7667</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%2F4141431%2F5df949af-56d8-4a1b-9f9e-8dfe420cfb94.jpg</url>
      <title>DEV Community: tomek7667</title>
      <link>https://dev.to/_tomek7667</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_tomek7667"/>
    <language>en</language>
    <item>
      <title>Ask ten questions, read the text once: making a decision model 6.7x faster</title>
      <dc:creator>tomek7667</dc:creator>
      <pubDate>Fri, 25 Sep 2026 15:34:47 +0000</pubDate>
      <link>https://dev.to/_tomek7667/ask-ten-questions-read-the-text-once-making-a-decision-model-67x-faster-k0e</link>
      <guid>https://dev.to/_tomek7667/ask-ten-questions-read-the-text-once-making-a-decision-model-67x-faster-k0e</guid>
      <description>&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%2Fazl4jogvcuqyf4ruh6us.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%2Fazl4jogvcuqyf4ruh6us.png" alt="cbjev vs Laya vs TypeSafe Jev"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of "AI" in production isn't generation at all. It's a pile of small decisions about a piece of text: &lt;em&gt;which team should handle this ticket? how urgent is it? is the customer threatening to leave? is this e-mail phishing?&lt;/em&gt; Sending each of those to an LLM and parsing the answer is slow, expensive and occasionally creative in ways you didn't ask for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/NandhaKishorM/laya" rel="noopener noreferrer"&gt;Laya&lt;/a&gt; is an open model for exactly this job: you send a state (a ticket, an e-mail, a JSON document) and typed questions — &lt;code&gt;choice&lt;/code&gt;, &lt;code&gt;score&lt;/code&gt;, or a yes/no &lt;code&gt;noul&lt;/code&gt; — and a ModernBERT encoder returns calibrated probabilities in one forward pass. I built &lt;strong&gt;&lt;a href="https://github.com/tomek7667/cbjev" rel="noopener noreferrer"&gt;cbjev&lt;/a&gt;&lt;/strong&gt; on top of it. This post is about the one idea that made it fast, how it could reuse Laya's weights, and what didn't work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: the document is read once per question
&lt;/h2&gt;

&lt;p&gt;Laya builds one sequence per question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CLS] question 1 [SEP] options [SEP] document [SEP]
[CLS] question 2 [SEP] options [SEP] document [SEP]
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask ten questions about a 500-token document and the encoder processes the document ten times — about 5,500 tokens for a call whose unique content is about 1,000.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: one row, and an attention mask that does the separating
&lt;/h2&gt;

&lt;p&gt;cbjev packs a whole call into one row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[CLS] q1 | q2 | ... | q10 | document [SEP]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and shapes the attention mask so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;questions never see each other&lt;/strong&gt; (each segment attends only to itself and the document),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the document reads every question&lt;/strong&gt; (so its encoding is still question-aware, like in Laya),&lt;/li&gt;
&lt;li&gt;every question segment &lt;strong&gt;restarts its positions right after &lt;code&gt;[CLS]&lt;/code&gt;&lt;/strong&gt;, and the document starts after the longest segment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last detail is the important one. With a single question, the row is token for token and position for position exactly what Laya sees. So a Laya checkpoint dropped into this layout already works (I measured 0.749 on the 5-question typed-decisions benchmark before any training, against 0.768 in its own layout), and fine-tuning starts from Laya's full ability instead of relearning the task.&lt;/p&gt;

&lt;p&gt;My first attempt didn't have this: the document came first and could not read the questions. It was just as fast, but fine-tuning had to rebuild skills Laya already had, and it kept losing on half the benchmarks. Switching to the "shared" layout above is what made the accuracy numbers work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the rest of the call cheap
&lt;/h2&gt;

&lt;p&gt;Once the token count is down, a small call is dominated by overhead, not math:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;from-scratch ModernBERT forward&lt;/strong&gt; (no &lt;code&gt;transformers&lt;/code&gt; model at runtime) with bf16 matmuls over an fp32 residual stream,&lt;/li&gt;
&lt;li&gt;each layer &lt;strong&gt;fused with &lt;code&gt;torch.compile&lt;/code&gt;&lt;/strong&gt; (one dynamic-shape compile, a few seconds, cached),&lt;/li&gt;
&lt;li&gt;the whole forward &lt;strong&gt;replayed as a CUDA graph&lt;/strong&gt; per 32-token shape bucket — one launch instead of ~300,&lt;/li&gt;
&lt;li&gt;the document tokenized &lt;strong&gt;once per call&lt;/strong&gt;, question text tokenized once and cached.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;Measured side by side with Laya on one RTX 4090, same cases, through both libraries' public &lt;code&gt;predict&lt;/code&gt; APIs:&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;cbjev&lt;/th&gt;
&lt;th&gt;Laya (better checkpoint)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10 questions, 500-token document&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.4 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;75.8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 question, short ticket&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.0 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.4 ms (TileLang fast path)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean accuracy, 15 English suites&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.741&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.710&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;typed-decisions, 2,000 decisions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.783&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;answers that change when options are reordered&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.2 %&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.8 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MASSIVE intent, 51 languages&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.436&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.401&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The option-order number comes from a cheap trick the packed layout makes almost free: every choice and score question is also asked with its options reversed, and the two answers are averaged. That's one extra short segment, not another pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  What didn't work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adding a prompt-injection dataset&lt;/strong&gt; to training &lt;em&gt;lowered&lt;/em&gt; the prompt-injection benchmark by 10 points. The benchmark is half German and short; the added data taught the model that instruction-looking text is usually benign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weight soups&lt;/strong&gt; (averaging several fine-tuned runs, or blending with Laya's original weights) nudged the mean up slightly but never closed the specific gaps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seven training rounds&lt;/strong&gt; later, cbjev still trails Laya on four suites: support triage (−4.0), prompt injection (−3.5), DAIR emotion (−2.5) and AG News (−0.8, three cases out of 400). They're listed in the README rather than tuned away.&lt;/li&gt;
&lt;/ul&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;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"cbjev[serve] @ git+https://github.com/tomek7667/cbjev"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;cbjev&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;cbjev&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="c1"&gt;# weights download from Hugging Face
&lt;/span&gt;&lt;span class="n"&gt;res&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&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;Billed twice for March. Refund it today or we cancel.&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;team&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;Which team should handle this?&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;billing&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;invoices, refunds&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;technical&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;bugs&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;other&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;anything else&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;churn&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;noul&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;Does the customer threaten to cancel their subscription?&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also ships a server that speaks TypeSafe Jev's &lt;code&gt;/v1/systemone&lt;/code&gt; wire format, so an existing Jev client can point at it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code (GPL-3.0), benchmarks and training pipeline: &lt;a href="https://github.com/tomek7667/cbjev" rel="noopener noreferrer"&gt;https://github.com/tomek7667/cbjev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Weights: &lt;a href="https://huggingface.co/0010101010-1/cbjev" rel="noopener noreferrer"&gt;https://huggingface.co/0010101010-1/cbjev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Project page: &lt;a href="https://tomek7667.github.io/cbjev/" rel="noopener noreferrer"&gt;https://tomek7667.github.io/cbjev/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks to Convai Innovations for releasing Laya openly; cbjev is fine-tuned from their Apache-2.0 checkpoints. I'd love to hear where it breaks on your data.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
