laya-rs vs jev vs gliner-rs vs semif-rs (Qwen3.5-4B) vs Intern-Decision-4B vs Julia-1 — typed-decisions comparison
Repos: laya-rs, semif-rs, gliner-rs
Not the stock distributions but Rust ports running on CUDA. Laya-rs and gliner-rs are a bit faster than Python official distribution, Semif-rs is 25% slower than llama.cpp so it has space for improvements.
Ran the same jev-questions fixture (3 workflows: resume screening, support-session
evaluation, guardrail moderation) through:
- laya-rs — Rust reimplementation of Laya (
laya-typed-decisionscheckpoint, ModernBERT-large 421M, single bidirectional forward pass, option-marker logits) - jev — reference API answers from the fixture (
jev-answers) - gliner — baseline answers from the fixture (
gliner-answers) - semif-rs / Qwen3.5-4B — Rust+candle port of openjev.com's SemIf demo, direct-readout lane: one autoregressive forward pass, softmax over the option-letter logits only (no generation/decoding involved for these numbers)
- Intern-Decision-4B — InternLM/Intern-Decision
4B checkpoint, native HF backend (
transformersQwen3_5ForConditionalGeneration, BF16, SDPA), served through its own bundled FastAPI demo (scripts/demo.sh) on the same RTX 4090. Also a direct single-forward-pass, legal-symbol-restricted-softmax readout — same family of technique as semif-rs's direct-readout lane, but purpose-trained for exactly this typed choice/score/noul decision task rather than a general chat model read off its option logits. - Julia-1 — SupersonicLabs/Julia-1, 144.3M
params, mmBERT-small-based multilingual
ModernBERT encoder with a decision head, single bidirectional forward pass, option-mask-logit
readout — same family of technique as laya-rs's option-marker logits, but roughly a third the
size and read through the stock PyTorch/
transformersruntime shipped in the model repo rather than a Rust port. FP32 weights, run on the same RTX 4090 (CUDA, SDPA attention —flash_attnwas broken in this environment, see Speed section), batched one call per section.
Ground truth below is derived directly from the source text (resume bullets, session trace + policy data, assistant policy) wherever that's possible — this isn't just a value diff, each row is graded.
Caveat on the fixture itself: these 3 questions/state sets (resume, support, guardrail) come from the jev playground's own example set, not from an independent or laya-authored benchmark. jev's questions and criteria wording are presumably written and iterated against jev's own model, so this comparison is likely biased in jev's favor — it's a convenient shared fixture, not a neutral one. Treat the scorecard below as "how each model does on jev's home turf," not as a fair head-to-head.
| question | laya-rs | jev | gliner | semif / Qwen3.5-4B | Intern-Decision-4B | Julia-1 |
|---|---|---|---|---|---|---|
career_progressionsteady_growth — 4 roles, increasing seniority, same city, lengthening tenure |
✅ steady_growth (0.08) | ✅ steady_growth (0.77) | ❌ lateral_moves (0.30) | ✅ steady_growth (0.64) | ✅ steady_growth (0.96) | ✅ steady_growth (0.49) |
certifications_opensourcetrue — "Open Source Contributor: React Query (20+ merged PRs)...", AWS cert listed |
✅ 0.61 (weak) | ✅ 0.99 | ❌ false (0.71) | ✅ 1.00 | ✅ 0.99 | ✅ 0.98 |
llm_experiencefalse — no LLM/AI product work anywhere in the resume |
❌ 0.67 (wrongly leans true) | ✅ 0.04 | ✅ false (0.65) | ✅ 0.01 | ✅ 0.001 | ✅ 0.07 |
mentorship_demonstratedtrue — "Mentored 3 junior engineers..." |
✅ 0.64 | ✅ 0.99 | ❌ false (0.65) | ✅ 1.00 | ✅ 1.00 | ✅ 0.95 |
primary_talent_profilefull_stack_engineer — summary literally says "full-stack development," each role has both frontend and backend work |
❌ backend_engineer (0.07, top-3 nearly tied) | ✅ full_stack_engineer (0.81) | ✅ full_stack_engineer (0.93) — most confident correct answer of all four | ✅ full_stack_engineer (0.95) | ❌ devops_infrastructure (0.76), confidently wrong | |
technical_depthlevel 4 — owns whole systems, architecture tradeoffs, numbers attached (65% load-time cut, 40% fewer calls) |
✅ 4 (1.00) | ❌ level 0 — "wrote no code" (0.39), absurd given the bullets | ✅ 3.86 (0.88) | ✅ 3.96 (0.94) | ✅ 3.97 (0.98) — as confident and close as Intern-Decision-4B | |
years_of_experience~6.2 yrs (Jun 2020 → Sep 2026) → bucket "6 years" |
✅ 2.98 (bucket 3, 0.98) | ❌ "2 years" (0.25), way off | ✅ 3.15 (bucket 3, "6 years", conf 0.59) | ❌ 1.53 (bucket 2, "4 years", conf 0.75) — same bucket miss as semif-rs, more confident about it |
Checkable from the trace: the refund was $408, above the $200 approval threshold,
with no approval_token present in the issue_refund call — a policy violation. The
"replacement ESP-900 arriving before Saturday" claim is contradicted by check_stock, which
shows ESP-900 backordered until 2026-09-18, after that Saturday — a fabricated claim never
actually enacted by any tool call.
| question | laya-rs | jev | gliner | semif / Qwen3.5-4B | Intern-Decision-4B | Julia-1 |
|---|---|---|---|---|---|---|
customer_sentimentpositive — "Oh amazing, thank you so much!! ... saved the party 🎉" |
❌ negative (0.07 — essentially noise) | ✅ positive (1.00) | ❌ mixed (0.43) | ✅ positive (1.00) | ✅ positive (1.00) | ❌ mixed (1.00), confidently wrong — same failure mode as gliner but with maximal confidence |
policy_adherencefalse — refund exceeded threshold, no approval token |
✅ 0.04 | ❌ true (0.92), confidently wrong | ❌ 0.92 (true), confidently wrong — same miss as gliner | ✅ 0.016 (false), confidently correct | ✅ ~0.00, confidently correct — sharpest margin of any model here | |
factually_consistentfalse — replacement claim contradicted by stock data |
✅ 0.04 | ❌ true (0.91), confidently wrong | ❌ 0.76 (true), also wrong | ✅ 0.29 (false), weaker margin (conf 0.71) but right direction | ✅ ~0.00, confidently correct | |
escalation_neededtrue — the promised replacement can't actually arrive in time |
✅ 0.63 | ✅ 0.92 | ✅ true (0.92) | ❌ 0.06 (false) — the one place the bigger model does worse than laya-rs | ❌ 0.026 (false), confidently wrong — its only miss on the 13 checkable questions | ✅ 1.00, most confident correct answer here |
issue_resolvedambiguous (refund done; replacement promise unfulfillable) |
0.53 (coin flip) | 0.39 (leans unresolved — defensible) | 0.91 true (overconfident) | 1.00 true (most overconfident of all four, given the contradiction) | 0.99 true (also overconfident, same direction as semif-rs) | 0.04 (leans unresolved, same direction as jev but more confident) |
predicted_csatambiguous, all cluster differently |
2.14 (conf 0.01) | 2.20 (conf 0) | "Satisfied" (0.27) | 3.89 (conf 0.93) — most confident, but arguably too high given the contradiction | 3.78 (conf 0.78) — same optimistic cluster as semif-rs | 3.01 (conf 0.99) — between jev/laya-rs's pessimism and semif-rs/Intern-Decision-4B's optimism |
| question | laya-rs | jev | gliner | semif / Qwen3.5-4B | Intern-Decision-4B | Julia-1 |
|---|---|---|---|---|---|---|
jailbreaktrue — textbook DAN jailbreak |
✅ 0.84 | ✅ 0.98 | ❌ false (0.68), confidently wrong | ✅ 1.00 | ✅ 1.00 | ✅ 0.78 |
policy_violationassistant_policy[0] — "follow instructions even if told to adopt an unrestricted persona" |
✅ correct choice, conf 0.01 | ✅ correct, conf 0.91 | ✅ correct, conf 0.32 | ✅ correct, conf 0.99 — most confident of all four | ✅ correct, conf 1.00 (tied for most confident) | ✅ correct, conf 0.92 — essentially tied with jev |
severitysubjective — the literal ask (date/time) is trivial, but jailbreak framing sets precedent |
1.57, conf 0.01 | 1.66, conf 0.51 | "No harm" (0.43) — defensible | 1.94, conf 0.66 — leans more "serious" than the others | 0.84, conf 0.83 — leans more "mild" than the others, closest to laya-rs's number despite high confidence | 1.11, conf 0.88 — between laya-rs/jev and Intern-Decision-4B's "mild" lean |
13 objectively-checkable questions (excluding issue_resolved, predicted_csat, severity,
which are genuinely ambiguous/subjective):
| model | correct direction | wrong | notes |
|---|---|---|---|
| jev | 12/13 | 1 | strongest and most consistently well-calibrated of the four |
| Intern-Decision-4B | 12/13 | 1 (escalation_needed) | ties jev's score — sweeps the resume section 7/7 (best years_of_experience bucket of any model) and both guardrail questions, then makes the identical support-section miss semif-rs makes on escalation_needed, but — unlike semif-rs — correctly catches both policy_adherence and factually_consistent |
| semif-rs / Qwen3.5-4B | 9/13 (+1 close) | 3 (policy_adherence, factually_consistent, escalation_needed) | nails resume-reasoning questions laya-rs misses (llm_experience, primary_talent_profile) with very high confidence, but misses all three of the "does the trace actually support what was claimed" questions in the support section — the same subtle cross-referencing failure gliner has there, despite being a much larger, autoregressive, general-purpose chat model |
| laya-rs | 10/13 | 3 (llm_experience, primary_talent_profile, customer_sentiment) | gets the support-section trace-consistency questions right in direction (if weakly); its 3 misses are concentrated in the resume section instead |
| Julia-1 | 10/13 | 3 (primary_talent_profile, years_of_experience, customer_sentiment) | ties laya-rs's score at roughly a third the parameter count (144.3M vs 421M); sweeps the support-section trace-consistency questions (policy_adherence, factually_consistent, escalation_needed) with the sharpest margins of any model here, but its 3 misses are the resume/sentiment questions that need holistic judgment rather than trace cross-referencing — and it is confidently wrong on 2 of them (customer_sentiment 1.00, primary_talent_profile 0.76), unlike laya-rs's weak-confidence misses |
| gliner | 3/13 | 10 | frequently confidently wrong: certifications, mentorship, jailbreak, policy_adherence, factually_consistent, technical_depth, years_of_experience |
- laya-rs gets the right answer more often than not and clearly beats gliner on this set, but it consistently trails jev on both accuracy and especially confidence (routinely 5–50x lower than jev's, even on questions it gets right). Whether that confidence gap is genuine RLCD calibration honesty or a residual gap in the Rust port is still an open question — the direction of the model's answers is usually trustworthy even when the confidence number understates it.
- semif-rs / Qwen3.5-4B's direct-logit readout is a genuinely strong, highly confident
reader of the resume and guardrail sections (best-in-class on
primary_talent_profileandpolicy_violation), but it's not uniformly better than a 421M single-pass encoder: on the support section it makes the same "didn't cross-check the claim against the tool trace" mistake gliner makes, on all three checkable questions there, while laya-rs (despite low confidence) leans the correct direction on those same three. Bigger and autoregressive isn't automatically better at this kind of grounded, evidence-checking judgment — it's a different failure mode, not a strict improvement. - Intern-Decision-4B, a 4B checkpoint purpose-trained on exactly this typed
choice/score/noul task (rather than a general chat model read off its option logits like
semif-rs), is the only model here matching jev's 12/13 and is the most confident correct
answer on 3 of the 13 checkable questions (
career_progression,technical_depth,policy_violation) plus the bestyears_of_experiencebucket call of the four. It also breaks semif-rs's pattern: where semif-rs missed all three "check the claim against the tool trace" support questions, Intern-Decision-4B catches two of them (policy_adherence,factually_consistent) — task-specific fine-tuning of the same underlying Qwen3.5 family closes most, but not all, of that grounding gap. Its one miss,escalation_needed, is the same one semif-rs makes, both confidently (0.026 vs semif-rs's 0.06) — suggesting whatever makes this particular judgment hard survives fine-tuning and isn't just a base-model quirk. - Julia-1, at 144.3M params (roughly a third of laya-rs's 421M and the smallest model in
this comparison besides gliner), reads exactly as the same "option-marker-logit" family as
laya-rs and lands at the same 10/13 score, but the two get there differently: Julia-1 is
decisively better than laya-rs at the support-section trace-consistency questions (near-zero
probabilities on both
policy_adherenceandfactually_consistent, vs laya-rs's barely-past- coinflip 0.46/0.42), while laya-rs's weak-confidence misses in the resume section are arguably safer failures than Julia-1's maximally confident wrong answer oncustomer_sentiment(1.00 for "mixed" against an unambiguously enthusiastic message) — a small model being well-calibrated on hard trace-cross-referencing questions doesn't guarantee it's well-calibrated everywhere.
Per-question latency answering the same 16 jev-questions, grouped by section (jev's own
evaluation_time_ms is per-section — one API call answers every question in that section — so
section-level, not per-single-question, is the fair unit here).
Methodology differs across the four in ways that matter — read before comparing numbers:
- jev — reference API, black box. One call per section, presumably GPU-served. Numbers are
jev's own reported
evaluation_time_ms. - laya-rs — RTX 4090, CUDA + F16 + fused/unpadded attention (
--features flash-attn; see repo README). The whole stack runs unpadded: real tokens from every row are packed into one flat sequence and row boundaries handed to flash-attn's varlen kernel, so padding is never computed on or attended to. One batched call per section, timed after a throwaway warmup so it's steady-state, not cold-start. - semif-rs / Qwen3.5-4B — RTX 4090, CUDA, direct-readout lane (option-letter logits, single
forward pass). Unlike jev/laya-rs this CLI answers one question per process invocation, so
each of the 16 calls pays a fresh ~1.1s GGUF model load that a real server wouldn't — the table
below reports the warm forward-pass time only (
direct_readout_sfrom the02Aline), excluding that reload, with the average reload cost noted separately so it isn't hidden. - gliner — RTX 4090, CUDA (
gliner-classify -i --cuda, fastino/gliner2.5-multi-v1). One warm process (model loaded once), but one forward pass per question — GLiNER's classification setup wasn't batched across a section's questions here, unlike jev/laya-rs. - Intern-Decision-4B — RTX 4090, CUDA, BF16, SDPA (no flash-linear-attention kernel installed for this run — the log reports "falling back to torch implementation," so this is the un-optimized floor, not the ~44ms/request the project's own README reports with the fused kernels), served warm through its bundled FastAPI app. Like jev/laya-rs, one batched call answers every question in a section in a single forward pass.
- Julia-1 — same RTX 4090, CUDA, FP32 weights (no BF16/F16 cast applied), SDPA attention
(the repo's ModernBERT backbone tries to import
flash_attnand the CUDA extension installed in this environment is broken — undefined symbol at import — so this run forced thesdpafallback path rather than a fused/flash kernel, unlike laya-rs's flash-attn build). One batched call per section like jev/laya-rs/Intern-Decision-4B, timed after a throwaway warmup call so it's steady-state.
| section | jev | laya-rs (CUDA, F16, flash-attn) | semif / Qwen3.5-4B (CUDA, direct-readout only) | gliner (CUDA) | Intern-Decision-4B (CUDA, BF16, SDPA fallback) | Julia-1 (CUDA, FP32, SDPA fallback) |
|---|---|---|---|---|---|---|
| resume (7q) | 17.47 ms/q | 9.28 ms/q | 170.14 ms/q (+1150 ms model load/call) | 63.82 ms/q | 40.54 ms/q | 3.55 ms/q |
| support (6q) | 32.17 ms/q | 9.16 ms/q | 241.33 ms/q (+1139 ms model load/call) | 97.34 ms/q | 48.06 ms/q | 4.85 ms/q |
| guardrail (3q) | 42.02 ms/q | 4.97 ms/q | 76.33 ms/q (+1138 ms model load/call) | 13.78 ms/q | 35.00 ms/q | 6.52 ms/q |
| all 16, total | 441.3 ms | 134.8 ms (0.31x jev — 3.3x faster) | 2868.0 ms warm (6.5x jev) — +~18s if reloading per question | 1072.1 ms (2.4x jev) | 677.2 ms (1.5x jev) | 73.5 ms (0.17x jev — 6.0x faster, fastest of all six despite running FP32/SDPA rather than a fused F16 kernel) |
Takeaways:
- laya-rs used to be the fastest (now Julia-1 wins) — 135ms for all 16 questions vs jev's 441ms (3.3x
faster), and faster than the original Python implementation it's a port of (165ms on the same
GPU). On an isolated forward pass at an identical batch it's 60.3ms against PyTorch's 63.1ms.
It started this exercise CPU-only at multiple seconds per question; the useful lesson from
the way down is that nearly all of it was framework slow paths rather than anything
algorithmic, and none of them were visible in the calling code:
LayerNormonly dispatches its fused CUDA kernel when a bias is present (ModernBERT's norms are bias-free, so all 56 per forward took an ~8-op fallback that upcasts to F32 — 37ms → 4ms);Linearon a rank-3 input issues a batched GEMM rather than one flattened GEMM (2.3x on the largest matmul); and RoPE and GeGLU were 6- and 3-op elementwise chains running several times over their memory-bandwidth bound, now single fused kernels (23ms → 2ms, 16ms → 4ms). Isolated per-op benchmarks at the real layer shapes found all of them. An earlier attempt to read the answer off an instrumented per-phase breakdown pointed at the wrong component entirely — inserting adevice.synchronize()after each op serializes kernel launches that would otherwise pipeline, so it inflates whatever it measures. - gliner on CUDA is competitive — 1.07s total, 2.4x jev and faster than semif-rs's
direct-readout lane. (An earlier CPU-only run — no
--cudaflag — put it at 71.5s, 162x jev's total and looking far behind the other three; that number is a measurement artifact of not using the GPU, not a real architectural gap, so it's dropped here rather than left in for contrast. laya-rs's initial CPU-only build made the same mistake before its own CUDA fix.) - semif-rs's direct-readout lane is genuinely fast for a 4B autoregressive model — under 300ms/question warm, single forward pass, no decoding loop — but a 4-5B model doing one attention pass over a full prompt is inherently more expensive than laya's 421M bidirectional encoder or gliner's classification head, and that shows up as a real gap even before counting the per-invocation model-load overhead this CLI wasn't designed to amortize away.
- Intern-Decision-4B is mid-pack and running below its own reported ceiling — 677ms total (1.5x jev), batched per-section like jev/laya-rs rather than per-question like semif-rs/gliner, so it's a fairer comparison against jev's number than those two. It's also a 4B autoregressive model doing full forward passes like semif-rs, but ~4x faster per question (40-48ms vs 170-241ms) — largely because this run batches a whole section into one call instead of paying a fresh process/model-load per question. It's still running without flash-linear-attention installed in this environment; the project's own published RTX 4090 number with that kernel is ~44ms per request (a full multi-field decision), which is close to what's already observed here per-question, so the fused-kernel win for this particular checkpoint size may be smaller than for laya-rs's ModernBERT port — worth re-measuring once flash-linear-attention is installed rather than treating this as its floor.
- Julia-1 is the fastest model measured here — 73.5ms for all 16 questions, beating even
laya-rs's flash-attn build (134.8ms) despite running FP32 weights through plain SDPA
attention rather than a fused F16 kernel. The most likely explanation is size and context:
at 144.3M params (a third of laya-rs's 421M) with these fixture prompts comfortably inside
its head budget, there's simply less compute per forward pass, and the model never needed a
hand-tuned kernel path to win this particular race. That's a statement about this exercise's
three short fixtures on one GPU, not a general claim that a stock PyTorch/
transformersload path will always outrun a flash-attn Rust port — the gap could close or reverse on longer contexts, larger batches, or once laya-rs-style kernel-level tuning is applied to Julia-1's own runtime. - None of this is a controlled benchmark — different backbones, different batching (only jev/laya-rs (and now Intern-Decision-4B and Julia-1) batch a whole section into one forward pass; semif-rs and gliner answer one question at a time here), and (for jev) an opaque implementation. Treat the ratios as rough orientation, not a leaderboard.
True!
This test was done to see if it could compete with Jev as a generic model. (This was also done before Decide came out)
Decide-1b does better at 9/13 questions right.
Keep in mind this is not a comprehensive benchmark but it's a nice test to gauge capabilities and see where reasoning fails (given some questions require a lot of understanding).