Repository navigation
Conversation
cuBLASLt's heuristic picks an algorithm per M, so a row's result can change with the number of rows in the call, and a prefix and its branch run separately round differently from one pass over the same tokens. Add cs1_gemm_create_fixed: one algorithm per weight shape (N, K, ldy), the heuristic's first choice at a reference M among algorithms without split-K, for every M. The default handle is unchanged. Bump the ABI to 7 and add a GPU test that each projection's rows match across M.
forward_shared captures each prefix with the prefix continuation from ThinkFlowLab#96 and continues every branch from it, with the fixed-algorithm GEMM handle; forward_fixed is the same path without a prefix. run_window takes a GEMM handle, can capture after a continuation, and indexes the text rotary tables by position.
c888122 to
063e3b5
Compare
…_window) for the worker
Serve OmniJev-4B v1.1 on /v1/systemone with the shared Qwen3.5 vision and language path: the vision tower once per request, one fixed-GEMM pass per (question, option) row with its readouts, the heads on the CPU and MSO1._finish. The shared crate gains Model::forward_multimodal_readout (final-norm hidden states at given rows and token log-probabilities from the tied embedding), and image_decode and inputs::image_positions, moved from Cua-S1, which now calls them. VisionConfig also accepts the qwen3_5_vision model type that Transformers writes.
063e3b5 to
fc27b75
Compare
hsliuustc0106
left a comment
There was a problem hiding this comment.
Independent review — PR #129 "omnijev: add the native eager worker" (DRAFT)
Reviewer: independent ZCode review agent. Not posted to GitHub; no repo state modified.
0. Provenance and pinned revisions
- Skill:
.agents/skills/system1-omni-review/SKILL.md, read from the working tree at
/Users/hsliu/thinkflowlab/system1-omni/. SHA-256 of bytes read:
58be3bc60806c4c5af7038a0c6a80fadb94f3360d714556a3e965c8903dde108(matches the pinned value;
identical to the copy atmain@4a79980). Repository ThinkFlowLab/system1-omni (ID 1386454853). - References actually read (content hash verified):
docs/architecture.md— byte-identical tomain@4a79980(526e0f7a…f091f6)..agents/skills/system1-omni-review/references/repository-contracts.md— working-tree copy,
byte-identical tomain@4a79980(d6ae9650…f1202).src/runtime/README.md— working-tree copy, byte-identical tomain@4a79980(1bda45b0…a44bf).CONTRIBUTING.md— read at the PR head (git show fc27b75:CONTRIBUTING.md, includes the
large-code-change section).
- Head re-check (skill requirement): the review request pinned head
063e3b508ac1…, which does
not exist in the local clone or on the remote. The live PR head is
fc27b756fcc91f030908c67efbaf67b7e6d71647(author twu3202, branchomnijev-worker), which is
whatrefs/remotes/remote-pr/129resolves to and whatgh pr viewreports (headRefOid,
re-checked at the end of the review). This review coversfc27b75. The PR's actual base on GitHub
ismain@7cbce19f117f…(merge of PR #120), notorigin/main@4a79980(the localorigin/mainref
is stale). PR state: OPEN, DRAFT,mergeable: MERGEABLE. - Diff scopes (all personally computed):
git diff 4a79980..fc27b75(stale merge-base): 160 files, +14,994/−866. Inflated: it contains
the already-reviewed #120 content plus other branches merged into the PR branch.git diff 7cbce19..fc27b75(GitHub "Files changed"): 56 files, +4,675/−1,114. Most of this
is inherited from #98/#99 (fixed-algorithm GEMMs,run_window,forward_shared), #117
(shared vision tower,vision.cu,image_preprocess) and #108 (prefix tests) merged into the
branch; the author discloses exactly this in the PR description.- Authored change = commit
fc27b75alone: 33 files, +2,274/−267 (verified with
git show --shortstat/--numstat). This is the scope reviewed in depth.
- Host constraints honored: no Rust toolchain (no cargo commands run), no GPU. Rust source was read
directly at the head commit. Python available but the only Python artifact (validate.py) needs
the pinned reference checkout and a GPU; it was read, not run.
1. Verdict
No P0/P1/P2 findings. 1 P3 finding. 7 notes. The authored change is a faithful, well-bounded
implementation of the eager native worker for OmniJev-4B v1.1 within the repository's contracts.
The evidence base, however, is thin outside author-reported results: CI has produced zero check
runs on this branch, and all GPU/parity numbers are author-reported from one sm_89 GPU. This is
reported as an evidence gap, not a PASS. Draft state is appropriate; the author's own proposed
numerical gate (float32-reference comparison, not Cua-S1's two-gate policy) is the right contract
for a score/noul model and is honestly stated, including one decision that flips between the
worker's PNG and JPEG runs at a 0.18 float32 margin.
2. Findings
P3 — Worker admission slot is released on client disconnect while the request still runs
- File:
src/models/omnijev/native/src/main.rs:40-50(Admissionguard) and:62-67,:96-104
(decide), at headfc27b75. - Trigger: a client disconnects after its request has been dispatched into
shared.scheduler.run(...), or while it is preparing. Axum drops the handler future;Admission's
Dropdecrementsshared.admittedimmediately, but the owned closure (and theSerialScheduler
permit, and the executor mutex) continue to run to completion on the blocking pool, as the runtime
contract requires. - Consequence: the 503 budget (
ADMITTED = 16, documented in code as "preparing, waiting or
running" and inrecipe/omnijev/native.mdas "holds at most 16, preparing, waiting or running")
undercounts still-executing work after disconnects, so more requests can be admitted than the
comment/recipe semantics imply. Execution stays correctly serialized (the scheduler permit is
retained persrc/runtime/README.md), and responses to disconnected clients are discarded — this
is an accounting/documentation nuance, not a correctness or lifetime violation. - Suggested fix (optional): move the
Admissionguard into the scheduler closure (it is
Arc-based, so it can be cloned in) so the slot is held until execution completes, or reword the
comment/recipe.
Severity rationale: worker-level overload accounting only; the contract-binding scheduler permit
and resource lifetimes are correct.
Notes (non-blocking)
main.rs:73—acquire_owned().await'sErr(semaphore closed) would proceed without a
preparing permit. The semaphore is never closed, so the path is dead; noting for awareness only.executor.rs:96-104— per-questionlatency_sis the request total divided by the question
count, not a per-question measured time. Disclosed insrc/models/omnijev/README.md
("which time the request's preparation and its vision and language passes"); informational fields.executor.rs:97— the question statezqis taken fromrows[0]; correct because every row's
pass shares the identical prefix+head and the fixed-algorithm GEMMs are M-independent (the
invariance property the PR tests on GPU). Relies on the author-reported GPU invariance result.export.rs:96-116— every worker start SHA-256s all listed export files (multi-GB shards).
Deliberate pinned-identity choice; startup cost only.docs/supported-models.md:22— "for 84 answers" (7 stills × 12 speed_bench questions) vs
native.md's 140 (adds the 8 gate questions). Both accurate for their subsets; the row cites the
smaller one.- The GitHub diff contains ~3,100 added lines authored in #98/#99/#117/#108 (not yet merged to
main). Those were not re-reviewed here beyond their interaction with the authored code; they are
the reason the PR must stay draft until they merge, which the author states. - The strict
json::parseruns twice per request (once indecidefor the 400 mapping, once
insidecontract::compile). Harmless; keeps the error-status contract clean.
3. Contract-compliance walkthrough (authored code)
- Ownership layers (docs/architecture.md): transport is a plain Axum worker
(main.rs); processing stays model-specific (processing.rs, contract unchanged from #120);
scheduling reusesomni_runtime::SerialScheduler(serial admission, one complete request per
permit — the Open-Jev precedent); the model executor (executor.rs) owns vision + language +
CPU heads; no CUDA/C++ is authored here — the shared crate andsrc/backends/cuda/qwen3_5/
changes are inherited from #98/#99/#117. Rust owns host orchestration; device ops stay in the
backend. Compliant. - Chain fidelity (serialization → prompt/template → token IDs → prefill → scoring → response):
prompt()retains the<think>suffix and Jinja-style trailing-whitespace trim;
layout()computes the questions' common token prefix cut at least one token before any first
<|opt|>(opens[0] >= 2enforced,prefix.max(1)safe because every row pass is exactly the
original single-question sequence truncated at an option close); each row pass
(executor.rs:120-160) is a full unpadded forward over prefix+row with per-row M-RoPE positions
from the sharedimage_positions(verified against Transformers 5.17.0get_rope_index
semantics and pinned bypositions_sha256fixtures from the reference); readouts arezqat the
token before<|opt|>,uat<|/opt|>, option-text log-probabilities with the first token also
predicted fromzq; Score rows skip the LM readout and use the ordinal head — no Cua-S1 26-letter
or A–Z choice semantics are imposed anywhere. Reconstruction zips answers back by question id
(duplicate ids impossible: the strict parser rejects duplicate JSON keys), whole-request failure
preserved (any error → single 500, no partial answers), usage reports the reference's token count
(prefix once + rows), padding adds nothing. Compliant. - Pinned identity:
export.rspins formatomnijev-merged/1, checkpoint revision
ffe5f436…, base revision851bf6e8…(matches the repository-contracts base pin
851bf6e806ef…), vocabulary 248,079, option-token ids,max_pixels602,112, head config, and
calibration (positive finite temperatures, finite noul bias); it hashes every listed file,
requires the manifest to cover everyweight_mapshard, rejects path-traversal names and an
unlistedmodel.safetensors.Executor::loadadditionally checks
cfg.image_token_id == IMAGE_TOKENandcfg.hidden == heads::HIDDEN, andProcessor::load
pins the three special tokens' ids intokenizer.json. Strong chain. - Eager execution path / lifetimes:
forward_multimodal_readout(model.rs:971-1074)
validates the wholeMultimodalInputboundary before allocation; scratch is grown by the
existingprepare_scratch; the vocabulary pass reuses persistentvocab_buffers
(devicepicked/logits+ host mirror, sizedVOCAB_ROWS(64) × row,ld = vocab next_multiple_of(8), all copies/GEMM/downloads bounds-checked againstm ≤ 64, rows< t,
token < vocab); on any error the stream is synchronized before returning ("Work already queued
may still use the buffers"), andExecutor::execute(executor.rs:63-72) synchronizes both
vision and language even on the error path before releasing admission — exactly the runtime
contract's "executors must finish queued device work before returning". FFI integer args are
far inside i32 range; buffer arithmetic useschecked_mul/next_multiple_of;Model::drop
destroys the fixed GEMM handle only when non-null. No sizes/overflow issues found on the FFI
boundary touched by the authored code. - Cancellation / admission: waiting callers are removed without dispatch and dispatched closures
retain permit+resources until completion (owned by the already-mergedomni-runtime, unchanged
here); the worker adds preparation concurrency 2, a 16-slot worker cap with 503+Retry-After,
and admission counting that is race-safe (fetch_add/fetch_subpairing, RAII release). One P3
accounting nuance above. Compliant with the runtime contract; the execution unit is one complete
request, consistent with the Open-Jev row of the runtime table. - Tests: all new tests live in the root
tests/tree with explicit[[test]]registrations
(CONTRIBUTING rule). CPU tests are fixture-based and reference-pinned (PNG decode bit-exact vs
Pillow, preprocessing bit-exact given Pillow's pixels, JPEG bounded at ±3 with the processor
re-verified bit-exact on Pillow's pixels, export manifest negative cases withDropcleanup,
token_logproblog-softmax-over-vocabulary-only including padding exclusion). GPU tests
(readout.rs,worker.rs) are#[ignore]-gated on env-pinned checkpoint/library and check
bit-equality withforward_fixed, M-independence over 130 targets/two vocabulary chunks, a float64
CPU log-prob within one BF16 ULP + 1e-3, and answer repeatability/order-independence.
4. Changed-file inventory and 3k-rule readiness
- Authored (
fc27b75): 33 files, +2,274/−267 (2,541 changed lines total).
Code (src + tests + validation scripts, excluding docs/lockfile/static fixtures per
CONTRIBUTING): my count 2,148 (author reports 2,047 — same order, both below 3,000).
Excluded from my code count:README.md,docs/supported-models.md,mkdocs.yml,
recipe/README.md,recipe/omnijev/README.md,recipe/omnijev/native.md(123),
recipe/omnijev/gate_questions.json(242),tests/omnijev/data/pixels.json(1-line/48 KB
generated fixture),Cargo.lock. - Total GitHub diff (
7cbce19..fc27b75): 56 files, +4,675/−1,114 (5,789 lines), of which the
authored commit is ~44% of additions. Total stale merge-base diff (4a79980..fc27b75):
160 files, +14,994/−866 — not the operative scope. - 3k rule: not triggered on authored code (≈2.1–2.5k lines). Regardless, the PR description
carries the large-change preparation anyway: full self-review (all four boxes ticked by the
author, not touched by this review), split rationale (#120/#98/#99/#117 dependencies, draft until
merged), a component map (shared crate vs omnijev sections), and per-area validation (CPU checks,
GPU kernel/readout/worker tests, 140-answer reference comparison, latency table, version/hardware
pins: RTX 6000 Ada sm_89, CUDA 13.2, driver 595.91.07, Transformers 5.17.0/torch 2.14.0/PEFT
0.19.1). Interfaces between components (sharedModel,VisionConfig, movedimage_decode) are
covered. Readiness: adequate. - Authored-file list (all at
fc27b75):src/models/omnijev/native/src/{main.rs (new, 181), executor.rs (new, 178), export.rs (new, 150), processing.rs (+75/−70), contract.rs (+2/−2), lib.rs (+3/−1), Cargo.toml (+22/−3)};src/models/qwen3_5/native/src/{model.rs (+143/−2), inputs.rs (+43), image_decode.rs (new, 134, moved from Cua-S1), lib.rs (+3/−2), vision/mod.rs (+2/−1), Cargo.toml (+5)};src/models/cua_s1/native/src/{image_request.rs (+13/−90), multimodal.rs (+5/−42)}(mechanical moves, limits preserved exactly:
2048/1,048,576/200/32 MiB); docs/recipes as listed above; tests
tests/omnijev/{export.rs (new 180), pixels.rs (new 135), worker.rs (new 93), processing.rs (+4/−3), generate_fixtures.py (+57/−2), data/pixels.json (new)},
tests/qwen3_5/{readout.rs (new 187), model.rs (+34)}.
5. Committed artifact hygiene (complete inventory)
- GitHub-diff inventory (56 files): 26
.rs, 9 docs (.md), 8.json, 6.toml/lock, 3.py,
plus 4 inherited backend files (.cu/.h) and doc navigation. No binaries, no logs, no
benchmark output, no editor droppings, no generated data without a committed generator. tests/omnijev/data/pixels.json(48 KB, single line): generated by the maintained
tests/omnijev/generate_fixtures.py(extended in this PR); pattern images are redrawn in test
code and only encoding-sensitive files (palette, 16-bit gray, JPEG) are stored. This is a
maintained fixture, not one-off run output — acceptable, keep.recipe/omnijev/gate_questions.json(242 lines): maintained validation question set consumed by
validate.py --questions— maintained corpus, acceptable.- Stale merge-base-only artifacts (
tests/jev_vl/data/replay/image.png,fail_graph_capture.cpp,
LICENSE.omnijev, etc.) belong to other branches' merges, not to this PR's authored content; no
hygiene action.
6. Commands run and results
| Command | Result | Class |
|---|---|---|
shasum -a 256 .agents/skills/system1-omni-review/SKILL.md |
58be3bc6…dde108 (matches pin) |
personally run |
git log / git rev-parse refs/remotes/remote-pr/129 / git cat-file -t 063e3b50… |
head = fc27b75…; pinned 063e3b50… does not exist |
personally run |
gh pr view 129 (twice) |
OPEN, draft, author twu3202, base main@7cbce19, head fc27b75, MERGEABLE; body quoted in this review |
observed (live API) |
git diff --stat 4a79980..fc27b75; git diff --stat 7cbce19..fc27b75; git show --shortstat fc27b75 |
160 files +14,994/−866; 56 files +4,675/−1,114; 33 files +2,274/−267 | personally run |
Full reads at head: main.rs, executor.rs, processing.rs, export.rs, contract.rs (grep), image_decode.rs, inputs.rs, model.rs (readout + hidden_row + run_window signature), vision/mod.rs, Cua-S1 move diffs, tests/omnijev/{worker,export,pixels,processing}.rs, tests/qwen3_5/{readout,model}.rs, generate_fixtures.py diff, validate.py, native.md, README/mkdocs/supported-models diffs, .github/workflows/{ci,docs}.yml |
no P0–P2 issues found | source inspected |
cargo fmt/clippy/test/build |
not run — host has no Rust toolchain (constraint) | not run |
mkdocs build --strict |
not run (docs changes are present; author reports strict pass) | not run |
GPU tests (tests/qwen3_5/readout.rs, tests/omnijev/worker.rs, kernel tests) |
not run — no GPU (constraint); author-reported pass on one RTX 6000 Ada | author-reported |
validate.py 140-answer comparison + latency table |
not run — needs pinned reference checkout + GPU | author-reported |
7. CI observed
gh pr checks 129 --repo ThinkFlowLab/system1-omni→
"no checks reported on the 'omnijev-worker' branch" (exit 1). Retry produced the same result.- Corroborated via API on the head SHA:
…/commits/fc27b75…/check-runs→{"runs":[],"total_count":0};
…/status→{"state":"pending","statuses":[]};gh run list --branch omnijev-worker→ empty. - So there are no CI check states at all to report — including no
action_requiredentries.
Both workflows (ci.yml,docs.yml) trigger onpull_request(drafts included), so the
absence means CI never started for this head (likely timing — the commit is dated today — or a
fork-run approval). Whatever the cause, no CI evidence exists for this PR; the five Rust steps
andmkdocs --strictare author-reported (macOS) only.
8. Material unverified checks
- All five CI Rust checks and
mkdocs build --strict— author-reported on macOS; no observed
CI run of any kind (Section 7). TheCargo.toml/Cargo.lockchanges (axum/tokio/sha2/image
additions,[[test]]registrations) are compile-verified only author-side. - All GPU behavior — the fixed-GEMM M-independence that the whole eager design leans on
(rows[0].zqreuse, chunked vocabulary logits, bit-equality withforward_fixed), the kernel
tests, and the worker repeat/order tests: author-reported on a single sm_89 GPU. Not observed by
CI or this review; no other GPU arch covered. - Numerical parity vs the reference (140 answers, float32 baseline, JPEG/PNG split,
LoRA-merge and branch-chunk isolations; latency table; 9.4 GB memory): author-reported via
validate.py; raw outputs are "kept locally" and not attached — the numbers are plausible and
honestly caveated (one PNG-run decision flips at float32 margin 0.18; the proposed float32 gate
is a contract decision for maintainers, and the author explicitly invites a different gate), but
they are not independently reproduced. warmup/readiness over HTTP and the frontend-proxied path — no transport regression test for
the new worker's HTTP surface (status mapping incl. 413 rejection body, health after warmup,
503 behavior); the worker tests call the library directly. The recipe's
OMNI_JEV_BACKEND_URLrouting guidance is unverified here.- Rebase-on-merge caveat — the tests were run, per the PR body, "on the same tree before the
rebase onto #120's merge"; the head tree itself has not been revalidated by anyone. - Inherited #98/#99/#117/#108 content inside the GitHub diff (~3,100 added lines incl. CUDA) is
covered by their own PRs, not re-reviewed here.
9. Bottom line
Static review of the authored commit finds the worker faithful to the omnijev contract and the
architecture/runtime/lifetime contracts, with careful pinned-identity, bounds and
error-synchronization handling; one P3 admission-accounting nuance. The blocking gap before this
draft can leave draft state is evidence, not code: no CI run exists for the head, and every GPU and
parity claim is author-reported on one GPU with locally-held raw outputs. Recommended next steps for
the author: get CI to run on the head SHA (or report the exact local commands + full output),
attach the validate.py outputs, and decide the numerical gate question the PR honestly raises.
The slot moved with the handler, so a client that went away released it while its request still ran. It now moves into the preparation and then into the scheduled work. The supported-models row cites the 140 answers.
|
Thanks for the review. Pushed
On the evidence:
I'll follow whichever gate you choose and rerun against it. |
hsliuustc0106
left a comment
There was a problem hiding this comment.
Incremental re-review at new head 5cf7c4ed27: one new commit since my review at fc27b756fc — "omnijev: hold the admission slot until the request's work ends" (2 files, +16/−8).
This fixes the P3 from my review, verified in the diff: the Admission guard is now named and moves through the preparation spawn_blocking closure into the scheduler.run closure, so a client disconnect that drops the handler future no longer releases the slot while preparation or executor work continues — the slot is held until the request's work actually ends, and the struct doc says so. The previously-dead semaphore-close path now returns a typed 500 via let-else. Also addressed from my notes: docs/supported-models.md now cites the full 140-answer comparison instead of the 84-answer subset.
Unverified (unchanged): Rust behavior on this host (no toolchain) and all GPU/parity evidence remain author-reported; no CI has reported on this head. Verdict: COMMENTED (draft; zero actionable findings at this head).
# Conflicts: # README.md # docs/supported-models.md
hsliuustc0106
left a comment
There was a problem hiding this comment.
Incremental re-check at new head a8f523ab04: the branch absorbed main's advancement (merges of #107, #115, #71, #34, #29, #32/#77 content — all patch-identical in range-diff against my reviewed head 5cf7c4ed27) and adds two small commits, both reviewed:
d8cd04f: declaresabi_version: 7inqwen3_5.backend.json— verified consistent withops.h'sCS1_ABI_VERSION 7at this head, and the repo's cuda-contract checker (which validates exactly this) passes in CI at the exact head.a8f523a: listssrc/models/omnijev/native/in the manifest'smodelsconsumers — the crate exists at that path; this is the #120-promised registration completed on the worker side.
Zero actionable findings at this head; prior reviews' zero-finding status carries (own commits patch-identical). Still a draft, so no approval. Unverified (unchanged): GPU behavior and parity evidence author-reported; no CI-reported GPU execution; no Rust toolchain on this host (fork CI rust/build/benchmarks/cuda-contract green at this exact head).
… parity
float_repr renders structured state into the prompt of every
shared-Qwen consumer (Cua-S1, Open-Jev, JEV-VL). Rust's {:e} picks the
upper digit at some exact shortest-decimal ties, while Python repr()
breaks them to even — a divergence the old doc comment admitted.
Select digits via serde_json's default formatter (ryu) instead, and
pin 15 Python repr() examples and five bit-exact tie cases in tests.
Provenance: the change arrived inside ThinkFlowLab#117's merge 20b480a ("Merge
main and preserve shared vision graph regressions", authored by
levius); its result blobs match neither parent and belong to no named
commit. Extracted unchanged here to give it a named, bisectable home,
as requested by the ThinkFlowLab#139 review.
Co-Authored-By: Claude Code <noreply@anthropic.com>
# Conflicts: # src/models/qwen3_5/native/src/model.rs
# Conflicts: # recipe/cua_s1/native_vision_graph.md # src/models/qwen3_5/native/src/model.rs
hsliuustc0106
left a comment
There was a problem hiding this comment.
Incremental re-check at new head dac6ad6702 after the rebase wave (prior review: zero findings at a8f523ab04). Wave-verification note: this pass reviewed the whole 10-branch rebase wave systematically — every range-diff shows the own-commit series patch-identical; all post-pin merge resolutions were checked file-by-file against both parents (source/test/config/docs) with zero dropped lines; approved-content markers verified present in every tree (jemm v2 export, decider fixture READMEs, serving-matrix docs, vision FIFO regressions). Fork/upstream CI states as noted per PR.
All commits patch-identical; resolutions clean (Cargo.lock union; the native_vision_graph recipe and model.rs folds take this branch's reviewed versions). Zero actionable findings; still a draft, so no approval. Template state (post-#138 answers/checklist) still to be completed before leaving draft.
# Conflicts: # src/backends/cuda/qwen3_5/qwen3_5.backend.json
# Conflicts: # README.md # docs/supported-models.md
hsliuustc0106
left a comment
There was a problem hiding this comment.
Incremental re-check at new head b220f476b5 after the rebase/merge wave (prior review: zero findings at dac6ad6702). All commits patch-identical (git range-diff, named refs, cat-file verified); the same three merge resolutions as #134 were checked — manifest abi_version: 7 matches ops.h, omnijev models entry retained (plus main's new decider entry), supported-models table intact. Fork CI rust/build/benchmarks/cuda-contract green at this exact head. Zero actionable findings; still a draft, so no approval. Template state (post-#138 answers/checklist) still to be completed before leaving draft.
Required human contributor answers
What outcome are you trying to achieve?
Serve OmniJev-4B v1.1 on system1-omni's native Rust/CUDA path.
What is the highest-leverage variable for that outcome?
answer agreement with the reference first, asn then per-request latency.
How will this work move that variable rather than create busywork?
omnijev: add the native eager worker #129 tries to minimize the difference from the reference, omnijev: reuse the prefix within a request #134 makes it faster without changing any answer.
Who is the customer or user, and what do they actually need?
Anyone who wants OmniJev decisions rom system1-omni's server, and they need correct answers with low latency.
After answering the four questions: is a PR needed at all to achieve this outcome?
yes, to answer #114.
For a bug fix: link actual observed failure evidence.
N/A,, not a bug fix.
Purpose
Second PR for #114, the "native eager worker" step of the plan. It serves OmniJev-4B v1.1 on
/v1/systemonewith the vision tower and the language model on the shared Qwen3.5 CUDA path, one plain pass per (question, option) row, and the heads on the CPU. It builds on #120 (the contract and CPU side, merged), #98 and #99 (the fixed-algorithm GEMMs andrun_window) and #117 (the shared vision tower and image preprocessing), and should be merged after them, in the order #98, #99, #117, then this PR; until then the diff here includes them. Its own changes are three commits:fc27b75(33 files, +2,274/−267, of which 2,047 lines are code, without documentation, fixtures and the question file),5cf7c4e, which keeps the worker's admission slot until a request's work ends, anda8f523a, which lists the worker in theqwen3_5backend manifest that #77 added.Shared Qwen crate:
Model::forward_multimodal_readout(input, rows, targets): one multimodal prefill with the fixed-algorithm GEMMs, returning the final-norm hidden states atrowsand, for each(row, token), the log-probability oftokenafterrowfrom the tied embedding (BF16 logits over the vocabulary in chunks of 64 rows, then a log-softmax on the CPU). With the fixed GEMMs a result doesn't depend on what else the call reads, which the request-local reuse in the next PR relies on.image_decode::decode_rgb8andinputs::image_positions, moved from Cua-S1'simage_request.rsandmultimodal.rs, which now call them, and replacing OmniJev's ownimage_gridandimage_positionsfrom omnijev: add the request contract, preparation, heads and export #120.VisionConfigacceptsqwen3_5_visionas well asqwen3_5: the Hub's Qwen3.5 configs sayqwen3_5, and Transformers writes its class'sqwen3_5_visionwhen it saves one, asexport.pydoes.OmniJev (
src/models/omnijev/native):processing.rsdecodes the image as Pillow'sconvert("RGB")does and preprocesses it withMSO1's 602,112-pixel budget.export.rschecks the export's manifest against the pinned release and every file it lists, the language weights included, against its SHA-256, and reads the calibration.executor.rsruns the vision tower once per request, then one pass per row over the shared prefix and that row, readingzq,uand, except for Score, the option-text log-probabilities; then the heads andMSO1._finish.main.rsis the worker (omni-omnijev-native):/healthafter a real warmup request, 400/413/422 withdetail, serial admission, two requests preparing at a time and 503 beyond 16 in the worker. Until the rows reuse the prefix, a request may use at most 262,144 tokens in its rows' passes and read 16,384 option-text tokens.recipe/omnijev/native.mdbuilds, runs and compares the worker;validate.pyruns the reference (also merged, or in float32) and the worker on the same images and compares every answer, andgate_questions.jsonadds eight harder questions for it. The model README and the supported-models rows describe the worker.Test Plan
System1-Omni Version / Commit:
b220f47, which merges #11718094f1andmain426e158into the earlier headdac6ad6(a merge of #9918cbd99, with #98d8cd04f, intoa8f523a);5cf7c4eis on #992ff1396(with #98d717e49) and #117c15a989, onmain7cbce19. The comparison and the latency runs used the tree offc27b75(before the rebase onto #120's merge, which didn't change it); the HTTP checks ran on5cf7c4e. Onb220f47the CPU checks pass, and so do the readout and worker GPU tests (one RTX 6000 Ada, CUDA 13.2).mkdocs build --strict. New fixtures from the reference's own image loading and processor: decoded RGB and pixel values for nine images (patterns at four sizes including 1920×1080 and 2000×20, RGBA, grayscale, palette, 16-bit grayscale and JPEG). Export-manifest checks on a stand-in export.tests/qwen3_5/readout.rs: for text, the readout equalsforward_fixedbit for bit; hidden states and log-probabilities don't depend on which other rows and targets a call reads (130 targets, over two vocabulary chunks); a log-probability against a float64 CPU computation over the whole vocabulary;tests/omnijev/worker.rs: repeatable answers, and the same answers with the questions in reverse order;validate.py): the reference's seven v1.1 demo stills withbench/speed_bench.py's twelve questions and the eight ingate_questions.json(options that differ in one token or share a long start, a Choice where no option fits, a 32-option Choice, a 10-level Score, plain and region Noul), 140 answers, with the stills as JPEG and as PNG (the RGB values Pillow decodes from the JPEG files). Reference runs: BF16 as released, in float32 with TF32 off (--fp32), and on the first set with the LoRA merged (--merge) and with rows one at a time (MSO_BRANCH_CHUNK=1). Transformers 5.17.0, torch 2.14.0, PEFT 0.19.1, nofla-core.bench/speed_bench.py's first 1, 3, 6 and 12 questions and Choices of 32 and 255 options.Test Result
CPU. All checks pass on macOS. PNG decoding and the pixel values equal the reference's bit for bit for all eight PNG images. For the JPEG, the
imagecrate's decoder differs from Pillow's libjpeg-turbo by rounding (4,852 of 18,432 values, by at most 3); given Pillow's values, the pixel values are again bit for bit equal.GPU tests. All pass. The worker holds 9.4 GB.
Comparison. Input-token counts equal the reference's on every answer. The largest probability difference per answer, against the float32 reference as the common baseline:
Where the difference comes from, on the speed_bench set and one still:
zqanduare within 1.1% and 0.7% of the reference's, and its option-text log-probabilities within 0.018.The decisions that differ from float32:
web,one_token(float32 margin 0.19): the BF16 reference and both worker runs.phone,choice32(0.03): the BF16 reference only.phone,one_token(0.02) andvideo,noul_region(0.02): the worker's JPEG run.video, speed_bench question 9 (0.18): the worker's PNG run, where it givescontinue0.539 against the float32 reference'sstop0.585 andcontinue0.409; in the JPEG run, whose pixels differ by at most 3, it givesstop0.595.So the worker is about as far from float32 as the BF16 reference is. That meets the first gate Cua-S1's README sets (at most twice the BF16 reference's difference plus 0.01), but not the 0.01 the RFC proposed, which the BF16 reference doesn't meet either. Cua-S1's second gate (the same top option wherever the float32 margin is at least 0.05) fails once for the BF16 reference and once more for the worker's PNG run. I'd take the comparison against float32 as the gate for OmniJev too, and look at the vision tower's BF16 rounding separately; I'm happy to follow a different gate if you prefer.
Latency. The worker reruns the prefix for every row; the reference reuses it:
Medians. With 8 and 16 clients the worker's throughput is unchanged and requests wait in turn: 16.0 s and 32.1 s for 12 questions, and 119 s for 255 options with 8 clients, past the frontend's 60-second default. The next PR reuses the prefix within the request.
Demo / evidence
Every answer behind the tables, the GPU test logs, the latency runs, the scripts and the local CI log: omnijev-129-evidence.tgz (SHA-256
55a63ce4282a8acdf430e505df0d8d35f662bc06ba42bc5d91912434bd4433ea).Self-review